| name | review-pr |
| description | Recommend the right reviewer count for a PR based on size + bias factors. Outputs a concrete plan (inline spot-check / 1 reviewer / 3-axis parallel) plus ready-to-paste Agent dispatch prompts when reviewers are warranted. |
| argument-hint | <PR-number> |
PR Review Recommendation
Decide how much review rigor a PR warrants — and surface the dispatch prompts for the orchestrator to copy-paste. Running none on a large security-sensitive PR misses bugs; the tiers below say what a PR needs AT MINIMUM.
The recommended tier is a FLOOR, not a cap, and wall-clock / token cost is never a reason to come in under it or to stop at it. See the "Cost is not a tiebreaker" rule in CLAUDE.md → Workflow Rules: when two options differ in how thoroughly they verify, take the more thorough one; when you are unsure which tier applies, take the higher one. The tiers exist to stop a PR being UNDER-reviewed, not to ration review. Reviewers are read-only agents that run in parallel, so the only thing a larger tier costs is time the maintainer has already said to spend.
The skill itself never spawns reviewers. It reads PR stats, applies the heuristic, and prints a recommendation. The main session orchestrator (the parent reading this skill's output) is responsible for actually issuing the Agent tool calls when the recommendation says to.
Inputs
- Required: PR number (positional). Example:
/review-pr 237.
Steps
-
Fetch PR stats via gh:
gh pr view <N> --json additions,deletions,changedFiles,title,headRefName,files \
-q '{a: .additions, d: .deletions, fc: .changedFiles, title: .title, branch: .headRefName, paths: [.files[].path]}'
Record: additions (a), deletions (d), changedFiles (fc), title, branch, list of file paths.
Compute loc = a + d.
Subtract auto-generated LOC before computing the tier — generated artifacts under docs/_generated/** (provider-coverage matrices, integ-coverage matrices, snapshot fixtures, etc.) and lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock) inflate LOC without adding reviewer surface. Reviewers do not (and cannot meaningfully) audit these files line-by-line; they only verify the SCRIPT that produced them. Compute loc from the diff with these paths excluded:
excluded=$(gh pr view <N> --json files \
-q '[.files[] | select(.path | test("^docs/_generated/|(^|/)pnpm-lock\\.yaml$|(^|/)package-lock\\.json$|(^|/)yarn\\.lock$")) | .additions + .deletions] | add // 0')
loc=$(( a + d - excluded ))
Caught in PR #404 (issue #392): 4286 raw LOC → 3-axis tier, but ~2900 LOC was auto-generated docs/_generated/integ-coverage.json + docs/integ-coverage.md. Substantive surface was ~1100 LOC, which is squarely 1-reviewer tier. Auto-gen exclusion produces the right answer without sacrificing rigor on the substantive code. Note: fc is NOT adjusted — a 12-file diff is still cross-cutting even when 2 of the files are generated. The lockfile patterns are (^|/)-anchored because cdkd's pnpm-lock.yaml lives at the repo ROOT — a bare /pnpm-lock\.yaml$ pattern silently never matched it (caught on PR #1082, whose 2747-LOC lockfile churn pushed a 37-LOC toolchain PR into 3-axis). The same exclusion now also lives in .claude/hooks/pr-review-gate.sh (it previously computed raw LOC, disagreeing with this skill) — keep the two regexes in sync when editing.
-
Determine the base tier from (loc, fc) per the heuristic:
| Condition | Base tier |
|---|
loc < 300 OR fc < 5 | inline (inline spot-check by the orchestrator) |
300 <= loc < 1000 AND 5 <= fc < 10 | 1-reviewer (single code-quality pass) |
loc >= 1000 OR fc >= 10 | 3-axis (spec + code + test in parallel) |
The two boundary conditions overlap intentionally — a 200-LOC / 12-file PR triggers 3-axis via the file count even though LOC is small (a 12-file diff has cross-cutting risk regardless of LOC), and a 5000-LOC / 3-file PR triggers 3-axis via LOC (rare in practice but covered).
-
Compute bias factors by scanning the paths list:
Up-bias triggers (move tier UP by one step, never above 3-axis):
-
Any path matches security / process-launch surface:
src/utils/role-arn.ts
src/utils/docker-cmd.ts
src/local/cognito-jwt.ts
src/local/authorizer-resolver.ts
src/local/authorizer-cache.ts
src/local/sigv4-verify.ts
src/local/agentcore-sigv4-sign.ts
src/local/docker-runner.ts
src/local/docker-image-builder.ts
src/local/ecr-puller.ts
src/local/ecs-secrets-resolver.ts
src/local/ecs-task-runner.ts
What belongs here: a file is on this list when it (a) verifies or mints authn material or loads credentials, (b) resolves secret material, or (c) launches a process or derives the executable path one is launched from. Consumers of those primitives are NOT listed — container-pool.ts and rie-client.ts drive containers through docker-runner.ts, which is listed, so listing them too would only blur what the up-bias means.
Several entries (cognito-jwt.ts, authorizer-resolver.ts, authorizer-cache.ts, sigv4-verify.ts, agentcore-sigv4-sign.ts) are thin re-export shims whose implementation now lives in cdk-local; docker-image-builder.ts is a wrapper that translates cdk-local's error class rather than a bare re-export. They stay listed on purpose: a shim edit changes WHICH implementation cdkd consumes, which is exactly the swap the up-bias exists to catch. Note this gates the CHOICE, not the logic — when the implementation itself changes, the review happens on the cdk-local bump PR.
Two ways this list rots, both silent and both seen in issue #1972: an entry stops existing (src/local/lambda-authorizer.ts outlived its move to cdk-local in PR #691; src/local-invoke/docker-runner.ts outlived the PR #228 rename), or a live surface never gets added (the AWS_IAM verifier sigv4-verify.ts and the exec-path primitive were both missing while their callers were listed). now fences the first failure mode and the four-copy sync; the second still needs judgment, so re-apply the (a)/(b)/(c) test above when this area changes.
-
Apply the bias to compute the final tier:
inline + up → 1-reviewer
1-reviewer + up → 3-axis
3-axis + up → 3-axis (clamp)
3-axis + down → 1-reviewer
1-reviewer + down → inline
inline + down → inline (clamp)
-
Render the recommendation in the format below.
-
Dispatch reviewers + set the marker (only when final_tier is 1-reviewer or 3-axis):
The recommendation tells the orchestrator what to do. The orchestrator
then dispatches the recommended reviewers (1 or 3) via the Agent tool,
waits for all of them to complete, and synthesizes the findings:
-
If any blocker surfaces (correctness bugs, security issues,
test gaps that justify rejecting the PR), the marker is NOT set
— the orchestrator addresses the blockers (or asks the
implementing agent to fix them) and re-runs /review-pr <N>.
-
If every finding is minor / nit / clean, the orchestrator
sets the marker bound to the PR's current HEAD sha:
gh pr view <N> --json headRefOid -q .headRefOid > .markgate-pr-review-sha
mise exec -- markgate set pr-review
For the inline tier, the marker is NOT set — the gate's heuristic
also outputs inline for the same PR, so no enforcement fires and
the merge proceeds without a marker.
Security add-on dispatch. When the security add-on trigger fired (see
step 3), dispatch pr-security-reviewer ALONGSIDE the tier reviewers (add it
to the same parallel batch), wait for it too, and fold its findings into the
synthesis: a security blocker blocks the marker exactly like any other. This
applies at EVERY tier, included — an -by-size security PR
still gets the security reviewer, even though the size-tier gate (
marker) does not fire for it. The hook is unchanged in
this phase (it enforces only the size tier), so the security reviewer here is
a skill-level requirement the orchestrator honours, not a hard merge gate; a
hard gate is the deferred phase-2 in the tracking issue.
Output template
Recommendation: <inline | 1-reviewer | 3-axis>
PR #<N>: <title>
Stats: +<additions> / -<deletions> = <loc> LOC, <fc> files
Branch: <branch>
Base tier (from stats): <base>
Bias factors:
- <factor 1, or "none">
- <factor 2>
Applied bias: <up / down / none>
Final tier: <final>
Rationale: <one line — e.g. "Small infra-only diff; orchestrator can spot-check in 5 min." / "Touches src/local/cognito-jwt.ts (credential surface), bumps base tier up.">
Then, if final tier is 1-reviewer, emit:
Dispatch this single reviewer (run via Agent tool in the main session):
Agent {
subagent_type: "general-purpose",
description: "PR <N> code review",
prompt: |
Read your role definition at `.claude/agents/pr-code-reviewer.md` (relative to the repo root) and follow it.
Inputs:
- PR number: <N>
- Branch: <branch>
}
If final tier is 3-axis, emit:
Dispatch these three reviewers IN PARALLEL (single message, three Agent tool calls):
Agent {
subagent_type: "general-purpose",
description: "PR <N> spec compliance review",
prompt: |
Read your role definition at `.claude/agents/pr-spec-reviewer.md` (relative to the repo root) and follow it.
Inputs:
- PR number: <N>
- Branch: <branch>
- Design doc: <ASK THE USER — the orchestrator should fill this in before dispatching; spec review is meaningless without a design doc to compare against. If no design doc exists for this PR, downgrade to 1-reviewer instead.>
}
Agent {
subagent_type: "general-purpose",
description: "PR <N> code review",
prompt: |
Read your role definition at `.claude/agents/pr-code-reviewer.md` (relative to the repo root) and follow it.
Inputs:
- PR number: <N>
- Branch: <branch>
}
Agent {
subagent_type: "general-purpose",
description: "PR <N> test adequacy review",
prompt: |
Read your role definition at `.claude/agents/pr-test-reviewer.md` (relative to the repo root) and follow it.
Inputs:
- PR number: <N>
- Branch: <branch>
}
If final tier is inline, emit:
No reviewer dispatch — orchestrator should spot-check inline:
- `gh pr diff <N>` — read the full diff in one pass
- For each changed file, ask: is it correct, complete, necessary?
- Estimated time: 5 min
If during the inline read you discover a non-obvious bug class (cross-cutting state machine, race, security-sensitive logic), STOP and re-run /review-pr <N> after manually adding the file path to the up-bias trigger list locally, or just dispatch a code reviewer by hand.
ADDITIONALLY, if the security add-on trigger fired (step 3 — a security /
process-launch surface path, src/provisioning/providers/**, or a security fix),
append this to the dispatch (at ANY tier, inline included — add it to the same
parallel batch as the tier reviewers):
Also dispatch the security reviewer IN PARALLEL (add to the batch above):
Agent {
subagent_type: "general-purpose",
description: "PR <N> security review",
prompt: |
Read your role definition at `.claude/agents/pr-security-reviewer.md` (relative to the repo root) and follow it.
Inputs:
- PR number: <N>
- Branch: <branch>
- Security concern to focus on: <ASK THE ORCHESTRATOR to name the sensitive value(s) / surface this PR touches — e.g. "the redacted secret expression persisted to state + journal; trace every reader", "the cross-account role assumption", "the authorizer token validation". A security review with no named value to trace defaults to enumerating all sensitive values in the diff.>
}
Important
- Never auto-dispatch the Agent tool from inside this skill. Skills run in the main conversation; this skill's job is to recommend, the orchestrator's job is to act.
- The orchestrator can extend each reviewer prompt with PR-specific context (concerns to deep-dive, design doc path for spec-reviewer, files to focus on). Treat the dispatch blocks as starting templates, not final prompts.
- For 3-axis dispatches: the spec reviewer needs a design doc path. If no design doc exists for the PR (small features, bug fixes, refactors), downgrade to 1-reviewer rather than dispatching spec-reviewer with no inputs.
- Thresholds are heuristics, not laws. When in doubt, go UP — the question is not "could I spot-check this in 5 minutes?" but "would I be comfortable being wrong about this reaching main?"; if no, dispatch.
- For an honest reading of the trade-off, see
~/.claude/projects/-Users-goto-pc-github-cdkd/memory/feedback_pr_review_scale_rule.md.
Dry-run reference (sanity check)
These three PRs are the calibration set. Running this skill against them should produce:
| PR | Stats | Base tier | Bias | Final |
|---|
| #240 | 390 LOC, 4 files (.claude/hooks/*, CLAUDE.md, .claude/settings.json) | inline (fc < 5) | none (agent-instruction files are NOT in the docs bucket) | inline (base tier alone) |
| #237 | 4515 LOC, 24 files (incl. src/local/cognito-jwt.ts, lambda-authorizer.ts) | 3-axis (loc >= 1000 AND fc >= 10) | up (security surface) → clamps at 3-axis | 3-axis |
| #236 | 269 LOC, 9 files (incl. src/local/docker-image-builder.ts, ecr-puller.ts) | inline (loc < 300) | up (process-launch surface) → 1-reviewer | 1-reviewer |
| #344 | 1488 LOC, 13 files (all .md — docs/plans/*.md deletes + CLAUDE.md / docs/*.md / tests/integration/*/README.md link-fix) | 3-axis (loc >= 1000 AND fc >= 10) | none — CLAUDE.md is an agent-instruction file, so the "every path is inert docs" premise fails | 3-axis |
| #404 | 4286 raw LOC → ~1100 after subtracting docs/_generated/integ-coverage.json (2724 LOC) + docs/integ-coverage.md (~170 LOC) auto-gen, 19 files (scripts/, hooks, fixtures, sidecar JSON) | 3-axis (fc >= 10 still triggers — file count not adjusted for auto-gen) | none (mixed paths exclude pure docs/infra + tests-only bias) | 3-axis (file count alone, even after LOC adjustment) — but if PR had been split per memory feedback_split_tooling_from_backfill.md (tool vs backfill), each half would be 1-reviewer |
If the skill output diverges from these, the heuristic or the trigger lists have drifted — re-read this file and the linked memory entry before trusting the recommendation. These rows are RECALCULATED expectations, not history: #240 and #344 were originally decided under a docs bucket that included CLAUDE.md / .claude/**, and both now answer differently because that bucket was narrowed to inert documentation. Do not "restore" the old bucket to make a row match.