-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Expand file tree
/
Copy pathmaintainer-review-pr.yaml
More file actions
247 lines (214 loc) · 10.7 KB
/
Copy pathmaintainer-review-pr.yaml
File metadata and controls
247 lines (214 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
name: maintainer-review-pr
description: |
Use when: Maintainer wants a deep review on a SINGLE PR they've already
decided is worth reviewing (e.g. picked from the standup brief).
Triggers: "maintainer review", "maintainer review pr <n>",
"review pr as maintainer".
Does: Fetches the PR + diff, classifies which review aspects apply,
runs the relevant aspects (code-review, error-handling, test-coverage,
comment-quality, docs-impact) in parallel, synthesizes findings, posts
a draft comment to the PR, and records the review in shared state so
the next maintainer-standup can mark "✓ reviewed Nd ago".
Provider: Pi (Minimax M2.7) — runs cheaper than Claude. Each review aspect
is its own Archon node, so Pi handles them as independent calls.
NOT for: Comprehensive review of a PR you've already decided to merge
(use archon-comprehensive-pr-review). Quick triage of all open PRs
(use maintainer-standup). Direction/scope gating on
unfiltered PRs — that path was removed; the maintainer is expected
to have done that filtering when picking the PR.
provider: pi
model: minimax/MiniMax-M2.7
worktree:
enabled: false # Live checkout — needs to read .archon/maintainer-standup/
mutates_checkout: false # Read-only + per-run artifact writes; concurrent runs safe
nodes:
# ═══════════════════════════════════════════════════════════════
# PHASE 1: EXTRACT PR NUMBER FROM ARGUMENTS
# ═══════════════════════════════════════════════════════════════
- id: extract-pr-number
prompt: |
Find the GitHub PR number for this request.
Request: $ARGUMENTS
Rules:
- If the message contains an explicit PR number (e.g., "#1428", "PR 1428", "1428"), extract that number.
- If the message contains a PR URL (https://github.com/.../pull/N), extract N.
- If you cannot determine a single PR number, output ERROR.
CRITICAL: Output ONLY the bare number with no quotes, markdown, or explanation.
Example correct output: 1428
allowed_tools: []
idle_timeout: 30000
# ═══════════════════════════════════════════════════════════════
# PHASE 2: GATHER PR DATA (parallel)
# ═══════════════════════════════════════════════════════════════
- id: fetch-pr
bash: |
RAW_PR_NUM=$extract-pr-number.output
PR_NUM=$(echo "$RAW_PR_NUM" | grep -oE '[0-9]+' | head -1)
if [ -z "$PR_NUM" ]; then
echo "Failed to extract PR number from: $RAW_PR_NUM" >&2
exit 1
fi
echo "$PR_NUM" > "$ARTIFACTS_DIR/.pr-number"
gh pr view "$PR_NUM" --json number,title,body,labels,comments,reviews,state,mergeable,mergeStateStatus,additions,deletions,changedFiles,files,author,createdAt,updatedAt,baseRefName,headRefName,reviewDecision,reviewRequests,isDraft
depends_on: [extract-pr-number]
timeout: 30000
- id: fetch-diff
bash: |
PR_NUM=$(cat "$ARTIFACTS_DIR/.pr-number")
# Let auth / network / deleted-PR failures surface as a node failure
# rather than feeding an empty diff to review-classify (which would
# produce a confident "skip everything" decision on no evidence).
if ! diff_output=$(gh pr diff "$PR_NUM"); then
echo "ERROR: gh pr diff failed for PR #$PR_NUM" >&2
exit 1
fi
# Cap at 2500 lines to keep prompt size bounded; classifier cares about shape, not every line.
if [ -z "$diff_output" ]; then
echo "(empty diff — PR has no changes)"
else
echo "$diff_output" | head -2500
fi
depends_on: [fetch-pr]
timeout: 30000
# ═══════════════════════════════════════════════════════════════
# PHASE 3: CLASSIFY WHICH ASPECTS APPLY
# ═══════════════════════════════════════════════════════════════
- id: review-classify
prompt: |
Determine which review aspects to run for this PR.
## PR Metadata
$fetch-pr.output
## Diff (truncated)
$fetch-diff.output
## Rules
- **Code review**: ALWAYS run. Mandatory for every PR.
- **Error handling**: Run if diff touches code with try/catch, async/await, or new failure paths.
- **Test coverage**: Run if diff touches source code (not just tests, docs, or config).
- **Comment quality**: Run if diff adds/modifies comments, docstrings, JSDoc, or in-code documentation.
- **Docs impact**: Run if diff adds/removes/renames public APIs, CLI flags, env vars, or user-facing features.
Provide reasoning for each decision. Output JSON only.
depends_on: [fetch-pr, fetch-diff]
allowed_tools: []
context: fresh
output_format:
type: object
properties:
run_code_review:
type: string
enum: ['true', 'false']
run_error_handling:
type: string
enum: ['true', 'false']
run_test_coverage:
type: string
enum: ['true', 'false']
run_comment_quality:
type: string
enum: ['true', 'false']
run_docs_impact:
type: string
enum: ['true', 'false']
reasoning:
type: string
required:
- run_code_review
- run_error_handling
- run_test_coverage
- run_comment_quality
- run_docs_impact
- reasoning
# ═══════════════════════════════════════════════════════════════
# PHASE 4: RUN ASPECTS (parallel, gated by classifier)
# ═══════════════════════════════════════════════════════════════
- id: code-review
command: maintainer-review-code-review
depends_on: [review-classify]
when: "$review-classify.output.run_code_review == 'true'"
context: fresh
- id: error-handling
command: maintainer-review-error-handling
depends_on: [review-classify]
when: "$review-classify.output.run_error_handling == 'true'"
context: fresh
- id: test-coverage
command: maintainer-review-test-coverage
depends_on: [review-classify]
when: "$review-classify.output.run_test_coverage == 'true'"
context: fresh
- id: comment-quality
command: maintainer-review-comment-quality
depends_on: [review-classify]
when: "$review-classify.output.run_comment_quality == 'true'"
context: fresh
- id: docs-impact
command: maintainer-review-docs-impact
depends_on: [review-classify]
when: "$review-classify.output.run_docs_impact == 'true'"
context: fresh
- id: synthesize-review
command: maintainer-review-synthesize
depends_on: [code-review, error-handling, test-coverage, comment-quality, docs-impact]
trigger_rule: one_success
context: fresh
# Auto-post — once the deep review is drafted, the feedback is worth
# delivering. No approval required; the maintainer can always edit/delete
# on GitHub.
- id: post-review
bash: |
PR_NUM=$(cat "$ARTIFACTS_DIR/.pr-number")
if [ ! -f "$ARTIFACTS_DIR/review/review-comment.md" ]; then
echo "ERROR: review-comment.md missing — synthesize did not write it" >&2
exit 1
fi
gh pr comment "$PR_NUM" --body-file "$ARTIFACTS_DIR/review/review-comment.md"
echo "Posted review comment to PR #$PR_NUM"
depends_on: [synthesize-review]
timeout: 30000
# ═══════════════════════════════════════════════════════════════
# PHASE 5: RECORD REVIEW IN SHARED STATE
# ═══════════════════════════════════════════════════════════════
# Append this run's PR number + timestamp to
# .archon/maintainer-standup/reviewed-prs.json so the morning standup
# brief can mark "✓ reviewed Nd ago" next to PRs that have already
# been triaged. Cross-workflow memory; gitignored, per-maintainer.
#
# `gate_verdict` is kept in the record as the literal string "review"
# for backward compatibility with the standup synthesis prompt, which
# branches the brief marker on it (review/declined/triaged). Older
# entries written before the gate was removed may still carry
# `decline` / `needs_split` / `unclear` — the standup keeps reading
# them correctly.
- id: record-review
runtime: bun
timeout: 10000
depends_on: [post-review]
script: |
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
const baseDir = resolve(process.cwd(), '.archon/maintainer-standup');
if (!existsSync(baseDir)) mkdirSync(baseDir, { recursive: true });
const prPath = resolve(process.cwd(), '$ARTIFACTS_DIR/.pr-number');
const prNumber = readFileSync(prPath, 'utf8').trim();
const reviewedPath = resolve(baseDir, 'reviewed-prs.json');
let reviewed = {};
if (existsSync(reviewedPath)) {
try {
reviewed = JSON.parse(readFileSync(reviewedPath, 'utf8'));
} catch {
reviewed = {};
}
}
reviewed[prNumber] = {
reviewed_at: new Date().toISOString(),
gate_verdict: 'review',
run_id: '$WORKFLOW_ID',
};
writeFileSync(reviewedPath, JSON.stringify(reviewed, null, 2) + '\n');
console.log(`Recorded review of PR #${prNumber}`);
# ═══════════════════════════════════════════════════════════════
# PHASE 6: FINAL REPORT
# ═══════════════════════════════════════════════════════════════
- id: report
command: maintainer-review-report
depends_on: [record-review]
context: fresh