Code Review
Reviews code changes using dynamically selected reviewer personas. Spawns parallel sub-agents that return structured JSON, then merges and deduplicates findings into a single report.
When to Use
- Before creating a PR
- After completing a task during iterative implementation
- When feedback is needed on any code changes
- Can be invoked standalone
- Can run as a read-only or autofix review step inside larger workflows
Argument Parsing
Parse $ARGUMENTS for the following optional tokens. Strip each recognized token before interpreting the remainder as the PR number, GitHub URL, or branch name.
| Token | Example | Effect |
|---|---|---|
mode:autofix |
mode:autofix |
Select autofix mode (see Mode Detection below) |
mode:report-only |
mode:report-only |
Select report-only mode |
mode:headless |
mode:headless |
Select headless mode for programmatic callers (see Mode Detection below) |
base:<sha-or-ref> |
base:abc1234 or base:origin/main |
Skip scope detection — use this as the diff base directly |
plan:<path> |
plan:docs/plans/2026-03-25-001-feat-foo-plan.md |
Load this plan for requirements verification |
All tokens are optional. Each one present means one less thing to infer. When absent, fall back to existing behavior for that stage.
Conflicting mode flags: If multiple mode tokens appear in arguments, stop and do not dispatch agents. If mode:headless is one of the conflicting tokens, emit the headless error envelope: Review failed (headless mode). Reason: conflicting mode flags — <mode_a> and <mode_b> cannot be combined. Otherwise emit the generic form: Review failed. Reason: conflicting mode flags — <mode_a> and <mode_b> cannot be combined.
Mode Detection
| Mode | When | Behavior |
|---|---|---|
| Interactive (default) | No mode token present | Review, apply safe_auto fixes automatically, present findings, ask for policy decisions on gated/manual findings, and optionally continue into fix/push/PR next steps |
| Autofix | mode:autofix in arguments |
No user interaction. Review, apply only policy-allowed safe_auto fixes, re-review in bounded rounds, write a run artifact capturing residual downstream work |
| Report-only | mode:report-only in arguments |
Strictly read-only. Review and report only, then stop with no edits, artifacts, commits, pushes, or PR actions |
| Headless | mode:headless in arguments |
Programmatic mode for skill-to-skill invocation. Apply safe_auto fixes silently (single pass), return all other findings as structured text output, write run artifacts, and return "Review complete" signal. No interactive prompts. |
Autofix mode rules
- Skip all user questions. Never pause for approval or clarification once scope has been established.
- Apply only
safe_auto -> review-fixerfindings. Leavegated_auto,manual,human, andreleasework unresolved. - Write a run artifact under
.context/compound-engineering/ce-code-review/<run-id>/summarizing findings, applied fixes, residual actionable work, and advisory outputs. Orchestrators read this artifact to route residualdownstream-resolverfindings; the skill itself does not file tickets or prompt the user in autofix. - Emit a compact Residual Actionable Work summary in the autofix return listing each residual
downstream-resolverfinding with severity, file:line, title, and autofix_class. Include the run-artifact path. Callers read this summary directly without parsing the artifact. When no residuals exist, stateResidual actionable work: none.explicitly. - Never commit, push, or create a PR from autofix mode. Parent workflows own those decisions.
Report-only mode rules
- Skip all user questions. Infer intent conservatively if the diff metadata is thin.
- Never edit files or externalize work. Do not write
.context/compound-engineering/ce-code-review/<run-id>/, do not file tickets, and do not commit, push, or create a PR. - Safe for parallel read-only verification.
mode:report-onlyis the only mode that is safe to run concurrently with browser testing on the same checkout. - Do not switch the shared checkout. If the caller passes an explicit PR or branch target,
mode:report-onlymust run in an isolated checkout/worktree or stop instead of runninggh pr checkout/git checkout. - Do not overlap mutating review with browser testing on the same checkout. If a future orchestrator wants fixes, run the mutating review phase after browser testing or in an isolated checkout/worktree.
Headless mode rules
- Skip all user questions. Never use the platform question tool (
AskUserQuestionin Claude Code,request_user_inputin Codex,ask_userin Gemini,ask_userin Pi (requires thepi-ask-userextension)) or other interactive prompts. Infer intent conservatively if the diff metadata is thin. - Require a determinable diff scope. If headless mode cannot determine a diff scope (no branch, PR, or
base:ref determinable without user interaction), emitReview failed (headless mode). Reason: no diff scope detected. Re-invoke with a branch name, PR number, or base:<ref>.and stop without dispatching agents. - Apply only
safe_auto -> review-fixerfindings in a single pass. No bounded re-review rounds. Leavegated_auto,manual,human, andreleasework unresolved and return them in the structured output. - Return all non-auto findings as structured text output. Use the headless output envelope format (see Stage 6 below) preserving severity, autofix_class, owner, requires_verification, confidence, pre_existing, and suggested_fix per finding. Enrich with detail-tier fields (why_it_matters, evidence[]) from the per-agent artifact files on disk (see Detail enrichment in Stage 6).
- Write a run artifact under
.context/compound-engineering/ce-code-review/<run-id>/summarizing findings, applied fixes, and advisory outputs. Include the artifact path in the structured output. - Do not file tickets or externalize work. The caller receives structured findings and routes downstream work itself.
- Do not switch the shared checkout. If the caller passes an explicit PR or branch target,
mode:headlessmust run in an isolated checkout/worktree or stop instead of runninggh pr checkout/git checkout. When stopping, emitReview failed (headless mode). Reason: cannot switch shared checkout. Re-invoke with base:<ref> to review the current checkout, or run from an isolated worktree. - Not safe for concurrent use on a shared checkout. Unlike
mode:report-only, headless mutates files (appliessafe_autofixes). Callers must not run headless concurrently with other mutating operations on the same checkout. - Never commit, push, or create a PR from headless mode. The caller owns those decisions.
- End with "Review complete" as the terminal signal so callers can detect completion. If all reviewers fail or time out, emit
Code review degraded (headless mode). Reason: 0 of N reviewers returned results.followed by "Review complete".
Interactive mode rules
- Pre-load the platform question tool before any question fires. In Claude Code,
AskUserQuestionis a deferred tool — its schema is not available at session start. At the start of Interactive-mode work (before Stage 2 intent-ambiguity questions, the After-Review routing question, walk-through per-finding questions, bulk-preview Proceed/Cancel, and tracker-defer failure sub-questions), callToolSearchwith queryselect:AskUserQuestionto load the schema. Load it once, eagerly, at the top of the Interactive flow — do not wait for the first question site and do not decide it on a per-site basis. On Codex, Gemini, and Pi this preload step does not apply. - The numbered-list fallback only applies when the harness genuinely lacks a blocking question tool —
ToolSearchreturns no match, the tool call explicitly fails, or the runtime mode does not expose it (e.g., Codex edit modes whererequest_user_inputis unavailable). A pending schema load is not a fallback trigger; callToolSearchfirst per the pre-load rule. Rendering a question as narrative text because the tool feels inconvenient, because the model is in report-formatting mode, or because the instruction was buried in a long skill is a bug. A question that calls for a user decision must either fire the tool or fall back loudly.
Severity Scale
All reviewers use P0-P3:
| Level | Meaning | Action |
|---|---|---|
| P0 | Critical breakage, exploitable vulnerability, data loss/corruption | Must fix before merge |
| P1 | High-impact defect likely hit in normal usage, breaking contract | Should fix |
| P2 | Moderate issue with meaningful downside (edge case, perf regression, maintainability trap) | Fix if straightforward |
| P3 | Low-impact, narrow scope, minor improvement | User's discretion |
Action Routing
Severity answers urgency. Routing answers who acts next and whether this skill may mutate the checkout.
autofix_class |
Default owner | Meaning |
|---|---|---|
safe_auto |
review-fixer |
Local, deterministic fix suitable for the in-skill fixer when the current mode allows mutation |
gated_auto |
downstream-resolver or human |
Concrete fix exists, but it changes behavior, contracts, permissions, or another sensitive boundary that should not be auto-applied by default |
manual |
downstream-resolver or human |
Actionable work that should be handed off rather than fixed in-skill |
advisory |
human or release |
Report-only output such as learnings, rollout notes, or residual risk |
Routing rules:
- Synthesis owns the final route. Persona-provided routing metadata is input, not the last word.
- Choose the more conservative route on disagreement. A merged finding may move from
safe_autotogated_autoormanual, but never the other way without stronger evidence. - Only
safe_auto -> review-fixerenters the in-skill fixer queue automatically. requires_verification: truemeans a fix is not complete without targeted tests, a focused re-review, or operational validation.
Reviewers
18 reviewer personas in layered conditionals, plus CE-specific agents. See the persona catalog included below for the full catalog.
Always-on (every review):
| Agent | Focus |
|---|---|
ce-correctness-reviewer |
Logic errors, edge cases, state bugs, error propagation |
ce-testing-reviewer |
Coverage gaps, weak assertions, brittle tests |
ce-maintainability-reviewer |
Coupling, complexity, naming, dead code, abstraction debt |
ce-project-standards-reviewer |
CLAUDE.md and AGENTS.md compliance -- frontmatter, references, naming, portability |
ce-agent-native-reviewer |
Verify new features are agent-accessible |
ce-learnings-researcher |
Search docs/solutions/ for past issues related to this PR |
Cross-cutting conditional (selected per diff):
| Agent | Select when diff touches... |
|---|---|
ce-security-reviewer |
Auth, public endpoints, user input, permissions |
ce-performance-reviewer |
DB queries, data transforms, caching, async |
ce-api-contract-reviewer |
Routes, serializers, type signatures, versioning |
ce-data-migrations-reviewer |
Migrations, schema changes, backfills |
ce-reliability-reviewer |
Error handling, retries, timeouts, background jobs |
ce-adversarial-reviewer |
Diff >=50 changed non-test/non-generated/non-lockfile lines, or auth, payments, data mutations, external APIs |
ce-cli-readiness-reviewer |
CLI command definitions, argument parsing, CLI framework usage, command handler implementations |
ce-previous-comments-reviewer |
Reviewing a PR that has existing review comments or threads |
Stack-specific conditional (selected per diff):
| Agent | Select when diff touches... |
|---|---|
ce-dhh-rails-reviewer |
Rails architecture, service objects, session/auth choices, or Hotwire-vs-SPA boundaries |
ce-kieran-rails-reviewer |
Rails application code where conventions, naming, and maintainability are in play |
ce-kieran-python-reviewer |
Python modules, endpoints, scripts, or services |
ce-kieran-typescript-reviewer |
TypeScript components, services, hooks, utilities, or shared types |
ce-julik-frontend-races-reviewer |
Stimulus/Turbo controllers, DOM events, timers, animations, or async UI flows |
ce-swift-ios-reviewer |
Swift files, SwiftUI views, UIKit controllers, entitlements, privacy manifests, Core Data models, SPM manifests, storyboards/XIBs, or semantic build-setting/target/signing changes in .pbxproj |
CE conditional (migration-specific):
| Agent | Select when diff includes migration files |
|---|---|
ce-schema-drift-detector |
Cross-references schema.rb against included migrations |
ce-deployment-verification-agent |
Produces deployment checklist with SQL verification queries |
Review Scope
Every review spawns all 4 always-on personas plus the 2 CE always-on agents, then adds whichever cross-cutting and stack-specific conditionals fit the diff. The model naturally right-sizes: a small config change triggers 0 conditionals = 6 reviewers. A Rails auth feature might trigger security + reliability + kieran-rails + dhh-rails = 10 reviewers.
Protected Artifacts
The following paths are compound-engineering pipeline artifacts and must never be flagged for deletion, removal, or gitignore by any reviewer:
docs/brainstorms/*-- requirements documents created by ce-brainstormdocs/plans/*.md-- plan files created by ce-plan (living documents with progress checkboxes)docs/solutions/*.md-- solution documents created during the pipeline
If a reviewer flags any file in these directories for cleanup or removal, discard that finding during synthesis.
How to Run
Stage 1: Determine scope
Compute the diff range, file list, and diff. Minimize permission prompts by combining into as few commands as possible.
If base: argument is provided (fast path):
The caller already knows the diff base. Skip all base-branch detection, remote resolution, and merge-base computation. Use the provided value directly:
BASE_ARG="{base_arg}"
BASE=$(git merge-base HEAD "$BASE_ARG" 2>/dev/null) || BASE="$BASE_ARG"
Then produce the same output as the other paths:
echo "BASE:$BASE" && echo "FILES:" && git diff --name-only $BASE && echo "DIFF:" && git diff -U10 $BASE && echo "UNTRACKED:" && git ls-files --others --exclude-standard
This path works with any ref — a SHA, origin/main, a branch name. Automated callers (ce-work, lfg, slfg) should prefer this to avoid the detection overhead. Do not combine base: with a PR number or branch target. If both are present, stop with an error: "Cannot use base: with a PR number or branch target — base: implies the current checkout is already the correct branch. Pass base: alone, or pass the target alone and let scope detection resolve the base." This avoids scope/intent mismatches where the diff base comes from one source but the code and metadata come from another.
If a PR number or GitHub URL is provided as an argument:
If mode:report-only or mode:headless is active, do not run gh pr checkout <number-or-url> on the shared checkout. For mode:report-only, tell the caller: "mode:report-only cannot switch the shared checkout to review a PR target. Run it from an isolated worktree/checkout for that PR, or run report-only with no target argument on the already checked out branch." For mode:headless, emit Review failed (headless mode). Reason: cannot switch shared checkout. Re-invoke with base:<ref> to review the current checkout, or run from an isolated worktree. Stop here unless the review is already running in an isolated checkout.
Skip-condition pre-check. Before checkout or scope detection, run a PR-state probe to decide whether the review should proceed:
gh pr view <number-or-url> --json state,title,body,files
Apply skip rules in order:
stateisCLOSEDorMERGED-> stop with messagePR is closed/merged; not reviewing.- Trivial-PR judgment: spawn a lightweight sub-agent (use
model: haikuin Claude Code; gpt-5.4-nano or equivalent in Codex) with the PR title, body, and changed file paths. The agent's task: "Is this an automated or trivial PR that does not warrant a code review? Consider: dependency lock-file or manifest-only bumps, automated release commits, chore version increments with no substantive code changes. When in doubt, answer no — false negatives (skipped reviews that should have run) are more costly than false positives (unnecessary reviews)." If the judgment returns yes: stop with messagePR appears to be a trivial automated PR; not reviewing. Run without a PR argument to review the current branch, or pass base:<ref> if review is intended.
When any skip rule fires, emit the message and stop without dispatching reviewers, switching the checkout, or running scope detection. Standalone branch mode and base: mode are unaffected -- they always run the full review. Draft PRs are reviewed normally -- draft status is not a skip condition; early feedback on in-progress work is valuable.
If no skip rule fires, proceed to the checkout logic below.
First, verify the worktree is clean before switching branches:
git status --porcelain
If the output is non-empty, inform the user: "You have uncommitted changes on the current branch. Stash or commit them before reviewing a PR, or use standalone mode (no argument) to review the current branch as-is." Do not proceed with checkout until the worktree is clean.
Then check out the PR branch so persona agents can read the actual code (not the current checkout):
gh pr checkout <number-or-url>
Then fetch PR metadata. Capture the base branch name and the PR base repository identity, not just the branch name:
gh pr view <number-or-url> --json title,body,baseRefName,headRefName,url
Use the repository portion of the returned PR URL as <base-repo> (for example, EveryInc/compound-engineering-plugin from https://github.com/EveryInc/compound-engineering-plugin/pull/348).
Then compute a local diff against the PR's base branch so re-reviews also include local fix commits and uncommitted edits. Substitute the PR base branch from metadata (shown here as <base>) and the PR base repository identity derived from the PR URL (shown here as <base-repo>). Resolve the base ref from the PR's actual base repository, not by assuming origin points at that repo:
PR_BASE_REMOTE=$(git remote -v | awk 'index($2, "github.com:<base-repo>") || index($2, "github.com/<base-repo>") {print $1; exit}')
if [ -n "$PR_BASE_REMOTE" ]; then PR_BASE_REMOTE_REF="$PR_BASE_REMOTE/<base>"; else PR_BASE_REMOTE_REF=""; fi
PR_BASE_REF=$(git rev-parse --verify "$PR_BASE_REMOTE_REF" 2>/dev/null || git rev-parse --verify <base> 2>/dev/null || true)
if [ -z "$PR_BASE_REF" ]; then
if [ -n "$PR_BASE_REMOTE_REF" ]; then
git fetch --no-tags "$PR_BASE_REMOTE" <base>:refs/remotes/"$PR_BASE_REMOTE"/<base> 2>/dev/null || git fetch --no-tags "$PR_BASE_REMOTE" <base> 2>/dev/null || true
PR_BASE_REF=$(git rev-parse --verify "$PR_BASE_REMOTE_REF" 2>/dev/null || git rev-parse --verify <base> 2>/dev/null || true)
else
if git fetch --no-tags https://github.com/<base-repo>.git <base> 2>/dev/null; then
PR_BASE_REF=$(git rev-parse --verify FETCH_HEAD 2>/dev/null || true)
fi
if [ -z "$PR_BASE_REF" ]; then PR_BASE_REF=$(git rev-parse --verify <base> 2>/dev/null || true); fi
fi
fi
if [ -n "$PR_BASE_REF" ]; then BASE=$(git merge-base HEAD "$PR_BASE_REF" 2>/dev/null) || BASE=""; else BASE=""; fi
if [ -n "$BASE" ]; then echo "BASE:$BASE" && echo "FILES:" && git diff --name-only $BASE && echo "DIFF:" && git diff -U10 $BASE && echo "UNTRACKED:" && git ls-files --others --exclude-standard; else echo "ERROR: Unable to resolve PR base branch <base> locally. Fetch the base branch and rerun so the review scope stays aligned with the PR."; fi
Extract PR title/body, base branch, and PR URL from gh pr view, then extract the base marker, file list, diff content, and UNTRACKED: list from the local command. Do not use gh pr diff as the review scope after checkout -- it only reflects the remote PR state and will miss local fix commits until they are pushed. If the base ref still cannot be resolved from the PR's actual base repository after the fetch attempt, stop instead of falling back to git diff HEAD; a PR review without the PR base branch is incomplete.
If a branch name is provided as an argument:
Check out the named branch, then diff it against the base branch. Substitute the provided branch name (shown here as <branch>).
If mode:report-only or mode:headless is active, do not run git checkout <branch> on the shared checkout. For mode:report-only, tell the caller: "mode:report-only cannot switch the shared checkout to review another branch. Run it from an isolated worktree/checkout for <branch>, or run report-only on the current checkout with no target argument." For mode:headless, emit Review failed (headless mode). Reason: cannot switch shared checkout. Re-invoke with base:<ref> to review the current checkout, or run from an isolated worktree. Stop here unless the review is already running in an isolated checkout.
First, verify the worktree is clean before switching branches:
git status --porcelain
If the output is non-empty, inform the user: "You have uncommitted changes on the current branch. Stash or commit them before reviewing another branch, or provide a PR number instead." Do not proceed with checkout until the worktree is clean.
git checkout <branch>
Then detect the review base branch and compute the merge-base. Run the references/resolve-base.sh script, which handles fork-safe remote resolution with multi-fallback detection (PR metadata -> origin/HEAD -> gh repo view -> common branch names):
RESOLVE_OUT=$(bash references/resolve-base.sh) || { echo "ERROR: resolve-base.sh failed"; exit 1; }
if [ -z "$RESOLVE_OUT" ] || echo "$RESOLVE_OUT" | grep -q '^ERROR:'; then echo "${RESOLVE_OUT:-ERROR: resolve-base.sh produced no output}"; exit 1; fi
BASE=$(echo "$RESOLVE_OUT" | sed 's/^BASE://')
If the script outputs an error, stop instead of falling back to git diff HEAD; a branch review without the base branch would only show uncommitted changes and silently miss all committed work.
On success, produce the diff:
echo "BASE:$BASE" && echo "FILES:" && git diff --name-only $BASE && echo "DIFF:" && git diff -U10 $BASE && echo "UNTRACKED:" && git ls-files --others --exclude-standard
You may still fetch additional PR metadata with gh pr view for title, body, and linked issues, but do not fail if no PR exists.
If no argument (standalone on current branch):
Detect the review base branch and compute the merge-base using the same references/resolve-base.sh script as branch mode:
RESOLVE_OUT=$(bash references/resolve-base.sh) || { echo "ERROR: resolve-base.sh failed"; exit 1; }
if [ -z "$RESOLVE_OUT" ] || echo "$RESOLVE_OUT" | grep -q '^ERROR:'; then echo "${RESOLVE_OUT:-ERROR: resolve-base.sh produced no output}"; exit 1; fi
BASE=$(echo "$RESOLVE_OUT" | sed 's/^BASE://')
If the script outputs an error, stop instead of falling back to git diff HEAD; a standalone review without the base branch would only show uncommitted changes and silently miss all committed work on the branch.
On success, produce the diff:
echo "BASE:$BASE" && echo "FILES:" && git diff --name-only $BASE && echo "DIFF:" && git diff -U10 $BASE && echo "UNTRACKED:" && git ls-files --others --exclude-standard
Using git diff $BASE (without ..HEAD) diffs the merge-base against the working tree, which includes committed, staged, and unstaged changes together.
Untracked file handling: Always inspect the UNTRACKED: list, even when FILES:/DIFF: are non-empty. Untracked files are outside review scope until staged. If the list is non-empty, tell the user which files are excluded. If any of them should be reviewed, stop and tell the user to git add them first and rerun. Only continue when the user is intentionally reviewing tracked changes only. In mode:headless or mode:autofix, do not stop to ask — proceed with tracked changes only and note the excluded untracked files in the Coverage section of the output.
Stage 2: Intent discovery
Understand what the change is trying to accomplish. The source of intent depends on which Stage 1 path was taken:
PR/URL mode: Use the PR title, body, and linked issues from gh pr view metadata. Supplement with commit messages from the PR if the body is sparse.
Branch mode: Run git log --oneline ${BASE}..<branch> using the resolved merge-base from Stage 1.
Standalone (current branch): Run:
echo "BRANCH:" && git rev-parse --abbrev-ref HEAD && echo "COMMITS:" && git log --oneline ${BASE}..HEAD
Combined with conversation context (plan section summary, PR description), write a 2-3 line intent summary:
Intent: Simplify tax calculation by replacing the multi-tier rate lookup
with a flat-rate computation. Must not regress edge cases in tax-exempt handling.
Pass this to every reviewer in their spawn prompt. Intent shapes how hard each reviewer looks, not which reviewers are selected.
When intent is ambiguous:
- Interactive mode: Ask one question using the platform's blocking question tool (
AskUserQuestionin Claude Code,request_user_inputin Codex,ask_userin Gemini,ask_userin Pi (requires thepi-ask-userextension)): "What is the primary goal of these changes?" Do not spawn reviewers until intent is established. Claude Code only: ifAskUserQuestionhas not yet been loaded this session (per the Interactive mode rules pre-load), callToolSearchwith queryselect:AskUserQuestionfirst before asking. Fall back to numbered options in chat only when the harness genuinely lacks a blocking tool or the call errors (e.g., Codex edit modes) — not because a schema load is required. Never silently skip the question. - Autofix/report-only/headless modes: Infer intent conservatively from the branch name, diff, PR metadata, and caller context. Note the uncertainty in Coverage or Verdict reasoning instead of blocking.
Stage 2b: Plan discovery (requirements verification)
Locate the plan document so Stage 6 can verify requirements completeness. Check these sources in priority order — stop at the first hit:
plan:argument. If the caller passed a plan path, use it directly. Read the file to confirm it exists.- PR body. If PR metadata was fetched in Stage 1, scan the body for paths matching
docs/plans/*.md. If exactly one match is found and the file exists, use it asplan_source: explicit. If multiple plan paths appear, treat as ambiguous — demote toplan_source: inferredfor the most recent match that exists on disk, or skip if none exist or none clearly relate to the PR title/intent. Always verify the selected file exists before using it — stale or copied plan links in PR descriptions are common. - Auto-discover. Extract 2-3 keywords from the branch name (e.g.,
feat/onboarding-skill->onboarding,skill). Globdocs/plans/*and filter filenames containing those keywords. If exactly one match, use it. If multiple matches or the match looks ambiguous (e.g., generic keywords likereview,fix,updatethat could hit many plans), skip auto-discovery — a wrong plan is worse than no plan. If zero matches, skip.
Confidence tagging: Record how the plan was found:
plan:argument ->plan_source: explicit(high confidence)- Single unambiguous PR body match ->
plan_source: explicit(high confidence) - Multiple/ambiguous PR body matches ->
plan_source: inferred(lower confidence) - Auto-discover with single unambiguous match ->
plan_source: inferred(lower confidence)
If a plan is found, read its Requirements Trace (R1, R2, etc.) and Implementation Units (checkbox items). Store the extracted requirements list and plan_source for Stage 6. Do not block the review if no plan is found — requirements verification is additive, not required.
Stage 3: Select reviewers
Read the diff and file list from Stage 1. The 4 always-on personas and 2 CE always-on agents are automatic. For each cross-cutting and stack-specific conditional persona in the persona catalog included below, decide whether the diff warrants it. This is agent judgment, not keyword matching.
File-type awareness for conditional selection: Instruction-prose files (Markdown skill definitions, JSON schemas, config files) are product code but do not benefit from runtime-focused reviewers. The adversarial reviewer's techniques (race conditions, cascade failures, abuse cases) target executable code behavior. For diffs that only change instruction-prose files, skip adversarial unless the prose describes auth, payment, or data-mutation behavior. Count only executable code lines toward line-count thresholds.
previous-comments is PR-only. Only select this persona when Stage 1 gathered PR metadata (PR number or URL was provided as an argument, or gh pr view returned metadata for the current branch). Skip it entirely for standalone branch reviews with no associated PR -- there are no prior comments to check.
Stack-specific personas are additive. A Rails UI change may warrant kieran-rails plus julik-frontend-races; a TypeScript API diff may warrant kieran-typescript plus api-contract and reliability.
For CE conditional agents, check if the diff includes files matching db/migrate/*.rb, db/schema.rb, or data backfill scripts.
Announce the team before spawning:
Review team:
- correctness (always)
- testing (always)
- maintainability (always)
- project-standards (always)
- ce-agent-native-reviewer (always)
- ce-learnings-researcher (always)
- security -- new endpoint in routes.rb accepts user-provided redirect URL
- kieran-rails -- controller and Turbo flow changed in app/controllers and app/views
- dhh-rails -- diff adds service objects around ordinary Rails CRUD
- data-migrations -- adds migration 20260303_add_index_to_orders
- ce-schema-drift-detector -- migration files present
This is progress reporting, not a blocking confirmation.
Stage 3b: Discover project standards paths
Before spawning sub-agents, find the file paths (not contents) of all relevant standards files for the project-standards persona. Use the native file-search/glob tool to locate:
- Use the native file-search tool (e.g., Glob in Claude Code) to find all
**/claude.mdand**/agents.mdin the repo. - Filter to those whose directory is an ancestor of at least one changed file. A standards file governs all files below it (e.g.,
plugins/compound-engineering/AGENTS.mdapplies to everything underplugins/compound-engineering/).
Pass the resulting path list to the project-standards persona inside a <standards-paths> block in its review context (see Stage 4). The persona reads the files itself, targeting only the sections relevant to the changed file types. This keeps the orchestrator's work cheap (path discovery only) and avoids bloating the subagent prompt with content the reviewer may not fully need.
Stage 4: Spawn sub-agents
Model tiering
Three reviewers inherit the session model with no override: ce-correctness-reviewer, ce-security-reviewer, and ce-adversarial-reviewer. These perform the highest-stakes analysis — logic bugs, security vulnerabilities, adversarial failure scenarios — and should run at whatever capability level the user has configured. If the user is on Opus, these get Opus.
All other persona sub-agents and CE agents use the platform's mid-tier model to reduce cost and latency. In Claude Code, pass model: "sonnet" in the Agent tool call. On other platforms, use the equivalent mid-tier (e.g., gpt-5.4-mini in Codex as of April 2026). If the platform has no model override mechanism or the available model names are unknown, omit the model parameter and let agents inherit the default -- a working review on the parent model is better than a broken dispatch from an unrecognized model name.
The orchestrator (this skill) also inherits the session model; it handles intent discovery, reviewer selection, finding merge/dedup, and synthesis -- tasks that benefit from the same reasoning capability the user configured.
Run ID
Generate a unique run identifier before dispatching any agents. This ID scopes all agent artifact files and the post-review run artifact to the same directory.
RUN_ID=$(date +%Y%m%d-%H%M%S)-$(head -c4 /dev/urandom | od -An -tx1 | tr -d ' ')
mkdir -p ".context/compound-engineering/ce-code-review/$RUN_ID"
Pass {run_id} to every persona sub-agent so they can write their full analysis to .context/compound-engineering/ce-code-review/{run_id}/{reviewer_name}.json.
Report-only mode: Skip run-id generation and directory creation. Do not pass {run_id} to agents. Agents return compact JSON only with no file write, consistent with report-only's no-write contract.
Spawning
Omit the mode parameter when dispatching sub-agents so the user's configured permission settings apply. Do not pass mode: "auto".
Spawn each selected persona reviewer as a parallel sub-agent using the subagent template included below. Each persona sub-agent receives:
- Their persona file content (identity, failure modes, calibration, suppress conditions)
- Shared diff-scope rules from the diff-scope reference included below
- The JSON output contract from the findings schema included below
- PR metadata: title, body, and URL when reviewing a PR (empty string otherwise). Passed in a
<pr-context>block so reviewers can verify code against stated intent - Review context: intent summary, file list, diff
- Run ID and reviewer name for the artifact file path
- For
project-standardsonly: the standards file path list from Stage 3b, wrapped in a<standards-paths>block appended to the review context
Persona sub-agents are read-only with respect to the project: they review and return structured JSON. They do not edit project files or propose refactors. The one permitted write is saving their full analysis to the .context/ artifact path specified in the output contract.
Read-only here means non-mutating, not "no shell access." Reviewer sub-agents may use non-mutating inspection commands when needed to gather evidence or verify scope, including read-oriented git / gh usage such as git diff, git show, git blame, git log, and gh pr view. They must not edit project files, change branches, commit, push, create PRs, or otherwise mutate the checkout or repository state.
Each persona sub-agent writes full JSON (all schema fields) to .context/compound-engineering/ce-code-review/{run_id}/{reviewer_name}.json and returns compact JSON with merge-tier fields only:
{
"reviewer": "security",
"findings": [
{
"title": "User-supplied ID in account lookup without ownership check",
"severity": "P0",
"file": "orders_controller.rb",
"line": 42,
"confidence": 100,
"autofix_class": "gated_auto",
"owner": "downstream-resolver",
"requires_verification": true,
"pre_existing": false,
"suggested_fix": "Add current_user.owns?(account) guard before lookup"
}
],
"residual_risks": [...],
"testing_gaps": [...]
}
Detail-tier fields (why_it_matters, evidence) are in the artifact file only. suggested_fix is optional in both tiers -- included in compact returns when present so the orchestrator has fix context for auto-apply decisions. If the file write fails, the compact return still provides everything the merge needs.
CE always-on agents (ce-agent-native-reviewer, ce-learnings-researcher) are dispatched as standard Agent calls in parallel with the persona agents. Give them the same review context bundle the personas receive: entry mode, any PR metadata gathered in Stage 1, intent summary, review base branch name when known, BASE: marker, file list, diff, and UNTRACKED: scope notes. Do not invoke them with a generic "review this" prompt. Their output is unstructured and synthesized separately in Stage 6.
CE conditional agents (ce-schema-drift-detector, ce-deployment-verification-agent) are also dispatched as standard Agent calls when applicable. Pass the same review context bundle plus the applicability reason (for example, which migration files triggered the agent). For ce-schema-drift-detector specifically, pass the resolved review base branch explicitly so it never assumes main. Their output is unstructured and must be preserved for Stage 6 synthesis just like the CE always-on agents.
Stage 5: Merge findings
Convert multiple reviewer compact JSON returns into one deduplicated, confidence-gated finding set. The compact returns contain merge-tier fields (title, severity, file, line, confidence, autofix_class, owner, requires_verification, pre_existing) plus the optional suggested_fix. Detail-tier fields (why_it_matters, evidence) are on disk in the per-agent artifact files and are not loaded at this stage.
confidence is one of 5 discrete anchors (0, 25, 50, 75, 100) with behavioral definitions in the findings schema. Synthesis treats anchors as integers; do not coerce to floats.
- Validate. Check each compact return for required top-level and per-finding fields, plus value constraints. Drop malformed returns or findings. Record the drop count.
- Top-level required: reviewer (string), findings (array), residual_risks (array), testing_gaps (array). Drop the entire return if any are missing or wrong type.
- Per-finding required: title, severity, file, line, confidence, autofix_class, owner, requires_verification, pre_existing
- Value constraints:
- severity: P0 | P1 | P2 | P3
- autofix_class: safe_auto | gated_auto | manual | advisory
- owner: review-fixer | downstream-resolver | human | release
- confidence: integer in {0, 25, 50, 75, 100}
- line: positive integer
- pre_existing, requires_verification: boolean
- Do not validate against the full schema here -- the full schema (including why_it_matters and evidence) applies to the artifact files on disk, not the compact returns.
- Deduplicate. Compute fingerprint:
normalize(file) + line_bucket(line, +/-3) + normalize(title). When fingerprints match, merge: keep highest severity, keep highest anchor, note which reviewers flagged it. Dedup runs over the full validated set (including anchor 50) so cross-reviewer promotion in step 3 can lift matching anchor-50 findings into the actionable tier. - Cross-reviewer agreement. When 2+ independent reviewers flag the same issue (same fingerprint), promote the merged finding by one anchor step:
50 -> 75,75 -> 100,100 -> 100. Cross-reviewer corroboration is a stronger signal than any single reviewer's anchor; the promotion routes a previously-soft finding into the actionable tier or strengthens its already-actionable position. Note the agreement in the Reviewer column of the output (e.g., "security, correctness"). - Separate pre-existing. Pull out findings with
pre_existing: trueinto a separate list. - Resolve disagreements. When reviewers flag the same code region but disagree on severity, autofix_class, or owner, ann
…(truncated)