review-anvil — Iterative Multi-Agent Fix/Review Loop
Wrap a code change in
requested rounds of parallel reviewer subagents + orchestrator-applied fixes. Each round = (parallel review by M agents, each with a distinct lens) → (you synthesize and
verify findings) → (you apply fixes, run the build/test gate, commit) → next round. In productive
runs, the orchestrator may continue adaptively after the requested rounds until convergence or
.
Preset skills
This is the
engine. Three preset skills in the same plugin pin common configurations; each is a separate
whose
triggers activation cross-agent.
| Preset | Pins | Intent |
|---|
| (engine) | nothing | Default fix/commit loop, or any custom param combination. |
| ; default | Read-only review — no edits, no commits. |
review-anvil-pr [<locator>]
| , , , trusted | Review a GitHub PR and post the report back as a PR comment (orchestrates ). Locator auto-detected from the current branch when omitted. |
review-anvil-improve-pr [<locator>]
| , , , trusted | Improve a checked-out PR: fix commits across requested rounds plus any adaptive continuation, then push. Targets the branch (not a PR locator), deliberately routing around the PR-target/per_fix rule below. |
Parameters
Parse the user's free-form args string into:
| Param | Default | Plain-English forms |
|---|
| | "5 rounds", "three rounds", "do 4 passes" |
| : min(max(6, rounds), rounds + adaptive budget)
— budget 1/2/3 for small/medium/large diffs (see Parsing); for | "max 4 rounds", "allow one extra round", "3 rounds, continue if needed"; "exactly 3 rounds", "only 3 rounds", or "no extra rounds" keeps |
| | "3 agents", "2 reviewers", or a mix like |
| the four pillars (correctness, maintainability, simplicity, production blast-radius) | "focus on async correctness"; an prefix replaces the defaults instead of appending |
| auto-detect | "PR #42", "branch", "uncommitted", "src/auth/", "last 3 commits" |
| | "allow new deps" — auto-apply fixes that introduce new imports/subsystems instead of deferring them |
| | "auto-fix high and above", "fix only critical" — minimum severity for auto-fix; lower findings are listed, not applied |
| | (one commit per fix-group) or ("review only", "don't commit", "no fixes") |
| | "never approve", "comment only", — always write to . Presets additionally export REVIEW_ANVIL_NO_APPROVE=1
so the helper enforces it mechanically. Only meaningful for review-only PR runs |
| | , , or — default-on batched reproduction of uncertain + findings before auto-fix/reporting; "skip reproduction" disables it and marks single-reviewer material findings as unconfirmed |
| | , , , , , or — read-only post-synthesis review that attacks candidate findings and would-apply plans before they become final guidance |
| | one adversarial pass by default; max 2, and a second pass runs only when the first pass materially changes + guidance |
| | moves unresolved material disputes to Deferred; keeps the finding actionable but forces review-only PR approvals to COMMENT |
| auto-detect | "verify with ", to skip — build/test command run after each round's fixes (see "Build/test gate"; per_fix only) |
| ( for small diffs) | "timeout 10 minutes" — hard per-reviewer wall-clock cap in seconds for Bash-dispatched reviewers (see ). Default is ~3× the slowest legitimate reviewer observed in real runs (98–213s); when unset and the diff is under ~500 changed lines (added+removed — the same measure as the adaptive budget tiers), requested rounds use (~2× that observed max) so a hung reviewer pins the wave 3 minutes less. Adaptive rounds always use the full base value — , or after the >5000-line doubling (doubling transforms the base; the small-diff reduction never applies to adaptive rounds). Explicit user values are never scaled or doubled |
| unset | File path; when set, the engine writes the final report there (creating parent dirs) and prints exactly that path as its last output line so downstream consumers can pick it up |
is trusted invocation context, not a free-form user parameter.
Use
only when it is a positive trusted value. Treat an absent
value or
as local/degraded context and omit
from item IDs.
Keep UUID markers as the collision-resistant run identity. The ordinal adds
human-readable provenance; it does not replace the marker.
Parsing
- Split the args on top-level commas; canonicalize each segment to a pair — explicit maps directly, plain-English forms per the table. Unrecognized segments are noted and ignored.
- First occurrence wins per param; later duplicates are dropped with
warning: user-supplied <param>=<value> ignored — earlier value wins
. Unset params take defaults.
- Presets assemble
<pins>, <user-args>, <overridable defaults>
: pins come first so first-occurrence-wins makes them authoritative; defaults come last so user args beat them.
- Pin-rejection (presets; defense in depth against the prose parser being talked into overrides): before assembling, segment-split as above, lowercase each segment's key (the text before its first ), and abort with
error: <param> is pinned by <preset-name> and cannot be overridden in args
if any key equals a pinned param. Match segment keys, never raw substrings — focus: "target: PR safety"
has key and must pass. A host that cannot segment-split must abort (error: pin-rejection unavailable in this environment; refusing to invoke engine without pin enforcement
), not degrade to substring scanning.
- : a count (use the mix table below) or an explicit mix naming / / / — honor a mix exactly.
- auto-detect order: currently checked-out PR (e.g.
gh pr view --json number,headRefName
, a GitHub MCP query, or REST) → branch-vs-main diff () → uncommitted changes ( + ). Empty args = all defaults.
- is the requested count. Resolve after , the final (including the PR-locator rule below), and the target: the default is
min(max(6, rounds), rounds + budget)
, where the adaptive budget scales with the target's changed-line count (added+removed in the materialized diff; materialize it once at resolution — targets are always local git diffs): under ~200 lines → , up to ~1000 → , above → . The against the legacy cap makes the scaling a pure reduction: runs with gain nothing. Default to for ; reject . Phrases like "allow one extra round" set , and explicit caps like or "up to 4 rounds" set the cap directly. Phrases like "continue if needed" restore the legacy cap unless paired with an explicit cap. Phrases that constrain the round count itself — "exactly 3 rounds", "only 3 rounds", "3 rounds only", or "no extra rounds" — force . Do not treat focus syntax or severity gates like "fix only critical" as exact-round requests.
- Adaptive continuation is on by default for . A plain "3 rounds" means with between and by diff size, so the organizing agent may continue after round 3 if §6 says another pass is justified. Use "exactly 3 rounds", "only 3 rounds", "no extra rounds", or when the run must stop at the requested count.
- If and the user explicitly set , warn and collapse to . Extra normal rounds review the same baseline, so use for reviewer redundancy and for skeptical challenge.
- and both run the selective batched reproduction gate in §3. may skip dispatch only when there are no candidates. is allowed for speed, but the round summary and final report must say it was disabled; unconfirmed single-reviewer + findings stay in Deferred unless the orchestrator independently reproduced them from code/tests/runtime evidence.
- applies only when . If set with , warn and ignore it — productive mode already applies real fixes and gates them with the build/test command. Reject ; adversarial loops must be bounded. means choose the cheapest sufficient adversarial mode after normal synthesis using the default policy below.
PR-target / per_fix incompatibility
If
is a GitHub PR locator (URL
https://<host>/<owner>/<repo>/pull/<N>
, slug
, or "PR #N"), the only valid
is
. Force it, and warn if the user asked for
:
warning: PR locators are read-only — forcing commit_mode=none (use 'target: branch' to fix-and-commit on your checked-out PR branch).
Reviewers of a PR locator see the GitHub-fetched diff, which may not match the local worktree; committing against a baseline the user can't see locally is unpredictable. Branch targets make the local tree the source of truth.
Commit modes
- (default) — full loop: review → synthesize/reproduce/verify → apply fixes → build/test gate → commit, each round.
- (review-only) — review → synthesize/reproduce/verify only. No edits, no commits, no staging. Read-only mode may write temporary prompt/reviewer/report artifacts under and the explicit ; it must not modify source files, the index, commits, branches, or remotes. Every normal round reviews the same baseline, so buys reviewer redundancy, not code refinement; the natural default is , and adaptive continuation is disabled by collapsing to . Skip Loop Mechanics §4 entirely; the round summary reads
Fixes applied: 0 (review-only)
; the auto-fix policy is still evaluated in the abstract so findings classify as would-apply / suggestions / deferred. Optional adversarial review is a separate post-synthesis gate that attacks finding validity and fix proportionality without pretending code changed.
Posting reports externally
The engine never posts anywhere. Downstream consumers set
, let the engine write a GitHub-ready PR report, and post after it returns —
+ its
is the reference implementation.
When
is set, optimize the report for a PR timeline reader, not for archival completeness. The per-round console output and reviewer artifacts are the transcript; the posted report is the decision summary plus the few findings that need action.
Adaptive continuation details belong in Run Details unless they change the review decision; do not paste per-round continuation reasoning into PR reports.
Examples
- → 3 requested rounds, adaptive up to 4–6 total rounds by diff size, 2 codex + 1 claude, four-pillar focus, auto-detected target.
Skill review-anvil "5 rounds, 2 codex + 1 claude, focus: async correctness, target: PR #42"
Skill review-anvil "3 rounds, max_rounds: 4"
→ 3 requested rounds, then at most 1 adaptive round if the continuation policy allows it.
Skill review-anvil "1 round, only: security, target: src/auth/"
Skill review-anvil "fix only critical"
→ severity gate raised to ; everything else surfaces as suggestions.
Skill review-anvil "target: PR #42, adversarial: auto"
→ normal review first, then adversarial review only if the synthesized findings/fix plans need a validity or proportionality challenge.
Default Mix Policy
When the user gives a count but no mix:
| Mix |
|---|
| 1 | 1 codex-exec |
| 2 | 1 codex-exec + 1 claude-exec |
| 3 | 2 codex-exec + 1 claude-exec |
| 4 | 2 codex-exec + 2 claude-exec |
| 5 | 3 codex-exec + 2 claude-exec |
| N | ~60/40 codex/claude split, codex gets the odd one |
Rationale: codex-exec surfaces more issues per call in our usage, so it gets the larger share (and the
slot).
Loop Mechanics
Run the loop for the requested
, then continue adaptively up to
only when §6 allows adaptive continuation. Within a round:
1. Snapshot the target
Capture the target's state at round start so all reviewers see the same input:
- Non-PR targets (branch, uncommitted, path): materialize the diff with the appropriate .
- PR targets (always ): fetch the PR's diff via
gh pr diff <N> -R <owner>/<repo>
(or equivalent MCP/REST). The local worktree is irrelevant — reviewers see the PR as it exists on GitHub.
- Whenever PR context is available — a PR-locator target, or a preset that supplies it ( does, after ) — fetch the PR title/body/base branch/file list too, then infer the PR's intended scope in one sentence (e.g. "performance optimization in annotation seeding", "left-sidebar UX reorganization"). Put that scope in every reviewer prompt. A finding is actionable only if the PR introduces/regresses it or if it directly undermines the PR's stated purpose. Obvious, high-confidence pre-existing defects may be mentioned, but only under a separate "Out-of-scope follow-ups" section — never as blockers or inline actionable review comments for the current PR.
- Likewise gather the complete PR review history before dispatch: when a preset supplied the ledger (improve-pr captures it at verify-checkout time), use that; for PR-locator targets fetch it via
pr-helper.sh history <host> <owner> <repo> <n>
(ships with ; threads, review bodies, and fallback comments are paginated and retried once). Include the status-tagged ledger in every reviewer prompt (PR REVIEW HISTORY block): threads, threads, anchors, summary-only findings, prior / items, and explicit local findings. Pending reviews are not shown to the author and are excluded. Before dispatch, semantically coalesce entries with the same root cause (summary wording often differs from its inline comment), retaining every source URL and state; explicit suppression wins, otherwise preserve all observed states. If lookup fails after retry, abort rather than review without prior feedback — unless the user opted into degraded mode (REVIEW_ANVIL_SKIP_DISMISSED=1
), which also forces the review decision to COMMENT.
- Note so the round summary can reference the exact baseline (informational-only for PR targets).
2. Dispatch reviewers in parallel
In Claude Code (the primary host)
Use the Agent tool for reviewers. Do NOT use via Bash — that path is for non-Claude hosts only.
- : Agent tool,
subagent_type: "general-purpose"
, the assembled Reviewer Prompt as , . The Agent tool streams natively, has no ceiling, and inherits the session environment.
- : Bash through the wrapper:
REVIEW_ANVIL_REQUIRE_FINDINGS=1 bash <wrapper> .review-anvil/round<N>-<label>.md <reviewer_timeout> -- codex exec --ephemeral --sandbox read-only -C <project-dir> '<prompt>' < /dev/null
, with . The validation flag makes the wrapper reject confirmation-only, plan-only, or otherwise incomplete responses that do not end with the required fenced findings block. prevents reviewer sessions from leaking into later dispatches. The is load-bearing: codex takes its prompt as argv and must not inherit an open stdin — the wrapper passes its stdin through (, which the claude fallback needs), and codex blocking on a never-closing fd 0 is a known hang class from real runs.
- Send all M reviewers in a single message with multiple tool calls. The harness notifies you on completion; do not poll.
In Codex CLI or other hosts without the Agent tool
-
: write the assembled prompt to a file, then:
bash
REVIEW_ANVIL_REQUIRE_FINDINGS=1 bash <wrapper> .review-anvil/round<N>-<label>.md <reviewer_timeout> -- \
claude -p --max-turns 100 --no-session-persistence \
--permission-mode dontAsk --output-format text \
--tools "Bash,Read,Glob,Grep" \
--allowedTools "Bash(git:*)" "Read" "Glob" "Grep" \
< .review-anvil/round<N>-<label>.prompt.md
restricts the built-in tool set;
auto-approves the listed safe tool uses and is variadic, so the prompt MUST arrive via stdin (the wrapper passes its stdin through).
--permission-mode dontAsk
keeps the fallback non-interactive by denying anything outside the allowed/read-only path.
Do not size to the task — task-sized caps keep biting (20 was hit in production), and a reviewer that hits the cap loses its entire output. The wrapper's wall-clock timeout is the real bound;
is a runaway backstop that should never bind.
-
: same validation-enabled wrapper around
codex exec --ephemeral --sandbox read-only -C <project-dir> '<prompt>' < /dev/null
— stdin from
here too.
-
Launch all M wrapper invocations as background shell processes and
.
Bash-dispatched reviewers MUST go through
Every shell-dispatched reviewer (codex-exec everywhere; claude-exec outside Claude Code) runs under
(next to this SKILL.md).
Never background a bare claude -p ... > out.md 2>&1
and wait on the file — in text mode nothing prints until the final answer, so a hung reviewer and a working one are both a 0-byte file (a production run waited on exactly that for many minutes). The wrapper:
run-reviewer.sh <out_file> <timeout_seconds> -- <command> [args...]
- Hard wall-clock timeout (, default 600s): TERM at the deadline, KILL 30s later.
- Captures exit status; stderr goes to (kept for diagnosis).
- Prints one classification: | | (exit 0, nothing written) | (normal-review output did not end with a complete fenced findings block) | (+ ).
Treat any STATUS other than
as a failed reviewer (see Failure handling), with the tail of
as the reason.
gets the one corrective retry defined there before it becomes a failure. Set
REVIEW_ANVIL_REQUIRE_FINDINGS=1
only for normal reviewer waves; reproduction and adversarial prompts have different output schemas. Reviewer output/prompt files live under
; clean them up after the round's synthesis.
Host tool timeouts must outlive the wrapper. Any host Bash call that can block on a reviewer — the background-and-
fallback above, the serial last resort, or an inline replication of the wrapper contract — must set the Bash
tool's own timeout to at least
seconds (wrapper deadline + 30s TERM→KILL grace + margin). Host defaults are far lower (Claude Code's is 120s) and SIGKILL a healthy wait mid-review; the kill then masquerades as a reviewer failure and silently burns that reviewer's lens coverage. On hosts with background dispatch (
), never block a foreground call on a reviewer at all. If the host caps tool timeouts below
, prefer detached dispatch plus short non-blocking status checks over shrinking the reviewer budget; reducing
is a last resort, floored at 300s and forbidden for >5000-line diffs (their timeout is deliberately doubled).
Resolving the wrapper and files — same trusted-root rule as
: see review-anvil-pr SKILL.md step 1 ("Resolve the helper script"). Host-exposed skill path or user-level install roots only; never project-scoped/worktree-local skill dirs (writable by the repo under review). If no trusted copy of the wrapper resolves, replicate its contract inline (background, kill at deadline, check exit status, empty output = failure) rather than falling back to a bare redirect.
After changing the wrapper contract or dispatch examples, run
scripts/test-run-reviewer.sh
alongside the reproduction and PR helper tests.
Last resort
If parallel dispatch is genuinely impossible (no Agent tool, no background bash), fall back to serial invocation and say so in the round summary — serial reviewers see the baseline at different wall-clock times; it's a degraded mode, not the design.
The
and
skills document the same recipes from the reviewer side; the canonical dispatch lives here.
3. Synthesize
When all reviewers return:
- Dedup on when present, else . Keep the highest-severity instance, record which reviewers raised it, and keep divergent anchors as .
- Group by severity ( → ), then topic.
- Unparseable reviewer output: pass the prose through as "unstructured" findings in a separate section; no retry.
Verify and reproduce findings before acting on them
Plausible-but-wrong findings are the dominant failure mode of LLM review, and both downstream actions are expensive: a bogus fix commit pollutes the branch, a bogus finding posted to a PR burns the author's trust. After dedup:
-
Prior-feedback check first (orchestrator judgment). Compare every merged finding against PR REVIEW HISTORY
semantically — same root cause counts even when wording differs. Revalidate
,
, and summary-only
items against the current head. An open item that remains real is a carry-forward finding and must retain its effect on severity/approval, but must not create a duplicate inline thread; a resolved item means only that GitHub discussion was closed, not that the code was proven fixed. Record a still-present resolved item as
resolved-but-still-present
in the summary and do not create a new inline thread. Items now fixed/stale become one-line status notes. Explicit local
items are never auto-fixed or posted as actionable findings, but remain as compact status-only audit rows. Keep
items in PR REVIEW HISTORY for reviewer context. After synthesis and dedup, drop semantic matches to
items before building reproduction candidates. Exception: retain a finding when the reviewer explicitly set
prior_feedback: reintroduced
for a distinct new instance with new evidence. Do not report, post, auto-fix, or let ordinary
matches affect approval. A reintroduced finding remains actionable; it affects approval only at
or
severity. The post-time helper catches near-verbatim repeats (exact path + text similarity ≥ 0.9) as a deterministic duplicate-thread backstop.
-
Scope/artifact filter next (orchestrator judgment). Drop or move to out-of-scope follow-ups before reproduction when the claim is about archived design notes, changelogs, old migration examples, generated fixtures, vendored files, or historical docs that are not the review's live product surface. Do not spend verifier budget proving historical provenance is stale. Conversely, live docs that users rely on — README usage, CLI help, API docs, config reference, plugin metadata, or marketplace copy — are product surface and may become reproduction candidates when they drift from code/runtime behavior.
-
Assign provenance IDs. Assign IDs after semantic deduplication, prior-feedback classification, and scope filtering, and before reproduction or adversarial dispatch. Use this output recipe:
text
PR finding: `RAV-RUN<run>-R<origin-round>-F<ordinal>`
PR plan: `RAV-RUN<run>-R<origin-round>-P<ordinal>`
Local: `RAV-R<origin-round>-F<ordinal>` / `RAV-R<origin-round>-P<ordinal>`
Canonical grammar:
RAV-(RUN<run>-)?R<origin-round>-(F|P)<ordinal>
.
and
are unpadded positive base-10 integers. Encode
from an unpadded positive base-10 integer by left-padding only to a
minimum width of three digits: encode 1 as
, 10 as
, 100 as
, and 1000 as
; do not add any other leading zero. Valid
multi-digit examples are
and
. Invalid forms include
,
,
,
,
, and
.
ID legend:
is the observed PR review run,
is the immutable origin round,
is a finding, and
is a plan. Examples are
,
,
, and
.
Cross-round allocation example: in trusted PR run 3, a history finding with
keeps that ID. A new round-1 finding receives
and keeps it when re-raised in round 2. The next new
round-2 finding receives
. A round-2 plan covering those
two current-run findings receives
. The matching
local/degraded allocation for the same newly allocated items has no history
carry-forward:
,
, and
.
The carried
has no new inline comment. It remains
available in history, the report, reproduction, and adversarial review.
Finding and plan ordinals are independent, run-wide counters named
and
. Both start at
.
Counters never reset each round and never reuse gaps. Before assignment,
order findings by priority, normalized path, line, and topic; order plans by
covered-finding priority, area, and subject.
A finding's origin round is the first normal review round that raises it. A
plan's origin round is the normal review round in which its concrete fix
group is first assembled.
The origin round never changes after confirmation, refutation, priority change, deferral, fix, or re-raise.
Reproduction and adversarial passes are not rounds.
Carried findings consume the exact
value supplied in
;
use that complete ID unchanged without advancing a current-run
counter. For an actionable history entry that has only
,
use the
value as a source alias and assign the next canonical ID. Do not
assign a new ID to a non-actionable legacy-only entry. Historical
,
,
, and
forms are migration/read-boundary aliases only. New findings and plans always use the recipe above.
Once assigned, keep the complete ID unchanged in reproduction candidates,
adversarial targets, report rows, plan coverage, and later round references.
ID reuse flows to an inline body only when the finding is otherwise eligible
for a new inline. Ordinary
,
, and
carry-forwards
retain their IDs in history, reports, reproduction, and adversarial targets,
but do not create a new inline thread.
Already-assigned in-scope low/nit and set-aside findings keep their complete canonical F IDs.
Truly unassigned out-of-scope follow-ups stay distinct and do not receive an F ID.
-
Build
after prior-feedback classification and ID assignment:
- every + finding raised by exactly one reviewer,
- every + deletion/dead-code/unused/redundant-code/simplification finding, and any deletion/simplification that would remove runtime code, public docs/API, compatibility behavior, or another high-blast-radius surface, regardless of reviewer count,
- every / finding whose evidence is mostly inferred from a hunk rather than confirmed from code/runtime context,
- every finding the orchestrator is materially uncertain about after reading the cited files.
-
When
or
and candidates exist, dispatch
one batched reproduction verifier using
references/reproduction-prompt.md
. Do not spawn one verifier per finding unless the batch is too large to fit in one prompt. Dispatch it backgrounded under the Concurrency section's deadline rule — never an unbounded foreground wait. The verifier is not another broad review pass; it returns
,
,
,
, or
verdicts for the supplied complete canonical finding IDs only, and returns each ID unchanged.
-
Apply reproduction verdicts before auto-fix/reporting:
- and findings may remain actionable, with narrowed wording when supplied.
- findings re-enter the normal severity gates after changing severity.
- findings are dropped from final Findings (or, if useful for transparency, one-line Deferred notes).
- findings move to Deferred with
We set this aside because <plain-language description of the missing proof>.
Rewrite the verifier's reason; do not copy it.
-
Findings raised independently by 2+ reviewers and not listed as reproduction candidates may skip batched reproduction; consensus is the signal (this is why dedup records who raised what). Still open enough code/context before destructive action to ensure the fix path is coherent.
-
Deletions ("delete this"/dead/unused/redundant) require reproduction plus execution when applies the cut — the highest-blast-radius, highest-false-positive class. In
, after reproduction confirms the cut, apply it and run the full test suite: a
red gate means keep it. A green gate is necessary but not sufficient (it only proves
test-covered behavior), so the reproduction/skeptic pass must also look for a concrete reason the code must stay, visible in the diff (trust boundary, aliasing copy, ordering, back-compat, dedup, edge semantics — or another specific contract). The two cover different blind spots: the gate catches callers the skeptic can't see; the skeptic catches behavior no test exercises. Block
only on a red gate or a specific skeptic refutation — not on generic "there might be an unseen caller" (that's what the gate tests). Read-only mode has only the skeptic. Blocked →
Deferred (
We set this aside because the code is still needed — <what>
).
-
If
, say so in the round summary and final report. Required reproduction candidates — including single-reviewer
+ findings, deletion/high-risk findings, and orchestrator-uncertain findings — cannot become actionable unless the orchestrator independently reproduces them from code/tests/runtime evidence; otherwise move them to Deferred with
We set this aside because the needed check was not run.
-
/
findings skip verification: they're below the auto-fix gate and surface as suggestions either way.
Canonical examples for where reproduction helps and where it must stay out of
the way live in
references/reproduction-examples.md
. After changing this
policy or the reproduction prompt, run
scripts/test-reproduction-policy.sh
alongside the PR helper tests.
Approving out-of-scope follow-ups
A pre-existing issue outside the PR's scope can still be worth noting, but it must not become an inline/blocking PR finding. Classify each out-of-scope follow-up:
- Auto-approved follow-up — create/queue separate work when all are true: severity is / (or clearly reproducible ), the bug is confirmed from code/tests/runtime evidence, it is not a product decision/style preference, it is not already tracked in prior PR feedback or explicitly suppressed, and the fix is plausibly separable from the current PR.
- Needs human triage — mention only as a non-blocking follow-up when the issue is real but severity/ownership/product intent is ambiguous.
- Do not surface — drop if speculative, low/nit, a product decision, already dismissed/tracked, or only discoverable by reviewing unrelated code paths deeply.
When
is set, write follow-ups once, after the final round, to
<report_path>.followups.json
— schema in
references/report-artifacts.md
. Automation may file issues only for
entries after duplicate search; presets read the file before posting (the helper deletes it afterwards).
Optional adversarial review ( only)
When
is not
, run a bounded post-synthesis gate after
dedup/reproduction and before writing the final report artifacts. Read
references/adversarial-prompt.md
before dispatching adversarial reviewers.
Dispatch every adversary of the selected mode in parallel — one message,
multiple background tool calls, exactly like §2 reviewers, under the
Concurrency section's deadline rule — and synthesize verdicts when all return
or the deadline fires. Never await one adversary before launching the next.
Adversarial review is not another broad review pass and not a simulated patch
application. It attacks the candidate synthesis:
- Finding validity — false-positive claims, wrong anchors, dismissed
findings, out-of-scope issues, over-severity, and missing reachability
evidence.
- Fix proportionality — suggested fixes that would technically address a
problem but create more trouble than they solve: harmful blast radius,
unnecessary dependencies, bloated abstractions, future tech debt, non-local
churn, unsafe deletions, brittle tests, or symptom fixes that miss root cause.
- Report safety — unsafe one-click GitHub suggestions, unclear fix paths,
overconfident approvals, and actionable comments that should be deferred.
Modes:
| Mode | Dispatch | Intent |
|---|
| Chosen after synthesis | Selects , , , or using the default policy below. |
| 1 adversary | Cheap local check over all + findings and would-apply plans. |
| 2 adversaries | Recommended PR mode: false-positive/scope auditor + fix-plan breaker. Force a deletion skeptic when any would-apply item removes code. |
| 3 adversaries | Adds second-order bug hunting across interacting plans, config, migrations, and tests. |
| Same as | Approval-sensitive: any required adversary failure or unresolved + dispute forces COMMENT. |
Role mapping:
- : one combined adversary using the core prompt plus both the
false-positive-scope-auditor
and role additions.
- : two adversaries, one
false-positive-scope-auditor
and one
; add/replace with the deletion skeptic behavior from
when any would-apply plan removes code.
- /:
false-positive-scope-auditor
, , and
; add only if the report/approval
artifact itself is the risky surface.
Default policy:
- Local defaults to . If the user asks for careful,
skeptical, high-confidence, low-noise, or thorough read-only review, the
orchestrator should append unless the user explicitly
asked for a fast/rough pass.
- defaults to because GitHub output is
public reviewer speech and may include inline comments, one-click
suggestions, or an approval event.
- Explicit user input wins: disables the gate; explicit
/// uses that mode. In review-only PR runs,
explicit also forces to ; unchallenged LLM review should not satisfy branch protection by
accident.
selection after normal synthesis:
- First estimate meaningful changed size from the reviewed snapshot. Exclude
generated/vendor/build artifacts, lockfiles, and snapshot/fixture churn unless
those files are the review's product surface. Treat meaningful changed
lines or meaningful files as large, and meaningful changed
lines, meaningful files, or several interacting subsystems as very
large. Size is an escalation floor, not the only signal: a small risky auth or
migration diff can still choose , while a huge mechanical rename
may stay below after exclusions.
- Use only when approval is disabled/impossible and the result is clean
or low/nit-only, has no + inline comments, no GitHub suggestion
blocks, no / actionable or deferred author-action items, and
no would-apply plan with deletion, dependency, non-local behavior, or
abstraction/churn risk. For local non-PR runs, ignore the approval condition.
- Use for small or self-authored comment-only reviews with material
feedback but no suggestion blocks, no high-risk fix plans, and /
REVIEW_ANVIL_NO_APPROVE=1
.
- Use when candidate output includes any + inline comment,
any GitHub suggestion block, any / actionable/deferred item,
any would-apply plan that removes code, adds dependencies, changes behavior
non-locally, touches auth/security/data/schema/migrations/concurrency/config,
or looks like abstraction/tech-debt risk, or when open/resolved prior PR
feedback touches the same files/root causes, or when the diff is large by
meaningful changed size.
- Use when the meaningful diff is very large or cross-cutting across
several subsystems, unless exclusions show it is mostly mechanical/generated
churn. adds second-order plan scrutiny without making approval more
brittle by itself.
- Use only when the user explicitly asks for approval-sensitive
behavior or branch protection / CODEOWNER requirements are confirmed. If any
required adversary fails, times out, or returns unparseable output in ,
force . Do not escalate to merely because branch protection
is unknown; use and force if approval safety cannot be
established.
Build would-apply plans from the same concrete fix groups
would have
committed. Assign each plan its provenance ID when that fix group is first
assembled, before adversarial dispatch; reproduction results may remove or
reshape a group, but they never renumber a surviving plan or let another plan
reuse its gap. Each plan lists covered finding IDs unchanged, the simulated
conventional-commit subject, the intended fix path, risk tags such as
/
/
/
, and any exact suggestion
blocks that would be emitted. For local non-PR reviews, set
to
; for PR/report-path reviews, set it to the tentative
event/reason. Adversarial reviewers return verdicts against
those complete unchanged IDs using the schema in
.
Apply verdicts conservatively:
- only when concrete evidence shows a false positive, dismissed finding,
or out-of-scope claim.
- when the issue may be real but the proposed fix is harmful,
bloated, tech-debt-heavy, too non-local, or disproportionate for this PR.
- to harden or simplify the prose fix path while keeping the finding
actionable.
- when the finding and fix path survive challenge.
Generic uncertainty does
not defer a finding. The adversary must cite code,
configuration, tests, runtime behavior, status-aware PR feedback, or PR scope
evidence. Adversaries must not create new actionable findings. If they notice a
new issue while attacking a plan such as
, record it only as
a second-order plan risk or follow-up; it remains Deferred unless the
orchestrator runs a separate normal review/verification pass.
Adversarial review may make the output more conservative. It must never make a
speculative fix more authoritative. It can strip unsafe
fields
from
, move items to Deferred, harden/simplify fix prose, or
downgrade approval. It must not create patches, commits, or final actionable
findings from unverified adversarial ideas.
Run at most
passes, capped at 2. A second adversarial pass
runs only when the first pass materially changes
+ guidance, changes
approval, or rewrites a would-apply plan. Complete
target verdicts affect
fix plans, suggestions, and fix prose only; linked complete
finding IDs
change only when an independent verdict against that same finding ID refutes or
defers it. With
disagreement_policy=defer
, unresolved
adversarial
disputes move the item to Deferred but do not by themselves block
;
unresolved
/
disputes block
. With
disagreement_policy=comment
, unresolved
+ disputes keep the item
actionable but force the review event to
.
4. Apply fixes
Skip entirely when (the policy below is still evaluated in the abstract for the report).
Otherwise
read references/fix-application.md
before making any edit: it defines the conventional-commit fix-group style, the auto-fix proportionality rules (severity gate >=
; no new dependencies without
; per-round size cap; noise is deferred with a reason, never silently dropped), and the build/test gate (
resolution, baseline run, fix-forward-or-revert, revert-failure escalation). The invariant:
a round never ends with the build/test gate newly red.
5. Round summary
Append to running output:
### Round N — <convergence flag>
- Parameters: rounds=3, max_rounds=3, target=acme/widgets#42 [pin], commit_mode=none [pin], focus=4-pillar
- Reviewers: <list dispatched>
- Earlier review comments: none | <open> open, <resolved> closed, <reported> summary-only, <author-resolved> author-resolved (skipped), <suppressed> skipped; <still-present>/<fixed>/<stale> after checking
- What I noticed: C critical, H high, M medium, L low, N nit
- Fixes applied: K commits (<sha1>..<shaN>) # or "0 (review-only)"
- Verification: <cmd> — passed | failed → round reverted | pre-existing failures (no new) | none detected | skipped # per_fix only
- Checks: off | skipped (no findings needed checking) | <C> concerns checked, <confirmed> confirmed, <refuted> ruled out, <deferred> set aside, <downgraded> lowered in priority; <elapsed>
- Things to try: W items # commit_mode=none only
- Second check: off | <mode>, <A> reviewers, <upheld> kept, <hardened> clarified, <deferred> set aside, <dropped> removed
- Other notes: S items (low-priority items; not applied)
- Set aside: D items (reasons: noise / new dependency / size cap / not confirmed / failed verification / product decision)
- More rounds: off | not continued because <reason> | continued to round <next_round> because <reason>; cap=<max_rounds>
Pinned params carry
(authority comes from preset argument order; the PR-target/per_fix rule is the final safety net). Convergence flag:
(no findings),
(nothing above
),
(≥1 medium+).
6. Continue or finish
If completed rounds are still below
, start the next requested round;
round N+1 reviews the new state including round N's commits.
Early exit on convergence ( only). A
or
round ends the loop even if requested or adaptive rounds remain — further
rounds re-review converged code and mostly produce noise. Note this in Run
Details, e.g.
Rounds: N completed (R requested + A adaptive, max M); converged after round N of R requested
. (In
, multi-round = reviewer
redundancy over the same baseline; no early exit and no adaptive continuation.)
Adaptive continuation ( only). After the requested
are
complete, the orchestrator may start one more round only while
completed_rounds < max_rounds
and all continuation criteria hold:
- The latest round was and produced at least one new
material finding. New = not raised in any earlier round, or a
reproduction-confirmed re-detection of a finding a previous round fixed and
verified (evidence the fix failed — the core case continuation exists for).
Unconfirmed or refuted re-detections and items already Deferred do not count.
- The latest round applied at least one verified fix commit, or changed a
risky/shared surface where another pass is likely to catch second-order bugs.
- Verification for the latest round ended ; , ,
pre-existing failures (no new)
, and are acceptable
for requested rounds but not strong enough to justify extra automatic work.
- All expected reviewers succeeded with parseable output. Degraded reviewer
signal can finish requested rounds, but it must not trigger adaptive rounds.
During an adaptive round, this is a hard gate before §4: any reviewer failure,
timeout, empty output, or unparseable findings block stops the run before
applying fixes from that adaptive round, writes a failure report when
is set, and prevents improve-PR pushes.
- The remaining risk is likely to converge with another review/fix pass. Do not
extend for low/nit-only feedback, suggestions below , items
already Deferred, product decisions, new-dependency deferrals, size-cap
deferrals, or repeated findings that were not fixed.
Record the continuation decision in the final requested round's summary and in
the adaptive round summary when one runs. If an adaptive round still has
and hits
, finish and use the tuning suggestion
rule; do not keep extending without a larger explicit cap.
After the final round, emit the
Final Report (Output Format). If
is set:
-
Write the rendered PR report there (creating parent dirs).
-
Write a sibling
<report_path>.inline.json
: an array of GitHub PR review comment payloads for findings with both
and
—
json
[
{"path": "src/auth.ts", "line": 50, "side": "RIGHT", "severity": "high", "body": "**Refresh creates a session before CSRF validation**\n\nThe handler rotates the session before it checks the state token. A stale tab can create a new session with an invalid token.\n\n**What to change**\n\n- Check the state token before rotating the session.\n- Reject invalid tokens without creating sessions.\n- Add a missing-state-token test.\n\n<!-- review-anvil: id=RAV-RUN3-R2-F001 severity=high area=auth -->"},
{"path": "src/db.ts", "start_line": 100, "line": 110, "side": "RIGHT", "start_side": "RIGHT", "severity": "medium", "body": "**Retry accounting records success before the write succeeds**\n\nThe retry block increments `attempts_succeeded` before `insert_event` returns. A timeout records success even when no row was written.\n\n**What to change**\n\n- Increment `attempts_succeeded` only after `insert_event` returns.\n- Keep timed-out attempts eligible for retry.\n- Add a timeout test.\n\n<!-- review-anvil: id=RAV-RUN3-R2-F002 severity=medium area=db -->", "suggestion": "result = insert_event(payload)\nattempts_succeeded += 1\nreturn result"}
]
Single line →
{"line": N, "side": "RIGHT"}
; range
→
{"start_line": N, "line": M, "side": "RIGHT", "start_side": "RIGHT"}
. Findings without anchors stay in the markdown body only; no anchored findings →
. Start each identified inline body with the natural bold title
. End it with exactly one
<!-- review-anvil: id=<complete-id> severity=<severity> area=<area> -->
marker on its own final line. The marker area is the same machine-safe area token used by the report row: letters, digits,
,
,
, or single
separators. No visible ID, severity tag, or area label belongs in the title. A reader must be able to create the fix from each visible
alone.
Include helper-only
for every inline item. The posting helper strips it before calling GitHub and uses it to keep low/nit findings summary-only by default. Include helper-only
only when the fix is an exact replacement for the commented line/range; the helper turns it into a GitHub suggestion fenced block, inserts that block before the final finding-metadata marker, and strips the extra key before posting. Preserve an exact source suggestion only when it satisfies every safety exclusion in the next sentence and still matches the verified anchor and replacement. Do not include suggestions for design fixes, cross-file edits, deleted lines, anything that requires judgment, or any suggestion whose anchor/replacement/blast-radius was disputed by adversarial review.
The helper-only
must match the terminal marker severity exactly; the helper aborts before filtering or posting when they disagree.
A present but unrecognized helper severity also aborts; an absent helper field may use the terminal marker during migration.
For an explicitly reintroduced
finding, place
<!-- review-anvil: prior_feedback=reintroduced -->
immediately after its visible final-report finding row or bullet. Its matching inline item must carry helper-only
"prior_feedback": "reintroduced"
; the posting helper uses it before author-resolved suppression, strips the JSON field before the GitHub REST request, and inserts the hidden prior-feedback marker before the final finding-metadata marker so later history retains the disposition.
Each eligible new
puts the same complete finding ID as its report row, reproduction target, and adversarial target inside the final finding-metadata marker, then follows the
inline-comment voice in
references/report-artifacts.md
— read it before composing bodies. Keep it short and plain: say what the code does, what happens because of it, and every source-backed requested obligation. Treat remediation as a request, not as code already present. Group work by cohesive implementation obligation, not by grammar; do not split values governed by one rule. Only work the author must perform belongs under
. Keep each permission, exception, carve-out, or already-correct behavior with the action it limits or in short modal prose immediately after that action. Keep exact source scope wording. Treat a required source-backed test as author work. For one
/
/
obligation, use one concise
sentence. For two or more independently implementable obligations, use
and one direct-action bullet per obligation. A reader must be able to act without reopening the diff. Include a safe exact
or a short code sketch only when it removes doubt. The finding-metadata marker is the last nonblank line: no prose, suggestion fence, or prior-feedback marker may follow it. By default, inline comments are for
/
/
anchored findings;
/
findings remain in the top-level summary unless the user or environment lowers
REVIEW_ANVIL_INLINE_MIN_SEVERITY
. Ordinary prior-feedback carry-forwards do not produce new inline payloads.
Classify every source predicate before rendering it. Target behavior and required verification or documentation are author work. Accepted current behavior, allowed implementation boundaries, and explicitly optional follow-ups are no-change boundaries. Source intent outranks modal grammar:
,
, and
do not make target behavior optional.
Apply the omission counterfactual only after that classification. Split every source sentence that mixes author work with a no-change boundary into predicates for classification; do not keep an unsplit fallback. If leaving the current code without a target behavior, test, or document change leaves the defect, safety boundary, or reviewer-required verification unresolved, treat that predicate as author work.
A no-change boundary is not a separate obligation. When accepted current behavior directly constrains a requested change, keep it in the same sentence using
or
while keeping … unchanged
; do not create a separate action bullet for it. If that sentence would be dense, use short modal prose immediately after the action. Preserve other accepted current behavior or an explicitly optional follow-up as standalone modal prose. Request the smallest new mechanism needed to resolve the failure, but keep each required purpose, result, or safety clause with the action it constrains. Treat source-backed verification or documentation stated to cover, demonstrate, clarify, or make a required boundary clear as author work unless it is explicitly optional. Do not convert acceptable unchanged behavior, an allowed implementation boundary, or an explicitly optional follow-up into mandatory work.
Before drafting, freeze the exact requested-work prose, the minimum source context needed to resolve antecedents and current-versus-target behavior, and the exact evidence and code fragments that the body may retain from each accepted synthesized finding. Run two independent post-render action-lock auditors from
references/action-lock-audit.md
in one parallel wave before writing inline JSON. Give each clean verifier only that frozen source material and each exact complete rendered inline body; do not send unrelated repository or report context. For a passed rewrite, write only the byte-identical audited body to
; any later body edit invalidates the verdict and requires a new audit wave. The exact-source fallback in the next step is exempt and forces COMMENT.
Validate each auditor's complete per-ID output and combine valid failures from either auditor. Repair only inline bodies with a valid failed verdict, then run two new clean auditors once more. For a first-audit row that is missing, duplicate, malformed, timed out, or otherwise unverifiable, restore the exact source requested-work prose without attempting a repair and force the review event to COMMENT. If either second-audit verdict still fails or is unverifiable, use the same exact-source fallback and COMMENT event. Never omit the finding or a requested predicate to make the audit pass.
-
Write a sibling
<report_path>.approval.json
so the PR-posting helper can choose the GitHub review event (review-only PR runs; for other runs write
or omit the file — the helper defaults to COMMENT):
json
{
"event": "APPROVE | COMMENT",
"head_sha": "<the HEAD_SHA the preset captured at init/verify-checkout>",
"adversarial_mode": "off | auto | challenge | targeted | full | strict",
"approval_allowed": true,
"reason": "No high/critical in-scope findings; medium-and-lower items are left to the author."
}
Include
— the
the preset captured at init/verify-checkout time — so the posting helper can verify the approval still matches the reviewed state (it downgrades to COMMENT if the PR head moved mid-run). Include
and set
"approval_allowed": false
when approval must be mechanically disabled, including explicit
in a PR run or any run where an action-lock exact-source fallback has been used.
Use
for review-only PR runs when all of these hold:
is not
, at least one reviewer succeeded, no action-lock exact-source fallback has been used, there are no
/
actionable in-scope findings, no
/
in-scope deferred finding needs author action, no prior
/
open or resolved-but-still-present item remains unaddressed, no unresolved
/
adversarial dispute remains, and remaining items are only
/
/
findings, suggestions, deferred notes, or out-of-scope follow-ups. Medium-and-lower in-scope findings should still be posted clearly, but the review event is approval: leave those fixes to the author. Use
otherwise. Out-of-scope follow-ups do not block approval.
-
Print the report path as the last output line; the
and
files are implied by convention.
-
For out-of-scope follow-ups, write the sibling
<report_path>.followups.json
once, after the final round, using the follow-ups schema from §3 "Approving out-of-scope follow-ups" (NOT the
schema above). The posting helper deletes it after a successful post, so any consumer (surfacing follow-ups to the user, filing issues for
entries after duplicate search) must read it
before the post/post-update step — the presets do this.
Failure handling
- A reviewer fails or times out in a requested round → log
<agent>: failed (<reason>)
in the round summary and proceed; no retries except the protocol-only case below. For Bash-dispatched reviewers, failure = wrapper STATUS //, reason = tail of ; follows the next rule. In an adaptive round, any reviewer failure is an abort before fixes from that round are applied.
- Protocol-only corrective retry: a non-empty response that asks for confirmation, emits only a plan/status update, or otherwise omits the complete fenced findings block is not review output. Bash reviewers surface this as ; detect the same shape directly for Agent-tool reviewers. Re-dispatch that reviewer exactly once on the same snapshot and lens with this prefix:
PROTOCOL RETRY: Begin the already-authorized read-only review immediately. Do not ask for confirmation or present a plan. Return the completed review now and end with the required fenced findings block.
Do not answer the confirmation request and do not count the first attempt as a reviewer result. If the retry also violates the protocol, record the reviewer as failed (confirmation/plan-only output after corrective retry
) and follow the normal requested/adaptive-round failure rules. This is the only content-level retry; do not retry weak, clean, or merely unstructured reviews.
- A dispatch call — foreground or background — that dies with exit 143 or ends with no line in its output was killed by the host tool's timeout before the wrapper could classify — misconfigured dispatch (see §2 "Host tool timeouts"), not a reviewer failure. Fix the dispatch mode (raise the tool timeout or go detached) and re-dispatch that reviewer exactly once; only a second identical death counts as a failed reviewer.
- All reviewers fail → abort the loop and report. Never carry on with zero findings — that's a misleading clean signal.
- fails (hook, conflict) → surface the error, stop the loop, leave partial fixes in the worktree. Never , never amend earlier commits.
- On any abort, if is set, write a failure report to it before stopping — the usual header block, a section stating what happened and at which round, plus any completed round summaries. Downstream consumers ('s post-update step) depend on the file existing on every exit path, success or failure. Write to in this case.
Concurrency
Within a round: parallel (single multi-tool-call message). Between rounds: strictly sequential.
Synthesis-side dispatches — the batched reproduction verifier, every adversary, and each two-auditor action-lock wave — follow the same parallel rule (all agents of a pass launched backgrounded in one message, never serially awaited) and get a hard deadline equal to the
effective (after any >5000-line doubling):
- Bash dispatches enforce the deadline mechanically: run them under with that cap.
- Agent-tool dispatches have no built-in timeout — the source of a production run hanging forever at "awaiting verdicts". Give each dispatch wave exactly one one-shot deadline alarm: a single background Bash task launched in the same message as the agents, killed as soon as they all return. When the alarm fires first, salvage every complete per-ID verdict already present in output files and apply the safe failure for anything missing: reproduction verifier failure → Deferred; adversary failure → continue and note; action-lock failure → restore exact source requested-work prose for every unverifiable row and force COMMENT; → COMMENT. Record in Run Details.
No recurring or polling timers: the harness notifies on completion, so the one-shot alarm above is the only sanctioned wakeup. After the final report is emitted (and its path printed, when
is set), kill any still-pending alarm — nothing may fire after completion.
Reviewer Prompt Template
Read references/reviewer-prompt.md
(next to this SKILL.md; same trusted-root resolution as scripts) at dispatch time. It defines the per-reviewer lens assignment (the four pillars partition across reviewers — M identical prompts buy redundancy and dedup work, not coverage), the context block (TARGET / PRIOR ROUNDS / SCOPE OF THIS REVIEW / PR REVIEW HISTORY / YOUR LENS), the fixed task block (review principles, severity guide, structured finding keys, the fenced findings-YAML output contract), and the fill-in rules (itemized PRIOR ROUNDS construction;
variations). Reviewers return prose findings only — never patches.
When
is enabled, normal reviewers still use
.
When
dispatches, the verifier uses
references/reproduction-prompt.md
and receives only the stable candidate
finding IDs selected by synthesis. The post-synthesis adversaries use
references/adversarial-prompt.md
and receive the surviving stable candidate
finding / would-apply IDs produced by synthesis and reproduction.
Output Format
During execution: print
Round 2/3: dispatching 2 codex-exec + 1 claude-exec on PR #42 …
before requested rounds, or
Round 4 (adaptive; requested 3, max 4): dispatching …
for adaptive rounds, then the round summary block (§5) after.
Final report
After the last round, emit a fresh top-level report (a new document, not a replacement for the per-round blocks).
The final report is an external-facing decision summary. It must include every finding, but it should read like a scan-friendly index, not a transcript. Do not paste raw reviewer output, full round transcripts, repeated metadata, or paragraph-sized low-priority notes. Put each finding in exactly one focused row or bullet, grouped by severity/priority. Keep the decision, result, scope, and actionable critical/high/medium findings visible. Use progressive disclosure for supporting detail whenever the destination supports it: GitHub uses collapsed
blocks; other destinations should use their native disclosure controls. If the destination cannot collapse content, post a concise decision summary and preserve the complete report in an attached artifact or stable link. Never hide blockers only in an expandable section. If the report feels too bulky, rewrite and organize it yourself; do not depend on the posting helper to compact or shorten it.
# ⚒️ review-anvil report
**Review decision:** APPROVE | COMMENT — <one-sentence reason> # review-only PR runs
**Result:** <one sentence: blockers/non-blockers/fixes/verification outcome>
**Scope:** <For PR targets: one sentence summarizing what this PR is trying to change.>
**Verification:** <verify_cmd used, or "none detected" / "skipped"> # per_fix only
**Checks:** off | skipped (no findings needed checking) | <C> concerns checked; <confirmed> confirmed, <refuted> ruled out, <deferred> set aside, <downgraded> lowered in priority
**Second check:** off | <mode>, <A> reviewers; <upheld> kept, <hardened> clarified, <deferred> set aside, <dropped> removed
## Earlier review comments
<For PR-context runs, list each earlier comment once as open, still present after
being marked resolved, fixed, no longer relevant, or intentionally skipped.
For `author-resolved` items, count them separately as skipped but omit their
individual finding text; retain the original URL and a short plain-language
status reason. Actionable carry-forwards affect the review decision even when no
new inline comment is needed; `author-resolved` history does not affect the review decision or approval. Keep 1-3 rows visible; for
4+ rows use a collapsed block with summary `Earlier review comments (N items)`.
Preserve the exact internal status in the artifact; write the visible
explanation in plain language.>
## What I noticed
<Show every confirmed issue once. Critical/high issues go first, then medium,
then low/nit. Start with the facts: what the code does and what happens because
of it. Inline comments carry supporting evidence and the source-backed
requested change. Otherwise, add the smallest supporting fact to what you noticed.
If none: "No confirmed problems found.">
ID legend: `RUN` is the observed PR review run, `R` is the immutable origin round, `F` is a finding, and `P` is a plan.
| ID | Priority | Topic | Code location | What I noticed |
|---|---|---|---|---|
| RAV-RUN3-R2-F001 | high | auth | `src/auth.ts:42` | Refresh creates a session before it checks CSRF validation |
<If the table would be hard to read, use grouped bullets instead:>
- **RAV-RUN3-R2-F001 [high] auth** `src/auth.ts:42` — Refresh creates a session before it checks CSRF validation. (inline)
<details>
<summary>Non-blocking low/nit findings</summary>
- **RAV-RUN3-R2-F002 [low] docs** — The CLI help could use the same option name.
- **RAV-RUN3-R2-F003 [nit] tests** — The duplicate fixture setup can be shared.
</details>
## Changes made / Things to try
<For per_fix: focused commit list or "No fixes were made." For review-only:
include each thing to try as one short, plain-language behavior change. In
external reports, collapse this section when it contains more than 3 items.>
- `<sha>` — <subject> # per_fix only
- **[severity] area** — <plain-language behavior change>. (`RAV-RUN3-R2-P001`; covers `RAV-RUN3-R2-F001`) # commit_mode=none only
## Set aside / Outside this change
<Include each item not addressed here in one line. Collapse this section when
it contains more than 3 items. Omit it when empty.>
- **RAV-RUN3-R2-F004 [medium] runtime** — set aside because the failing path could not be confirmed.
- **RAV-RUN3-R2-F005 [medium] config** — set aside after the second check: the fix is too large for a one-line default.
- **[severity] area** — follow-up outside this change: <why separate>.
<details>
<summary>Run details</summary>
- Target: <e.g. "PR #42 (feature/auth-rewrite, 12 files, +340/-89)">
- Run ordinal: <positive observed PR run ordinal | unavailable (local/degraded)>
- Rounds: <completed> completed (<requested> requested + <adaptive> adaptive, max <max_rounds>); <convergence/adaptive stop note> # productive adaptive-capable runs
- Rounds: <completed>/<requested> completed; adaptive off; <convergence note> # review-only/exact/no-extra runs
- Mix: <e.g. "2 codex-exec + 1 claude-exec">
- Focus: <focus list actually used>
- Earlier review comments: none | <total> comments; <open>/<still-present>/<fixed>/<not-relevant>/<author-resolved-skipped>/<skipped>
- Finding counts: <C critical, H high, M medium, L low, N nit; other notes S>
- Checks: off | skipped | concerns=<C>; confirmed=<confirmed>/ruled-out=<refuted>/set-aside=<deferred>/lowered=<downgraded>; elapsed=<duration>
- Second check: off | <mode>; reviewers=<A>; kept=<kept>/clarified=<clarified>/set-aside=<deferred>/removed=<removed>; approval changed yes/no
- Set aside: <D> items; reasons=<reasons>
- Next time: <one line; see rule below> # omit in review-only
</details>
---
_Reviewed with [review-anvil](https://github.com/mrshu/agent-skills/#review-anvil)._
= post-dedup count of unique findings auto-applied across all rounds.
Tuning suggestion rule
Omit in review-only. Early exit already stops the loop when a round comes back
clean, and an adaptive round that converges needs no tuning suggestion. The
remaining cases:
- If every completed round was and ,
suggest re-enabling adaptive continuation () or
setting for the next run.
- If the adaptive cap was reached and the final round was still
, suggest increasing by 1 only when the final
round applied verified fixes; otherwise suggest resolving Deferred blockers or
verification gaps before adding more rounds.
- If adaptive continuation was blocked by skipped/missing verification, suggest
setting a trustworthy before increasing .
When the cap that bound was the diff-size-scaled default, name the tier and the
exact value to re-run with, e.g.
small-diff adaptive cap (rounds+1=4) reached; re-run with max_rounds: 5 to continue
.
Edge Cases
| Case | Behavior |
|---|
| Missing reviewer backend | Validate only the backends the resolved mix actually names, before round 1. Abort with: "review-anvil requires the skill from the mrshu-skills marketplace. Install via /plugin install <missing-skill>@mrshu-skills
(Claude Code) or npx skills add mrshu/agent-skills --skill <missing-skill>
(cross-agent)." |
| No diff in auto-detected target | Abort: "No target detected — nothing to review." Don't invent work. |
| Raw diff > ~5000 lines | Warn in the round status and continue; tell reviewers they may focus on the most impactful slice; double (unless the user set it explicitly). For , estimate meaningful changed size after exclusions; very large meaningful diffs select at least , but generated/mechanical churn alone does not force deeper adversarial review. |
| Reject before round 1 — more dedup work than signal. |
| Reject — almost certainly a typo. |
| Reject before round 1 — the adaptive cap cannot be below the requested round count. |
| User-supplied with | Warn and set ; read-only extra rounds are explicit redundancy via , not adaptive refinement. |
| with | Warn and ignore — productive mode verifies real fixes with the build/test gate. |
| Reject before dispatch — adversarial review is bounded critique, not an open-ended debate. |
| Reproduction verifier failure | Keep consensus findings that did not require reproduction, but move required single-reviewer + and deletion/high-risk candidates to Deferred with We set this aside because the verification check could not be completed.
; never silently promote them. |
| Adversary failure | Continue with the normal synthesized report and note the failure in Run Details; in , any required adversary failure forces . |
| Unparseable findings block | Confirmation/plan-only output follows the one protocol retry above. Otherwise, in requested rounds use substantive review prose as free-form findings with no retry and note <agent>: unstructured findings (parse failed)
; in adaptive rounds, abort before fixes from that round are applied. |
| Reviewers contradict each other | Surface both under the same area with reviewers tagged; orchestrator judgment decides the fix; mention the disagreement in the round summary. |
| Re-runs | Not idempotent: a new run reviews the latest state, including the prior run's commits. Surface still-present deferred items under "Deferred from previous runs (still present)". |
Generated Language
Apply
references/asd-ste100-inspired.md
to every generated message.