forgedock 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +244 -0
- package/README.md +251 -0
- package/bin/forgedock.mjs +232 -0
- package/commands/analytics.md +777 -0
- package/commands/audit-agents.md +410 -0
- package/commands/audit.md +410 -0
- package/commands/autopilot.md +432 -0
- package/commands/cleanup.md +264 -0
- package/commands/deploy-info.md +292 -0
- package/commands/failure-recon.md +375 -0
- package/commands/forge-stats.md +183 -0
- package/commands/geo-audit.md +343 -0
- package/commands/incident-response.md +384 -0
- package/commands/issue.md +506 -0
- package/commands/milestone.md +599 -0
- package/commands/orchestrate.md +1044 -0
- package/commands/pipeline-health.md +2343 -0
- package/commands/qa-sweep.md +237 -0
- package/commands/quality-gate.md +777 -0
- package/commands/review-pr-agents.md +1711 -0
- package/commands/review-pr-staging.md +325 -0
- package/commands/review-pr.md +888 -0
- package/commands/rollback.md +253 -0
- package/commands/security-audit.md +529 -0
- package/commands/sync-ecosystem.md +527 -0
- package/commands/validate.md +131 -0
- package/commands/work-on/build/architect.md +291 -0
- package/commands/work-on/build/context.md +349 -0
- package/commands/work-on/build/implement.md +280 -0
- package/commands/work-on/build/validate.md +292 -0
- package/commands/work-on/build.md +262 -0
- package/commands/work-on/close.md +350 -0
- package/commands/work-on/decompose.md +254 -0
- package/commands/work-on/investigate.md +437 -0
- package/commands/work-on/review.md +245 -0
- package/commands/work-on-monolithic.md +298 -0
- package/commands/work-on.md +1089 -0
- package/docs/CODEX.md +92 -0
- package/docs/WORKFLOW.md +104 -0
- package/package.json +45 -0
- package/scripts/verify-env-vars.sh +201 -0
- package/scripts/verify-host-headers.sh +117 -0
- package/scripts/verify-route-registration.sh +118 -0
- package/scripts/verify-sops-chain.sh +163 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Context-aware PR review — analyzes what the PR touches, spawns domain-specific agents with project conventions. Supports staging reviews.
|
|
3
|
+
argument-hint: [PR number, URL, "open", or "staging" for feature→main review]
|
|
4
|
+
allowed-tools: Task, Bash, Read, Grep, Glob, WebFetch, Skill
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AlterLab PR Review — Orchestrator
|
|
8
|
+
|
|
9
|
+
**Input**: $ARGUMENTS
|
|
10
|
+
|
|
11
|
+
**NEVER use plan mode (EnterPlanMode)** during review — it breaks execution context.
|
|
12
|
+
|
|
13
|
+
**Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`. User can override with `--model <name>`. Pass the resolved model in every `Task` tool call.
|
|
14
|
+
|
|
15
|
+
## Architecture — How This Command Works
|
|
16
|
+
|
|
17
|
+
This is the **orchestrator**. It routes to the right review mode, runs automated checks, spawns domain-specific agents, triages findings, and posts the verdict.
|
|
18
|
+
|
|
19
|
+
**Sub-files** (loaded on demand — NOT auto-loaded):
|
|
20
|
+
|
|
21
|
+
| File | What | How to invoke |
|
|
22
|
+
|------|------|---------------|
|
|
23
|
+
| `/home/mrdubey/.claude/commands/review-pr-agents.md` | Agent prompt templates (9 agents + protocols) | `Read` tool during Phase 3C |
|
|
24
|
+
| `/home/mrdubey/.claude/commands/review-pr-staging.md` | Full staging→main review pipeline | `Skill("review-pr-staging", ...)` during Phase 0 |
|
|
25
|
+
|
|
26
|
+
**Invocation flow:**
|
|
27
|
+
```
|
|
28
|
+
/review-pr 5428 → Phase 0 detects single PR → runs Phases 1-9 inline
|
|
29
|
+
/review-pr staging → Phase 0 detects staging mode → Skill("review-pr-staging", "staging")
|
|
30
|
+
/review-pr 5500 → Phase 0 auto-detects staging→main PR → Skill("review-pr-staging", "5500")
|
|
31
|
+
/review-pr 3126 --auto-merge --issue 3124 --base staging → single PR + auto-merge after approval
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### Auto-Merge Flag
|
|
37
|
+
|
|
38
|
+
If `$ARGUMENTS` contains `--auto-merge`, this review was invoked from `/work-on` and must merge the PR after approval. Parse:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Example: "3126 --auto-merge --issue 3124 --base staging --gh-flag -R RapierCraft/AlterLab --worktree /path/to/worktree"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Extract: `PR_NUMBER`, `AUTO_MERGE=true`, `MERGE_ISSUE`, `MERGE_BASE`, `MERGE_GH_FLAG`, `MERGE_WORKTREE` (optional — the absolute path to the git worktree to clean up after merge)
|
|
45
|
+
|
|
46
|
+
If `--auto-merge` is NOT present, `AUTO_MERGE=false` — Phase 8 (Auto-Merge) will be skipped.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Phase 0: Route to Review Mode
|
|
51
|
+
|
|
52
|
+
Check input to determine which mode:
|
|
53
|
+
|
|
54
|
+
**CRITICAL — NO DELTA REVIEWS**: If PR was reviewed before, always run the FULL pipeline. Prior findings are already in GitHub issues. Full re-review catches build failures and prerender crashes.
|
|
55
|
+
|
|
56
|
+
### MODE 1: Staging Review
|
|
57
|
+
|
|
58
|
+
If `$ARGUMENTS` is "staging", "feature", or "staging:feature":
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
>>> INVOKE: Skill("review-pr-staging", "$ARGUMENTS")
|
|
62
|
+
>>> THEN STOP — the staging command handles the full flow.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### MODE 2: Multiple PR Review ("open", "all")
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
gh pr list --state open --json number,title,author,createdAt,headRefName
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Show list, ask user which to review, then loop through each with full review.
|
|
72
|
+
|
|
73
|
+
### MODE 3: Single PR (number or URL)
|
|
74
|
+
|
|
75
|
+
**Auto-detect staging mode:**
|
|
76
|
+
```bash
|
|
77
|
+
PR_INFO=$(gh pr view $ARGUMENTS --json baseRefName,headRefName,additions,deletions,title)
|
|
78
|
+
HEAD=$(echo $PR_INFO | jq -r '.headRefName')
|
|
79
|
+
BASE=$(echo $PR_INFO | jq -r '.baseRefName')
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
If `HEAD = "staging" AND BASE = "main"` OR `HEAD = "feature" AND BASE = "main"`:
|
|
83
|
+
```
|
|
84
|
+
>>> INVOKE: Skill("review-pr-staging", "$ARGUMENTS")
|
|
85
|
+
>>> THEN STOP.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If `HEAD starts with "milestone/" AND BASE = "staging"`:
|
|
89
|
+
```
|
|
90
|
+
>>> This is a milestone shipping PR. It has a large accumulated diff from many feat/* PRs.
|
|
91
|
+
>>> Run the FULL inline multi-agent review pipeline (Phases 1-9), NOT a single-PR review.
|
|
92
|
+
>>> The diff is large — treat it like a staging review in terms of thoroughness.
|
|
93
|
+
>>> Proceed to Phase 1.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Otherwise → proceed to Phase 1.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Phase 1: PR Context & Classification
|
|
101
|
+
|
|
102
|
+
### 1A: Fetch PR Data
|
|
103
|
+
```bash
|
|
104
|
+
gh pr view $ARGUMENTS --json number,title,body,author,baseRefName,headRefName,files,additions,deletions
|
|
105
|
+
REVIEW_SHA=$(gh pr view $ARGUMENTS --json headRefOid --jq '.headRefOid')
|
|
106
|
+
REVIEW_SHA_SHORT=$(echo "$REVIEW_SHA" | cut -c1-7)
|
|
107
|
+
gh pr diff $ARGUMENTS --name-only
|
|
108
|
+
gh pr diff $ARGUMENTS
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 1B: Classify
|
|
112
|
+
```bash
|
|
113
|
+
FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
114
|
+
DIFF=$(gh pr diff $ARGUMENTS)
|
|
115
|
+
|
|
116
|
+
echo "=== SERVICES ==="
|
|
117
|
+
echo "$FILES" | grep -c "^services/api/" && echo "API" || true
|
|
118
|
+
echo "$FILES" | grep -c "^services/worker/" && echo "WORKER" || true
|
|
119
|
+
echo "$FILES" | grep -c "^web/" && echo "WEB" || true
|
|
120
|
+
echo "$FILES" | grep -c "^shared/" && echo "SHARED" || true
|
|
121
|
+
echo "$FILES" | grep -cE "^(docker|infra/|\.github|Makefile|traefik)" && echo "INFRA" || true
|
|
122
|
+
|
|
123
|
+
echo "=== DOMAINS ==="
|
|
124
|
+
echo "$DIFF" | grep -cE "SessionUser|CurrentUser|get_current_user|jwt|oauth|login|logout|Depends\(get_|x.forwarded.for|x_forwarded_for|forwarded_for|rate.limit.*ip|ip.*rate.limit|algorithm.*HS256|algorithm.*RS256|NEXTAUTH_SECRET|JWT_SECRET|admin.proxy" && echo "AUTH" || true
|
|
125
|
+
echo "$DIFF" | grep -cE "credit|balance|debit|reconcil|tier_cost|pricing|charge|refund|stripe|subscription" && echo "BILLING" || true
|
|
126
|
+
echo "$DIFF" | grep -cE "scrape|tier.*escalat|proxy|anti_bot|stealth|playwright|playbook" && echo "SCRAPING" || true
|
|
127
|
+
echo "$DIFF" | grep -cE "FOR UPDATE|atomic|transaction|pipeline|MULTI|distributed_lock|acquire_lock|reserved_by|promo.*claim|voucher.*redeem|is_byop|byop_discount" && echo "CONCURRENCY" || true
|
|
128
|
+
echo "$FILES" | grep -cE "migration|\.sql$" && echo "DATABASE" || true
|
|
129
|
+
echo "$DIFF" | grep -cE "create_async_engine|AsyncSession|connect_args|pool_size|prepared_statement|engine_from_config|sessionmaker" && echo "DB_CONFIG" || true
|
|
130
|
+
echo "$FILES" | grep -cE "router|routes" && echo "API_DESIGN" || true
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 1C: Document
|
|
134
|
+
Record: services touched, domains detected, PR scope (1-2 sentences), change categories.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Phase 2: Automated Checks (Run ALL in Parallel)
|
|
139
|
+
|
|
140
|
+
### 2A: Python Linting (if Python changed)
|
|
141
|
+
```bash
|
|
142
|
+
cd services/api && poetry run black --check app/
|
|
143
|
+
cd services/api && poetry run isort --check app/
|
|
144
|
+
cd services/worker && poetry run black --check worker/
|
|
145
|
+
cd services/worker && poetry run isort --check worker/
|
|
146
|
+
python3 -m py_compile <each-changed-python-file>
|
|
147
|
+
```
|
|
148
|
+
**IMPORTANT**: Always `cd` into service dir and `poetry run black`. Never `pipx run black`.
|
|
149
|
+
|
|
150
|
+
### 2B: TypeScript/JS (if web/ changed)
|
|
151
|
+
```bash
|
|
152
|
+
cd web && npx prettier --check "src/**/*.{ts,tsx}"
|
|
153
|
+
cd web && npx tsc --noEmit
|
|
154
|
+
cd web && npx next lint
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 2C: Static Type Checking (if Python changed)
|
|
158
|
+
```bash
|
|
159
|
+
cd services/api && poetry run mypy app/ --ignore-missing-imports --no-error-summary 2>&1 | head -50
|
|
160
|
+
cd services/api && poetry run ruff check app/ --select=E,W,F,B,S --ignore=E501 2>&1 | head -30
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### 2D: Environment Variable Audit
|
|
164
|
+
```bash
|
|
165
|
+
gh pr diff $ARGUMENTS | grep -E "os\.getenv|os\.environ|process\.env" | head -30
|
|
166
|
+
```
|
|
167
|
+
Flag if new env vars not in `.env.example`.
|
|
168
|
+
|
|
169
|
+
### 2E: Secrets Detection (CRITICAL — BLOCKING if found)
|
|
170
|
+
```bash
|
|
171
|
+
gh pr diff $ARGUMENTS | grep -iE "(api[_-]?key|secret[_-]?key|password|token|credential|private[_-]?key)" | grep -vE "(#|//|\.example|placeholder|PLACEHOLDER|YOUR_|<|>)" | head -20
|
|
172
|
+
gh pr diff $ARGUMENTS | grep -oE "['\"][A-Za-z0-9+/=]{40,}['\"]" | head -10
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 2F: SQL Migration Validation (if *.sql changed)
|
|
176
|
+
```bash
|
|
177
|
+
for sql_file in $(gh pr diff $ARGUMENTS --name-only | grep "\.sql$"); do
|
|
178
|
+
grep -E "FOR UPDATE" "$sql_file" | grep -qE "(SUM|COUNT|AVG|MIN|MAX)\s*\(" && echo "ERROR: FOR UPDATE with aggregate"
|
|
179
|
+
grep -qE "DROP (TABLE|COLUMN|INDEX)" "$sql_file" && ! grep -qE "IF EXISTS" "$sql_file" && echo "WARNING: DROP without IF EXISTS"
|
|
180
|
+
grep -qE "ALTER TABLE.*ADD COLUMN.*NOT NULL" "$sql_file" && ! grep -qE "DEFAULT" "$sql_file" && echo "WARNING: NOT NULL without DEFAULT"
|
|
181
|
+
done
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### 2G: Dependency Audit (if pyproject.toml or package.json changed)
|
|
185
|
+
```bash
|
|
186
|
+
git diff origin/main...HEAD -- "**/pyproject.toml" | grep -E "^\+" | grep -v "^\+\+\+" | head -20
|
|
187
|
+
git diff origin/main...HEAD -- "**/package.json" | grep -E "^\+" | grep -v "^\+\+\+" | head -20
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 2H: Tests
|
|
191
|
+
```bash
|
|
192
|
+
cd services/api && poetry run pytest tests/ -x -q --tb=short 2>&1 | tail -30
|
|
193
|
+
```
|
|
194
|
+
**BLOCKING if tests fail.**
|
|
195
|
+
|
|
196
|
+
### 2I: Build Verification (MANDATORY for staging→main AND milestone→staging)
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
CHANGED_FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
200
|
+
HAS_TS=$(echo "$CHANGED_FILES" | grep -E '\.(tsx?|jsx?)$' | head -1)
|
|
201
|
+
HAS_PY=$(echo "$CHANGED_FILES" | grep -E '\.py$' | head -1)
|
|
202
|
+
IS_STAGING_TO_MAIN=$([[ "$HEAD" == "staging" && "$BASE" == "main" ]] && echo "true" || echo "false")
|
|
203
|
+
IS_MILESTONE_TO_STAGING=$([[ "$HEAD" =~ ^milestone/ && "$BASE" == "staging" ]] && echo "true" || echo "false")
|
|
204
|
+
REQUIRES_FULL_BUILD=$([[ "$IS_STAGING_TO_MAIN" == "true" || "$IS_MILESTONE_TO_STAGING" == "true" ]] && echo "true" || echo "false")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**TypeScript files changed:**
|
|
208
|
+
```bash
|
|
209
|
+
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
210
|
+
cd web && npx tsc --noEmit 2>&1
|
|
211
|
+
TSC_EXIT=$?
|
|
212
|
+
if [ "$REQUIRES_FULL_BUILD" = "true" ] || [ "$TSC_EXIT" -eq 0 ]; then
|
|
213
|
+
npx next build 2>&1 | tail -30
|
|
214
|
+
BUILD_EXIT=$?
|
|
215
|
+
fi
|
|
216
|
+
cd .. && git checkout - 2>/dev/null
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
If `TSC_EXIT != 0`: **CONFIRMED blocking** — type errors.
|
|
220
|
+
If `BUILD_EXIT != 0`: **CONFIRMED blocking** — SSG prerender failure.
|
|
221
|
+
|
|
222
|
+
**CRITICAL**: `tsc --noEmit` alone is NOT sufficient for staging→main or milestone→staging. PR #2565 passed tsc but failed next build. PR #11637 (Session Intelligence milestone) was APPROVED without any build check because milestone→staging was excluded — 4 build errors shipped to staging.
|
|
223
|
+
|
|
224
|
+
**Python files changed:**
|
|
225
|
+
```bash
|
|
226
|
+
gh pr checkout $ARGUMENTS --detach 2>/dev/null
|
|
227
|
+
for f in $(echo "$CHANGED_FILES" | grep '\.py$'); do python3 -m py_compile "$f" 2>&1; done
|
|
228
|
+
if [ "$REQUIRES_FULL_BUILD" = "true" ]; then
|
|
229
|
+
cd services/api && poetry run black --check app/ 2>&1
|
|
230
|
+
cd ../.. && cd services/worker && poetry run black --check worker/ 2>&1
|
|
231
|
+
fi
|
|
232
|
+
git checkout - 2>/dev/null
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**BLOCKING if any check fails.** Fix before merge — do not approve with known build/format failures.
|
|
236
|
+
|
|
237
|
+
### 2J: Builder Contract Scope Check (if PR is from /work-on pipeline)
|
|
238
|
+
|
|
239
|
+
Check whether the PR's actual changes match what the builder committed to in its contract:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# Find the contract comment on the linked issue
|
|
243
|
+
ISSUE_NUM=$(gh pr view $ARGUMENTS --json body --jq '.body' | grep -oP 'Closes #\K\d+|#\K\d+' | head -1)
|
|
244
|
+
if [ -n "$ISSUE_NUM" ]; then
|
|
245
|
+
CONTRACT_FILES=$(gh api repos/${REPO}/issues/${ISSUE_NUM}/comments --jq '[.[] | select(.body | contains("FORGE:CONTRACT"))] | .[0].body' 2>/dev/null | grep -oP '`[^`]+\.(py|tsx?|sql|sh|yml|yaml|json)`' | tr -d '`' | sort -u)
|
|
246
|
+
PR_FILES=$(gh pr diff $ARGUMENTS --name-only | sort -u)
|
|
247
|
+
|
|
248
|
+
# Files in PR but NOT in contract
|
|
249
|
+
SCOPE_CREEP=$(comm -23 <(echo "$PR_FILES") <(echo "$CONTRACT_FILES") 2>/dev/null | grep -vE '\.(md|txt|example)$')
|
|
250
|
+
if [ -n "$SCOPE_CREEP" ]; then
|
|
251
|
+
echo "SCOPE: PR changes files not in builder contract:"
|
|
252
|
+
echo "$SCOPE_CREEP"
|
|
253
|
+
echo "This is informational — may be legitimate (discovered during implementation). Flag if suspicious."
|
|
254
|
+
fi
|
|
255
|
+
fi
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
This is not blocking — scope expansion during implementation is normal. But large unexplained scope creep (5+ uncontracted files) should be flagged in the review summary.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Phase 2.5: Assumption Verification (Integration Integrity)
|
|
263
|
+
|
|
264
|
+
**WHY THIS EXISTS**: Code review catches logic bugs inside changed files. But bugs increasingly come from correct code that doesn't execute because another system layer blocks, shadows, or reroutes it. As the system grows more layers (nginx → next.config → route handlers → middleware → business logic → Redis → Postgres), the probability of "correct but unreachable" code increases combinatorially.
|
|
265
|
+
|
|
266
|
+
This phase asks: **"What must be true in the rest of the system for each changed file to actually work? Are those assumptions true?"**
|
|
267
|
+
|
|
268
|
+
### How It Works
|
|
269
|
+
|
|
270
|
+
For each changed file, identify its **activation path** — how does execution reach this code? Then verify the path is intact by checking unchanged system files.
|
|
271
|
+
|
|
272
|
+
### Step 2.5A: Identify File Types and Their Registration Points
|
|
273
|
+
|
|
274
|
+
Map each changed file to its activation requirements:
|
|
275
|
+
|
|
276
|
+
| Changed File Pattern | Assumption | Verification Target |
|
|
277
|
+
|---------------------|------------|---------------------|
|
|
278
|
+
| `web/src/app/api/**/*.ts` (Route Handler) | Requests reach this handler | Check `web/next.config.js` rewrites don't shadow it; check `infra/nginx/nginx.conf` routes path to Next.js |
|
|
279
|
+
| `services/api/app/routers/*.py` (API Router) | Router is registered in app | Check `services/api/app/main.py` includes this router |
|
|
280
|
+
| `services/api/app/middleware/*.py` | Middleware is in the stack | Check `services/api/app/main.py` middleware registration order |
|
|
281
|
+
| `infra/migrations/*.sql` | Migration runs on current schema | Check previous migration's end state matches assumptions |
|
|
282
|
+
| `shared/**/*.py` | Imported by consumer services | Verify import paths exist in api/worker; check Docker volume mounts |
|
|
283
|
+
| `services/worker/worker/*.py` (Consumer) | Queue consumer is registered | Check consumer registration in worker startup |
|
|
284
|
+
| Any file using `os.getenv("NEW_VAR")` | Env var is set at runtime | Check `docker-compose.yml`, `.env.example`, `services/api/app/core/env_validation.py` |
|
|
285
|
+
| `scripts/decrypt-secrets.sh` (ENV_MAPPING) | Secret reaches running container | Trace full chain: SOPS key → ENV_MAPPING → deploy workflow SCP target → merge script path → `docker-compose.prod.yml` env_file. See Step 2.5B SOPS deploy chain check. |
|
|
286
|
+
| `.secrets/prod.enc.yaml` | SOPS key maps to ENV_MAPPING | Verify key path in YAML matches the tuple in `decrypt-secrets.sh` ENV_MAPPING |
|
|
287
|
+
| `.github/workflows/deploy-production.yml` | Deploy paths are consistent | Verify SCP target + merge script `PROJECT` var resolve to same dir as `docker-compose.prod.yml` env_file |
|
|
288
|
+
| `web/src/components/**/*.tsx` | Component is imported somewhere | Check for at least one import of this component |
|
|
289
|
+
| `services/*/config/*.json` | Config is baked into image | Check Dockerfile COPY or volume mount in `docker-compose.yml` |
|
|
290
|
+
| `infra/nginx/*.conf` | Nginx loads this config | Check `docker-compose.yml` volume mount for nginx |
|
|
291
|
+
| `infra/**/*.sh`, `scripts/**/*.sh` with `curl`/`wget` to internal services | HTTP request is accepted by target service | Read target service's middleware stack (`main.py` for API: TrustedHostMiddleware, CORS, auth). Verify Host header, auth headers, and URL path will produce the expected status code. |
|
|
292
|
+
| `web/src/app/**/*-client.tsx`, `web/src/components/**/*.tsx`, `web/src/lib/*.ts` (Client-side code with `fetch()`/`useSWR()`) | Client requests go through Next.js proxy (`/api/...`), never directly to FastAPI (`/api/v1/...`) | Grep changed `.tsx`/`.ts` files (excluding `route.ts` proxy handlers) for `fetch("/api/v1/` or `` `/api/v1/ `` in template literals or `useSWR.*"/api/v1/`. Any match is a **CONFIRMED BLOCKING** integration bug — direct calls bypass session auth (admin proxy JWT, session-to-bearer conversion) and fail locally (no nginx). |
|
|
293
|
+
| `.github/workflows/*.yml` (Workflow with test/build jobs) | Sibling workflows with same-named jobs stay in sync | For each job name in the changed workflow, check if the same job name exists in sibling workflows (`ci.yml` ↔ `deploy-production.yml` ↔ `hotfix-deploy.yml`). Compare env vars, PYTHONPATH, working-directory, and run commands for meaningful drift. |
|
|
294
|
+
| `docker-compose*.yml` changes to `postgres` or `redis` service (`command:`, `image:`, `volumes:`, `environment:`) | Stateful container will NOT be recreated during deploy, OR restart is safe | **Auto-escalate to HIGH risk.** Changing `command:` args, `image:` tag, or `volumes:` forces container recreation on `docker-compose up`. For stateful services (postgres, redis), verify: (1) `stop_grace_period` is set and sufficient (≥30s for PG); (2) `full_page_writes = on` in PG config (protects against partial page writes on crash); (3) `fsync = on` (ensures write durability); (4) No long-running transactions will be interrupted. If container recreation is unavoidable, recommend scheduling during a maintenance window — NOT as a side effect of a routine deploy. **This check prevents the class of incident documented in issue #146**: a PG `command:` arg change triggered container restart under active load, corrupting btree indexes. |
|
|
295
|
+
| `docker-compose*.yml` changes `entrypoint:` or `command:` to reference a script (`.sh` file) | Env vars used inside the entrypoint script are available inside the container at runtime | **Run `verify-env-vars.sh`** which detects shell `${VAR}` references in entrypoint scripts and cross-checks against the service's `environment:` section. Docker Compose `${VAR}` in YAML `command:` is parsed at Compose load time (host-side) — the var doesn't need to be in the container. But `${VAR}` inside an entrypoint `.sh` script runs at container runtime — it MUST be injected via `environment:` or `env_file:`. **This check prevents the class of incident documented in RapierCraftStudios/forge#185**: Redis migrated from `command: --requirepass ${REDIS_PASSWORD}` (Compose interpolation) to `entrypoint.sh` (container runtime) without adding `environment: REDIS_PASSWORD`, causing a restart loop on deploy. |
|
|
296
|
+
| **ANY staging→main PR** (regardless of files changed) | `ci.yml` and `deploy-production.yml` test jobs are in sync | **ALWAYS runs for staging→main PRs.** Pre-existing drift is invisible until deploy. Deep-diff shared jobs (test-api, test-web): compare PYTHONPATH values, dependency install steps, and step names. A missing PYTHONPATH or install step in deploy is CONFIRMED BLOCKING — CI passes but deploy fails (PR #11356 incident). |
|
|
297
|
+
|
|
298
|
+
### Step 2.5B: Run Verification
|
|
299
|
+
|
|
300
|
+
For each changed file, execute the relevant checks using the standalone verification scripts in `~/projects/forge/scripts/`. These scripts can also be run independently outside the review context (e.g., from `/quality-gate` or `/work-on` builder steps).
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
CHANGED_FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
304
|
+
REPO_ROOT="." # Assumes cwd is the repo root
|
|
305
|
+
|
|
306
|
+
# Write changed files and diff to temp files for script consumption
|
|
307
|
+
CHANGED_FILES_TMP=$(mktemp)
|
|
308
|
+
DIFF_TMP=$(mktemp)
|
|
309
|
+
echo "$CHANGED_FILES" > "$CHANGED_FILES_TMP"
|
|
310
|
+
gh pr diff $ARGUMENTS > "$DIFF_TMP"
|
|
311
|
+
|
|
312
|
+
# --- Script-based checks (reusable, testable, deterministic) ---
|
|
313
|
+
# Each script exits 0 (pass), 1 (blocking findings), or 2 (warnings only).
|
|
314
|
+
# Output is structured: "BLOCKING: ...", "WARNING: ...", "OK: ..." per line.
|
|
315
|
+
|
|
316
|
+
# 1. Route/router/middleware/shared-module/component registration
|
|
317
|
+
echo "=== Running: verify-route-registration.sh ==="
|
|
318
|
+
~/projects/forge/scripts/verify-route-registration.sh "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
319
|
+
|
|
320
|
+
# 2. Environment variable wiring (checks .env.example, docker-compose, env_validation, SOPS mapping)
|
|
321
|
+
echo "=== Running: verify-env-vars.sh ==="
|
|
322
|
+
~/projects/forge/scripts/verify-env-vars.sh "$DIFF_TMP" "$REPO_ROOT" || true
|
|
323
|
+
|
|
324
|
+
# 3. Host headers in shell scripts + client-side proxy bypass check
|
|
325
|
+
echo "=== Running: verify-host-headers.sh ==="
|
|
326
|
+
~/projects/forge/scripts/verify-host-headers.sh "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
327
|
+
|
|
328
|
+
# 4. SOPS deploy chain (ENV_MAPPING consistency, deploy path drift, hotfix sync)
|
|
329
|
+
echo "=== Running: verify-sops-chain.sh ==="
|
|
330
|
+
~/projects/forge/scripts/verify-sops-chain.sh "$DIFF_TMP" "$CHANGED_FILES_TMP" "$REPO_ROOT" || true
|
|
331
|
+
|
|
332
|
+
# Cleanup temp files
|
|
333
|
+
rm -f "$CHANGED_FILES_TMP" "$DIFF_TMP"
|
|
334
|
+
|
|
335
|
+
# --- Inline checks (not yet extracted to scripts) ---
|
|
336
|
+
|
|
337
|
+
# Python scoping hazard check — local imports that shadow module-level names
|
|
338
|
+
# A local `import X` makes X a local variable for the ENTIRE function scope.
|
|
339
|
+
# Any reference to X ABOVE the local import will crash with UnboundLocalError.
|
|
340
|
+
for f in $(echo "$CHANGED_FILES" | grep -E '\.py$'); do
|
|
341
|
+
echo "=== Python Scoping Check: $f ==="
|
|
342
|
+
# Find function-scoped imports (indented import statements)
|
|
343
|
+
grep -nE "^\s+import [a-z]" "$f" 2>/dev/null | while read line; do
|
|
344
|
+
LINENO=$(echo "$line" | cut -d: -f1)
|
|
345
|
+
MODULE=$(echo "$line" | grep -oE "import [a-z_]+" | awk '{print $2}')
|
|
346
|
+
# Check if the same module is used BEFORE this line in the same function
|
|
347
|
+
# (simplified check — agents should do full scope analysis)
|
|
348
|
+
[ -n "$MODULE" ] && head -n $((LINENO-1)) "$f" 2>/dev/null | grep -qE "^\s+.*\b${MODULE}\." && \
|
|
349
|
+
echo "WARNING: Local 'import $MODULE' at line $LINENO may shadow module-level import — check for UnboundLocalError on references above this line"
|
|
350
|
+
done
|
|
351
|
+
done
|
|
352
|
+
|
|
353
|
+
# Config file assumption check (baked into Docker image vs volume-mounted)
|
|
354
|
+
for f in $(echo "$CHANGED_FILES" | grep -E "config/.*\.(json|yaml|yml)$"); do
|
|
355
|
+
echo "=== Config File: $f ==="
|
|
356
|
+
grep -n "$(dirname $f)" docker-compose.yml 2>/dev/null || echo "WARNING: Config dir may not be mounted — changes may require --build"
|
|
357
|
+
grep -n "$(dirname $f)" services/*/Dockerfile 2>/dev/null || true
|
|
358
|
+
done
|
|
359
|
+
|
|
360
|
+
# Sibling workflow drift check — ALWAYS runs for staging→main PRs.
|
|
361
|
+
# Also runs when any workflow file changes on non-staging PRs.
|
|
362
|
+
#
|
|
363
|
+
# The class of bug this catches: ci.yml has PYTHONPATH + worker deps,
|
|
364
|
+
# deploy-production.yml doesn't. CI passes, deploy fails.
|
|
365
|
+
# PR #11356 was approved with green CI but deploy pipeline broke.
|
|
366
|
+
#
|
|
367
|
+
# CRITICAL: This check must NOT be gated on workflow files being in the
|
|
368
|
+
# diff. Pre-existing drift is the most dangerous kind — it lurks until
|
|
369
|
+
# staging→main and then blocks the deploy.
|
|
370
|
+
WORKFLOW_FILES=$(echo "$CHANGED_FILES" | grep -E "^\.github/workflows/.*\.yml$" || true)
|
|
371
|
+
IS_STAGING_PR=$([[ "$HEAD" == "staging" && "$BASE" == "main" ]] && echo "true" || echo "false")
|
|
372
|
+
|
|
373
|
+
if [ -n "$WORKFLOW_FILES" ] || [ "$IS_STAGING_PR" = "true" ]; then
|
|
374
|
+
echo "=== Sibling Workflow Drift Check (MANDATORY for staging→main) ==="
|
|
375
|
+
|
|
376
|
+
CI_WF=".github/workflows/ci.yml"
|
|
377
|
+
DEPLOY_WF=".github/workflows/deploy-production.yml"
|
|
378
|
+
|
|
379
|
+
if [ -f "$CI_WF" ] && [ -f "$DEPLOY_WF" ]; then
|
|
380
|
+
# Deep comparison: extract the full test step (name + run + env) from
|
|
381
|
+
# each shared job and diff them. Keyword grepping missed the PR #11356
|
|
382
|
+
# failure — PYTHONPATH was present in CI but absent in deploy.
|
|
383
|
+
for JOB in test-api test-web; do
|
|
384
|
+
CI_HAS=$(grep -c "name: Test.*${JOB#test-}" "$CI_WF" 2>/dev/null || echo 0)
|
|
385
|
+
DEPLOY_HAS=$(grep -c "name: Test.*${JOB#test-}" "$DEPLOY_WF" 2>/dev/null || echo 0)
|
|
386
|
+
[ "$CI_HAS" -eq 0 ] || [ "$DEPLOY_HAS" -eq 0 ] && continue
|
|
387
|
+
|
|
388
|
+
echo "--- Comparing '$JOB' job between ci.yml and deploy-production.yml ---"
|
|
389
|
+
|
|
390
|
+
# Extract env vars from ALL steps in the job (not just pytest)
|
|
391
|
+
CI_ENVS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$CI_WF" 2>/dev/null | grep -E "PYTHONPATH|DATABASE_URL|REDIS_URL|TESTING" | sed 's/^ *//' | sort)
|
|
392
|
+
DEPLOY_ENVS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$DEPLOY_WF" 2>/dev/null | grep -E "PYTHONPATH|DATABASE_URL|REDIS_URL|TESTING" | sed 's/^ *//' | sort)
|
|
393
|
+
|
|
394
|
+
# Check for PYTHONPATH specifically — the exact var that caused the #11356 failure
|
|
395
|
+
CI_PYPATH=$(echo "$CI_ENVS" | grep "PYTHONPATH" || echo "(not set)")
|
|
396
|
+
DEPLOY_PYPATH=$(echo "$DEPLOY_ENVS" | grep "PYTHONPATH" || echo "(not set)")
|
|
397
|
+
if [ "$CI_PYPATH" != "$DEPLOY_PYPATH" ]; then
|
|
398
|
+
echo " BLOCKING: PYTHONPATH differs between ci.yml and deploy-production.yml for job '$JOB'"
|
|
399
|
+
echo " ci.yml: $CI_PYPATH"
|
|
400
|
+
echo " deploy-production: $DEPLOY_PYPATH"
|
|
401
|
+
echo " This WILL cause deploy failure — CI passes but deploy test step uses different Python path."
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
# Check for dependency installation steps that exist in one but not the other
|
|
405
|
+
CI_INSTALLS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$CI_WF" 2>/dev/null | grep -c "poetry install\|pip install\|npm install" || echo 0)
|
|
406
|
+
DEPLOY_INSTALLS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$DEPLOY_WF" 2>/dev/null | grep -c "poetry install\|pip install\|npm install" || echo 0)
|
|
407
|
+
if [ "$CI_INSTALLS" != "$DEPLOY_INSTALLS" ]; then
|
|
408
|
+
echo " WARNING: Different number of dependency install steps in '$JOB' — ci.yml has $CI_INSTALLS, deploy has $DEPLOY_INSTALLS"
|
|
409
|
+
echo " ACTION: Read both files and verify all dependencies needed by tests are installed in both workflows."
|
|
410
|
+
fi
|
|
411
|
+
|
|
412
|
+
# Check step names — if CI has a step that deploy doesn't, flag it
|
|
413
|
+
CI_STEPS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$CI_WF" 2>/dev/null | grep "- name:" | sed 's/.*- name: //' | sort)
|
|
414
|
+
DEPLOY_STEPS=$(sed -n "/^ ${JOB}:/,/^ [a-z]/p" "$DEPLOY_WF" 2>/dev/null | grep "- name:" | sed 's/.*- name: //' | sort)
|
|
415
|
+
MISSING_IN_DEPLOY=$(comm -23 <(echo "$CI_STEPS") <(echo "$DEPLOY_STEPS") 2>/dev/null || true)
|
|
416
|
+
if [ -n "$MISSING_IN_DEPLOY" ]; then
|
|
417
|
+
echo " WARNING: Steps in ci.yml '$JOB' missing from deploy-production.yml:"
|
|
418
|
+
echo "$MISSING_IN_DEPLOY" | sed 's/^/ - /'
|
|
419
|
+
fi
|
|
420
|
+
done
|
|
421
|
+
fi
|
|
422
|
+
fi
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Step 2.5C: Record Broken Assumptions
|
|
426
|
+
|
|
427
|
+
Any WARNING from the checks above is a **CONFIRMED finding** — the changed code may not execute as intended. Record these as pre-found issues that will be included in the agent context (Phase 3) and in the final findings (Phase 6).
|
|
428
|
+
|
|
429
|
+
Format: `INTEG-N|CONFIRMED|HIGH|file:line|Changed code may be unreachable: {reason}`
|
|
430
|
+
|
|
431
|
+
**This phase is NOT optional.** It runs for every review, regardless of PR size or domain. A 3-line route handler change that fails this check is more dangerous than a 500-line refactor that passes.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## Phase 3: Agent Selection & Launch
|
|
436
|
+
|
|
437
|
+
### 3A: Risk Assessment
|
|
438
|
+
|
|
439
|
+
**NEVER scale review depth by line count.** A 5-line shell script processing LLM output is more dangerous than a 500-line React component.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
DIFF=$(gh pr diff $ARGUMENTS)
|
|
443
|
+
FILES=$(gh pr diff $ARGUMENTS --name-only)
|
|
444
|
+
echo "=== RISK SIGNALS ==="
|
|
445
|
+
echo "$DIFF" | grep -cE "subprocess|exec|eval|system\(|popen|heredoc|EMEMO_EOF|LLM_|llm_" && echo " UNTRUSTED_INPUT_PROCESSING" || true
|
|
446
|
+
echo "$DIFF" | grep -cE "\.sh$|bash|shell|cron" && echo " SHELL_SCRIPT" || true
|
|
447
|
+
echo "$DIFF" | grep -cE "credit|balance|debit|charge|refund|stripe" && echo " FINANCIAL" || true
|
|
448
|
+
echo "$DIFF" | grep -cE "SessionUser|CurrentUser|jwt|oauth|password|token|secret|x.forwarded.for|x_forwarded_for|forwarded_for|rate.limit.*ip|ip.*rate.limit|algorithm.*HS256|algorithm.*RS256|NEXTAUTH_SECRET|JWT_SECRET|admin.proxy" && echo " AUTH_SENSITIVE" || true
|
|
449
|
+
echo "$DIFF" | grep -cE "\.sql$|migration|DROP|ALTER|DELETE FROM" && echo " DATABASE_MUTATION" || true
|
|
450
|
+
echo "$DIFF" | grep -cE "docker|deploy|traefik|nginx|\.yml.*service" && echo " INFRASTRUCTURE" || true
|
|
451
|
+
echo "$DIFF" | grep -cE "docker-compose.*postgres|docker-compose.*redis|postgres.*command:|redis.*command:|image:.*postgres|image:.*redis" && echo " DATABASE_CONTAINER" || true
|
|
452
|
+
echo "$DIFF" | grep -cE "create_async_engine|AsyncSession|connect_args|pool_size|prepared_statement|engine_from_config|sessionmaker" && echo " DB_CONFIG" || true
|
|
453
|
+
echo "$DIFF" | grep -cE "subprocess|os\.system|eval\(|exec\(|pickle|yaml\.load[^_]" && echo " CODE_EXECUTION" || true
|
|
454
|
+
echo "$FILES" | grep -cE "^sdk/|openapi.*\.json$|openapi-versions/" && echo " SDK_OPENAPI" || true
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 3B: Select Agents
|
|
458
|
+
|
|
459
|
+
| Risk Signal | Required Agents |
|
|
460
|
+
|-------------|----------------|
|
|
461
|
+
| UNTRUSTED_INPUT_PROCESSING | Security (deep) + relevant domain |
|
|
462
|
+
| SHELL_SCRIPT | Security (deep) + Infrastructure |
|
|
463
|
+
| FINANCIAL | Security + Billing + Concurrency |
|
|
464
|
+
| AUTH_SENSITIVE | Security + Auth Conventions |
|
|
465
|
+
| DATABASE_MUTATION | Security + Database |
|
|
466
|
+
| DB_CONFIG | Security + Database + Infrastructure |
|
|
467
|
+
| INFRASTRUCTURE | Security + Infrastructure |
|
|
468
|
+
| DATABASE_CONTAINER | Security + Infrastructure (escalate to HIGH risk — stateful container restart) |
|
|
469
|
+
| CODE_EXECUTION | Security (deep) |
|
|
470
|
+
| SDK_OPENAPI | Security + API Design & Consistency (runs cross-PR schema check #10) |
|
|
471
|
+
| None | Security + all matching domain agents |
|
|
472
|
+
|
|
473
|
+
**General Security agent ALWAYS runs.** Domain agents selected by classification, not PR size. If BILLING detected, always also spawn Concurrency agent. If SHARED touched, spawn agents for all importing services.
|
|
474
|
+
|
|
475
|
+
**Domain-overlap dispatch (MANDATORY for multi-signal PRs):**
|
|
476
|
+
|
|
477
|
+
1. **Union semantics**: If multiple risk signals are detected, spawn the UNION of all agents from ALL matched rows. A PR triggering both `AUTH_SENSITIVE` and `FINANCIAL` spawns Security + Auth + Billing + Concurrency — not just one row's agents.
|
|
478
|
+
|
|
479
|
+
2. **Critical domain override**: If 2+ of these critical domains are detected in the same PR, spawn agents for ALL affected domains regardless of file count or line changes:
|
|
480
|
+
- AUTH + BILLING → Security (deep) + Auth + Billing + Concurrency
|
|
481
|
+
- AUTH + DATABASE_MUTATION → Security (deep) + Auth + Database
|
|
482
|
+
- FINANCIAL + CONCURRENCY → Security (deep) + Billing + Concurrency + Database
|
|
483
|
+
- AUTH + CODE_EXECUTION → Security (deep) + Auth
|
|
484
|
+
|
|
485
|
+
3. **Why this exists**: A 2-file PR touching both `services/api/app/core/auth.py` and `services/api/app/routers/billing.py` is MORE dangerous than a 20-file refactor in one domain. Small cross-domain PRs create interaction bugs that single-domain reviewers cannot catch. Never reduce agent count based on file count when multiple critical domains are involved.
|
|
486
|
+
|
|
487
|
+
### 3C: Load Agent Templates & Launch
|
|
488
|
+
|
|
489
|
+
**>>> INVOCATION: Read the agent catalog file:**
|
|
490
|
+
```
|
|
491
|
+
Read the file: /home/mrdubey/.claude/commands/review-pr-agents.md
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
This file contains the Evidence-Based Review Protocol, Structured Findings Protocol, and all 9 agent prompt templates. For each selected agent:
|
|
495
|
+
1. Extract its template from the catalog
|
|
496
|
+
2. Substitute: `[PR_NUMBER]`, `[REVIEW_SHA]`, `[REVIEW_SHA_SHORT]`, `[TITLE]`, relevant files list
|
|
497
|
+
3. If Phase 2.5 found broken assumptions, append them to the agent's prompt as "Pre-found integration issues to verify"
|
|
498
|
+
4. Launch via `Task` tool with the resolved model (default `"sonnet"`, fallback `"opus"` if rate-limited)
|
|
499
|
+
|
|
500
|
+
**CRITICAL**: Launch ALL selected agents in a SINGLE message using multiple Task tool calls. Each agent posts findings directly to the PR via `gh pr comment`.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## Phase 4: Wait for Agents
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
gh pr view $ARGUMENTS --json comments --jq '.comments | length'
|
|
508
|
+
gh api repos/{owner}/{repo}/issues/$ARGUMENTS/comments --jq '.[-10:] | .[].body[:100]'
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Do NOT proceed until ALL launched agent comments are visible on the PR.**
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Phase 5: Synthesis (Multi-Agent Arbitration)
|
|
516
|
+
|
|
517
|
+
**Skip if**: Only 1 agent OR total findings ≤ 3.
|
|
518
|
+
|
|
519
|
+
```bash
|
|
520
|
+
ALL_FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '.[].body' | grep -oP '(?<=<!-- FINDING:).*?(?= -->)')
|
|
521
|
+
AGENT_COUNT=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '[.[] | select(.body | test("REVIEW-FINDINGS-START"))] | length')
|
|
522
|
+
FINDING_COUNT=$(echo "$ALL_FINDINGS" | grep -c '.' || echo 0)
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
If synthesis needed, launch a `general-purpose` Task (model: resolved per policy — default sonnet, fallback opus):
|
|
526
|
+
- Deduplicate findings by file + line range ±5 (keep higher confidence)
|
|
527
|
+
- Resolve contradictions by reading disputed code
|
|
528
|
+
- Dismiss false positives with evidence
|
|
529
|
+
- Post synthesis comment with `<!-- REVIEW-FINDINGS-SYNTHESIZED-START -->` block
|
|
530
|
+
- Do NOT add new findings — only triage existing ones
|
|
531
|
+
|
|
532
|
+
**IMPORTANT**: When synthesized block exists, Phase 6 MUST use it instead of raw findings.
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Phase 6: Finding Triage & Issue Creation (MANDATORY)
|
|
537
|
+
|
|
538
|
+
**STOP. DO NOT skip this phase. DO NOT post summary first.** Every finding MUST become a GitHub issue BEFORE the summary.
|
|
539
|
+
|
|
540
|
+
### 6A: Extract Findings
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
544
|
+
HAS_SYNTHESIS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '.[].body' | grep -c 'REVIEW-FINDINGS-SYNTHESIZED-START' || echo 0)
|
|
545
|
+
|
|
546
|
+
if [ "$HAS_SYNTHESIS" -gt 0 ]; then
|
|
547
|
+
FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '.[] | select(.body | test("REVIEW-FINDINGS-SYNTHESIZED-START")) | .body' | grep -oP '(?<=<!-- FINDING:).*?(?= -->)')
|
|
548
|
+
else
|
|
549
|
+
FINDINGS=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" --jq '.[].body' | grep -oP '(?<=<!-- FINDING:).*?(?= -->)')
|
|
550
|
+
fi
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Also include any `INTEG-N` findings from Phase 2.5 that weren't already covered by agents.
|
|
554
|
+
|
|
555
|
+
If 0 structured findings: scan agent comments for unstructured findings (lines starting with "Finding", "Issue", "Bug", "Warning"; sections titled "Findings", "Issues Found"). Extract manually.
|
|
556
|
+
|
|
557
|
+
If still 0: review is clean — skip to Phase 7.
|
|
558
|
+
|
|
559
|
+
### 6B: Deduplicate
|
|
560
|
+
- Keep ALL confidence levels (CONFIRMED, LIKELY, POSSIBLE)
|
|
561
|
+
- Dedup by file + line range ±5 — keep higher confidence (covers off-by-one from upstream insertions)
|
|
562
|
+
- Also dedup by title similarity: if two findings share the same file and 3+ title keywords, keep the higher confidence one
|
|
563
|
+
- Sort: CONFIRMED first, then LIKELY, then POSSIBLE; within group by severity
|
|
564
|
+
|
|
565
|
+
### 6C: Create Issues
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
gh label create "review-finding" --color "D93F0B" --force 2>/dev/null
|
|
569
|
+
gh label create "needs-validation" --color "FBCA04" --force 2>/dev/null
|
|
570
|
+
gh label create "validated" --color "0E8A16" --force 2>/dev/null
|
|
571
|
+
gh label create "false-positive" --color "CCCCCC" --force 2>/dev/null
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Milestone detection:**
|
|
575
|
+
```bash
|
|
576
|
+
# Check BOTH head and base branches — feature PRs targeting milestone/* branches
|
|
577
|
+
# should inherit the milestone for their review findings
|
|
578
|
+
BASE_BRANCH=$(gh pr view ${PR_NUMBER} --json baseRefName --jq '.baseRefName')
|
|
579
|
+
HEAD_BRANCH=$(gh pr view ${PR_NUMBER} --json headRefName --jq '.headRefName')
|
|
580
|
+
MILESTONE_FLAG=""
|
|
581
|
+
|
|
582
|
+
# First check: PR's base branch is a milestone branch (most common for feature-lane PRs)
|
|
583
|
+
MILESTONE_BRANCH=""
|
|
584
|
+
if echo "$BASE_BRANCH" | grep -qE "^milestone/"; then
|
|
585
|
+
MILESTONE_BRANCH="$BASE_BRANCH"
|
|
586
|
+
elif echo "$HEAD_BRANCH" | grep -qE "^milestone/"; then
|
|
587
|
+
MILESTONE_BRANCH="$HEAD_BRANCH"
|
|
588
|
+
fi
|
|
589
|
+
|
|
590
|
+
if [ -n "$MILESTONE_BRANCH" ]; then
|
|
591
|
+
BRANCH_SLUG=$(echo "$MILESTONE_BRANCH" | sed 's|^milestone/||')
|
|
592
|
+
MILESTONE_NUMBER=$(gh api repos/${REPO}/milestones --jq --arg slug "$BRANCH_SLUG" '.[] | select((.title | ascii_downcase | gsub("[^a-z0-9]+"; "-")) == $slug) | .number' 2>/dev/null | head -1)
|
|
593
|
+
[ -z "$MILESTONE_NUMBER" ] && MILESTONE_NUMBER=$(gh api repos/${REPO}/milestones --jq --arg slug "$BRANCH_SLUG" '.[] | select((.title | ascii_downcase | gsub("[^a-z0-9]+"; "-")) | test($slug)) | .number' 2>/dev/null | head -1)
|
|
594
|
+
[ -n "$MILESTONE_NUMBER" ] && MILESTONE_FLAG="--milestone $(gh api repos/${REPO}/milestones/${MILESTONE_NUMBER} --jq '.title')"
|
|
595
|
+
fi
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
**Dedup against existing issues (MANDATORY before creating):**
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
# For each finding, check if an open issue already exists at the same file within ±5 lines
|
|
602
|
+
FINDING_FILE="path/to/file.py"
|
|
603
|
+
FINDING_LINE="123"
|
|
604
|
+
FINDING_TITLE="fix: brief description of finding (review finding — PR #${PR_NUMBER})"
|
|
605
|
+
|
|
606
|
+
# Build line-range bounds: ±5 tolerance covers typical off-by-one from upstream insertions
|
|
607
|
+
LINE_MIN=$((FINDING_LINE - 5))
|
|
608
|
+
LINE_MAX=$((FINDING_LINE + 5))
|
|
609
|
+
|
|
610
|
+
# Check open issues for line-range overlap OR title similarity on the same file
|
|
611
|
+
CANDIDATES=$(gh issue list --state open --label "review-finding" --limit 100 --json number,title,body \
|
|
612
|
+
--jq "[.[] | select(.body | test(\"${FINDING_FILE}\"))]" 2>/dev/null)
|
|
613
|
+
|
|
614
|
+
EXISTING=$(echo "$CANDIDATES" | jq -r --arg file "$FINDING_FILE" --argjson min "$LINE_MIN" --argjson max "$LINE_MAX" --arg title "$FINDING_TITLE" '
|
|
615
|
+
.[] |
|
|
616
|
+
# Extract line number from issue body (pattern: `file.py:NNN`)
|
|
617
|
+
((.body | capture($file + ":(?<ln>[0-9]+)") .ln // "0") | tonumber) as $existing_line |
|
|
618
|
+
if $existing_line >= $min and $existing_line <= $max then
|
|
619
|
+
.number
|
|
620
|
+
# Fallback: title keyword overlap (3+ shared words of length >3 → likely same finding)
|
|
621
|
+
elif ([($title | ascii_downcase | split(" ") | .[] | select(length > 3))] -
|
|
622
|
+
[(.title | ascii_downcase | split(" ") | .[] | select(length > 3))]) |
|
|
623
|
+
length <= ([$title | ascii_downcase | split(" ") | .[] | select(length > 3)] | length) - 3 then
|
|
624
|
+
.number
|
|
625
|
+
else empty end
|
|
626
|
+
' 2>/dev/null | head -1)
|
|
627
|
+
|
|
628
|
+
if [ -n "$EXISTING" ]; then
|
|
629
|
+
echo "DEDUP: Skipping — open issue #${EXISTING} already covers ${FINDING_FILE}:${FINDING_LINE} (range/title match)"
|
|
630
|
+
# Skip this finding — do NOT create a duplicate issue
|
|
631
|
+
else
|
|
632
|
+
# Check if a CLOSED issue exists at the same location ±5 lines (regression)
|
|
633
|
+
CLOSED_CANDIDATES=$(gh issue list --state closed --label "review-finding" --limit 100 --json number,title,body \
|
|
634
|
+
--jq "[.[] | select(.body | test(\"${FINDING_FILE}\"))]" 2>/dev/null)
|
|
635
|
+
|
|
636
|
+
REGRESSION=$(echo "$CLOSED_CANDIDATES" | jq -r --arg file "$FINDING_FILE" --argjson min "$LINE_MIN" --argjson max "$LINE_MAX" '
|
|
637
|
+
.[] |
|
|
638
|
+
((.body | capture($file + ":(?<ln>[0-9]+)") .ln // "0") | tonumber) as $existing_line |
|
|
639
|
+
if $existing_line >= $min and $existing_line <= $max then
|
|
640
|
+
.number
|
|
641
|
+
else empty end
|
|
642
|
+
' 2>/dev/null | head -1)
|
|
643
|
+
if [ -n "$REGRESSION" ]; then
|
|
644
|
+
echo "REGRESSION: Previously fixed in #${REGRESSION} — elevating priority"
|
|
645
|
+
# Create with regression warning and P1 priority
|
|
646
|
+
fi
|
|
647
|
+
fi
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**Rules:**
|
|
651
|
+
- Open `review-finding` issue at same file within ±5 lines → **skip** (do not create duplicate)
|
|
652
|
+
- Open `review-finding` issue at same file with similar title (3+ shared keywords) → **skip** (likely same finding despite line drift)
|
|
653
|
+
- Closed `review-finding` at same file within ±5 lines → create with regression warning, elevate to P1
|
|
654
|
+
|
|
655
|
+
**For each finding** (that passes dedup), create issue:
|
|
656
|
+
```bash
|
|
657
|
+
ISSUE_NUM=$(gh issue create \
|
|
658
|
+
--title "fix: [summary] (review finding — PR #${PR_NUMBER})" \
|
|
659
|
+
--label "review-finding,needs-validation,{priority}" \
|
|
660
|
+
${MILESTONE_FLAG} \
|
|
661
|
+
--body "$(cat <<'ISSUE_EOF'
|
|
662
|
+
## Problem
|
|
663
|
+
|
|
664
|
+
[One sentence: what bug or issue was found. Where it occurs (`file:line`) and what it causes.]
|
|
665
|
+
|
|
666
|
+
**Source**: PR #[PR_NUMBER] — [TITLE]
|
|
667
|
+
**Agent**: [name] ([domain])
|
|
668
|
+
**Confidence**: [CONFIRMED/LIKELY/POSSIBLE]
|
|
669
|
+
**Severity**: [CRITICAL/HIGH/MEDIUM/LOW]
|
|
670
|
+
**Review comment**: [permalink to agent comment]
|
|
671
|
+
|
|
672
|
+
## Pattern Metadata
|
|
673
|
+
|
|
674
|
+
**Pattern**: [short slug identifying the bug class, e.g. type-coercion-at-boundary, missing-auth-check, n+1-query]
|
|
675
|
+
**Files**: [affected file path(s)]
|
|
676
|
+
**Root cause**: [one sentence — why the bug occurs mechanically]
|
|
677
|
+
**Prevention**: [one sentence — what the builder must do to avoid this class of bug]
|
|
678
|
+
|
|
679
|
+
## Affected Files
|
|
680
|
+
|
|
681
|
+
Files that need changes:
|
|
682
|
+
1. `[file:line]` — [what needs to change to fix this finding]
|
|
683
|
+
|
|
684
|
+
## Source Branch Context
|
|
685
|
+
|
|
686
|
+
**Code branch**: `[HEAD_BRANCH]`
|
|
687
|
+
**Worktree base**: `origin/[HEAD_BRANCH]`
|
|
688
|
+
|
|
689
|
+
> When fixing: `git worktree add ../fix-{slug} -b fix/{slug} origin/[HEAD_BRANCH]`
|
|
690
|
+
|
|
691
|
+
## Code Context
|
|
692
|
+
[10 lines around finding]
|
|
693
|
+
|
|
694
|
+
## Evidence
|
|
695
|
+
[From agent comment]
|
|
696
|
+
|
|
697
|
+
## Acceptance Criteria
|
|
698
|
+
|
|
699
|
+
- [ ] Finding validated: VALIDATED / FALSE_POSITIVE / INCONCLUSIVE
|
|
700
|
+
- [ ] If VALIDATED: fix implemented and tested on correct branch
|
|
701
|
+
- [ ] Read code at location on correct branch
|
|
702
|
+
- [ ] Trace code path to verify issue
|
|
703
|
+
- [ ] Check existing mitigations
|
|
704
|
+
- [ ] Reproduce or construct proof-of-concept
|
|
705
|
+
ISSUE_EOF
|
|
706
|
+
)" --json number --jq '.number')
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
Labels: `review-finding` + `needs-validation` + priority (`P1` CONFIRMED, `P2` LIKELY, `P3` POSSIBLE).
|
|
710
|
+
|
|
711
|
+
**Add to project board:**
|
|
712
|
+
```bash
|
|
713
|
+
for FINDING_NUM in {numbers}; do
|
|
714
|
+
ITEM_ID=$(gh project item-add 1 --owner RapierCraft --url "https://github.com/RapierCraft/AlterLab/issues/${FINDING_NUM}" --format json --jq '.id' 2>/dev/null)
|
|
715
|
+
[ -n "$ITEM_ID" ] && {
|
|
716
|
+
gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF6E --single-select-option-id f75ad846 2>/dev/null || true
|
|
717
|
+
gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF98 --single-select-option-id 62864af4 2>/dev/null || true
|
|
718
|
+
}
|
|
719
|
+
done
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### 6D: Update PR Description
|
|
723
|
+
|
|
724
|
+
Append `## Review Findings` table to PR body with finding summaries and issue links.
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## Phase 7: Official Review Action
|
|
729
|
+
|
|
730
|
+
### 7A: Purpose Regression Gate (Milestone PRs Only)
|
|
731
|
+
|
|
732
|
+
**Skip if**: `IS_MILESTONE_TO_STAGING` is false (i.e., HEAD branch does NOT start with `milestone/`). This gate fires ONLY for milestone→staging PRs.
|
|
733
|
+
|
|
734
|
+
**Why this exists**: For milestone PRs, a CONFIRMED finding can be a functional regression even if it doesn't cause a runtime crash. A stealth milestone shipping a detectable signal is the stealth equivalent of a crash — the milestone's entire purpose is negated. The orchestrator's default heuristic (crash or data corruption = blocking) is insufficient here. This gate adds an explicit purpose-aware blocking criterion.
|
|
735
|
+
|
|
736
|
+
**Step 1 — Extract milestone purpose:**
|
|
737
|
+
```bash
|
|
738
|
+
# PR title and milestone name were fetched in Phase 1A
|
|
739
|
+
# Examples: "Stealth Engine Overhaul", "Session Intelligence", "Billing Reconciliation"
|
|
740
|
+
# Derive the capability domain from the milestone/PR title:
|
|
741
|
+
# "stealth" → detection avoidance, fingerprint consistency, proxy signal coherence
|
|
742
|
+
# "performance" → latency, throughput, resource utilization
|
|
743
|
+
# "billing" → charge accuracy, credit calculation, subscription state
|
|
744
|
+
# "auth" → session validity, token correctness, permission enforcement
|
|
745
|
+
# "session" → session state consistency, persistence, expiry
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Step 2 — Evaluate each finding for purpose regression:**
|
|
749
|
+
|
|
750
|
+
For each finding that is CONFIRMED or LIKELY at MEDIUM+ severity (already created as a GitHub issue in Phase 6), apply the purpose regression test:
|
|
751
|
+
|
|
752
|
+
> **The test**: "If someone described this milestone's goal in one sentence (e.g., 'Improve stealth to avoid bot detection'), would this finding represent the opposite of that goal?"
|
|
753
|
+
>
|
|
754
|
+
> - A **stealth milestone** + a CONFIRMED finding about a detectable signal/fingerprint mismatch → **PURPOSE REGRESSION** → BLOCKING
|
|
755
|
+
> - A **performance milestone** + a CONFIRMED finding about increased latency or higher resource usage → **PURPOSE REGRESSION** → BLOCKING
|
|
756
|
+
> - A **billing milestone** + a CONFIRMED finding about incorrect charge calculation or credit leak → **PURPOSE REGRESSION** → BLOCKING
|
|
757
|
+
> - A **stealth milestone** + a CONFIRMED finding about a formatting inconsistency or a missing log line → **NOT a purpose regression** → advisory only (still gets a GitHub issue, but does not block)
|
|
758
|
+
|
|
759
|
+
**Step 3 — Set verdict flag:**
|
|
760
|
+
```bash
|
|
761
|
+
HAS_PURPOSE_REGRESSION=false
|
|
762
|
+
|
|
763
|
+
# For each CONFIRMED/LIKELY finding at MEDIUM+ severity:
|
|
764
|
+
# Read the finding's title/description from the GitHub issue created in Phase 6.
|
|
765
|
+
# Apply the purpose regression test above.
|
|
766
|
+
# If the finding contradicts the milestone's stated capability improvement:
|
|
767
|
+
HAS_PURPOSE_REGRESSION=true
|
|
768
|
+
PURPOSE_REGRESSION_FINDINGS+=("Finding ID: ..., Reason: ...")
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
**Step 4 — Log result:**
|
|
772
|
+
|
|
773
|
+
If `HAS_PURPOSE_REGRESSION=true`:
|
|
774
|
+
```
|
|
775
|
+
PURPOSE REGRESSION GATE: BLOCKED
|
|
776
|
+
Reason: [finding] contradicts milestone goal "[milestone name]"
|
|
777
|
+
Verdict escalated to CHANGES REQUESTED.
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
If no purpose regression found:
|
|
781
|
+
```
|
|
782
|
+
PURPOSE REGRESSION GATE: PASSED
|
|
783
|
+
No findings contradict the milestone's stated purpose.
|
|
784
|
+
Verdict determined by standard blocking criteria.
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
789
|
+
### 7B: Post Verdict
|
|
790
|
+
|
|
791
|
+
`gh pr review --approve` fails for self-reviews. Always use `--comment`.
|
|
792
|
+
|
|
793
|
+
**Blocking criteria** — a finding is BLOCKING if ANY of the following are true:
|
|
794
|
+
1. Phase 2 automated checks failed (build error, type error, test failure)
|
|
795
|
+
2. Agent finding is CONFIRMED at HIGH or CRITICAL severity
|
|
796
|
+
3. **[Milestone PRs only]** Phase 7A Purpose Regression Gate flagged `HAS_PURPOSE_REGRESSION=true` for this finding — regardless of whether it causes a runtime error
|
|
797
|
+
|
|
798
|
+
```bash
|
|
799
|
+
# Stale review:
|
|
800
|
+
gh pr review $ARGUMENTS --comment --body "Review of commit $REVIEW_SHA_SHORT is stale — PR HEAD changed. Re-run /review-pr."
|
|
801
|
+
|
|
802
|
+
# Clean (no blocking issues):
|
|
803
|
+
gh pr review $ARGUMENTS --comment --body "APPROVED: commit $REVIEW_SHA_SHORT after context-aware review ([N] agents: [names]). [M] findings created as issues. Safe to merge."
|
|
804
|
+
|
|
805
|
+
# Blocking issues (including purpose regressions):
|
|
806
|
+
gh pr review $ARGUMENTS --comment --body "CHANGES REQUESTED: commit $REVIEW_SHA_SHORT — [N] blocking issues found. See GitHub issues.
|
|
807
|
+
$([ "$HAS_PURPOSE_REGRESSION" = "true" ] && echo "
|
|
808
|
+
⚠ Purpose Regression: [N] finding(s) contradict the milestone's stated goal and are automatically blocking regardless of runtime impact. See: ${PURPOSE_REGRESSION_FINDINGS[@]}")"
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## Phase 8: Auto-Merge (Conditional)
|
|
814
|
+
|
|
815
|
+
**Skip if** `AUTO_MERGE=false`.
|
|
816
|
+
|
|
817
|
+
```bash
|
|
818
|
+
# Checkpoint comment on issue
|
|
819
|
+
gh issue comment {MERGE_ISSUE} {MERGE_GH_FLAG} --body "Review complete for PR #{PR_NUMBER}. Verdict: {VERDICT}. Proceeding to merge."
|
|
820
|
+
|
|
821
|
+
# Merge
|
|
822
|
+
gh pr merge {PR_NUMBER} {MERGE_GH_FLAG} --merge
|
|
823
|
+
|
|
824
|
+
# Verify
|
|
825
|
+
MERGE_STATE=$(gh pr view {PR_NUMBER} {MERGE_GH_FLAG} --json state --jq '.state')
|
|
826
|
+
[ "$MERGE_STATE" != "MERGED" ] && gh issue comment {MERGE_ISSUE} {MERGE_GH_FLAG} --body "PR #{PR_NUMBER} merge failed. State: $MERGE_STATE."
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Important**: Phase 8 ONLY merges the PR. It does NOT close the issue, update labels, or clean up worktrees. When invoked via `/work-on`, those responsibilities belong to `work-on/close.md` — which runs after the router detects state 4 (MERGE_COMPLETE: PR merged + issue open). Doing them here would cause the router to hit TERMINAL_MERGED (state 1) and skip the close phase entirely.
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## Phase 9: Integrity Check & Summary (LAST)
|
|
834
|
+
|
|
835
|
+
**Run AFTER Phase 6 (issues created), Phase 7 (verdict posted), Phase 8 (merge if applicable).**
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
CURRENT_SHA=$(gh pr view $ARGUMENTS --json headRefOid --jq '.headRefOid')
|
|
839
|
+
REVIEW_IS_STALE=$([[ "$CURRENT_SHA" != "$REVIEW_SHA" ]] && echo "true" || echo "false")
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
```bash
|
|
843
|
+
gh pr comment $ARGUMENTS --body "$(cat <<'EOF'
|
|
844
|
+
# PR Review Summary: #[NUMBER] - [TITLE]
|
|
845
|
+
|
|
846
|
+
## Review Integrity
|
|
847
|
+
**Reviewed commit**: `[SHA]` | **Current HEAD**: `[SHA]` | **Status**: [CURRENT/STALE]
|
|
848
|
+
|
|
849
|
+
## Verdict: [APPROVE / CHANGES REQUESTED / NEEDS RE-REVIEW]
|
|
850
|
+
|
|
851
|
+
## Context-Aware Review
|
|
852
|
+
**Domains**: [list] | **Agents**: [N] ([names])
|
|
853
|
+
|
|
854
|
+
## Integration Checks (Phase 2.5)
|
|
855
|
+
**Code registration**: [pass / N broken activation paths found]
|
|
856
|
+
**SOPS deploy chain**: [pass / not applicable / N warnings found]
|
|
857
|
+
**Purpose Regression Gate (7A)**: [N/A — non-milestone PR / PASSED — no purpose regressions / BLOCKED — N finding(s) contradict milestone goal]
|
|
858
|
+
|
|
859
|
+
## Risk Matrix
|
|
860
|
+
| Category | Risk | Blocking? | Confidence |
|
|
861
|
+
|----------|------|-----------|------------|
|
|
862
|
+
|
|
863
|
+
## Findings
|
|
864
|
+
| Finding | Severity | Confidence | Issue |
|
|
865
|
+
|---------|----------|------------|-------|
|
|
866
|
+
|
|
867
|
+
## Automated Checks
|
|
868
|
+
| Check | Result |
|
|
869
|
+
|-------|--------|
|
|
870
|
+
|
|
871
|
+
## Recommendation
|
|
872
|
+
[Final recommendation]
|
|
873
|
+
|
|
874
|
+
---
|
|
875
|
+
*Context-aware review complete. [N] agents + integration checks. [M] findings triaged.*
|
|
876
|
+
EOF
|
|
877
|
+
)"
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
Notify user:
|
|
881
|
+
```
|
|
882
|
+
Review complete for PR #X. Verdict: [VERDICT].
|
|
883
|
+
- Domains: [list]
|
|
884
|
+
- Agents: [N] ([names])
|
|
885
|
+
- Integration checks: [pass/N broken paths found]
|
|
886
|
+
- Issues created: [M]
|
|
887
|
+
{IF AUTO_MERGE: "PR merged and issue closed." / "Merge FAILED — see issue."}
|
|
888
|
+
```
|