Agent Skills: Quality Review

Adversarial implementation review with triage and fix loop, tiered by role. Hard-gates on `pnpm check`, delegates initial discovery to the quality-reviewer agent (opus/xhigh) for categorized findings (Critical/High/Medium/Nice-to-Have/Approved), then triages and fixes findings — mechanical, prose-only, and gate-clearing fix-now items applied directly by the orchestrator, substantive fixes dispatched to sonnet-tier developer agents. Re-reviews and confirmations run on the lighter quality-verifier agent and verify the fix delta (carried as an inline diff, or as a precomputed diff file above 200 lines) rather than re-reviewing from scratch; after cycle 2 only fix regressions and Critical/High findings on the change re-arm the loop (new Mediums are filed as severity-carrying deferred items), so the loop converges when a re-review surfaces no new substantive findings (an all-prose/mechanical batch — the initial review's included — converges without a re-review at all) — with a soft ceiling of 5 cycles that drains the mechanical/prose lanes before asking the user how to proceed (in auto mode, a decreasing recent findings trend instead extends the ceiling once); option 3 of that prompt terminates with verdict `escalated-to-architect`. Autonomous mode via the `auto` token (prompts resolve to documented defaults; used by /auto via /start auto). Use when the user says 'review my work', 'check this implementation', 'adversarial review', 'quality review', or invokes /quality-review.

UncategorizedID: alienfast/claude/quality-review

Install this agent skill to your local

pnpm dlx add-skill https://github.com/alienfast/claude/tree/HEAD/skills/quality-review

Skill Files

Browse the full folder contents for quality-review.

Download Skill

Loading file tree…

skills/quality-review/SKILL.md

Skill Metadata

Name
quality-review
Description
Adversarial implementation review with triage and fix loop, tiered by role. Hard-gates on `pnpm check`, delegates initial discovery to the quality-reviewer agent (opus/xhigh) for categorized findings (Critical/High/Medium/Nice-to-Have/Approved), then triages and fixes findings — mechanical, prose-only, and gate-clearing fix-now items applied directly by the orchestrator, substantive fixes dispatched to sonnet-tier developer agents. Re-reviews and confirmations run on the lighter quality-verifier agent and verify the fix delta (carried as an inline diff, or as a precomputed diff file above 200 lines) rather than re-reviewing from scratch; after cycle 2 only fix regressions and Critical/High findings on the change re-arm the loop (new Mediums are filed as severity-carrying deferred items), so the loop converges when a re-review surfaces no new substantive findings (an all-prose/mechanical batch — the initial review's included — converges without a re-review at all) — with a soft ceiling of 5 cycles that drains the mechanical/prose lanes before asking the user how to proceed (in auto mode, a decreasing recent findings trend instead extends the ceiling once); option 3 of that prompt terminates with verdict `escalated-to-architect`. Autonomous mode via the `auto` token (prompts resolve to documented defaults; used by /auto via /start auto). Use when the user says 'review my work', 'check this implementation', 'adversarial review', 'quality review', or invokes /quality-review.

Quality Review

Run an adversarial review of the current implementation, then triage and fix findings until the implementation passes cleanly. Designed for use mid-development (before /finish) — either standalone, or delegated from /start Step 9.

Arguments

  • Issue identifier (e.g., PL-13) — optional. Auto-detected from branch/commit when omitted; if no issue can be resolved, the skill runs without requirements-conformance context.
  • Positional file paths — optional override of the auto-detected scope.
  • auto — optional token (case-insensitive, position-agnostic). Autonomous mode: every prompt resolves to a documented default instead of asking — the Step 5 soft-ceiling prompt resolves by its trend gate, then its lane-drain + provenance auto rule (a run whose per-cycle loop-fuel findings are decreasing over the most recent cycles extends the ceiling once by 3 and resumes; otherwise mechanical and prose-only survivors are fixed and confirmed rather than terminated over, any remaining substantive Critical/High or regression → option 2, terminated-with-open-items, and an all-Medium residue that adjudicates as pre-existing → converge and file the survivors as severity-carrying deferred items), sub-step 6's filing prompt resolves to suggested, and sub-step 6's state-name fallback files into the team default state (noted in the verdict block) rather than asking. Nothing else changes. Passed through by /start auto for the /auto loop.

Examples: /quality-review, /quality-review PL-13, /quality-review auto PL-13, /quality-review src/foo.ts src/bar.ts, /quality-review PL-13 packages/api/

Reject any bare argument that is neither an issue-ID ([A-Z]+-[0-9]+), the auto token, nor a path that exists on disk. In particular, the tokens merge, pr, no push, don't push, skip push are /finish arguments — if seen here, error with: Argument 'X' is a /finish argument, not a /quality-review argument rather than silently treating it as a file path that doesn't exist (which would just produce an empty scope and warn about nothing to review).

Invariant

Working Application Contract. This skill assumes the application was working before the changes under review. pnpm check is the gate that proves it still is, for the suites it actually runs — a project may have suites outside it (basefund's rspec), which /start's contract rule 1 covers. A check failure after our changes is never "pre-existing", never "out of scope" — it is our breakage and must be fixed before review proceeds. Turborepo caching makes repeated runs cheap.

Workflow

Preflight: Exit Plan Mode If Active

If the session is in plan mode when /quality-review is invoked standalone, call ExitPlanMode before any other step. Step 1 onward needs Bash (detect-issue-id.sh, git diff, pnpm check, etc.), Agent (for the quality-reviewer/quality-verifier/developer delegations), and Write (verdict persistence) — all blocked in plan mode.

Detection. Use the harness's plan-mode indicator visible at skill entry (the same signal that was gating tool calls just before this skill loaded). If that indicator is ambiguous or unavailable, attempt Step 1; if detect-issue-id.sh fails with a plan-mode block, return here, call ExitPlanMode, then retry Step 1. Do NOT speculatively call ExitPlanMode when plan mode is not active — it raises a spurious approval prompt the user must dismiss.

Plan body. Pass a one-line plan summarizing what /quality-review is about to do. There is nothing to design — /quality-review is a fixed mechanical workflow — but ExitPlanMode is the only way to leave plan mode and it requires a plan body. For the <ISSUE-ID> substitution: only inline a user-supplied token if it matches ^[A-Z]+-[0-9]+$ (case-insensitive, uppercase it before substituting); otherwise use the current change set.

  • Approved by the user: proceed to Step 1.
  • Rejected by the user: stop with /quality-review aborted at preflight: user rejected plan-mode exit. No review ran; no verdict persisted. Do NOT emit a lifecycle tag — /quality-review does not normally emit one, and no skill-stage code ran. The absence of a persisted verdict file is the signal a later /finish will see (VERDICT=none-found).
  • Tool-error / harness failure (not a user rejection — the tool itself returns an error, or the harness reports ExitPlanMode failed for a non-user-cancel reason): surface the error verbatim and stop with /quality-review aborted at preflight: ExitPlanMode failed (<first line of error>). No review ran; no verdict persisted. Do NOT continue to Step 1; plan mode is still active.

Delegated invocation note. When /quality-review is invoked by /start Step 9, plan mode is not active — the parent session either already exited it at /start Step 6 or (in auto mode) never entered it — so this preflight is a no-op in that path. The hazard only applies to ad-hoc standalone invocations of /quality-review.

Skip this preflight only when plan mode is NOT active (the common case).

Step 1: Resolve Scope

Issue ID — delegate to the shared script (same one /start and /finish use):

~/.claude/scripts/detect-issue-id.sh [--input <USER-SUPPLIED-ID>]

Pass --input only when the user typed an explicit ID (e.g., /quality-review PL-13). The script tries --input → current branch → latest commit subject, in that order. On exit 1 (no ID resolvable), proceed without issue context — the requirements-conformance bullet in the reviewer prompt is omitted.

Scope-mismatch guard. When the issue ID was auto-detected (branch/commit — not typed by the user) and the resolved scope is clearly not that issue's implementation (e.g., side work — config, docs, tooling — reviewed from a feature branch named for an unrelated issue), proceed WITHOUT issue context and state that in chat. This fires on auto-detected scope (the git diff + git status union) as readily as on explicit positional paths, and the auto-detected route is the more common one: detect-issue-id.sh falls back to the latest commit subject, so any HEAD whose tip commit names a merged issue — a branch cut from a just-pulled main after a /finish pr merge, or side work on main itself — resolves to that shipped issue whenever the current branch name carries no ID of its own (a /start-created branch does, and detection stops there). Decide on the diff, not on the issue's state: whether the reviewed change is that issue's implementation is the test, and a detected issue that is already merged is only a prompt to run this check — a deliberate re-review of a shipped issue's own branch is legitimate and keeps its context. Never persist a verdict keyed to an issue whose implementation the reviewed diff is not — the persisted file would overwrite that issue's real verdict at tmp/quality-review-verdict-<issue>.md with a fresher mtime, misleading /finish Step 8's gate and defeating its staleness check. A user-typed explicit issue ID is an instruction, not a detection — this guard does not apply.

Changed files (in priority order):

  1. Explicit positional args from the invocation

  2. Auto-detected via:

    git merge-base HEAD origin/main   # run alone first; substitute the literal SHA below — the `$(git merge-base …)` inline form is refused in `wt` sessions (standards/git.md § Worktree-isolated sessions)
    
    git diff --name-only <merge-base-sha>...HEAD
    git status --porcelain -z --untracked-files=all --no-renames | tr '\0' '\n' | cut -c4-
    

    Same bare-path form /start Step 0 uses (-z avoids C-quoting/octal-escaping under core.quotePath; cut -c4- strips the fixed-width status prefix; --no-renames keeps every record to one field). Union the two sets. If the union is empty, warn the user and exit — there is nothing to review.

    Before exiting on an empty union: the change may live outside this repo. A keeper-labeled issue's deliverable is in the user-level ~/.claude repo (/reflect attaches that label iff ≥1 proposal targets it), so this worktree's union is empty by construction while there is very much something to review — and exiting here persists no verdict, which /finish Step 8 refuses as none-found (auto mode aborts outright). Resolve scope from the issue body, not from git status: /reflect files each proposal as - [ ] **<target file>** — <observation> with a diff, so those named targets are the scope — intersect them with git -C ~/.claude status --porcelain and review exactly that. Never widen it to the rest of that repo's dirt. ~/.claude is shared and routinely dirty for unrelated reasons — /reflect's own apply-now edits stay uncommitted for keeper review, and the keeper's in-progress edits sit there too — so pulling them in keys this issue's verdict to someone else's work (the Scope-mismatch guard above) and points the fix loop's write authority at it. If the intersection is empty, exit as above. Step 2 then runs pnpm check in that repo as well as the project's — the project's is green because the project was never touched, which proves nothing about the change under review; expect ~/.claude's markdownlint-cli2 --fix to reformat out-of-scope .md files, and leave those alone. An empty union is only the loudest symptom: run this same check whenever the issue names targets outside this repo, since a mixed filing has a non-empty union whose user-level half would otherwise go unreviewed.

    Worktree source-branch awareness. Before defaulting to origin/main as the merge-base target, check whether this session is inside a /start wt worktree with a recorded source branch:

    git rev-parse --show-toplevel   # output = WT_ABS; run alone, then substitute the literal below
    
    git -C <WT_ABS> config --worktree --get start.source-branch
    

    If it returns a value, use that branch instead of origin/main for the merge-base diff (same two-call form as above — git merge-base HEAD <source-branch> alone, then substitute the literal SHA):

    git diff --name-only <merge-base-sha>...HEAD
    

    origin/main is very likely NOT this worktree's actual parent — diffing against it can return the entire accumulated history of a long-running feature branch (thousands of files) instead of the session's actual changes. Fall back to origin/main when the config is absent.

Issue requirements (only if an issue was resolved):

linear-cli issues get PL-13

Cache the output for the entire run — do not re-fetch on each review cycle.

Worktree isolation (wt mode) — pin every delegation, verify every placement. This skill can't see /start's session-level IS_WT (Step 0), so it gates on the caller-independent form /start Step 8 item 1 names for exactly this purpose — the same two plain probes, each its own Bash call (the worktree-isolation guard refuses $(git …) command substitution in exactly the sessions this blockquote governs — standards/git.md § Worktree-isolated sessions):

git rev-parse --show-toplevel   # output = WT_ABS
git rev-parse --path-format=absolute --git-common-dir   # prints <MAIN_CHECKOUT>/.git — its parent directory is MAIN_CHECKOUT

Apply the guards by inspection and carry the resulting literal absolute paths through this run (shell variables do not survive across Bash calls): both values must be non-empty, existing directories — git -C "" is a documented no-op, so an empty value silently measures the wrong tree instead of failing loudly; STOP on either. Do NOT additionally require WT_ABS != MAIN_CHECKOUT the way /start's derivation does: there, equality means worktree registration failed (a hard error); here, equality is the legitimate not-a-worktree case — this skill running standalone outside any /start wt worktree — so the gate below simply doesn't arm.

WT_ABS != MAIN_CHECKOUT is the gate — necessary and sufficient. Unequal means a separate main checkout exists this session and can be contaminated; that alone is enough to arm the mitigation below, regardless of any config value. Equal means there is no separate checkout in the picture at all (this session's cwd IS the main checkout), so wt mode is false — there is nothing a mis-bound delegate could contaminate beyond the tree it's already in.

As corroboration only — never as a necessary condition — the per-worktree config confirms this is specifically a /start wt worktree with a recorded source branch (<WT_ABS> is the literal derived above; an in-worktree -C target runs under the guard normally):

git -C <WT_ABS> config --worktree --get start.source-branch

Do NOT require this probe to return a value before arming the mitigation: standards/lifecycle-tags.md's BLOCKED-ON-RECOVERY row documents "source-branch config wiped" as a real hijack-corruption scenario, so a wiped config in a genuine, still-separate worktree would otherwise silently disarm the mitigation exactly when it matters most. A missing value here is worth noting, not trusting as "not a worktree."

Ensure the baseline is session-fresh before this skill's first delegation/start Step 0 sub-step 2 requires exactly this of "any skill that arms this mitigation," and a standalone /quality-review is the case it names explicitly. Both the capture and the item-1 placement check go through ~/.claude/scripts/wt-baseline.sh (capture / diff), which anchors its files to WT_ABS (derived above) rather than bare tmp/ — the calls run as separate Bash invocations with no shared cwd. Pass the lowercased issue ID as the token, or the literal no-issue if Step 1 resolved no issue ID (which can only happen standalone, since /start Step 9 always dispatches this skill with an explicit issue ID). The script is authoritative for the measurement mechanics; do not re-implement them here.

  • Delegated from /start Step 9: Step 0 already took this session's baseline before Step 8 ran. Reuse it as-is — do NOT retake it here. Retaking now would fold Step 8's already-verified-clean activity into a brand-new baseline, blinding this skill's own checks to anything that slipped past Step 8's per-delegation verification in the interim.
  • Standalone (the user invoked /quality-review directly): no session-fresh baseline exists yet. Take it now, before Step 2's first delegation — ~/.claude/scripts/wt-baseline.sh capture <WT_ABS> <issue-id-lowercased|no-issue> (the literal path, per the derivation above) — which overwrites any stale file left on disk, per /start Step 0's "never reused across sessions" rule. Proceed only on CAPTURED; a FAILED verdict is a fail-closed STOP.
  • Telling them apart: the reliable signal is session continuity — a delegated run has /start Step 0 moments earlier in this same conversation; a standalone run has no such history. When that history cannot be confirmed, treat it as standalone and retake: an unneeded retake before the first delegation costs one hash recompute, while reusing a baseline that turns out stale reproduces the exact bug this note exists to prevent (false accusations from paths main acquired since, misses from paths already dirty then). Concretely: retake whenever the baseline file (<wt-abs>/tmp/main-dirty-baseline-<token>.txt — the file wt-baseline.sh capture writes) is absent, or its this-session provenance cannot be confirmed — never skip retaking merely because a file happens to exist on disk.

When wt mode is detected, every delegation below is exposed to the hazard /start Step 8 documents — EnterWorktree's isolation registration is best-effort, not a guarantee, and a delegate can write into the main checkout while reporting success and while pnpm check stays green (a worktree missing a delegate's changes still type-checks). /start Step 8 is the single source of truth for the response: its delegation-format template's READ-SCOPING and WRITE-PLACEMENT blocks, and its "After each delegation completes" item 1 placement check with its graduated classification — a hard-stop delta (any unexplained VANISHED path, overlap with the delegation's scope or reported footprint, expected edits missing from the worktree, a missing report from a write-capable delegate, or a FAILED measurement) is a STOP handed to a human with no automated recovery, while a benign disjoint delta (the concurrent-activity signature) is logged, absorbed with a fresh wt-baseline.sh capture, and continued past — and in neither branch does this skill restore, extract, or apply anything in either tree. Do not restate those commands, classification rules, or reporting steps here. Split by agent type, mirroring /start Step 8: READ-SCOPING is a floor required on every delegation below regardless of agent type; the WRITE-PLACEMENT prompt block (the changed-path self-report instructions) applies only to this skill's write-capable delegations (developer) — its read-only delegations (quality-reviewer, quality-verifier, architect) get no such block, since they change nothing and produce no changed-path list. The item-1 placement check itself is not scoped this way: mirroring /start Step 8's final form, it runs after every delegation below regardless of agent type — a read-only delegate can still write into the main checkout under a mis-bound registration, and the check costs the same one git status-equivalent pass either way. That is every delegation site in this skill: Step 2's developer fix delegation; Step 3's quality-reviewer review; Step 5 item 2's developer fix delegations and item 3's further-fix delegation; Step 5 item 4's quality-verifier re-review; item 5's quality-verifier Targeted fix confirmation dispatches (wherever they are issued — the mechanical lane, the convergence drain, or the ceiling's lane drain, all of which land after the last re-review, which makes them more exposed, not less — nothing downstream would notice a late problem that went unverified); option 3's architect escalation; Step 6 sub-step 5's developer fix delegations (the fix-now items above the direct-apply bound, and the corrective pass) and its quality-verifier re-reviews (the mandatory one and the optional confirmatory one under the upgrade-on-success exception); and the Error Handling section's corrective reviewer re-spawn (same agent type as the malformed dispatch) and its SendMessage resumes of any of the above (both the zero-tool one and the transient-API one). The mechanical-lane, prose-lane, drain, and gate-clearing fix-now fixes are not delegation sites: the orchestrator applies those edits itself (see Dispatch tiers below), in its own working tree, so they sit outside this delegation machinery and its placement checks — the session's own edits land where the session runs.

This matters most in the fix loop: a fix that lands in the wrong tree makes the re-review pass against code that was never actually in the worktree — and neither a green pnpm check nor a delegate's success report is evidence of correct placement.

A read-only delegation must leave the worktree's dirty set unchanged — check that too. The item-1 check watches <MAIN_CHECKOUT> only, so it is blind by construction to a delegate that edits a file inside the worktree and fails to restore it. That is not hypothetical: a quality-verifier carrying an explicit "do not edit anything" instruction added a probe arm to a spec file to test a claim, was terminated mid-task by an API session limit, and left it behind — it reddened the full suite, and pnpm check, which never looks at Ruby, could not see it (BF-821). After every read-only delegation (quality-reviewer, quality-verifier, architect, Explore), compare git -C "$WT_ABS" status --porcelain --untracked-files=all against the paths expected to be dirty — Step 1's resolved scope plus the fix deltas applied so far this run. Any extra path is the delegate's residue: inspect it and undo it (an untracked leftover is deleted; a tracked path is rewritten from git show HEAD:<path> by file copy — never git restore, which is hook-blocked and multi-session-unsafe, and never git stash, which pushes onto the repo's one worktree-shared stash stack (hook-blocked in its mutating forms — standards/git.md § Safe Commands)), and record it in the Output block's process notes — never accept it silently as part of the change under review.

The dispatch side of the same hazard: a bare "do not edit anything" reads as "do not change the implementation" while leaving a throwaway experiment fair game — that is exactly how the probe above got written. So every read-only delegation prompt that forbids edits also carries: "If you need a temporary probe to test a claim, remove it before you finish and state in your report that you did — delete only the specific paths you created, never tmp/ itself: it also holds this run's contamination baseline, the fix-delta snapshots, and cross-skill handoff artifacts, so rm -rf tmp/ destroys the placement check the orchestrator runs the moment you return. Restore any file you touch by file copy — never git checkout, never git restore, and never git stash/git stash pop: the stash stack is shared across every worktree of the repo, so a concurrent session's push between your push and your pop makes pop apply their work into your tree and drop their entry — and the permissions hook sees only top-level git commands, so run from a script nothing will stop you." A probe is a legitimate verification technique; an un-removed probe is indistinguishable from the change under review. The removal clause needs the same bound as the restore clause beside it, and for the same reason a prompt states what standards/git.md already says ambiently: this sentence is what invites the cleanup, and a delegate reads its dispatch far more attentively than ambient config — ~/.claude/CLAUDE.md's "never rm -rf tmp/" rule was in a quality-verifier's context and lost to this sentence's own imperative anyway (BF-736), taking the baseline the item-1 check was about to read. No hook backstops it: scratch-path-guard.sh guards the system /tmp only.

And a delegation can DELETE a path the change needs — compare that set in both directions, after every delegation. The paragraph above catches a path a delegate added and failed to remove; the mirror is an expected path that vanished, and nothing in this skill or /start Step 8 sees it. wt-baseline.sh diff measures <MAIN_CHECKOUT> and its pathspec excludes .claude/worktrees outright, so it can never observe a worktree-internal change in either direction. /start Step 8 item 1's missing-expected-edits hard stop keys on paths the delegate reported, and WRITE-PLACEMENT builds that report from the delegate's own Edit/Write calls — a restore-by-file-copy cycle run through Bash produces no entry for what it removed. And the comparison above flags only an extra path, which is exactly backwards here: a file this session created is untracked, so Step 1's scope union already carries it (its git status --untracked-files=all half), making it an expected-dirty path whose disappearance reads as silence rather than as a delta. pnpm check closes nothing — the artifact at risk is the one nothing imports, and a generated manifest, fixture, or codegen output referenced only from a docblock is precisely that. So extend the comparison: an expected-dirty path now absent from git -C "$WT_ABS" status --porcelain --untracked-files=all is a finding exactly like an unexpected extra one — inspect it, restore it (re-run its generator; if it was tracked, rewrite it from git show HEAD:<path> by file copy, per the restore rule above), and record it in the Output block's process notes. Two bounds. Run it after write-capable delegations too, not just the read-only ones the paragraph above names: mutation testing is where this happens, and a developer that restores a mutated source file by copy need not restore the untracked artifacts sharing its directory. And keep --untracked-files=all — under the default the whole directory collapses to one ?? dir/ line that is byte-identical after a file inside it is deleted and simply disappears when the last one is (measured), so both a single-file loss and a whole-directory loss go unseen. A path this run deliberately removed leaves the expected set with it — drop it from the set rather than re-flagging it on every subsequent delegation.

A bad restore leaves the dirty SET unchanged — hash the expected-dirty paths, don't just list them. The two paragraphs above are set comparisons: one flags a path that appeared, the other a path that vanished. A delegate that mutation-tests a file already in the change under review — the normal case, since that is what there is to mutate (agents/quality-reviewer.md, "Reviewing test code: a green suite is not evidence") — and restores it badly changes neither set, so both report clean. Restores do fail: an observed cp-based restore silently did not take effect, and the delegate caught it only by re-reading. wt-baseline.sh already solved exactly this shape for the main checkout, and its solution is the one to copy — a bare path set misses a write landing on a path that was already dirty, because the path is a set member either way, while a content hash makes it a different line the diff still catches. So carry a shasum -a 256 per expected-dirty path alongside the set (<WT_ABS>/tmp/qr-expected-hashes.txt, rewritten whenever a fix batch legitimately changes one), and re-hash after every delegation: a path whose hash moved with no fix landing in between is a bad restore. Compare by hashing only — never by replaying the fix delta through patch (item 2 bans it), and never gate on the delegate having reported a restore, which is corroboration and not the check, exactly as WRITE-PLACEMENT is held. When the comparison runs through shasum -c, count mismatches positively (grep -c ': FAILED') rather than by inverting the OK lines (grep -cv ': OK$'): on any failure shasum emits a WARNING: N computed checksums did NOT match summary to stderr, so an inverted count over a 2>&1-merged capture reads one high — an extra path no fix touched, the exact bad-restore signature this check exists to catch (measured on BF-1100, where 6 legitimate fix-batch changes counted as 7 and cost a false-alarm investigation). The positive count is correct whether or not stderr was merged. pnpm check is a partial backstop and no substitute: a live mutation that reddens an arm does turn a later check red — item 3's, or failing that /finish's pre-commit gate — but it arrives detached from its cause and reads as a regression in the change, while the mutation that reddens nothing is the finding-worthy case, where the check stays green and the reverted code ships under a verdict describing an implementation that is not the one in the tree. Treat a mismatch as a hard stop, not a re-restore, and record it in the Output block's process notes. Capture it — and every later rewrite of it — in a message that dispatches nothing. Tool calls in one message run concurrently, so a shasum issued alongside an Agent dispatch is unordered against that agent's edits and can hash a file mid-probe: read-only delegates mutate production files to test a claim (the dispatch sentence above invites exactly that), and one restored moments later leaves the baseline holding the mutated bytes. That breaks the check in both directions — a correctly-restored file then mismatches and reads as the bad restore this exists to catch, while a delegate that never restores hashes equal to the mutated baseline and reports clean, the false negative that defeats the check outright. Measured on BF-816: the capture went out in the same message as three dispatches, one of which had been asked to delete a lock! to test a claim, and the post-delegation check then FAILED against a tree that was correct. Hash first, dispatch next — standards/agent-coordination.md's "writing to tmp/ … is free" governs orchestrator edits colliding with an agent's reads and does not license this.

Tag authority. /quality-review is not a lifecycle-tag authority (see the preflight above and standards/lifecycle-tags.md's emitter table). Only a hard-stop classification terminates — the benign disjoint branch logs (auto mode: posts /start item 1's note comment to the issue), re-captures, and continues the review without touching the verdict. If a hard-stop contamination classification fires inside one of this skill's own delegations: when an issue ID was resolved in Step 1, write the flagged bare repo-relative paths to tmp/contamination-comment-<issue-id-lowercased>.md (the Write tool) and post it — ~/.claude/scripts/linear-post.sh comment <ISSUE-ID> tmp/contamination-comment-<issue-id-lowercased>.md — before terminating; this is the durable record a human needs to recover, and under /auto the only place it gets written (/start Step 8's own Linear comment lives in its own auto-mode branch, which contamination inside this skill's delegations never reaches). If no issue ID was resolved, there is nowhere to post — say so and surface to chat instead. Either way, do NOT emit BLOCKED-ON-REVIEW or any other lifecycle tag — terminate with Verdict: terminated-with-open-items and an Open items: line carrying the exact literal sentinel /start Step 10 keys off, e.g. Open items: MAIN-CHECKOUT-CONTAMINATION — <bare repo-relative paths, comma-separated>; <any other open items> (paths are the mis-landed ones reported above; omit the trailing ; ... clause when nothing else is open) — the same machinery the Step 5 soft ceiling and Step 6 sub-step 5 regression cap already use to terminate this way, now with that one literal token added. The run still proceeds through the Output section's mandatory verdict persistence (skipping it would leave /finish Step 1.5 seeing VERDICT=none-found — a generic no-artifact refusal in place of this verdict's open items, contamination sentinel included). /start Step 10 maps the persisted verdict to the single lifecycle tag for the session.

Delegation mechanics — every delegation site in this skill, wt or not. Spawn each delegation as a one-shot unnamed Agent call — never pass name — per standards/agent-coordination.md § "Background-agent completion reports": a named spawn becomes a mailbox teammate whose turn-final report text is silently discarded, so the orchestrator receives only a bare idle notification and stalls waiting for a report that was never sent. Unnamed one-shot calls return the report in the completion notification, and stay resumable via SendMessage by agent ID (all the Error Handling SendMessage resumes need). If a delegation was nonetheless spawned named and goes idle without a report, recover by pinging it once via SendMessage with its agent id and then verifying independently — never via TaskOutput, which is deprecated and whose .output path for a subagent is a full-transcript symlink you must not Read (standards/agent-coordination.md § "Background-agent completion reports" carries the per-task-type ordering) — rather than ending the turn waiting. That applies once the idle notification has arrived; while a dispatch is still running, end the turn instead of polling — its completion notification is what wakes the session (standards/agent-coordination.md § "Background-agent completion reports").

Every one of them is blocking by construction — the gate fix, the review, each fix batch, each re-review, each Targeted fix confirmation, the architect escalation, Step 6 sub-step 5's fix-now and corrective passes, and the Error Handling corrective re-spawn all produce a result the very next step consumes, so there is never independent work to fill the wait. (Its SendMessage resumes are not Agent calls and take no such parameter.) Where the Agent tool exposes run_in_background, pass false on every one of them and the wait costs nothing. Where it does not, the dispatch backgrounds and nothing you pass can change that — that is the harness, not an author error. Decide by the tool result, never by the schema: a result reading Async agent launched successfully … instead of the agent's report means it is in the background, and the schema alone cannot tell you, since one harness context omits the parameter precisely because only synchronous subagents are supported. Do not pass it when the schema omits it — it is silently accepted, has no effect, and is still recorded in the transcript, where scripts/fleet-metrics.py's dispatch census (bg is None or bg is True → background, else sync) counts the backgrounded dispatch as sync and misreports dispatch mode to /fleet-retro. Then end the turn and let the completion notification wake the session — do not poll, and do not arm a short wakeup hoping to catch the result (standards/agent-coordination.md § "Background-agent completion reports"). Parallelism comes from sending several dispatches in ONE message, never from backgrounding. Under a self-paced /loop /auto, a turn that ends on an in-flight dispatch MUST still arm a ScheduleWakeup as its last action — the 1200–1800s long fallback of skills/auto/SKILL.md's "Heartbeat re-entry mid-iteration", not the 60s AUTO-CONTINUE tick — or the loop dies silently; hooks/auto-heartbeat.sh blocks that stop and re-drives the arming, but it is the backstop, not the plan, and it gives up after 3 attempts.

Dispatch tiers — discovery is expensive; verification and fixes are not. Three tiers, fixed by role, referenced by every site below:

  • Initial adversarial review (Step 3): quality-reviewer — its frontmatter pins opus/xhigh. Discovery depth is the one place the heavy configuration pays for itself; nothing in this skill reduces it.
  • Every re-review and fix confirmation: quality-verifier — a lighter agent (sonnet-tier, pinned in its frontmatter) purpose-built for verifying a fix delta under the same parsed findings contract. This covers Step 5 item 4's re-reviews, item 5's Targeted fix confirmation dispatches (wherever issued), Step 6 sub-step 5's mandatory and optional confirmatory re-reviews, and the Error Handling corrective re-spawn of any of those. Verification run on the discovery configuration costs discovery prices while needing none of its depth — the delta-scoped contract already forbids new modalities, and the re-review's job is proving fixes sound, not finding new ground.
  • Fix dispatches: developer, always with model: sonnet passed on the Agent call. Every developer delegation this skill makes — Step 2's gate fix, Step 5's fix batches, Step 6 sub-step 5's above-bound fix-now items and corrective pass — overrides the agent's opus frontmatter pin, which exists for /start's open-ended implementation work, not for applying enumerated review findings. The re-review gate, run fresh on the verifier tier, remains the correctness backstop for every fix regardless of which tier produced it.

And the smallest fixes dispatch nowhere at all: the orchestrator itself runs opus-tier or above, so mechanical-lane, prose-lane, and gate-clearing fix-now items are applied directly in-session — their lane definitions below say which qualify. Those lanes' gates (single file, ~<15 lines, no contract change) already encode the "handful of tool calls" boundary from CLAUDE.md's delegation rule, below which a dispatch buys no quality, only measured overhead, and the confirmation/check gates on those lanes remain the independent verification.

Carry forward filed/deferred issue context into every re-review prompt. Once a finding has been filed as a Linear issue mid-run (Step 6 sub-step 6, or any other point in this workflow), every SUBSEQUENT reviewer delegation prompt (quality-reviewer or quality-verifier) for the rest of the run — Step 5 item 4's re-review, Step 5 item 5's Targeted fix confirmation (wherever it is issued — the mechanical lane, the convergence drain, or the ceiling's lane drain), Step 6 sub-step 5's mandatory and confirmatory re-reviews, and the Error Handling corrective re-spawn — must include the filed issue's ID and a one-line summary, with an explicit instruction not to re-flag it. Each re-review prompt is composed fresh from its own template in its own section of this file; the reviewer agent has no memory of prior cycles, and a template written elsewhere in this skill has no implicit access to what an earlier template included. Omitting this risks a later re-review re-discovering and re-flagging an already-tracked gap as a "new" finding. The same applies to findings already ROUTED to a deferred filing that Step 6 has not executed yet (severity-carrying pre-existing items, narrowed-fuel Mediums): name each in the re-review prompt as already-tracked with its planned disposition — a routed-but-unfiled finding re-flagged as new breaks the convergence assessment exactly like a re-discovered filed one. One carve-out, applying to filed and routed items alike: if a later fix delta made an already-tracked item worse — a latent gap turned reachable, a narrow case turned general — that is a new fix regression, not a re-flag, and the reviewer must report it at its real severity. Every prompt carrying the already-tracked content must say so, or the suppression instruction swallows a regression the loop itself introduced.

Step 2: Working Application Gate

pnpm check

If it fails: we took a working application and broke it. That is our failure. Do not proceed to review. Do not rationalize. Delegate fixes to developer immediately (sonnet-tier, per Dispatch tiers) with the explicit instruction: "The application was working before our changes. It is now broken. Fix it." Re-run pnpm check. Repeat until it passes. There is no path forward through a broken application.

If it passes: the Working Application Contract holds. Proceed to the adversarial review.

Step 3: Delegate Adversarial Review

Task for quality-reviewer: Adversarial implementation review for PL-13
Context: Implementation of [issue title or one-line summary of the change] is complete. Your job is to try to break it.
Issue requirements: [Paste requirement checkboxes from the issue — omit this line entirely if no issue was resolved in Step 1]
Files: [List every file in the resolved scope from Step 1]
Requirements:
- Verify every issue requirement is actually satisfied, not just approximately
- Find edge cases with concrete triggering scenarios
- Trace error paths for completeness (including partial failure)
- Check implicit assumptions about inputs, state, and ordering
- Identify concurrency/timing issues under load
- Assess security surface beyond obvious vulnerabilities
- Check integration boundaries with existing code
- Verify conformance against user-level and project-level conventions
- Report every issue you find, including ones you are uncertain about or judge low-severity. Your job at this stage is coverage, not filtering: Step 6 triage ranks and disposes of findings, so it is better to surface something that later gets filtered out than to silently drop a real bug. Put your severity estimate in the section you file it under — Nice-to-Have is a real lane that gets fixed, not a discard pile.
- Read as widely as the review requires — callers, integration boundaries, conventions — but ingest incrementally, file by file as questions arise. Do NOT diff against long-lived branches: the issue's own delta (merge-base..HEAD) is the widest diff you may run. Generated artifacts (e.g. src/generated/*, schema dumps, lockfiles) are grep-only — grep them for the symbols you need, never read them wholesale. Everything you read is live context that compaction cannot shed; one bulk ingestion ends your review before it starts.
Acceptance: Produce a categorized findings report following the Required findings format below — markdown sections with `## Review Findings` heading and the five `### <severity>` subheadings, in that order. Do NOT emit JSON arrays of findings, tables, "Verification summary" sections, "Categorization" tallies, or any alternative structure. The format is parsed by sub-step 1 below; deviations break the consolidation step and surface raw output to the user.

For large changes spanning multiple domains, always spawn parallel reviewers scoped by domain in a single message (e.g., one for backend, one for frontend). The same parallelism principle applies here — reviews are independent and must run simultaneously. Consolidate findings before proceeding.

Size each dispatch to the reviewer's context budget. A reviewer subagent starts at ~94k of fixed overhead, and its working material — files read, diffs run — is live context compaction cannot shed: measured 2026-08-14, a reviewer whose opening move was a branch-wide diff against a long-lived branch could not compact below ~209–219k and thrash-aborted without reviewing a single file (its re-spawn, carrying the incremental-ingestion bullet now in the template above, completed normally). The reviewer's breadth is not the problem — chasing callers and boundaries file-by-file accumulates slowly; bulk ingestions (branch-wide diffs, wholesale reads of generated artifacts) are what saturate it in one step. When the change's own scope is too large for one context, the domain split above is the mechanism — split until each dispatch's share plausibly fits, and prefer more, smaller reviewers over one saturated one: a saturated reviewer does not fail loudly, it compacts, re-reads, and burns its budget re-establishing state instead of reviewing.

Parallel reviewers share one tree, and each is licensed to probe it. The delegation mechanics above tell every read-only dispatch that a temporary probe is legitimate, and standards/git.md has it restored by file copy — so between an agent's edit and its restore the tree is genuinely broken, and a sibling running specs in that window sees real failures in files neither agent's scope names. Every parallel read-only dispatch therefore also carries: "Other reviewers are working in this same worktree and may be transiently editing files to test a claim. A red run, or a diff you did not write, may be a sibling's in-flight probe — re-run or re-read before you report it, and do not assert who wrote it." This narrows standards/git.md's "treat anything you did not positively write as another session's" without weakening it: that rule governs disposition — never revert, discard, or tidy away what you did not write — and holds in full. What it must not license is an attribution in a report. Say the tree was transiently dirty and the cause was not attributable; never name a concurrent session. Where a clean signal matters more than wall-clock, serialize the reviewers instead of warning them.

Assign each parallel dispatch its own probe path — do not let the delegates name it. The probe clause above invites a file-copy backup and standards/git.md makes that the only sanctioned restore, so every concurrent reviewer holds backups under tmp/ — and each is handed the same issue ID and the same instructions, so the obvious name is the same obvious name for all of them. Observed on BF-1087: two domain-split reviewers independently chose tmp/bf1087-review/; the first to finish deleted it as "the specific paths you created" — which it was — and the second found its backups already gone. Instructing delegates to choose a unique name does not close it, since identical inputs are what produced the identical choice. The orchestrator composes these dispatches in one message, so name the directory in each prompt (tmp/qr-probe-<domain>/, one per dispatch) — the same shared-path race mktemp -u closes for sub-step 6's body files. The cost of a collision is not a silent miss: the expected-dirty hash check above catches an unrestored mutation as a hard stop. What is lost is the work — the dirty-set paragraph's restore is git show HEAD:<path>, which recovers only committed content, so in a worktree whose change is still uncommitted that file's edits go with the backup.

A third option when the reviewers' technique is mutation testing: split by write license, not just by topic. Warning alone is thin there — relax-a-clause-and-confirm-exactly-one-arm-reddens is the mandated method for any change carrying tests (agents/quality-reviewer.md, "Reviewing test code: a green suite is not evidence"), so overlapping scopes mean continuous, not incidental, mutation of the same files; and serializing turns the review step's wall-clock from the max of the dispatches into their sum, on the one tier Dispatch tiers deliberately runs at discovery pricing. So scope the dispatches so no two concurrent dispatches hold write license over the same files. A domain split (backend/frontend) usually gives that for free; a production/spec split does not, so name one owner. The owner may transiently mutate (restoring by file copy) and owns the falsifiability proofs for every spec arm in its files' blast radius, including the sibling's — say so in its dispatch. The other is told it may not edit those files and assesses arm strength by reading — what else could make this arm pass or fail? which single change would leave it green? — which is a complement, never a substitute: reading cannot distinguish an assertion that pins behavior from one that passes regardless, which is exactly why the mutation obligation moves to the owner rather than lapsing. The transient-dirt warning above is unchanged and still carried by both dispatches — the non-mutating sibling is the one most likely to meet a red run it did not cause. Where the dispatches also run tests against a shared mutable resource (a test database, a fixture directory, a port), give each its own or let exactly one run them; the project's own rules name the mechanism.

Required findings format (this is the parsed contract — the quality-reviewer agent's system prompt also specifies it; both must agree):

## Review Findings

### Critical (must fix before done)
- [Finding]: [File:line] — [concrete scenario that triggers it]

### High (should fix)
- [Finding]: [File:line] — [concrete scenario that triggers it]

### Medium (real risk, lower probability)
- [Finding]: [File:line] — [scenario and likelihood assessment]

### Nice-to-Have (auto-fix lane)
- [Finding]: [file:line] — [the concrete fix — queued for /quality-review Step 6 triage]

### Approved
- [What survived adversarial review and why]

Mid-run surfacing. When reviewer findings are surfaced or summarized to the user before Step 6 (e.g., background-reviewer results arriving mid-run), state the Nice-to-Have lane's disposition explicitly: these items are queued for Step 6 triage — auto-fixed when safe or recorded under Deferred dropped (the lane never files — Step 6 sub-step 2's keeper rule), or (on early termination: terminated-with-open-items / escalated-to-architect) routed to Open items instead — never silently ignored. A raw report carries no disposition signal on its own, and "non-gating" reads as "won't fix" without it.

Persist the Nice-to-Have lane as each dispatch returns. Immediately after a review or re-review dispatch returns, before doing anything else with its report, append its verbatim ### Nice-to-Have section to tmp/quality-review-nth-<issue-lower>.md (create if absent; one append per dispatch, each prefixed with its cycle label). A plain Write/append — no script, and no dual worktree/main-checkout publish, since Step 6 always runs before /finish removes the worktree. Step 6 sub-step 1 consolidates this lane across every cycle from context, which a mid-run compaction destroys exactly as it destroys the finding text behind Findings resolved (see that field's recovery note in Output). Unlike that field, nothing downstream reveals the loss: an unrecoverable Nice-to-Have is simply never triaged, never filed, and never reported missing.

Step 4: Evaluate Verdict

If findings contain no Critical, High, or Medium items → review passes. Proceed to Step 6 with verdict passed-clean (cycle 1) or passed-after-fixes (later cycles).

If any Critical, High, or Medium findings exist → proceed to Step 5.

Nice-to-Have findings do not affect the verdict; they are handled in Step 6 (auto-fixed, filed, or recorded as dropped) once the loop terminates cleanly.

Step 5: Triage & Fix Loop

If Critical, High, or Medium findings exist, triage, fix, and re-review until the implementation passes cleanly.

1. Fix every Critical/High/Medium finding in scope, including pre-existing ones in touched files. Leave code better than you found it. If the reviewer flagged it at this severity, it is not deferrable — Step 6 handles deferrable items via the Nice-to-Have category. (This governs the initial review's findings; a re-review's new findings are routed by item 5's lanes instead — pre-existing gaps a re-review notices go to the deferred filing lane, not this fix-everything rule.) How each finding is fixed and verified follows the lanes from cycle 1 (BF-562): classify every initial-review finding prose-only / mechanical / substantive exactly as item 5 defines those lanes — prose and mechanical findings are direct-applied and verified by their lanes' own machinery, substantive ones go to item 2's fix dispatches. What the initial review does NOT get is item 5's deferred routes: at this stage nothing Critical/High/Medium is deferrable, per this rule — the lanes here change only how a fix is applied and verified, never whether.

A Nice-to-Have on the lines a fix must rewrite is fixed in that fix's batch, not deferred. Item 2's "hold the code THIS batch adds to the invariant the fix establishes" bullet extends here from the fix's own invariant to an already-reported finding: when this cycle's fix would propagate the flagged defect (a third copy of a block flagged as duplicated, a new call site missing the guard the finding names) or would author the very lines the Nice-to-Have's fix governs (a new spec arm whose missing assertion is the finding), deferring it makes the batch land on top of a defect the review already reported — and the re-review then sees that new copy inside the fix delta, where re-flagging it is legitimate and, at cycle 2, is loop fuel. Name it in item 2's Findings so the delegate is not inferring scope (its file is already a Findings file, so the dispatch stays inside its documented file-list bound), and record it under Deferred fixed in-session, excluded from Step 6 sub-step 1's consolidation so it is not re-classified and re-fixed there. This does not admit the Nice-to-Have lane into the loop: the trigger is propagation, not proximity — a Nice-to-Have merely nearby, or elsewhere in a file this batch happens to touch, still defers to Step 6 — and a fix needing a new shared abstraction or a cross-file move is exactly what item 2's sweep bound and its scope escape hatch exclude. The batch must only avoid propagating the defect (reuse the existing block rather than copy it); where it cannot without restructuring, the delegate stop-and-reports and the orchestrator routes it.

2. Fix all Critical/High/Medium items — delegate to developer (sonnet-tier, per Dispatch tiers). If multiple findings are in independent files, launch parallel fix agents.

Snapshot before fixing — the re-review needs the fix delta as a diff. Before any of this cycle's fixes land (dispatched or direct-applied, in any lane), rm -rf tmp/qr-fix-base-<issue-id-lowercased>-<cycle> and then copy each file a fix will touch aside under tmp/qr-fix-base-<issue-id-lowercased>-<cycle>/<repo-relative path> (create parent directories as needed), using the literal no-issue in the issue slot when Step 1 resolved no issue ID — the same scoping the baseline, contamination-comment, and verdict artifacts already use. After the cycle's fixes are applied and checked, produce the fix delta per file with diff -u against the snapshot and concatenate. The working tree usually carries the implementation's own uncommitted changes, so a plain git diff cannot isolate what the fixes changed. A file a fix touched without a snapshot (a dispatch strayed beyond the named files) falls back to git diff -- <file>, labeled in the prompt as possibly carrying pre-fix hunks. Issue-scope that directory and clear it — both halves are load-bearing. tmp/ is never cleaned between sessions (CLAUDE.md forbids removing it) while every cycle token repeats across runs — the integer restarts at 1, and deferred and corrective are fixed strings that collide on every run — so a cycle-only path accumulates whichever issues this repo reviewed before: measured at 52 files (46 from two earlier issues), at 61, and at 38 spanning three days and three issues, in three separate sessions. A stale base is more dangerous than a missing one because it disarms the fallback just described, which is keyed on the snapshot being absent: a previous issue's copy is present, readable, and diffable, so a strayed file is diffed against another issue's content and diff exits 0 either way. Never trust a base merely because a file happens to exist on disk. Scoping is also what makes the clear safe — an rm -rf of the shared un-scoped path would delete files another session created, which the multi-session rule forbids. Derive both file sets; never hand-write either. "Each file a fix will touch" is a prediction, and item 2's complete-the-class and scope-escape-hatch rules make fixes land outside the findings' named files routinely. Snapshot the dirty set instead (git status --porcelain --untracked-files=all -- ':!tmp' | cut -c4-): a file no fix touches diffs empty, so nothing already dirty can go missing, and the cost is one cp per path. Then at delta time re-derive the dirty set and take now \ <the snapshot listing> as the no-snapshot fallback set, so that list is derived too rather than recalled. Measured on BF-861: a 19-path hand-written snapshot list and a 7-path hand-written fallback list both omitted apps/api/app/controllers/descope/classify_mailbox_controller.rb, which a cycle-1 fix had edited (extracting a shared classifier, adding a fail-closed rescue); the resulting 774-line delta was the right length, parsed cleanly, and simply described one file fewer than the cycle changed. That same run already derived its expected-dirty path set this way for its hash check — the idiom was in hand and was not carried across. Item 4's re-review and item 5's Targeted fix confirmation both carry this diff inline — a bare changed-file list makes the verifier re-derive the delta from the whole tree, which is re-discovery pricing for verification work.

Never reconstruct an intermediate tree state by replaying a concatenated delta through patch. The delta above concatenates several files' diff -u output, and patch takes its target from each section's own +++ header for every file after the one named on the command line — so patch <one-file> < <the-delta> applies section 1 to the named file, then resolves sections 2..N against the working tree, finds them already applied, and reverse-applies them, exiting 0. The Reversed (or previously applied) patch detected! Assume -R? [y] prompt self-answers because an agent session has no controlling tty — not because stdin is the patch, so -i <file> is equally destructive (measured: identical result both ways). Measured on BF-939: two of three files were silently reverted to their pre-cycle state, taking a whole cycle's fixes with them, while git status still showed both as M — the implementation's own uncommitted changes were still there — and no .rej or .orig was written, so nothing in the normal signal set flagged it. To isolate a later cycle's delta, take that cycle's snapshot before its fixes land, as this note already requires (deferred and corrective included); never replay an earlier delta to recover a snapshot you skipped, and where none exists use this note's git diff -- <file> fallback. If you must apply a delta anyway, split it per file and pass each section with an explicit target — the split is what protects, since -o <output> redirects only the section whose target you named and leaves sections 2..N resolving from their headers into the working tree (measured) — and add -N/--forward, which refuses a reverse-apply, leaves the tree intact, and exits 1 with .rej files instead of silently succeeding.

A large delta may be passed by path instead of inlined — above 200 lines only. Inlining is mandatory for any concatenated delta of 200 lines or fewer: it cannot go stale, cannot go missing, and at that size costs nothing worth saving. Above 200 lines inlining becomes its own fidelity risk — the orchestrator has to re-emit the whole diff verbatim into the dispatch, and one elided or truncated hunk means the verifier confirms less than the delta while reporting it confirmed, which is the failure this rule exists to prevent. So above that bound, write the concatenated delta — same diff -u-against-snapshot recipe, same no-snapshot fallback and labeling — to tmp/qr-fix-delta-<issue-id-lowercased>-<cycle>.diff — the whole token matching the snapshot directory's (no-issue substitution and the deferred/corrective cycle tokens included), written with >| so an imported NO_CLOBBER cannot refuse the overwrite and silently leave a previous run's file in place (CLAUDE.md) — and pass that absolute path in the Fix delta slot, re-labeling the slot Fix delta (precomputed unified diff — read this file; do NOT re-derive) in whichever template is being used and stating the line count so the choice is auditable. The dispatch MUST then say, in as many words: read that file first as the authoritative delta; do NOT re-derive it from git diff over the tree, because the working tree still carries the implementation's own uncommitted changes and a plain git diff would mix pre-fix hunks in; and if the file is missing, empty, or unreadable, STOP and report that rather than falling back to anything — a verifier that silently re-derives is the original failure mode, and one that silently verifies an earlier cycle's stale file is worse. The issue token matters more here than for the snapshot directory, not less: that STOP condition structurally cannot catch a cross-issue stale file — a previous issue's delta is present, non-empty, and readable, so nothing about it looks wrong — leaving the Changed files: cross-check as the only thing between it and a verifier confirming another issue's changes as this run's. The template's existing Changed files: line is the staleness cross-check, and it is only sound when its two sides have independent sources: populate it from the fixes' own footprint — the developer dispatches' reported changed paths plus the orchestrator's direct edits — never from the delta file, which is what it exists to check (a cross-check derived from what it checks can never fire), and the dispatch tells the verifier to confirm the delta file covers exactly that list and to report a mismatch instead of proceeding. On BF-861 that independence is the only reason a delta omission surfaced at all: the verifier reported the mismatch and verified the missing file itself rather than accepting the diff — nothing else in the run could have, since pnpm check was green and the expected-dirty path-set and shasum checks both saw the omitted file as correctly dirty and unchanged. What the inline rule protects is that the verifier gets the exact fix delta rather than re-discovering it from a dirty tree; a precomputed file under these conditions satisfies that, a bare changed-file list does not.

Task for developer: Fix review findings for PL-13
Context: Quality reviewer identified the following issues.
Findings:
- [Finding 1]: [File:line] — [explanation]
- [Finding 2]: [File:line] — [explanation]
Requirements:
- Address each finding precisely — no unrelated changes
- When a finding involves classifying enum variants, error kinds, states, or cases into behavioral buckets (transient/terminal, retry/fail, etc.): enumerate EVERY variant from the defining source and classify each explicitly — never fix only the flagged variant(s) or write "etc.". Completing the partition is part of addressing the finding precisely, not an unrelated change; incomplete partitions are how the same defect resurfaces one variant at a time across re-review cycles.
- When a finding names one site of a class-shaped decision — the same predicate, guard, formatting rule, or authorization/scoping check reimplemented independently at more than one call site rather than at a single isolated site: fix EVERY sibling that lives in a file named in the `Findings` above, applying the same fix at each, and report the sites you covered. Do NOT edit a sibling outside those files — that is out of this loop's scope — and do NOT extract a new shared abstraction to deduplicate them, which is a refactor this loop does not undertake. Completing the class is part of addressing the finding precisely, not an unrelated change; patching only the named site is how one finding costs a re-review cycle per sibling.
- When a finding is that an EXIT PATH failed to carry or report something the function already knew — a `return` omitting a field the caller depends on, an early return skipping a stamp, a cleanup, or an audit write: enumerate EVERY `return` in that function and state, for each, whether the obligation applies to it and whether it is met; report that enumeration. Exits where the obligation genuinely does not apply (the value does not exist yet at that point) are part of the enumeration, not omissions from it. This stays inside the function named in the `Findings`, so it never reaches past the file-list bound. Completing the exits is part of addressing the finding precisely, not an unrelated change; fixing only the named exit is how one defect costs a re-review cycle per exit path, and the enumeration — not the fix — is what shows the class is closed.
- When a finding is addressed by adding or revising a **guard** — any predicate that halts, skips, fails, or flags rather than computing a value: enumerate in your report the input classes it will actually receive and its verdict for each — the triggering case, well-formed input, absent/empty input, malformed input (null elements, wrong container type), and input where the data the guard needs to decide is itself missing or unreadable. This is a reporting obligation, not licence to widen the fix. A guard specified only by its triggering case over-fires on a class nobody enumerated — which is how a fix becomes the next cycle's finding.
- Scope escape hatch: if resolving a finding requires restructuring beyond the findings listed above — a new abstraction, moving code across files, or edits outside the listed findings' files and their direct blast radius — STOP and report what the resolution needs instead of doing it. This does not relax the completion bullets above: completing any class they name, within the named files, is required precision rather than restructuring; the hatch covers only work those bullets already exclude. A stop-and-report is a successful outcome of this dispatch, not a failure.
- Hold the code THIS batch adds to the invariant the fix establishes — a widened rescue, a new guard, a closed fail-open path. The class bullet above sweeps pre-existing siblings; a helper you introduce on the fixed path is not one, so a batch that widens one rescue while writing a second, narrower one beside it re-opens the finding in its own new code. Before reporting, re-read what you added along the fixed path against that invariant.
- Do NOT annotate an edit with where it came from. `# Review Finding 3: …`, `# BF-123 review finding 2 — …`, `# Round 2 fix: …` is the provenance decoration `~/.claude/rules/comments.md` forbids — it rots as the code moves and tells a future reader nothing. This governs the WHY-header on a file the fix creates too: state the invariant the file pins, not the finding that asked for it. Keep a non-obvious WHY and drop the review citation; a forward pointer to a durable issue explaining an invariant (`# BF-123: must stay … or X breaks`) remains allowed.
- Verify with type checks or tests as appropriate
Acceptance: All listed findings resolved, no regressions — or a stop-and-report naming the restructuring a listed finding needs; for any class-shaped finding, the sites-covered report emitted; for any exit-path finding, the per-`return` enumeration emitted; for any added or revised guard, its input-class verdicts emitted — and state explicitly if no finding was class-shaped, exit-path-shaped, or guard-adding.

Routing a stop-and-report. When a fix dispatch invokes the escape hatch, the orchestrator decides — never the delegate: either resolve the finding another way this cycle (a narrower fix within the named files, or in-session), or — when the only real fix is the out-of-scope restructure — route the finding out of the loop as a severity-carrying deferred item (item 5's deferred machinery), stating in the filing that the fix requires the reported restructure. Never re-dispatch the same finding with instructions to "just do the restructure": an unreviewed restructure absorbed mid-loop is scope the issue never asked for.

The complete-the-class family — one principle, four paths, each with its own bound. A finding that names one site of a duplicated decision is completed as a class, never patched site-by-site — but every handling path carries its own discovery mechanism and its own bound, and none may borrow another's: (1) item 2's substantive fix dispatches — the delegate fixes every sibling in a file named in the Findings, bounded by that file list, never beyond it; the sweep-dependent template lines above belong to these delegations alone, and every other developer fix site in this skill (the developer entries in the worktree-isolation blockquote above Step 2) composes its prompt from this block's shape and drops them, because the file-list bound only item 2's delegations carry is what makes a delegated sweep safe. (2) item 4's re-reviews — the dispatch names the shared decision and the directory/domain to search, so cross-file siblings surface as findings routed by item 5's lanes. (3) the prose lane — the orchestrator sweeps the convention-defined sibling surface at fix time, bounded by the prose-only definition. (4) the mechanical lane — the orchestrator completes in-scope siblings and defers beyond-scope ones, bounded by the change's resolved scope and the lane's own gates. The drains inherit (3) and (4) by routing their survivors through those lanes.

A remedy clause names a scope the reviewer may not have read — enumerate it before authorizing a deletion. This is that family's bounding counterpart: the four paths above all widen a fix, and nothing bounds one the finding's own phrasing authorized. Reviewers write what they saw: "replace the constant + its two spec examples", "delete that describe block", "drop the helper and its callers". Relaying that phrasing into a fix dispatch's Findings block turns an observation into an authorization, and the delegate deletes exactly what it was told to — item 2's "no unrelated changes" cannot bound it, because the instruction is the scope. A member count is not an enumeration: the reviewer can count the group correctly and still have examined only the member that motivated the finding. So when a remedy prescribes deleting a group rather than a named line, open the group, name each member and what it pins, and dispatch the deletion by member name — dropping any member that turns out to carry its own invariant. Enumerate in addition to the reviewer's wording; the finding text still relays verbatim. Deletion-specific because of the asymmetry: a wrongly-added line reddens something, a deleted passing assertion reddens nothing and pnpm check cannot see it — item 4's re-review over the fix delta is the backstop, and it costs the cycle it catches it in. Observed on BF-1078: a finding correctly called a drift-guard constant dead and prescribed removing "the constant + two spec examples"; the second pinned an unrelated registry invariant, went with it, and cost a cycle to catch and restore.

A remedy that names an EXTERNAL artifact is a claim about that artifact — open it before applying. The rule above bounds a remedy that authorizes a deletion; this bounds one that authorizes a reference, and it binds every route that applies a remedy — item 2's fix dispatches, the prose and mechanical lanes, and Step 6's fix-now lane, where a comment-only remedy auto-applies with no prompt and, under sub-step 5's exemption, no re-review at all. Reviewers routinely close a finding by pointing somewhere — "append tracked in BF-XXX", "defer to <file> § <section>", "that is what <helper> is for" — and the pointer is the part they are least likely to have opened, because the finding was found by reading the code the pointer leads away from. The deletion rule's asymmetry applies here with even less to catch it: a citation that resolves but does not cover reddens nothing anywhere — the gates that exist test existence (a compiler, a markdown-anchor check), never whether the target says what the remedy claims — and rules/comments.md's durable-pointer exception licenses such a pointer on durability alone, saying nothing about accuracy. So fetch the named artifact — linear-cli issues get <ID>, read the section, open the helper — and confirm it covers what the remedy claims. When it does not, the finding may still be real while its remedy is wrong: resolve it another way (file the issue that would own it; leave already-accurate prose alone) and record why the prescribed pointer was not used. Observed on BF-1097, where a Nice-to-Have prescribed pointing an accepted per-email victim-denial risk at BF-1114 and BF-1115 — a body-shape evasion of the sign-in discriminator and an unbounded-WARN-volume concern, neither of which owns that risk; it was resolved by filing the issue that does and leaving the accurate prose alone.

A wording finding earns a wording fix. When a finding names an ambiguity, an over-claim, or an imprecise scope — not a behavioral defect — fix the text and stop, whichever route applies it (a fix dispatch here, or the orchestrator directly via item 5's prose lane). Do NOT specify the behavior the ambiguous sentence happened to touch: if that behavior was already unspecified before this change, it is a pre-existing gap — route it out via item 5's pre-existing lane as a severity-carrying deferred item and let the wording fix close the finding (which is resolved, not deferred, so item 1's not-deferrable rule still holds). Behavior specified in answer to a wording finding is new reviewable surface, and the remaining cycles get spent on ground the issue never asked about — BF-560 spent three of five cycles reviewing and repairing machinery invented in answer to one ambiguity finding. A wording fix that itself survives review escalates per item 5's prose ping-pong rule — a comprehensive rewrite of the surrounding paragraph, still within this wording scope — never a second minimal patch.

After fixes are applied, you MUST continue through items 3→4→5 below. Do not stop after fixing.

3. Verify check passes — after fixes, re-run pnpm check. If it fails, delegate further fixes before proceeding.

4. Re-review (mandatory whenever the cycle applied a substantive fix) — fixes are not complete until re-reviewed. One exemption — the all-prose/mechanical batch (BF-562): when a cycle's entire fix batch — the initial review's included — consists of prose-lane and mechanical-lane fixes, their own verification (the isolated prose checks and the targeted fix confirmation) is the completion gate and no re-review is spawned; spending a full review cycle to confirm a comment correction is the grind the lanes exist to end. Any substantive fix in the batch makes this re-review mandatory, unchanged. Spawn quality-verifier (per Dispatch tiers) scoped to verification of the fix delta, not a fresh review of the whole change:

Task for quality-verifier: Adversarial re-review of fixes for PL-13
Context: Previous adversarial review findings were addressed. Verify the fixes — this is a verification pass over the fix delta, NOT a from-scratch re-review of the whole change.
In scope: (a) each fix actually resolves its finding; (b) any finding closed by argument rather than by change — test the argument's soundness; (c) regressions in the fix delta's blast radius: callers, callees, and shared state of the changed lines.
Out of scope: review modalities the initial review did not run (do not introduce a mutation sweep, comment-accuracy audit, or similar new lens here), and re-litigating unchanged code a prior cycle already examined. If you notice a real issue outside the fix delta anyway, report it in its severity section flagged `[out-of-delta]` — it is routed by disposition, not automatically treated as loop fuel.
Changed files: [list of files touched by the fixes]
Fix delta (inline unified diff, captured per item 2's snapshot note — never just the file list):
[the concatenated per-file diffs]
Previous findings addressed: [list]
Already tracked (do NOT re-flag, unless this cycle's fix delta made one worse — a latent gap turned reachable, a narrow case turned general — which is a new fix regression to report at its real severity): [each finding already filed as a Linear issue — its issue ID and a one-line summary — and each finding already routed to a deferred filing but not yet filed, with its planned disposition; omit this line entirely if there are none]
Acceptance: Confirm findings resolved. Flag fix regressions and unsound closures as Critical/High/Medium with concrete scenarios; flag anything else `[out-of-delta]`.

Why verification, not re-discovery. The observed 5-cycle grinds were driven by re-reviews escalating their own modality each cycle — behavioral findings in cycle 1, spec-pinning mid-loop, a first-ever mutation sweep at cycle 5 — so convergence meant "the reviewer ran out of new lenses", which a strong reviewer never does (BF-515's five-cycle grind is the worked example: it produced both of the interventions in this step — comments do not require re-review, and re-reviews scope to the fix delta). Depth belongs in Step 3's initial review, which already fans out parallel domain reviewers and chooses its own modalities; the re-review's job is to prove the fixes sound. A latent finding the initial review missed can still arrive [out-of-delta] and substantive defects of the change still gate (item 5's routing) — but the loop stops paying a full review cycle for each new lens. The quality-verifier tier is the economic enforcement of the same boundary: a verification pass whose scope is bounded by contract does not need — and no longer gets — the discovery configuration. (The class-shaped sweep below remains the one sanctioned expansion of re-review scope — it names a specific shared decision to hunt for, not a new modality.)

Broaden scope for class-shaped findings. If any finding addressed this cycle described a duplicated, re-derived, or copy-pasted decision — the same predicate, guard, formatting rule, or authorization/scoping check reimplemented independently at more than one call site — rather than a single isolated site, do not scope the re-review dispatch to only the fixed files. Add an explicit sweep instruction naming the shared decision and the directory/domain to search (e.g. "Also search apps/app/src/domains/stx/ for any other call site re-deriving isCoordinator || isClosing || isFlowSender(flow) instead of importing it from entityScope.ts"). A class-shaped finding whose re-review stays file-scoped lets sibling instances surface one at a time across many cycles instead of being caught in one pass — the same failure mode item 2's enum-variant and sibling-completion rules already exist to prevent on the fix side, applied here to logic duplicated across files. (This is the re-review member of the complete-the-class family — item 2's family note maps all four.)

Bound the sweep so it terminates. Broadening is for finding the class, not for fixing all of it inside this ticket, and the loop's convergence test cannot terminate work discovered mid-review. So when a class-shaped sweep is dispatched, require the fix delegation to: (a) produce a complete audit list of examined sites with a disposition for each (fixed / already-guarded / deliberately-not, with the reason), so coverage is auditable without re-sweeping; (b) fix only the sites reachable from the change under review, plus any divergence the sweep itself introduces — a half-applied sweep can be worse than none, breaking an invariant that held precisely because both sides failed identically; and (c) report, do not fix, every remaining instance, routed as deferred items per item 5's lanes. (b) and (c) govern only what the sweep reaches BEYOND the findings' named files. Inside those files item 2's sibling rule is unchanged and still wins: every sibling there is fixed whether or not the change under review reaches it. State the bound in the delegation prompt in as many words ("fix every sibling in the findings' files; beyond them, fix only what this change reaches and report further instances"), or the next cycle re-opens the sweep at the new frontier and the loop cannot converge. Measured 2026-08-19: cycle 4 broadened into a repo-wide sweep across four files; cycle 5 found the next frontier plus two new High findings the partial sweep had itself introduced by stopping at a file boundary. Findings ran 3H+5M → 3M → 2M → 3M → 2H+5M — non-convergent, terminated only by the cycle ceiling.

An accepted risk is a closure by argument — name it in the re-review dispatch, with the question phrased as a measurement. A fix can close its finding by change while accepting a residual by argument: the recovery valve lands, and the remainder — "it degrades only to the pre-change behavior, and only for a narrow population" — is recorded as a deliberate trade-off. The degradation is read off the code that produces it; the population size is the half that gets inferred from that same code rather than measured, and when it turns out to be the common case the trade-off was never a trade-off — it is the requirement, undelivered. In scope (b) above already owns this on arrival (an accepted residual is a closure by argument, and its falsification is an unsound closure, which item 5's substantive lane makes loop fuel at any cycle), so this adds no new modality and is not an expansion of re-review scope — what is missing is only the routing: the dispatch's slots are Previous findings addressed and Already tracked, neither of which is where a residual accepted inside a fix lands, and the verifier cannot test an argument it was never shown. Little else is positioned to catch it — this template carries no Issue requirements: line (only Step 3's initial review does, and that ran before the fix landed), pnpm check cannot evaluate a claim about a population, and /finish decides the issue's checkboxes by this session's own assessment. So add a line naming each accepted risk this cycle produced, and state the question as a measurement — "determine how large that population actually is" — never as a summary of why it was accepted, which invites agreement instead of a check. Observed: a valve that could not distinguish "the actor lost their grant" from "the actor never had one" was accepted as narrow; measured, it was the majority of real bindings, because the approval path in question authorizes by participant identity and mints no grant at all.

5. Route the re-review's findings, then loop only on what deserves a cycle. Split every new Critical/High/Medium finding by lane, per finding:

  • Prose-only (Step 6 sub-step 2's prose-only rule, its three exclusions included — the fix edits only comments, doc-strings, or standalone doc/rule text; the severity label the reviewer gave it does not matter). One rule per bullet:

    • Handling: the orchestrator applies the fix directly — no dispatch, per Dispatch tiers — as its own isolated edit confirmed by its own pnpm check, before this cycle's substantive fixes land. The isolation is what makes a failing check attributable — item 3's check covers the whole batch.
    • A failing check is a regression, not a landed prose fix: it means the edit was not actually prose-only — treat it at its real severity.
    • Class completion: a class-shaped prose defect — the same stale claim, wrong cross-reference, or contradiction duplicated across sibling files — is completed across every sibling in the same pass, per the prose class completion rule below.
    • Second attempts: a prose finding whose fix already survived one attempt this run gets the ping-pong rule below, not another minimal patch.
    • Never loop fuel: it neither makes a re-review mandatory on its own nor counts against convergence, and it adds nothing to the Cycles: N value in the Output block (the review dispatch that surfaced it still counts — a cycle whose findings happened to be all prose-only is still a cycle).
    • Same handling at every drain site: this cycle's fixing phase when the loop continues, the convergence drain when this re-review is the converging one (see Termination below), and the soft ceiling's lane drain alike.

    One carve-out — when the prose IS the deliverable, confirm it. When the change under review's own deliverable is prose — an issue whose success criteria demand a recorded decision, rationale, or invariant ("record the reason at <call site>", an ADR, a documented contract), or a doc/config-only change such as a keeper-labeled /reflect filing whose diff is entirely rule/standard/skill text — a finding that the recorded claim is false, unqualified, or misleading is a defect of the change, not a comment nit. The lane still applies unchanged (still not loop fuel, still not counted in Cycles: N) — promoting it to substantive would make this lane a no-op on doc-only changes and restore the per-nit grind it exists to end. What changes is verification: pnpm check cannot evaluate a factual claim, so the fix additionally rides a Targeted fix confirmation (the mechanical lane's dispatch, which is never a review cycle), required wherever this lane drains with no re-review to follow it — the convergence drain, the ceiling's lane drain, any batch item 4's all-prose/mechanical exemption converges without a re-review, and Step 6 sub-step 5's exempt deferred batch. Nowhere else: item 2's snapshot note captures direct-applied edits in any lane, so a fixing-phase prose fix in a batch carrying substantive fixes already reaches item 4's re-review in that cycle's fix delta. If the confirmation flags a problem, the confirmation re-route rules below route it from the top and the ping-pong rule governs the second attempt. This matters because a correction to a load-bearing rationale routinely asserts new factual claims of its own — exactly what a verifier exists to check — and the non-gating treatment above would otherwise converge the loop with no one ever having read them. Read the trigger narrowly: it is the change's own deliverable being prose, never the fix batch happening to be prose. A code change whose cycle-N fixes edit only comments and doc-strings drains at convergence with no confirmation — that is the exemption working as designed, and the orchestrator's own measurement discipline (rules/comments.md) covers any empirical claims those comments carry. Measured 2026-08-15: BF-894, a code change, spent a 23-minute Targeted fix confirmation dispatch blessing its cycle-2 comment fixes — roughly a quarter of its entire review phase — on a carve-out whose trigger its deliverable did not meet.

    The confirmation this carve-out requires is bounded, and a pure deletion needs none. A flagged problem re-enters the prose lane by the re-route rules below, which at the convergence drain would otherwise buy another confirmation, and another: the ceiling's one-round bound (below) is scoped to the ceiling, and its "the loop re-arms normally" reasoning covers only a substantive re-route — a prose one is never loop fuel and a Targeted fix confirmation is never a review cycle, so nothing counts and nothing terminates. So one corrective round (the ping-pong rule's paragraph rewrite), re-confirmed once; still flagged after that, converge and record the surviving flag under Deferred dropped with its rationale. That is the one exception to Termination's "every mechanical confirmation has returned clean" exit gate, and it is narrow by construction — it reaches only a prose-only problem that has already spent its rewrite and its re-confirmation, on a lane that is never loop fuel and, per sub-step 6's membership definition, never fileable, so there is nothing further the loop could buy. And skip the confirmation entirely for a corrective edit that only removes text and removes no claim with it — a provenance anchor or byline, a verbatim duplicate of adjacent text, a cross-reference to something that no longer exists: this carve-out exists because a correction asserts new claims, and such a deletion asserts none. Deleting a qualifier, an exception, or a scope clause is not that — it silently widens what the surrounding text governs, which is a new claim by omission and owes the confirmation like any other. A corrective edit that deletes a claim the confirmation named as false, adding nothing in its place, likewise needs no confirmation — it asserts nothing, so there is nothing left to check. Prefer it to a replacement whenever the bound above is spent: shipping wording the confirmation handed back is shipping an unchecked new factual claim, and a run that has already produced two confident-but-false corrections is the worst possible place to trust a third. Where the surrounding rule requires a claim to be present at that site, the honest outcome is the surviving flag under Deferred dropped, not a replacement no one has verified.

  • Mechanical (definition and class-completion rule below): the orchestrator fixes it now, directly, and verifies it with a targeted fix confirmation — not a review cycle.

  • Substantive — loop fuel: return to the top of this step (triage → fix → check → re-review). Membership narrows as cycles accrue, with the cycle count grounded in dispatch history per the ceiling's count-grounding note. At any cycle, fix regressions and unsound closures qualify at every severity — the loop always finishes what it started — and so does any new Critical or High defect of the change under review (including [out-of-delta] ones that are neither prose nor mechanical). A new Medium defect of the change qualifies only when surfaced by cycle 2 (the first re-review); surfaced by any later cycle, it is not fuel — route it out with the pre-existing bullet's machinery as a severity-carrying deferred item (same rendering, same specified filing), noting in the filing that it is a defect of this change, not a pre-existing gap. This is the narrowed fuel that ends the grind: by cycle 3 the change has had a full discovery pass and a full-fuel verification pass, so a marginal Medium surfacing later is usually real but buys a filing, not a cycle — paying a fix batch plus a re-review per late Medium is how one-file edits ran to 7–9 dispatches (BF-534, BF-560).

  • Pre-existing beyond the change under review: not loop fuel and not fixed in this loop — route it out of the loop as a severity-carrying deferred item, which lands in Step 6 sub-step 6's "Suggested defer as issue" group (rendered with severity in the tag slot; certified with specified only when it clears sub-step 6's quality bar, else filed uncertified) on exactly the terms sub-step 5 prescribes for the pre-existing findings its own re-reviews surface. Step 6 ingests these directly — see its "Severity-carrying deferred items are a second input to this step" note — and they are rescued into Open items on the exits where Step 6 never runs, by Step 5's option-3 path and by Step 6's opening paragraph. Everything this bullet says about ingestion and rescue applies equally to the narrowed-fuel Mediums the substantive bullet routes here.

  • Ambiguity resolves to regression — sub-step 5's tie-break, re-anchored to this step's evidence, governing both deferred exits from the loop (the pre-existing lane and the narrowed-fuel Medium route): a finding leaves the loop only when this loop's own fix deltas are cleanly exonerated. Pre-existing means plainly already true before this loop began, judged against the previous cycles' fix deltas and the initial review's baseline; a narrowed-fuel Medium must be plainly a new defect of the change, not of any fix. When a fix delta cannot cleanly be exonerated, route the finding to the substantive lane as a regression instead. Mis-calling a true regression "pre-existing" (or "just a late Medium") ships a break; mis-calling the other way costs a cycle for a Medium, and — for a Critical or High surviving to the soft ceiling — the whole run. The classifier here is the party that wants to converge, so it owes the same conservatism the mechanical lane demands, and it must not read the ceiling as a safety net that will re-classify whatever it defers — see the ceiling's Auto mode paragraphs below for what that gate does and does not adjudicate.

Prose ping-pong — a surviving prose fix earns a paragraph rewrite, never a second qualifier. When a later verifier pass — a re-review or a Targeted fix confirmation — flags prose this run already fixed once (the corrected sentence still ambiguous, the qualifier still wrong, a new defect in the fix's own wording), do not patch it minimally again. Rewrite the surrounding paragraph comprehensively so the whole thought reads clearly and consistently, instead of threading one more qualifier into a sentence that has already failed review — qualifier-stacking is how one sentence ate three dispatches in BF-560. The rewrite stays inside the same wording scope item 2's "a wording finding earns a wording fix" rule sets for the first attempt — comprehensive at paragraph scale, but still restating only what the text already governs, never specifying previously-unspecified behavior, with the same pre-existing-gap routing. The two rules compose: first attempt minimal and wording-only (item 2's rule), second attempt paragraph-comprehensive, both inside wording scope — what burned BF-560's cycles 2–4 was new surface, not minimal patching, and neither attempt is licensed to create it. This rule applies wherever the prose lane drains: the fixing phase, the convergence drain, and the ceiling's lane drain alike.

Prose class completion — sweep the sibling surface in the same pass. Doc/rule prose duplicates by convention — the same cross-reference repeated across standards/*.md, the same claim restated in several rule docs or sibling skills — so a prose finding is often one visible site of a class, and in a prose-heavy repo (~/.claude is almost entirely instruction text) that is the dominant shape, not an edge case. When the prose defect is class-shaped — a stale claim, wrong cross-reference, or contradiction that convention duplicates across sibling files — do not stop at the named site: before applying the fix, grep the convention-defined sibling surface for the duplicated text (standards/*.md, rules/*.md, sibling skills/*/SKILL.md — whichever family the named file belongs to) and apply the same correction at every instance found, in the same direct-apply pass. Sweep by token, not by file family — the edited file included. The globs above are ~/.claude doc families and reach no source file, so a wrong docblock in apps/…/Foo.tsx has no enumerated surface at all; grep the claim's distinctive token (a header name, a symbol, an option) across the repo's source roots instead, and count a same-file duplicate as class-shaped even though it does not "duplicate across sibling files". That is the commonest shape and the cheapest to miss: a stale claim's nearest sibling is usually a few lines below, in the code consuming the value, where the use-site comment restates it — one such docblock was corrected while the identical false claim survived nineteen lines down, costing a corrective confirmation round. This is the prose lane's member of the complete-the-class family (item 2's family note maps all four members and their bounds), with its own bound in place of theirs: item 2's sweep is bounded by the Findings file list and backed by a pnpm check that can actually fail, while here the bound is the prose-only definition itself — every swept edit must touch only comments, doc-strings, or standalone doc/rule text, enforced by the orchestrator at edit time, because prose edits pass pnpm check by construction and the check can bound nothing. Swept sibling fixes are part of the same prose-lane pass: covered by the same isolated pnpm check, still never loop fuel, never re-review-mandating, adding nothing to the cycle count, and listed with the named site under Findings resolved (sites covered, mirroring item 2's report) so the sweep is visible without being gating. Like the ping-pong rule, this applies wherever the prose lane drains.

Mechanical findings — fix and confirm, never cycle. A finding is mechanical when its fix requires no choice between valid alternatives AND is localized (single file, or a spec file plus its subject) AND changes no public API, schema, or contract AND is small (~<15 lines) AND is directly checkable by pnpm check plus a targeted test run. A missing import, one spec arm pinning existing behavior, a wrong string in a message: mechanical. Classify by the fix, not the severity — a Medium with a one-line fix is mechanical — and doubt resolves to substantive: the classification is made by the party that wants to converge, so it must be conservative, and a fix that changes runtime behavior in any way the classifier cannot fully predict is not mechanical. Handling: the orchestrator applies the fix directly (no dispatch, per Dispatch tiers), runs pnpm check plus the targeted specs, then dispatches a single confirmation to quality-verifier:

Task for quality-verifier: Targeted fix confirmation for PL-13
Context: Mechanical fixes for the findings below were just applied. Confirm each fix resolves its finding and introduces nothing new in the touched lines. This is a confirmation pass, not a review — do not widen scope.
Fixes: [finding → fix applied, per item]
Class sweep: [only when a finding was class-shaped: the shared decision, the domain searched, the in-scope sites completed, and each beyond-scope sibling routed to a deferred filing — omit this line entirely otherwise]
Changed files: [list]
Fix delta (inline unified diff, captured per item 2's snapshot note):
[the concatenated per-file diffs]
Already tracked (do NOT re-flag, unless one of the fixes above made one worse — a latent gap turned reachable, a narrow case turned general — which is a new fix regression to report at its real severity): [each finding already filed as a Linear issue — its issue ID and a one-line summary — and each finding already routed to a deferred filing but not yet filed, with its planned disposition; omit this line entirely if there are none]
Acceptance: Per fix: confirmed, or a concrete scenario where it fails plus a severity (Critical/High/Medium) for the problem you are flagging.

Confirmation re-route — a flagged problem does not close its finding. Five rules govern, one per bullet:

  • Re-route from the top. The flagged problem re-enters item 5's lane split as its own classification — never promoted straight to substantive: a prose-only problem (say, a doc-string on a touched line left contradicting the new symbol) goes to the prose lane; a gap plainly pre-existing beyond the change under review routes out as a severity-carrying deferred item; everything else is substantive (loop fuel). At the soft ceiling's lane drain the loop cannot re-arm — the ceiling's one-round bound governs there instead (see the drain paragraph below).
  • The original finding closes on disposition, not on the flag. It closes when the re-routed problem is dispositioned — that lane's fix landing with a clean check, or the deferred item being filed or dropped — and is then listed under Findings resolved like any other fixed finding. If the run terminates first, it goes to Open items alongside the flagged problem.
  • Severity is inherited upward. The re-entrant carries the severity of the finding whose fix was being confirmed, unless the confirmation named a higher one — then the higher. Settle it before the re-route: the pre-existing lane renders it in the tag slot and the ceiling gate branches on it.
  • The mechanical lane is a one-way door. A fix whose confirmation flagged a problem is no longer directly checkable, so it can never re-enter the mechanical lane — that lane's doubt-resolves-to-substantive rule closes the door, and there is no fix-and-confirm ping-pong. (A Targeted fix confirmation dispatch is not a review cycle and is never counted as one — see the ceiling's count-grounding note.)
  • Mixed-lane batches order by lane. When a re-review's findings span lanes: the prose ones land first as their own isolated direct edits per item 5's prose lane, the mechanical ones are direct-applied alongside that cycle's fix batch, and the substantive ones drive the normal cycle — the lanes change what re-arms the loop, not which cycle fixes land in.

Mechanical class completion — complete in scope, defer beyond it. The mechanical lane classifies by the fix, so a class-shaped finding can land here legitimately — most often when its siblings live in the named file itself, which passes the localization gate intact. Before dispatching the confirmation, complete the class, split by where the siblings live: (a) in scope — siblings in the named file or in files inside the change under review's resolved scope (Step 1) are completed in the same direct-apply pass; each site is the same choice-free fix, each is listed per-item in the confirmation's Fixes and Class sweep lines, and the whole set rides the confirmation's inline diff, so no swept edit lands unverified; (b) beyond scope — grep the shared decision across its domain (the same named-decision-and-domain form item 4's sweep instruction uses), and route every instance in files the change never touched to the pre-existing lane as a severity-carrying deferred item carrying the class finding's severity — surfaced, never edited, because that is exactly what it is: a pre-existing instance of the defect in code outside this loop's scope. Editing it here would be the scope expansion item 2's sweep boundary exists to prevent; leaving it unmentioned is the silent-sibling gap this rule exists to close. The lane's gates still govern the completed fix as a whole: if completing the in-scope class blows the size bound or introduces a choice between alternatives, the finding was never mechanical — doubt resolves to substantive, exactly as the lane's definition already prescribes (in a drain, that lands on the Termination rule's existing dirty-drain withdrawal path). Like the prose rule, this applies wherever the mechanical lane runs: the fixing phase, the convergence drain, and the ceiling's lane drain alike — the drains are the most exposed paths, running after the last re-review, where nothing downstream would ever surface a missed sibling.

Termination: The loop terminates by convergence — when a re-review surfaces no new substantive findings, or when the initial review itself surfaced none and its all-prose/mechanical batch drained clean under item 4's exemption (a run that never needs a re-review converges at cycle 1). Prose-only, mechanical, and deferred-routed findings (pre-existing gaps at any cycle; narrowed-fuel Mediums from cycle 3 on), handled by their lanes above, do not block convergence. On convergence, run the same lane drain the soft ceiling below prescribes, then exit Step 5 with verdict passed-after-fixes and proceed to Step 6 — but only once every prose fix's check is clean and every mechanical confirmation has returned clean. Neither drain outcome is guaranteed clean, and a dirty one withdraws the convergence declaration: a failed prose check or a confirmation that flags a problem routes that finding back through item 5's lane split, and when it lands substantive the loop re-arms at the top of Step 5. Exiting with passed-after-fixes on a red pnpm check violates the Working Application Contract.

Soft ceiling: After 5 review cycles (initial review + up to 4 re-reviews), pause and ask the user how to proceed instead of looping silently. Reviewers tend to find something on every cycle, so an unbounded loop can run away even when the implementation is materially improving. The ceiling is not a hard cap — the user can extend it, and in auto mode the trend gate below extends it once on its own for a converging run.

Ground the count in dispatch history, not memory. Determine the cycle count by counting the actual review-cycle dispatches made so far in this loop — Step 3's initial quality-reviewer review plus each Step 5 item 4 quality-verifier re-review — never by trusting a running mental tally or a self-assigned label on a dispatch (e.g., naming an agent call "Cycle-5"). Targeted fix confirmation dispatches (item 5's mechanical lane) are not review cycles and are never counted — the count is keyed off the dispatch task headers, which are the history. Labels and mental counters can drift from the true count across a long loop with many interleaved fix/check delegations. Before pausing — or, in auto mode, before applying the ceiling's trend gate, lane drain, and provenance resolution — recount from the actual dispatch history if there is any doubt.

The implementation has gone through 5 review cycles and still has unresolved findings:
[list current findings]

Cycle-by-cycle trend: [e.g., "5 → 3 → 2 → 2 → 2" so the user can see whether progress is stalling or converging]

Options:
1. Continue fixing — run another N cycles, default N=3
2. Accept current state — terminate with verdict `terminated-with-open-items` and treat surviving findings as Open items
3. Revisit the approach with the architect agent

Reply with `1` (optionally `1 5` for a custom N), `2`, or `3`.

Auto mode, first: the trend gate — extend a converging run once instead of terminating it. The ceiling is a stop-the-bleeding circuit breaker for genuine non-convergence, not a routine intervention point: observed ceiling hits were almost always manually resumed with more room, and BF-560 converged at cycle 6 after a real fix regression surfaced at cycle 5 — a non-extending ceiling kills exactly such runs. So before draining or resolving anything, read the trend: list the per-cycle counts of new loop-fuel findings (item 5's substantive lane) for the cycles run so far, grounded in the dispatch and routing history like the cycle count above — never a remembered tally. If the count is decreasing over the most recent three cycles — the latest count strictly below the previous, and the previous no higher than the one before — the run is converging: raise the ceiling by 3, note the extension and the trend in chat, and resume the loop. The extension happens once per run — hitting the raised ceiling resolves below regardless of trend. The window is deliberately recent-cycles, not full-history: an early fix-regression spike (BF-574 ran 7 → 14 → 7 → 4, converging) must not disqualify a run that is clearly converging afterward. A flat or growing recent trend falls through to the drain and provenance resolution below. Interactive mode has no trend gate — the prompt above already puts the trend in front of the user, who can extend via option 1.

Drain the mechanical and prose lanes next — both modes (in auto mode, only a run the trend gate did not extend reaches this drain). Before rendering the prompt above or applying the auto resolution below, route every surviving finding through item 5's lanes: prose-only survivors are fixed under the prose lane — its ping-pong and class-completion rules included — and confirmed with pnpm check (a failing check means the edit was not prose-only — treat it as a regression, per that lane); mechanical survivors are fixed and verified under the mechanical lane — its class-completion rule included — with a targeted confirmation, not a new cycle. Only survivors remaining after the drain — substantive, judgment-requiring findings — reach the prompt or the auto resolution. A ceiling reached with only mechanical and prose survivors therefore converges (passed-after-fixes, once confirmations return clean) without asking anyone: terminating over an unapplied one-line fix is the deadlock this drain removes (observed twice in one weekend — a stall over a single missing import, and a stall over two one-arm spec additions, both on implementations whose success criteria were met with full suites green).

When a drain confirmation itself flags a problem — one round, then survivor (BF-563). The confirmation re-route rules still classify the flagged problem, but at the ceiling the loop cannot re-arm, so the drain owes exactly one corrective round: a problem landing in the prose lane is fixed under that lane (ping-pong applies — the paragraph rewrite, never another qualifier) and re-confirmed once; a problem landing substantive — including everything the mechanical one-way door pushes there — gets no round at all. Still flagged after the single re-confirmation, or landed substantive: it is a post-drain survivor — interactively it joins the prompt's findings list, in auto mode it enters the provenance resolution below. One round total per finding: BF-558's live instance resolved exactly this way, and an unbounded fix-and-re-confirm at the ceiling would be the very loop the ceiling exists to end. (At the convergence drain the bound splits by lane: a flagged problem landing substantive re-arms the loop normally, so none is owed there — see Termination — but one landing in the prose lane re-arms nothing, and is bounded by the carve-out's own rule in item 5.)

Auto mode, then: resolve the post-drain survivors by provenance — do not ask — using the regression-vs-pre-existing split Step 6 sub-step 5 defines. What this gate does and does not adjudicate, one rule per bullet:

  • Provenance is adjudicated for Medium survivors only. A surviving Critical or High is never adjudicated here at all: branch 1 below routes it on severity alone, before provenance is considered, and no provenance finding can rescue it.
  • This is where item 5's tie-break is spent, not re-applied. That tie-break deliberately parks ambiguous Mediums in the substantive lane so they reach a decision point with the whole loop's history in view — this is that decision point. Adjudicate each on the evidence now available; only one that still cannot be exonerated stays a regression.
  • Both branches are live for every survivor, ambiguous or not — an unambiguous Medium regression that reached the ceiling is routed by branch 1 like any other regression.

The two branches:

  • Any surviving Critical or High, or any surviving finding that is a regression/defect of the change under review → option 2 immediately (terminate with terminated-with-open-items, surviving findings as Open items). Five cycles of genuine non-convergence in an unattended run is a signal for a human, not more unattended cycles; the failing verdict makes /finish auto abort and /auto count the issue as a failure, which is the intended surfacing path.
  • Every surviving finding is Medium AND adjudicates here as pre-existing → the implementation itself has converged; the residue is reviewer scope-widening into adjacent gaps, not this change's defects (the BF-435 pattern: 28 findings fixed across 4 cycles, then blocked by a sibling-method one-worder and a "file siblings separately" recommendation). Treat as convergence: exit Step 5 with verdict passed-after-fixes and route every surviving finding into Step 6 as a severity-carrying deferred item (the same lane item 5's pre-existing bullet uses — rendered in sub-step 6's "Suggested defer as issue" group with severity in the tag slot, certified with specified only when it clears sub-step 6's quality bar, else filed uncertified, into sub-step 6's state split — Backlog for these Mediums unless security-labeled), where auto mode's suggested default files them.

This trend-then-drain-then-provenance resolution is what auto mode substitutes for the prompt above and nothing more: it does not scope item 5's lane split, which routes every re-review's findings in both modes, on every cycle, ceiling or no ceiling.

If the user picks option 1, resume the loop with the new ceiling raised by N. If 2, terminate with terminated-with-open-items. If 3, terminate with verdict escalated-to-architect: populate Open items with the surviving findings as of cycle N, plus every severity-carrying deferred item that item 5's deferred routes (the pre-existing lane and the narrowed-fuel Medium route) sent out of the loop in any cycle, plus the consolidated Nice-to-Have list per sub-step 1 (deferred items are never silently lost — Step 6, which is what would otherwise file the routed items, does not run on this path), skip Step 6 entirely, and surface the situation to the architect agent — its recommendation supersedes anything downstream consumers (/start Step 10, /finish Step 8) would otherwise do with the verdict block.

Step 6: Deferred Items Triage

Runs once, only after the fix loop terminates with passed-clean or passed-after-fixes. On terminated-with-open-items this step is skipped, but the consolidated list from sub-step 1 and every severity-carrying deferred item Step 5 item 5's deferred routes (the pre-existing lane and the narrowed-fuel Medium route) sent out of the loop are still appended to the verdict block under Open items so deferred findings are not silently lost.

Severity-carrying deferred items are a second input to this step. Alongside sub-step 1's Nice-to-Have consolidation, Step 6 ingests every Critical/High/Medium finding routed out of the fix loop as a deferred item — by Step 5 item 5's pre-existing lane and its narrowed-fuel Medium route, by Step 5's auto-mode ceiling resolution, and by sub-step 5's own re-reviews (the mandatory one and the optional confirmatory one alike). Nothing about them needs consolidating, classifying, or auto-fixing, so they land directly in sub-step 6's "Suggested defer as issue" group. Sub-step 1's empty-list short-circuit and sub-step 4's entry gate govern the Nice-to-Have lane only and never discard them: a run whose entire deferred output is one routed pre-existing High still reaches sub-step 6 and is offered for filing (auto mode's suggested default files it).

Substitution token. <ISSUE-ID> in the templates below means the issue ID resolved in Step 1. If no issue was resolved, replace for <ISSUE-ID> with for the current change set and skip the dependency-link command in sub-step 6.

Malformed user replies. Sub-step 6 is the only prompt in this step. If the user replies to it with input you cannot parse (e.g., 1-3, the first three, sure), re-prompt once with the exact accepted syntax — including the suggested shortcut — and a literal example. After a second malformed reply, fall back to the safe default all (filing extra Linear issues is recoverable, silently dropping findings is not). Note: suggested is an accepted input, never a fallback. On re-prompt, re-render the full template; do not abbreviate on retry.

Verdict downgrade. Step 6 can transition the run state from a passing verdict (passed-clean or passed-after-fixes) to terminated-with-open-items — by sub-step 5's regression-cap path, or by sub-step 6's create-failure rule when filing stops mid-run. When that happens, the new verdict overrides the verdict assigned at Step 4.

1. Consolidate. Collect every Nice-to-Have finding from tmp/quality-review-nth-<issue-lower>.md (written incrementally as each dispatch returned — see Step 3's persistence note) rather than from recollection; if that file is absent or predates a cycle, fall back to grepping the session transcript for each dispatch's ### Nice-to-Have block, the same recovery discipline Findings resolved uses. Strip any instructional or meta bullet — one whose text begins NOTE: and carries no file:line location — before consolidation: instruction-echo carries no location, while every actionable finding MUST include one per the Required findings format, so the location requirement spares a finding quoting an offending NOTE: comment (it carries its own file:line), but not a locationless NOTE:-prefixed one — the chat note below is its recovery. A stripped bullet is reviewer instruction-echo forbidden by the agent contract, not a finding, and is neither ingested, classified, nor rendered. When a bullet is stripped, emit a one-line chat note identifying it so the drop stays visible, never silent. This strip applies only to Nice-to-Have ingestion — here and sub-step 5's re-reviews — never to Critical/High/Medium, where a NOTE-shaped bullet must instead be surfaced for confirmation, not dropped: it either gates the verdict or is forbidden echo — surface it, never drop it. Deduplicate by file:line + finding text; if a finding was emitted without a file:line (a locationless finding, not malformed — no Error Handling criterion fires), fall back to deduplicating by finding text alone (trim, casefold, and collapse internal whitespace before comparison to absorb cosmetic differences). If the consolidated list is empty, skip to sub-step 6 — whose own entry gate decides whether any severity-carrying routed item remains to file, and exits the step when nothing does.

2. Classify. Assign each item exactly one of two outcomes — fix-now or note-only — using the decision procedure below. The Nice-to-Have lane never files an issue (keeper decision, 2026-08-15): a reviewer Nice-to-Have carries the same target-of-opportunity semantics as the spec template's ### Nice to Have tier (standards/issue-spec.md) — fix it if it is easy, otherwise record it and drop it. Filing from this lane is how the backlog's birth rate stayed above one issue filed per issue shipped (BF-673 shipped one issue's work and filed two non-defect children, both canceled at keeper review; BF-1182 was an optional spec line converted to tracked work the same day). Real risks reach Linear exclusively through the severity-carrying (Critical/High/Medium) routes, which this rule does not touch — if a Nice-to-Have seems to deserve a ticket, that is a severity question, not a filing question: it either carried C/H/M and rides those routes, or it drops. fix-now items are applied automatically in-session with no approval prompt (sub-steps 4–5) — they are gated to obviously-correct, localized, no-API-change changes, so the user has opted into fixing them without per-item review. note-only items are neither fixed nor filed — they are recorded in the verdict's Deferred dropped field and never surfaced as a decision.

Decision procedure — evaluate top-to-bottom; first match wins:

  1. Comment-, doc-, or rule-text-only fixfix-now. See the prose-only rule below, which overrides every gate that follows.

  2. Clears all of these fix-now gatesfix-now:

    • One obviously-correct change — no decision between valid alternatives. Following an established codebase pattern is not an open design choice: "I'd have to look at how it's done elsewhere" satisfies this sub-gate. It does not waive the others — a pattern applied across many files still fails the localization sub-gate, and one that changes an exported contract still fails the contract sub-gate; either failure sends the item to gate 3.
    • Localized (single file or small contiguous region).
    • No change to public APIs, schemas, exported types, or contracts.
    • Small (~<30 lines diff, no new abstractions).
    • Attach a one-word kind tag: [mechanical], [naming-only], [missing-guard], [typo], [dead-code], [comment-fix], [doc-fix], etc.

    This is the default home for trivial polish. When genuinely torn between fix-now and note-only for a small, safe change, prefer fix-now — the standing preference is to improve the code, not surface it. The lane is non-gating, not optional: every Nice-to-Have finding with a safe, concrete fix is fix-now, including rule/doc corrections, provably-inert cruft, and cosmetic regressions the change under review itself introduced.

    Dead code and duplication consolidate under relaxed gates (keeper ruling, 2026-08-15). Dead-code deletion is always fix-now — deadness is provable (remove, run the suite; rules/comments.md's discipline), and the reviewer grades substantial dead code Critical/High as a mandatory deletion, so it should rarely reach this lane at all. Consolidating duplicated code into an established home — a module every copy already includes, a support file that already hosts the pattern's siblings — is likewise fix-now even cross-file and even when it introduces a suite-wide name: the localization and no-new-abstraction sub-gates do not apply, because following the established home is not a design choice (gate 2's own pattern rule) and internal contract changes are embraced (standards/technical-debt-prevention.md). Exactly two escalations survive: a deliberate divergence among the copies — one copy intentionally differs (BF-785's sibling asserting a different denial shape; BF-900's five copies carrying one drifted assertion to settle) — which is a genuine decision the consolidation must not flatten silently; and sweep breadth — copies beyond the change's resolved scope follow the mechanical lane's complete-in-scope/surface-beyond rule, never an unbounded sweep from here. A consolidation applied under this ruling preserves any deliberately-diverging sibling untouched and rides the normal fix-batch verification, never a blind direct apply.

  3. Otherwisenote-only. Everything not safely fixable now is recorded and dropped — including items needing a design choice, cross-cutting scope, an API/contract change, or a broader test/perf/security strategy, which under the pre-2026-08-15 procedure earned filings: under the opportunity semantics they are simply not opportune. The guard runs in the other direction unchanged: "no visual harm," "cosmetic," "no-op," "not a regression," "harmless to the app," and "out of scope" are NOT grounds for note-only when a safe, localized fix exists — those are fix-now (gate 2). A provably-inert no-op prop, a redundant token, or a cosmetic regression the change under review itself introduced all have a concrete fix (delete the inert code / restore the intended behavior), so they are fix-now, never dropped here. Record each note-only item (verbatim finding + file:line + one-line rationale — plus, for a non-defect refinement of this run's own fix, its re-open trigger: the observable signal, a Sentry event or a measured production occurrence, that would make it worth a session) for the Deferred dropped verdict field; do not fix it, do not file it, do not prompt.

A fix-now candidate that fails a gate-2 safety check (e.g., a [mechanical] rename that also touches a public type) does not auto-apply — it falls through to note-only with the failed gate in its rationale: a contract-touching nit is dropped, not ticketed.

A non-defect refinement of this run's own fix is note-only wherever it arises — a Nice-to-Have finding, a checkpoint's "carried forward" note, a mid-implementation deferral. Record it with its re-open trigger (gate 3 above) in the completion-comment record, plus a durable code pointer at the site when regression-prone (rules/comments.md). And distinguish the stronger-arm case first: a refinement that IS the issue's own preferred either/or outcome arm was in-mandate to deliver in-session (standards/issue-spec.md § An either/or Desired Outcome is ordered) — reaching classification cold means the cheap moment was already missed, which argues for the note and its trigger, never a filing (BF-1175, canceled: the stronger arm of BF-673's own outcome, filed cold as a dead letter instead of delivered hot).

Prose-only fixes are always fix-now — never dropped and never fileable. A prose-only fix edits only non-executable text: a code comment, a doc-string, or a standalone documentation/rule file (.claude/rules/*.md, standards/*.md, CLAUDE.md, and the like). When the prose is stale, incorrect, misleading, or self-contradictory and the code/policy it describes is already correct, correcting it touches no behavior, API, schema, or contract — so it is trivially safe to apply in-session, and filing a Linear issue to correct prose is pure overhead. This overrides gates 2–3 of the procedure above and any "out of scope" reasoning — even when the prose lives in a file you would otherwise leave untouched (e.g. an already-run migration whose comment is wrong, or a rule doc in .claude/rules/ that points readers at the wrong file: correcting either changes no behavior). It applies wherever a prose-only finding surfaces, including ones first raised by the sub-step 5 re-reviews. Three exclusions: a fix that edits both code and prose is not prose-only — apply the standard criteria; prose that is wrong because the code is wrong is a code defect — fix the code at its real severity, never paper over it by rewording the prose; and a doc/rule edit that changes the rule's meaning or policy (not merely correcting a stale, wrong, or contradictory fact) is a design decision — apply the standard criteria, which under the lane's two outcomes means note-only unless it is safely fix-now. And one non-exclusion, ruled explicitly (keeper, 2026-08-15): a doc-string fix that forces regeneration of committed derived artifacts is STILL prose-only. A GraphQL description: change manifests exactly as a comment; schema regeneration is a routine task, and the regenerated schema.json/client-file diff from a description-only change is comments and metadata — zero code implications. Run the generator and commit its output as part of the fix; never scope around the regen, defer the fix for it, or mark the work solo (BF-894 declined a one-line description fix as "codegen collision" and the miss cost a filing plus a keeper audit — BF-1170).

3. Present the fix-now list. Render the fix-now group with numbered items. Omit the render entirely if the group is empty. note-only items are not rendered here — they are neither fixed nor filed, so listing them would surface exactly the minor items this classification is meant to keep out of the user's view; they appear only in the verdict's Deferred dropped field. (Nothing from this lane is fileable — the "Suggested defer as issue" group renders in sub-step 6 and contains only severity-carrying items.)

Deferred items surfaced during review:

Auto-fixing now (no approval needed):
  1. [Finding] — [file:line] — [tag] — [rationale]
  2. [Finding] — [file:line] — [tag] — [rationale]

Prompt mechanism (applies to sub-step 6). Sub-step 6 is the only prompt in Step 6 — it asks about exactly one action verb ("file as issue"). fix-now items are auto-applied in sub-steps 4–5 with no prompt, so there is no second prompt to disambiguate against. Specifically:

  • Render the unfixed-items list from sub-step 6 as plain text above the question. Do not rewrite an item's label to encode the action verb — the [tag] communicates the recommendation; the label is the finding.
  • The question itself MAY be an AskUserQuestion multiSelect, but if so:
    • Every option label must read as "file [finding]".
    • Pre-check ([✔]) every item in the rendered "Suggested defer as issue" group — sub-step 6's render rule defines its membership — so a one-click accept files exactly the set the suggested reply files.
    • Do not add an "Other" / "type something" / "I have a different choice" option to the options array. The AskUserQuestion tool surfaces an "Other" capability automatically (per its tool description: "Users will always be able to select 'Other' to provide custom text input"; "There should be no 'Other' option, that will be provided automatically"). The chat is always available for the user to interject a different reply (e.g., none, a comma-list, or free-text). An explicit "type something" option is redundant clutter — the multiSelect must contain exactly the N finding options and nothing else.
  • Equivalent plain-text reply is also acceptable per the existing suggested / all / none / comma-list semantics below.
  • Item labels in the rendered list and in the AskUserQuestion option labels MUST use the same identifiers. Use Arabic numerals only (1, 2, 3, …) — never letters, never roman numerals, never any other scheme. An option labeled "Items 5, 6" must refer to items literally labeled 5. and 6. in the rendered list directly above.

4. Select fix-now items for auto-apply. No prompt — every item in the fix-now group is applied in-session automatically. These are gated to obviously-correct, localized, no-API-change changes, and the user has opted into fixing them without per-item approval. The set to fix = every fix-now item from sub-step 2.

Entry gate — skip sub-steps 4–5 entirely if the fix-now group is empty. With nothing to apply, proceed directly to sub-step 6.

Before delegating, emit a one-line chat note listing the items being auto-applied so the user has visibility (e.g., Auto-fixing 3 deferred items in-session: #1, #2, #4). Then proceed to sub-step 5.

5. Fix the fix-now items. If the fix-now group is non-empty:

  • Snapshot every file a fix-now item will touch (Step 5 item 2's snapshot note, with deferred as the cycle token) — the re-review below carries the delta as an inline diff.

  • Apply the fix-now items. Every item that is prose-only or clears the mechanical-lane gates (single file, ~<15 lines, no contract change — Step 5 item 5's definition) is applied directly by the orchestrator, no dispatch, exactly as the Step 5 lanes prescribe. Only the remainder — fix-now items above that bound, up to gate 2's own ~<30-line, small-contiguous-region ceiling — goes to developer (sonnet-tier, per Dispatch tiers; parallel agents if findings are in independent files, same pattern as Step 5 — minus its sibling sweep, per item 2's scoping note).

  • Re-run pnpm check. If it fails after a single corrective developer delegation, surface the failure via the Error Handling path and stop — do not loop indefinitely.

  • Spawn a single quality-verifier re-review scoped to only the files touched by the deferred fixes — exempt on the same terms as Step 5 item 4: when the orchestrator applied the whole batch directly (every item prose-only or clearing the mechanical-lane gates) and neither a developer fix dispatch nor the corrective delegation above was needed, no re-review is spawned. The lanes' own verification is the completion gate in its place — and that is not the pnpm check above alone, which is all this sub-step otherwise carries: the mechanical-lane items ride a single Targeted fix confirmation (Step 5 item 5's template, carrying the snapshot delta from the first bullet inline — the snapshot is taken regardless of which verifier consumes it), and this is a fourth drain point for that item's prose carve-out, so where the change under review's own deliverable is prose the prose fixes ride that confirmation too. If a confirmation flags a problem, Step 5 item 5's confirmation re-route rules govern, and the regression/pre-existing split below applies to what they surface. Any item that went to a developer — gate 2 admits up to ~<30 lines, wider than the mechanical lane's ~<15 — keeps this re-review mandatory:

    Task for quality-verifier: Adversarial re-review of deferred-item fixes for <ISSUE-ID>
    Context: Previously deferred Nice-to-Have items were just fixed. Verify correctness; try to break them.
    Changed files: [list]
    Fix delta (inline unified diff, captured per Step 5 item 2's snapshot note):
    [the concatenated per-file diffs]
    Previous deferred findings addressed: [list]
    Already tracked (do NOT re-flag, unless the deferred-fix delta made one worse — a latent gap turned reachable, a narrow case turned general — which is a new fix regression to report at its real severity): [each finding already filed as a Linear issue — its issue ID and a one-line summary — and each finding already routed to a deferred filing but not yet filed, with its planned disposition; omit this line entirely if there are none]
    Acceptance: Confirm fixes are correct. Flag any new findings (any severity) with concrete scenarios.
    
  • If either of this sub-step's re-reviews — the mandatory one above or the optional confirmatory one under the upgrade-on-success exception below — surfaces new Nice-to-Have findings:

    • Prose-only findings (sub-step 2's prose-only rule — comments, doc-strings, and standalone doc/rule text) are never fileable. The orchestrator applies each directly — no dispatch, per Dispatch tiers — as its own isolated edit scoped to the comment/doc text, then re-runs pnpm check (cheap and turbo-cached — it catches an edit that was not actually prose-only). On a clean check, record them under Deferred fixed in-session; a clean-check prose-only edit touches only comment/doc text, so it cannot be implicated in any Critical/High/Medium regression and stays listed as fixed regardless of how the next bullet resolves. If the check fails, the edit strayed beyond prose text and is no longer prose-only — treat that breakage as a regression and handle it through the Critical/High/Medium bullet below, whose single corrective pass and verdict population then apply unchanged (the strayed change is routed to Open items if unrecovered, with no "fixed" label on a broken build). No further re-review cycle of its own is needed.
    • Run all other new Nice-to-Have findings through sub-step 2's two-outcome procedure. Everything not prose-only lands note-only — including a fix-now-shaped nit, which is not re-fixed here (this is a single confirmatory pass, not a new fix loop) — recorded for Deferred dropped and never rendered in sub-step 6's "Suggested defer as issue" group: the lane never files (sub-step 2), and surfacing a minor, non-fileable nit as a filing prompt is the exact regression that rule exists to prevent.
  • If either of this sub-step's re-reviews surfaces new Critical/High/Medium findings, first split them by provenance — per finding, not per batch:

    • Regression — the finding implicates this sub-step's fix delta: the deferred-fix applications (direct or dispatched), plus the corrective-pass delta when the finding comes from the confirmatory re-review. It describes behavior those fixes changed or broke. Test: the finding would have been FALSE against the code as it stood when Step 4 assigned its verdict, and became true only because of fixes this sub-step applied.
    • Pre-existing — the finding was already true when Step 4's verdict was assigned; the re-review merely noticed it while looking at adjacent code (reviewer scope-widening — common for test-coverage and error-path gaps). This sub-step's fix deltas neither created nor worsened it.
    • Ambiguity resolves to regression. Mis-calling a true regression "pre-existing" would ship a break; mis-calling a pre-existing gap "regression" merely reproduces the older, blocking behavior. When this sub-step's fix deltas cannot cleanly be exonerated, treat the finding as a regression.
  • Pre-existing C/H/M findings never downgrade the verdict and are never fixed in this pass. Fixing them re-arms the re-review cycle and breaks convergence — each fix invites a fresh re-review, which surfaces the next adjacent gap, unboundedly (the BF-448 pattern: a passed-clean run downgraded to terminated-with-open-items purely by its own polish lane). Terminating over them is equally wrong: the main loop already converged without them, so they are discovered work, not a failed review. Route each one directly into sub-step 6's "Suggested defer as issue" group as a severity-carrying deferred item — rendered with its severity in the tag slot (e.g. [MEDIUM]) and certified with specified per sub-step 6's label rule only when it clears the quality bar there — a filing below that bar files uncertified and waits for /spec, so /auto-eligibility follows the bar, not the severity. Do NOT run them through sub-step 2's decision procedure: severity alone makes them ticket-worthy (a real risk deliberately left unfixed this session has exactly one honest disposition — a tracked issue), a fix-now classification would re-enter the fix cycle, and note-only would silently drop a real risk. In auto mode they are filed by sub-step 6's suggested default like the rest of that group; interactively the user can still decline them at the prompt (declined items land in Deferred dropped, visibly).

  • If the mandatory re-review's split leaves any regression findings, make exactly one corrective pass: delegate to developer (sonnet-tier, per Dispatch tiers) to fix the regressions, then re-run pnpm check. Do not re-enter the Step 5 loop.

    • Upgrade-on-success exception. If the corrective pass produces a clean pnpm check AND the diff stays scoped to the regression area, an OPTIONAL single confirmatory quality-verifier re-review MAY be spawned (scoped to just the corrective-pass files). Compose its prompt inline — there is no template for it here — carrying the corrective-pass delta as an inline unified diff (Step 5 item 2's snapshot note, token corrective) and the Already tracked (do NOT re-flag, …) line the mandatory re-review template above carries, per Step 1's carry-forward rule, with its worsened-by-the-delta carve-out re-scoped to the corrective-pass delta ("unless the corrective-pass delta made one worse") rather than copied verbatim: this re-review runs after the corrective pass, not the deferred-fix delta the template above names, so a verbatim copy would leave an already-tracked item the corrective pass worsened uncovered by the carve-out — silently suppressed, and shipped under the restored passed-after-fixes verdict. The confirmatory re-review's findings route through the same lane bullets above — Nice-to-Have findings through the Nice-to-Have bullet, Critical/High/Medium findings through the provenance split (whose regression test already names the corrective-pass delta) — so every finding it returns reaches a disposition; none is discarded by the restore. If no finding lands as a regression, restore the verdict to passed-after-fixes rather than terminating with open items, and continue to sub-step 6 — routed-out pre-existing items and Nice-to-Have dispositions do not block the restore. This prevents the regression-cap path from forcing a downgrade on a genuinely successful recovery.
    • If the confirmatory re-review is skipped, or any of its findings lands as a regression under the split above (the single corrective pass is already spent — there is no second), or the corrective pnpm check still fails, or any of the original regressions remain unaddressed in the diff, terminate Step 6 immediately. On termination, populate the verdict block as follows:
    • Verdict: terminated-with-open-items (overriding the Step 4 verdict).
    • Deferred fixed in-session: deferred items whose fixes are not implicated in the regression. If causation cannot be cleanly attributed (the developer landed multiple fixes in one delegation and the re-review surfaced regressions from "this delta"), list none of them as fixed and route every auto-applied item to Open items — readers should not see "fixed" labels on changes that broke the application. This carve-out does not sweep in clean-check prose-only fixes from the independent edits above: having passed their pnpm check, they touch no code in the implicated delta, so they remain listed under Deferred fixed in-session. (A prose-only edit whose check failed is not one of these — per the Nice-to-Have bullet above it is itself treated as a regression and routed to Open items.)
    • Open items: the surviving Critical/High/Medium regressions, plus the implicated deferred-item fixes per the rule above, plus every not-yet-offered fileable item — sub-step 6's membership definition (its render rule) defines that set, and the whole set lands here regardless of which path routed each item into it (the user never reached sub-step 6, so fileable items are not silently dropped — they are surfaced for manual follow-up). note-only items are not swept here — see Deferred dropped below.
    • Deferred dropped: the note-only items, if any — from sub-step 2 or classified by the sub-step 5 re-reviews — their disposition was decided at classification and does not depend on reaching sub-step 6. (No items from sub-step 6's prompt exist here: sub-step 6 was skipped.)
    • Skip sub-step 6.

6. Offer Linear issues for unfixed items.

Entry gate — skip sub-step 6 entirely if no fileable items remain. Fileable items are exactly the members of the "Suggested defer as issue" group — membership is defined once, in the render rule below; do not re-derive it here. If none remain there is nothing to file — skip the prompt and emit the Output-section verdict block (the schema under ## Output, not the sub-step 6 template below). Skipping sub-step 6 does not exempt the auto-applied items from the verdict: they MUST still be listed in that block's Deferred fixed in-session: field, and any note-only items — from sub-step 2 or classified by the sub-step 5 re-reviews — MUST still be listed under Deferred dropped:. (This is the common case when every deferred item was fix-now or note-only: nothing is fileable and the run reaches Output with no prompt at all.)

Render the remaining unfixed items using the template below, then ask the question that follows. The render is REQUIRED regardless of prompt mechanism (markdown body, AskUserQuestion description, etc.) — do not collapse to a single sentence; assume the user is context-switching across parallel sessions and cannot scroll back to sub-step 3. Preserve original sub-step 3 numbering: auto-applied fix-now items are shown under "Auto-fixed in-session (for context)" with their original numbers (e.g., 1, 2), and the severity-carrying items take the next numbers in sequence. Membership definition — authoritative (BF-569). The "Suggested defer as issue" group's membership is defined HERE, once; every other site — sub-step 3's pre-check, the entry gate above, the auto-mode paragraph, the filing recipe's comments, and the Open-items rescue — points here rather than restating it, so a future fileable class is added here and nowhere else. Members:

  1. every severity-carrying deferred item — routed Critical/High/Medium findings; the "second input" note at the top of this step enumerates their sources — rendered with severity in the tag slot (e.g. [MEDIUM]).

Never members: Nice-to-Have findings of any origin — the lane never files (sub-step 2's keeper rule; sub-step 5's re-review routing) — prose-only findings (never fileable — sub-step 2 and sub-step 5), and auto-applied fix-now items (already fixed). Every member renders INSIDE the one group, in one continuous numbering run (if sub-step 3 numbered no items, number from 1) — because membership in that group is what makes the suggested reply (and auto mode's default) file them. There is never a second fileable group. Omit a sub-group header entirely if empty; do not print "(none)".

Every <...> token below is a substitution site — replace each with the resolved value before emitting; never emit the literal pipe-separated schema (<passed-clean | passed-after-fixes>) to the user. The verdict header line should read e.g. Quality review verdict: passed-after-fixes (cycles: 3). This is the canonical substitution rule other steps in this skill and /start point back to.

Quality review verdict: <one of: passed-clean | passed-after-fixes>  (cycles: N)
Deferred items still unfixed after sub-step 5:

Auto-fixed in-session (for context, not actionable here):
  1. [Finding] — [file:line] — [tag]
  2. [Finding] — [file:line] — [tag]

Suggested defer as issue (recommended to file):
  3. [Finding] — [file:line] — [tag] — [rationale]
  4. [Finding] — [file:line] — [tag] — [rationale]
  5. [Finding] — [file:line] — [tag] — [rationale]

Every actionable item MUST include: finding text (verbatim from reviewer, not paraphrased), file:line, tag, and rationale. If the reviewer emitted no file:line, render file:line: unknown rather than omitting the field. Then ask:

For which of the unfixed items should I create Linear issues? Reply with comma-separated numbers (e.g., 3, 4), suggested to file the "Suggested defer as issue" group, all, or none. Unfixed items you do not select — including numbers omitted from a comma-list — become Deferred dropped in the verdict block, not silently re-added to any other category.

Auto mode: do not ask — still render the grouped list (it is the audit trail), then proceed as if the user replied suggested. Filing the "Suggested defer as issue" group — its full membership per the membership definition above — keeps discovered work flowing back into Linear for later /auto iterations without over-filing. If the state-name fallback below would need to ask the user, file into the team's default state instead and note the deviation in the verdict block — in an unattended run a re-statable issue beats a dropped finding.

Reply semantics:

  • suggested → file every item in the "Suggested defer as issue" group.
  • all → identical to suggested; the safe default for a malformed reply (see "Malformed user replies").
  • none → file nothing; remaining items become Deferred dropped in the verdict block.
  • Numeric list → file the listed numbers verbatim, excluding any that reference the "Auto-fixed in-session" context group — those items are already fixed and sit outside the "Suggested defer as issue" group, so suggested skips them too; silently skip such numbers rather than filing a redundant issue.

Search before filing — every issue this skill files, here in sub-step 6 or anywhere mid-run, in interactive and auto mode alike. Before creating each item, probe for an existing open issue already covering the same defect: linear-cli search issues "<token>" using the finding's most distinctive identifiers — the file's basename when it is distinctive (rack_logger_spec), and the predicate or operation (has_shared_access?); search BOTH, since a near-duplicate that describes the mechanism differently will share only the file — plus the current run's already-tracked list (Step 1's carry-forward rule). One token per call — 2-3 separate calls, never one call carrying several words. search issues matches a contiguous phrase, so a query of two identifiers that are not adjacent in the target text matches nothing and exits 0, which is indistinguishable from a clean no-duplicate result (linear skill gotcha #14). A search worded as a description of the defect is not a weak search, it is a guaranteed empty one: BF-777 was filed Urgent behind three of them — resolve_list scopes injection, list_helper scopes, SQL injection anonymous — while BF-490, describing the same defect in the same file, sat open and is returned by the bare token rewhere. On a hit whose body describes the same root cause: do NOT create — append this finding's evidence as a comment on the existing issue (~/.claude/scripts/linear-post.sh comment), raise its priority or labels when the new evidence warrants it (a read-only defect gaining a write vector, say), and record it in the verdict block's Deferred filed as issues as BF-XX (existing — evidence appended); a deduped item counts as filed, not dropped. When the hit is adjacent but arguably distinct, file normally and add linear-cli relations add <new-ID> <existing-ID> -r related — a wrong dedup suppresses a real defect, while a redundant related-link costs nothing. When that adjacent hit is open against the same file this finding's file:line names, the new item is severity-carrying (filed specified), and the existing hit is itself specified (not needs decision, not solo) — matching standards/issue-spec.md's collision-edge guard — wire that one edge as linear-cli relations add <existing-ID> <new-ID> -r blocks instead of the related edge above; because a specified item is fleet-pickable the moment it files and a same-file open sibling would otherwise be picked concurrently (standards/issue-spec.md § Certification includes collision edges) — existing blocks new, the no-discernible-order default of standards/issue-spec.md's direction rule, matching the intra-batch step below. When the existing hit is uncertified, needs decision, or solo, keep the related edge instead — wiring blocks behind a blocker that will never ship unattended strands this issue invisibly. Same single edge either way: no extra search, no extra call. The one search per item is bought by what it prevents: BF-631 was filed during BF-617's review for a defect a sibling session had already specified as BF-618, and the duplicate was caught only at human triage and canceled by hand. Terminal hits are prior art, not noise: a Done/Canceled hit describing the same mechanism (judge state by NAME — gotcha #19) never blocks the filing, but the filed body MUST name it — Prior art: BF-XX (Done): <its fix's scope>; filing because <what still fails> — so the groomer can ask the only question that matters about shipped prior art: did its fix regress, or never cover this path? (fleet-retro 2026-08-13: two same-window filings re-measured Done BF-510's exact repro and neither named it).

A hit your filing SUPERSEDES — same gap, but the hit's premise has since dissolved (its deferral target shipped without covering it, the mechanism it describes is gone) — is consolidated, never left as a second open owner. File the successor carrying the fresh evidence, then mark the stale hit linear-cli relations add <OLD> <NEW> -r duplicate (linear gotcha #15: the FIRST argument becomes the Duplicate and auto-closes) with a one-line comment naming what dissolved — or, when the hit is certified and its spec still fits, append and refresh it instead of filing at all. What is never right is analyzing the hit inside the new body and leaving both open with no edge — BF-1189 did exactly that to BF-895, and two issues owned one gap until a keeper audit consolidated them.

A dedup inherits the hit's premise — re-derive it before appending or citing the issue from code. A Problem statement is a code reading taken at filing time, and a Backlog hit has had every sibling since to go stale; the staleness then propagates as sourced fact into the evidence you append and into any # BF-NNN tracks this comment left at the site. Grep the file for the code its Problem quotes, and if that code is gone, git log -S '<the quoted string>' -- <file> names the sibling that removed it. Then re-ask the root-cause question against current code: a sibling may have closed the hit outright, or narrowed it to a different defect, in which case the file-normally-plus-related default above applies. Where the match survives, append as usual and name the claim that no longer holds, so the groomer re-specs rather than implementing against a mechanism that is gone.

A surviving hit that is In Progress — or specified and fleet-pickable — can absorb this finding by its FIX, which the root-cause test above cannot see. That test reads the hit's body; absorption depends on its plan, and in a fleet the two come apart routinely because a sibling session may pick the hit up at any moment. Measured: BF-1052 was filed from BF-893's review against an unlocked Access.find_by while BF-1032 sat open and adjacent — specified, not yet started — and its body described a different defect (a duplicated access decision), so the dedup test correctly filed rather than appended. A sibling session picked BF-1032 up 27 minutes after the filing, shipped it 2h after, moved that resolution inside its transaction so the existing client_tenant.lock! spanned it, and absorbed BF-1052 whole; the duplicate was found and canceled by /fleet-retro ~14h after filing. So when a surviving hit is In Progress or specified, add one line to the filed body — Verify against <ID> once it ships: its fix may absorb this. — and record the item as BF-XX (may be absorbed by BF-YY) in Deferred filed as issues. This is additive to the edge rules above — the related-vs-blocks choice is unchanged. Do NOT hold the filing and do NOT fetch the sibling's plan to adjudicate: the finding is real when observed, the sibling may never land, and one line costs nothing where a silent duplicate costs a human triage.

Every filing this skill makes — the Step 6 batch AND any ad-hoc mid-run follow-up — goes through linear-create-child.sh (parent - for a standalone issue), never a raw issues create. A raw create without --state lands in the team's default state — Triage on a triage-enabled team — where it is invisible to /next and /auto permanently (measured 2026-08-15: three ad-hoc follow-up filings stranded this way in one fleet run); the helper resolves a Backlog-preferring state and verifies its writes. Filings land in Backlog by default — the human curates the release scope (keeper ruling 2026-08-15) — except where the recipe's state rule below routes a severity-carrying Critical/High (priority 1–2) or security-labeled item into Planned. The same ruling settles certification: a filing whose body meets standards/issue-spec.md's certification bar — problem mechanism verified in code, bounded blast radius, testable success criteria, the quality /spec would produce — MAY carry specified (the helper's label argument), making it fleet-eligible from Backlog via stage-first ranking; a filing below that bar files uncertified and waits for /spec. Certify on the bar, not on convenience — an uncertified filing costs a /spec interview later, but a falsely-certified one ships unattended.

File the chosen items sequentially — one at a time, in their rendered order, never as a parallel batch. Sequencing is what makes the stop-on-failure rule below meaningful: a mid-run create failure splits the chosen items cleanly into filed, failed, and never-attempted — each with a known disposition — instead of leaving an unordered batch in unknown states. For each item, create the issue and link its parent via linear-create-child.sh (it creates the issue, links the parent with relations parent, and verifies the linklinear-cli issues create has no --parent flag, and a bare --data parentId create sets the parent but never confirms it took, and a hand-rolled after-the-fact issues update is fragile: it can be skipped, silently fail, or orphan the new issue with no "Sub-issues" entry under the parent — see standards/linear-workflow.md "Spawned Issues Must Link to Their Parent"). Use mktemp -u for the body file so concurrent /quality-review runs in different sessions or worktrees do not race on a shared path. -u prints a unique name without creating the file — required, not cosmetic: plain mktemp creates an empty file, and the Write tool refuses any pre-existing file it has not Read (File has not been read yet), so the next step fails on every run. macOS BSD mktemp does not replace XXXXXX if a suffix follows it, so omit the extension on the template. Ensure tmp/ exists first:

mkdir -p tmp
# 1. Write description to a unique tmp file. The group's membership is the render rule's
#    membership definition above — every member is severity-carrying, so every body uses
#    the compact certifiable shape from standards/issue-spec.md — ## Problem (the finding
#    with its concrete scenario/evidence), ## Desired Outcome, ## Success Criteria
#    (checkboxes) — which is what licenses the `specified` label below under that
#    standard's trusted-pipeline carve-out.
#    Exception — a severity-carrying item whose remedy turns on an unresolved product or
#    design question: file it WITHOUT `specified`. Its Success Criteria would read "decide
#    X", which standards/issue-spec.md's quality bar forbids certifying — that standard's
#    "Filing before the decision exists" carve-out. Keep the severity priority (1-3), pass
#    only the class label(s) in the label arg below (or `-`), and say in the body what must
#    be decided. Attach `needs decision` AFTER the create, via
#    `~/.claude/scripts/linear-add-label.sh <new-id> 'needs decision'` — NOT in the label
#    arg: the helper trims each comma-separated label with `tr -d '[:space:]'`, so "needs
#    decision" becomes "needsdecision", which it then CREATES and attaches; next-candidates.sh
#    matches the name exactly, so the item ends up uncertified AND ungated. Skip EVERY
#    `blocks` edge for such an item — the parent->child edge below, the search-before-filing
#    edge above, and the same-file batch chain — wiring `related` instead: issue-spec.md's
#    collision-edge guard forbids `blocks` on either side of an issue that will not ship
#    unattended. Getting it wrong costs one wasted pickup, not a permanent loop: the item is
#    fleet-pickable, so an /auto session ranks and claims it and pays /start Step 0's worktree
#    create + `pnpm install` before the reason surfaces, then durably declines and applies
#    `needs decision` itself (skills/auto/SKILL.md), after which no session re-picks it.
body_file=$(mktemp -u tmp/deferred-XXXXXX)   # -u = name only; Write creates it. Plain mktemp pre-creates and Write then refuses.
# ...write body to "$body_file" via the Write tool...

# 2. Create the issue under the resolved parent (the helper links it + verifies).
#    State — never Triage: deferred items have a known design intent and a documented
#    location/rationale (sub-step 1's consolidated list), so filing into Triage would
#    queue them for re-evaluation that's already been done. Between the two workable
#    states, `Backlog` is the DEFAULT: Planned is the human-curated "work next" queue,
#    and machine filings crowd it out (2026-08-04: 96 of 140 Planned issues were
#    review-pipeline filings; 77 were bulk-demoted). File into `Planned` ONLY when the
#    item is severity-carrying at Critical/High (priority 1-2) or takes the `security`
#    class label below — release-relevant risks a human should see queued. On that
#    Planned path, prepend `--allow-planned` (leading flag): the helper refuses a
#    caller-passed Planned without it, and the flag is the audit record that the item
#    was asserted Critical/High-or-security at file time, so retros stop re-adjudicating
#    the exception by prose. Backlog
#    filings lose nothing: `specified` keeps them /auto-eligible, and /next simply
#    ranks them behind Planned — which is the intended ordering.
#    If no issue was resolved in Step 1, pass "-" as the parent (a top-level issue) —
#    do not invent a parent.
# 3. Label arg (comma-separated): `specified` for every item (all members are
#    severity-carrying; their spec-shaped bodies self-certify, making them /auto-eligible
#    immediately) — except the needs-decision exception above, which files without it.
#    ALSO classify severity-carrying items by failure mode and
#    append the class label(s) /next's ranking reads (security > bug > everything else).
#    The two are independent — apply each that fits, either, or, or both:
#      - `security` — unauthorized access/disclosure, viewer-scoping leak, PII exposure,
#        or a cross-boundary write (e.g. a preview reaching a production vendor) —
#        including latent ones a code change would make reachable
#      - `bug` — a behavioral defect of shipped code (wrong output, race, silent failure);
#        a live security defect is both (e.g. "specified,security,bug")
#      - neither — coverage gaps, hardening, docs: real work, but it must not jump the
#        queue disguised as a defect
# 4. Priority arg — REQUIRED: map the finding's severity to the Linear priority field
#    (Critical → 1, High → 2, Medium → 3). A severity rendered only in the title tag or
#    body prose is invisible
#    to /next's priority_rank — the field is what the ranking reads (BF-583: filings said
#    "Severity is High" while the issue sat at priority None).
# 5. Title: NO severity prefix — never "[MEDIUM] <title>". The severity tag belongs to
#    the sub-step 6 render (the prompt display), not the filed title: the priority field
#    now carries the grade, and a tag baked into the title duplicates it and goes stale
#    if the grade is later revised.
new_id=$(~/.claude/scripts/linear-create-child.sh [--allow-planned on the Planned path] <ISSUE-ID> <team> <Backlog|Planned per the state rule above> "<short title>" "$body_file" <specified|-> <1|2|3|->)
create_status=$?   # captured immediately, before any other command — the discriminator the
                   # filing-failure rules below branch on: 0 = filed and parent-linked; 2 = filed
                   # and linked, label not attached (keep the issue); anything else = create
                   # failed — stop filing (see below)

If the chosen state (Backlog or Planned) is rejected (the team uses different state names), follow this explicit fallback algorithm:

  1. Derive the team key from the issue ID prefix (e.g., PL-13 → team PL). Then probe: linear-cli statuses list -t PL.
  2. Pick the first state whose name matches /^(planned|backlog|to.?do)$/i (case-insensitive, exact match — NOT a prefix match). Deliberately exclude ready from this regex: a prefix match on ready would latch onto Ready For Release or Ready For Review on teams that have those states, silently filing new deferred issues into a release/review state.
  3. If none match, surface the available states to the user and ask which to use (Available: Backlog, In Review, Done … which is the "ready-to-work, not-yet-prioritized" state for this team?) rather than silently falling through to the team default — most teams default to Triage, which defeats the purpose of filing deferred items that are already triaged.

Do NOT silently fall through to the default.

After each creation, re-verify the parent link independently (linear-cli issues get "$new_id" -o json | jq -r '.parent.identifier' should print the parent's ID) before filing the next item — the helper verifies the link internally, but an orphaned deferred issue defeats the purpose of filing it, so the link is also confirmed from outside the helper.

A criterion citing this run's measurement must ship the apparatus, not just the number. When a filed body's Problem or Success Criteria cite a measurement this run produced — a repro rate ("8/8 under the barrier"), a baseline count, a remedy matrix — the runnable apparatus behind it (the repro script, schema setup, concurrent driver, invocation) must outlive the session: paste it verbatim into the filed body or a comment on the new issue, or commit it with the issue's changes when the repo has a home for such scripts. A prose summary is not a baseline — the next session re-pays the entire empirical setup before it can even confirm the problem still exists, which routinely costs more than the deferred work itself (BF-1175's criteria name BF-673's 8/8 repro as their baseline; the harness lived in that session's scratch and died with it).

A create_status of 2 means the issue was created and parent-linked but the requested specified label could not attach — NOT a failure: keep the issue, annotate it in the verdict block as PL-XX (specified label not attached — not /auto-eligible until labeled) so a human can fix it (~/.claude/scripts/linear-add-label.sh PL-XX specified), and continue filing.

A parent at Linear's sub-issue nesting cap is not a filing failure — do not stop, do not downgrade. A create_status of 3 (or 4) means the helper detected the cap — the parent already has 10 ancestors, so Linear deterministically rejects every new sub-issue, and this shape grows by construction: a review of issue N files sub-issues of N, so each generation sits one level deeper — and wired the linkage as a related peer edge itself. The issue exists, is labelled (on 4 the label also failed — apply the exit-2 annotation from the previous paragraph too), and is fully usable: keep filing the remaining items, skip the independent parent re-query for these (it will correctly print no parent, and the (sub-issues of <PARENT>) suffix does not apply to them), and annotate each in the verdict block as BF-XX (related, not sub-issue — parent at Linear's nesting cap).

Filing failure — three distinct modes. They differ in what is known to exist, so they carry different remediations and different verdict effects; under all three, every chosen item still lands in exactly one verdict field.

  • Create failedcreate_status is non-zero and not 2, 3, or 4, and stdout carried no issue id (the helper prints the id on every path where the issue was created, so an empty capture means the create itself failed). If create_status is 1 but stdout DID carry an id, the issue exists — its state or its linkage could not be established (stderr says which): record it under Deferred filed as issues annotated with the stderr's first line, continue filing, and do not downgrade. Otherwise the helper could not create the issue, so from this seat it may not exist in Linear at all: treat the item as unfiled, and stop filing immediately — never attempt the remaining items (their creates would run against the same failing API, widening the set whose existence is uncertain). This failure changes the verdict: downgrade the run to terminated-with-open-items — the same transition sub-step 5's regression cap uses — because the affected items have nowhere honest to go under a passing verdict: they are not filed, the reply selected them so they are not dropped, and Open items is read by /finish and /start only on a non-passing verdict, so routing them there without the downgrade would bury them. This holds in auto mode too: the failing verdict makes /finish auto abort and /auto surface the issue to a human, the intended path for a run whose filing could not complete. Populate the verdict block:
    • Deferred filed as issues: the items filed before the failure, by ID — a clean prefix of the rendered order — carrying the exit-2 and verification-failed annotations where those fired; the bare word none if the first create failed.
    • Open items: the failed item, marked create failed: <first line of helper stderr> — the issue may exist unlinked; check Linear, then re-file or link manually, plus every chosen-but-never-attempted item after it, each marked not attempted — filing stopped at the create failure above.
    • Every other field is unaffected: unchosen fileable items still sweep to Deferred dropped per the rule below, and the fix-now / note-only dispositions already recorded stay as they are.
  • Verification failed — the helper exited 0 (or 2), so the issue exists and the helper verified the link internally, but the independent re-query above errors or prints no/null parent. The finding IS tracked — nothing is lost — and what failed is the verification, not the creation: do NOT treat the item as unfiled and do NOT downgrade the verdict. Record it under Deferred filed as issues annotated PL-XX (parent link unverified — confirm in Linear or re-link: linear-cli relations parent PL-XX <ISSUE-ID>), and continue filing the remaining items — each later create verifies itself independently, so an earlier verification wobble is no reason to strand them.
  • Pre-flight refusal — the helper rejected the invocation itself, before creating anything: a body file that does not exist (ERROR: body file not found: <path>), the wrong argument count (usage: linear-create-child.sh …), or an out-of-range priority (ERROR: priority must be 0-4 (got '9')). Each exits 1 with no id — the same signature as Create failed above — but no issue was created: the body-file check runs before any linear-cli call at all, and the priority check after only a read-only statuses list probe. Nothing can exist unlinked, and the remaining creates are not running against a failing API. So do not treat the item as unfiled-and-unfilable, do not stop the batch, and do not downgrade the verdict: correct the invocation, re-run the same create, and continue filing normally. Discriminate on the stderr, not the exit code — those three messages name a local precondition, while ERROR: failed to create issue '<title>' and ERROR: issue created but no identifier returned name the create and stay in the Create-failed branch. Two boundaries: ERROR: '<cmd>' not found on PATH is a local precondition but not a bad invocation — the toolchain is broken and every remaining create would fail identically — so treat it as Create failed; and an existing-but-unreadable body file is not caught by the pre-flight at all ([ ! -f ] tests existence), so its failed stdin redirect surfaces as ERROR: failed to create issue, which is the correct conservative branch. An empty body file is likewise uncaught and does not fail — the create succeeds with a zero-byte description — so confirm the Write landed content before invoking. The commonest cause of the missing-body case is a relative body path after cwd drift: the detached-rspec launch recipe cds to apps/api and Bash cwd persists across tool calls, while the Write that created the body necessarily used an absolute path. Pass the helper that same absolute path. linear-file-improvement.sh and linear-post.sh carry the identical pre-flight and the identical body file not found string, so the same discrimination applies to every filing this skill does.

Wire same-file collision edges across the just-filed batch. After the last chosen item files successfully (and only when the batch created 2+ issues), group the new issues by the file paths their findings name (the file:line locations; a finding naming several files belongs to every group it names). For each group sharing a file, chain the issues in filed order — linear-cli relations add <earlier-ID> <later-ID> -r blocks, earlier blocks later, adjacent pairs only (A→B and B→C; never a redundant A→C). /next observes only blocks edges, so unwired same-file siblings are offered to parallel /auto sessions simultaneously and collide in their worktree merges (the BF-434 batch filed 10 unwired siblings, several overlapping in the same components — wired by hand afterward, BF-581). Best-effort: a failed relations add annotates the affected issue in the verdict block's Deferred filed as issues entry (BF-XX (collision edge to BF-YY not wired — add manually)) and never stops filing, re-opens the loop, or changes the verdict.

Wire the parent→child edge too. Items filed here are sub-issues of the issue under review, and on the /start Step 9 path that issue is In Progress — a non-terminal state, so an unwired child is fleet-pickable the moment it files, while the work it was discovered inside has not reached the source branch at all (/finish has not run yet). Nothing existing catches this: next-candidates.sh's spread_penalty fires only on a started sibling, and its parent_weight actively ranks a child of an In Progress parent best (weight 1) — which is how BF-867 was offered to a parallel session two minutes after filing, on a finding whose still-in-flight parent could change its premise. After a successful create, wire linear-cli relations add <ISSUE-ID> <new-ID> -r blocks (parent blocks child; /next observes only blocks edges). Scope it exactly like the search-before-filing edge above, honoring both halves of standards/issue-spec.md's guard: wire it only for severity-carrying items (the ones filed specified, hence fleet-pickable — an unlabeled deferred item /auto cannot pick gains nothing and only loses its place in a bare /next), and only while this run's verdict is still passing — a run already headed for terminated-with-open-items (Step 5's option 2 reaches this sub-step) ends with /auto abandoning the parent In Progress and stalled, which is precisely the blocker-that-never-ships that strands the child invisibly. Skip it when Step 1 resolved no issue (- parent, a top-level issue) or when the helper reported the nesting cap (create_status 3/4), where the issue is a related peer rather than a child. On a passing verdict the edge is self-clearing: /finish moves the parent to Ready For Release, which next-candidates.sh counts as terminal (case-insensitively), and the child unblocks minutes later. Best-effort, exactly like the same-file edges above: a failed relations add annotates the affected issue in the verdict block's Deferred filed as issues entry and never stops filing, re-opens the loop, or changes the verdict.

Every fileable item rendered at this prompt that the chosen reply did not select for filing goes to Deferred dropped — explicit declines, numbers simply omitted from a numeric list, and the whole rendered fileable set under none alike. These dropped items join any note-only items — from sub-step 2 or classified by the sub-step 5 re-reviews — record them all as one list for the verdict block. (Two classes route to Open items instead: items that never reached this prompt because Step 6 terminated early in sub-step 5 — see sub-step 5 — and chosen items a create failure left unfiled — see the filing-failure rules above.)

Output

When the skill returns to its caller (or to the user, when standalone), present a structured verdict block. The schema is:

Verdict: <one of: passed-clean | passed-after-fixes | terminated-with-open-items | escalated-to-architect>
Cycles: N (initial + N-1 re-reviews)
Findings resolved: [list — every severity tag carries an origin class, SEVERITY/origin; or the bare word none if passed-clean]
Deferred fixed in-session: [list of items applied in-session, including auto-applied fix-now items even when sub-step 6 was skipped; or the bare word none]
Deferred filed as issues: [PL-XX, PL-YY (sub-issues of <PARENT>), or the bare word none]
Collision edges: [required whenever the line above names an issue: each edge wired across the filed batch and to open hits (PL-XX blocks PL-YY; PL-XX related PL-ZZ), or the phrase none owed. A never-attempted wiring was indistinguishable from none-owed until this line existed — unwired same-mechanism siblings recurred on two consecutive fleets (BF-1226); the writer script WARNs when it is missing and fleet-metrics.py flags it at retro]
Deferred dropped: [items intentionally not filed — note-only items auto-classified too minor to track, plus every fileable item rendered at sub-step 6's prompt that the chosen reply did not select for filing; list, or the bare word none]
Open items: [list, or the bare word none — populated only on terminated-with-open-items or escalated-to-architect; includes any deferred items not handled above]

Free-form process notes go after the schema fields, never inside one. Two places in the worktree-isolation blockquote above — a read-only delegate's leftover path, and an expected-dirty path that vanished — direct a finding to "the Output block's process notes". That is not an eighth schema field: it is a trailing paragraph below Open items:. (The benign-disjoint contamination branch is routed differently — it logs, and in auto mode posts /start item 1's note comment to the issue.) Every consumer reads this block by anchored per-line match and ignores what follows it: quality-review-write-verdict.sh validates only the seven field lines, finish-read-verdict.sh extracts only Verdict: and Cycles:, and fleet-metrics.py's verdict regexes are all re.M-anchored. So a trailing note publishes at exit 0 (measured), persists in both verdict-file copies, and is reproduced verbatim by /start Step 10 item 4. It does not reach /finish's completion comment, whose Adversarial review section is composed from VERDICT, CYCLES, SUB_ISSUES, and — on a non-passing verdict only — Open items:. Never fold such a note into Open items: to give it a home: /finish Step 8 and /start Step 10 item 6 read that field as the run's unresolved work and refuse the ship or emit BLOCKED-ON-REVIEW off it, and the schema populates it only on terminated-with-open-items / escalated-to-architect in any case.

Every severity tag carries an origin class — written SEVERITY/origin (e.g. HIGH/plan), wherever a severity tag renders in this block: Findings resolved, severity-carrying deferred items, and Open items. Origin names the stage that would have prevented the finding; fleet-retro aggregates it across a fleet to decide where to tune (a plan-heavy fleet argues for a stronger or higher-model planning step, an impl-heavy one for developer model/effort, a spec-heavy one for /spec rigor). Pick exactly one:

  • plan — the posted plan was wrong or incomplete: a missed requirement or absorbed criterion, wrong scope, a missed sibling/adjacent issue, or a design choice the review had to reverse.
  • impl — the plan was right; the code diverged from it or carries a code-level defect (nil guard, race, wrong predicate, broken error path).
  • spec — the Linear issue's own description or success criteria were wrong or incomplete; no plan composed from that spec could have avoided the finding.
  • test — coverage-only gap: behavior is correct but unpinned.
  • latent — the finding adjudicated as pre-existing (Step 6 sub-step 5's provenance split, or Step 5's pre-existing deferred route): it was already true before this change and belongs to the codebase's latent pool, not to any stage of this session.

Substitute resolved values before rendering — never emit the schema verbatim. The Verdict: line MUST contain exactly one of the four enum values, with no | separators and no remaining angle-bracket placeholders. A concrete passing example:

Verdict: passed-after-fixes
Cycles: 3 (initial + 2 re-reviews)
Findings resolved: 2 (CRIT/impl: null-pointer in handler; HIGH/plan: race in retry loop)
Deferred fixed in-session: 1 (dead-code in spec_helper.rb)
Deferred filed as issues: PL-299, PL-300 (sub-issues of PL-190)
Deferred dropped: none
Open items: none

Compose Findings resolved from the run's own recorded text, never from recollection. Step 5's count-grounding note already forbids a remembered tally, but its remedy — recount the dispatch headers — yields counts, and counts are exactly what survives: this field is written at the end of a run whose findings were surfaced many cycles earlier, and an observed /loop /auto summarization preserved accurate per-cycle counts (cycle 1 → 1H/3M) while destroying every word of the finding text. Reconstructing that text from memory is how a verdict block acquires a finding that did not happen, and no consumer validates the field's contentquality-review-write-verdict.sh checks only that the line starts with a count or none (publish-then-warn, never refuses), finish-read-verdict.sh never reads the field at all, and fleet-metrics.py flags a missing field but nothing about a present one's truth. What a fabrication does reach: /start Step 10 item 4, which reproduces this block verbatim, both copies of the persisted verdict file, and fleet-metrics.py's findings-volume and origin aggregation — i.e. it corrupts the signal /fleet-retro tunes the fleet on. (It does not reach /finish's Linear completion comment, which carries only VERDICT, CYCLES, SUB_ISSUES and — on a non-passing verdict — Open items:.) Recover the wording instead of paraphrasing it: grep the session transcript for the fix dispatch's own Findings: block, which quotes every finding it closes, anchoring on the template literal — grep -oE 'Task for developer: Fix review findings.{0,1600}' <transcript> — or on the re-review's Previous findings addressed:. Do not grep a severity word: measured, the mixed-case alternation returns 82 hits from this skill's own text plus 16 from quality-reviewer.md, all quoted verbatim into the transcript when the skill loads, swamping any real finding; and a bare M[0-9] pattern matches base64 inside encrypted thinking blocks. That 1600-character window never reaches the findings in a worktree-isolated run — and every /auto run is one. /start Step 8 requires a READ-SCOPING block on every delegation and a WRITE-PLACEMENT block on every write-capable one, so both sit between the anchor and the finding text. Measured on BF-894's two cycle-1 fix dispatches: READ-SCOPING at +701 and +836, WRITE-PLACEMENT at +1192 and +1327, the ## Findings header only at +2104 and +2374, and the block running to Acceptance: at +13807 and +10812. A 1600 window expires mid-WRITE-PLACEMENT ("…do not adjudicate placement"), and a truncated match reads as a hit rather than a miss. Widen it — grep -oE 'Task for developer: Fix review findings.{0,14000}', which recovered both lanes' full finding text there. Widening is safe: grep is line-oriented and . excludes newline, so the window stops at the record boundary regardless. Do not bound it with [^"] instead — finding text quotes phrases, and the escaped \" truncates the match mid-finding (one MEDIUM came back at 73 characters). And do not anchor on a numbered finding tag (HIGH-1, MEDIUM-2): nothing mandates the numbering — the template above is - [Finding 1]: … — and BF-894's second lane wrote unnumbered **HIGH — …** headers, so that grep returned one lane's findings cleanly and zero of the other's, a partial recovery indistinguishable from a complete one. The anchor also matches this skill's own template prose; those copies are the ones with no READ-SCOPING block after them. A fix agent's own summary tally is not a census either — BF-894's ownership lane reported "Fixed all 7 findings (2 HIGH, 5 MEDIUM)" against a dispatch carrying 8, and had in fact fixed all 8; count the finding headers in the dispatch rather than reading the report's total. Use the transcript path printed in the compaction notice rather than picking a JSONL by recency — concurrent fleet sessions make a wrong-file match easy. Where a finding genuinely cannot be recovered, say so in the field rather than inventing a line for it. origin is a factual claim too, and the intuitive answer is often wrong: a defect in text the implementation wrote reads as impl until you check whether the issue's own description supplied it. One HIGH finding here named a read channel that never fires on the mutation path under review; linear-cli issues get <ISSUE-ID> -o json showed the description had named that same channel and never the correct one, so no plan composed from that spec could have avoided it — spec, not impl. One fetch settles it.

The persisted file (see "Persist the verdict" below) is parsed by finish-read-verdict.sh, which extracts the first whitespace-separated token after Verdict:. Writing the pipe-separated schema verbatim would silently produce Verdict=passed-clean downstream — the most permissive value — and bypass /finish Step 8's gate. The script applies that same first-token extraction to the Cycles: line, but there without the | guard and without the enum validation it applies to the verdict — so an unsubstituted Cycles: line yields a literal CYCLES=N downstream instead of being rejected. The file MUST contain resolved values only.

The (sub-issues of <PARENT>) suffix is required when issues are filed and a parent issue was resolved in Step 1 — it gives the user a one-glance audit that the parent link from sub-step 6 was set. Omit the suffix only when no parent issue was resolved (in which case the newly-filed issues are intentionally not sub-issues). The writer script WARNs whenever issue IDs render without the suffix — advisory, since it cannot tell the legitimate omission from a batch filed parentless in breach of the filing recipe (BF-1189 filed three that way, unlabeled too, which also kept the collision-edge mandate from ever engaging); on the WARN, confirm no parent was resolved or re-link with linear-cli relations parent <CHILD> <PARENT> and re-run the publish. When a severity-carrying item was filed but the helper exited 2, annotate that issue inline in the Deferred filed as issues: field: PL-XX (specified label not attached — not /auto-eligible until labeled). The same inline-annotation pattern carries sub-step 6's verification-failed marker: PL-XX (parent link unverified — confirm in Linear or re-link manually).

When delegated from /start, this block becomes the "Adversarial review" section of the completion summary verbatim.

Persist the verdict for /finish. After the block is composed, also write it to a file so a later /finish run (potentially in a different session or a different worktree of the same repo) can find it. Skip this step entirely if no issue ID was resolved in Step 1.

Do this in ONE call — pipe the resolved verdict block (every placeholder substituted; no |-separated schema lines) straight into the script on stdin. It publishes atomically to both the current worktree's tmp/ AND the main checkout's tmp/, which is what makes the handoff survive /finish merge deleting the worktree:

~/.claude/scripts/quality-review-write-verdict.sh <ISSUE-ID> - <<'VERDICT_EOF'
[the resolved verdict block, verbatim]
VERDICT_EOF

Quote the heredoc delimiter (<<'VERDICT_EOF', not <<VERDICT_EOF) so backticks and $ inside the verdict body are not expanded by the shell. The script rejects an empty body with exit 2, which is what an unquoted-and-mangled heredoc usually degrades to. It also validates the block's seven field lines against the same anchored regexes fleet-metrics.py parses with and exits 3 when any is missing or unparseable (an unsubstituted Cycles: N, a pipe-carrying Verdict: schema line, a field rendered as a heading instead of Field: value): the copies WERE still written, so do not treat 3 as a persistence failure — fix the block and re-run the same call, which overwrites idempotently.

Never stage the body with Write under the verdict's own basename — the scratch-path-guard PreToolUse hook denies any Write/Edit whose basename matches quality-review-verdict-*.md, in every directory. A staged file that is never published is invisible: it lives in the worktree, and /finish merge deletes the worktree with it. On the 2026-08-03 fleet this lost 4 of 12 shipped issues' verdicts, and BF-815 lost another after the isolation guard refused the main-checkout write and the worktree copy died with the worktree — the reviews had all run; only the audit trail died. The stdin form above is the route. When the isolation guard refuses the heredoc body itself (the token-shape check in standards/git.md § Worktree-isolated sessions), stage the body under a different basename (e.g. tmp/verdict-body-<issue>.md) and pass that path as the second argument:

~/.claude/scripts/quality-review-write-verdict.sh <ISSUE-ID> tmp/verdict-body-<issue>.md

In a /start wt worktree the harness blocks a direct write to the main checkout's tmp/Write to that path fails with "this session is now isolated in <worktree> … edit the worktree copy instead". That guard is correct and must not be worked around by editing the worktree copy and stopping there; this script is the sanctioned way across the boundary and is not subject to it.

The persisted file is the canonical /quality-review/finish handoff. The contents are the verdict block verbatim — downstream readers (/finish Step 1.5) parse the Verdict: line and the Open items: list.

Step 7: No per-issue reflection — it is batched at the fleet retro

Retired 2026-08-15: the per-issue /reflect tail cost 7–13 minutes plus two verification dispatches on every shipped issue (measured across the 2026-08-15 fleet), and its single-session vantage could not see the patterns that matter most — filing/linking/labeling quality and friction repeating across sessions. Scheduled reflection now runs once per fleet, at /fleet-retro's reflection step (/reflect fleet), where the whole run's evidence is on the table; interactive sessions invoke /reflect manually when friction warrants it. Do not invoke /reflect from this skill in any mode. This retirement also removes the post-reflect verdict-mtime refresh that lived here: with no reflect commit landing after the verdict is persisted, the self-inflicted staleness it corrected cannot occur (finish-read-verdict.sh's .claude/-only backstop remains for other post-verdict doc-commit shapes).

Error Handling

The termination verdict bodies below are shapes, not literals. Populate all seven Output-schema fields from what the run actually did, and write each Open items: diagnostic to describe the failure that actually occurred. Never assert something the run did not do, and never report none for something it did — including anything Step 6 had already applied, filed, or classified before this branch fired — and route anything Step 6 would otherwise have filed or surfaced, but will now never reach, into Open items. Cycles: is grounded in the quality-reviewer dispatch history exactly as Step 5's count-grounding note requires, and Verdict: is the literal shown.

  • pnpm check repeatedly fails after multiple developer delegations (Step 2 gate): surface to the user with the failing output. Do not proceed to review.

  • Sub-step 5 corrective pass leaves pnpm check failing or regressions unaddressed: the deferred-item fixes broke the application and the single corrective developer pass did not restore it. Set the run verdict to terminated-with-open-items, route per sub-step 5's verdict-population rules, and surface the failing output to the user along with the Open items list. Do not loop further or roll back automatically — let the user decide whether to revert, re-run /quality-review, or escalate to architect.

  • No changed files detected: warn the user and exit. Nothing to review.

  • Issue ID provided but linear-cli not authenticated: prompt linear-cli auth oauth, then continue without issue context if the user skips. Auto mode: do not prompt — continue without issue context immediately (the review still runs and the verdict still persists — the provided ID resolves locally without Linear auth, and persistence is local; only the requirements-conformance context is lost).

  • A reviewer dispatch is terminated mid-response by a transient API error (overload/529, rate limit/429, transient 5xx — the harness reports the response as incomplete). Evaluate this before the two branches below; it takes precedence over both. If the partial output shows real tool activity, the agent did substantial work a re-spawn would discard — this is infrastructure, not a format refusal and not an unavailable agent. Do not let the malformed branch's corrective re-spawn consume the attempt, and do not route it to the agent-unavailable branch on the strength of that branch's infrastructure error trigger. Instead: surface the partial output (as that branch's On detection step 1 requires, so the work isn't lost), then resume the same agent ONCE via SendMessage — restating the Required findings format, passing the Already tracked list per Step 1's carry-forward rule, and, when a sibling parallel reviewer has already reported, the ground it covered, so the resumed turn concentrates on what is still unreviewed. A well-formed resumed report continues the run normally and counts as a completed cycle for Cycles: (in wt mode, the standard post-delegation placement check applies to the resumed turn like any other delegation). If the resume returns nothing usable or is still malformed, the attempt is consumed — fall through to the branches below as written. This is the complement of the zero-tool-activity bullet, not a duplicate of it: no tool activity means the agent never started, tool activity plus a truncated response means it started and was cut off — same remedy, opposite evidence.

  • A reviewer dispatch returns malformed findings (quality-reviewer or quality-verifier — the two agents carry an identical findings-format contract) — the criteria are scoped to exactly five dispatches: Step 3's initial review, Step 5 item 4's re-review, Step 6 sub-step 5's mandatory and confirmatory re-reviews, and this branch's own corrective re-spawn (step 2 below). This enumeration is the authority — do not re-derive scope from whether a given prompt happens to spell the Required findings format out, since some in-scope dispatches inherit it from the agent's system prompt rather than restating it. Step 5 item 5's Targeted fix confirmation dispatch (wherever it is issued — the mechanical lane, the convergence drain, or the ceiling's lane drain) is exempt by name: its Acceptance asks for per-fix confirmation or a concrete failure scenario, which is not the categorized findings report these criteria police, so its response is judged against that Acceptance instead. The exemption keys on the dispatch shape, not on what the response happens to contain — an ordinary review that answers in confirmation shape is still malformed. For a dispatch in scope, a response is malformed if ANY of the following hold:

    • Missing the ## Review Findings heading.

    • Missing any of the five required subheadings (in order: ### Critical, ### High, ### Medium, ### Nice-to-Have, ### Approved). Match by line-prefix with a narrow tail: the heading word(s) must be followed by either end-of-line OR ( (single space + opening paren, for the optional parenthetical like (must fix before done) or (auto-fix lane)). Examples that match: ### Critical, ### Critical (must fix before done), ### Nice-to-Have (auto-fix lane). Examples that do NOT match (treated as malformed under criterion 4 below): ### Critical findings, ### Critical:, ### CRITICAL, ### Nice-to-Have / Out-of-Scope (the pre-rename heading — a stale agent emitting it must be re-spawned onto the current contract). Subheadings appearing out of order also count as malformed (downstream consumers scan positionally).

    • Contains a JSON array of findings ([{...}, {...}]) in ANY form — including as an appendix alongside the required headings, not only "instead of" them.

    • Uses non-standard headings ("Verification summary", "Categorization", "Final findings", or any other heading not in the Required findings format).

    • Contains free-form prose between or around the required sections (preamble before ## Review Findings, appendix after ### Approved, or commentary between subheadings).

      EXCEPTION — preamble-only: if the ONLY deviation is prose before ## Review Findings (the five-section block complete and in order, and no other malformed criterion firing), do not re-spawn. Instead scan the preamble for findings-like assertions absent from the block: if none, accept the block (discard the preamble from downstream parsing) and emit a one-line chat note recording the deviation; if any, treat the response as malformed and follow the standard On detection path below. On accept, surface the discarded preamble verbatim alongside the note (mirroring On detection step 1's surface-the-raw-output rule) so a scan false-negative remains recoverable by the user rather than silently lost.

    On detection:

    1. Surface the raw agent output to the user (so the work isn't lost).
    2. Re-spawn the agent ONCE — same agent type as the malformed dispatch — with a corrective prompt prepended: Your previous response did not follow the Required findings format from your system prompt. Your entire response MUST consist of ONLY the Required structure: ## Review Findings heading, then exactly five ### subheadings in the prescribed order (Critical, High, Medium, Nice-to-Have, Approved), each followed by bullet items or the literal "- None". Do NOT emit JSON arrays, JSON objects, or any other non-markdown structure. Do NOT emit tables, alternative section headings, instructional NOTE bullets, preamble, appendix, or prose between sections — markdown bullets only. Already tracked (do NOT re-flag, unless a fix delta made one worse — that is a new fix regression to report at its real severity): [ALREADY-TRACKED]. The [ALREADY-TRACKED] token is a substitution site, not a literal: fill it with the filed/routed issue context Step 1's carry-forward rule mandates, and drop that final sentence entirely when there is none.
    3. Route the re-spawn's outcome:
      • Re-spawn produced a well-formed response → continue normally with its findings.
      • Re-spawn still malformed → terminate /quality-review with the verdict body below.
      • Re-spawn failed to return at all (subagent crashed, infrastructure error, timeout) → route to the agent-unavailable branch below (use its verdict body).
      • Zero-tool-activity return (initial or corrective re-spawn) — if the response shows no tool activity and no findings block (harness boilerplate, a truncated fragment), the agent never actually ran: this is a startup glitch, not a format refusal, and the corrective format prompt does not address it. Before treating that attempt as consumed, resume the same agent ONCE via SendMessage (Continue the review you were spawned for; produce only the Required findings format from your system prompt). If the resumed agent produces a well-formed report, continue the run normally with its findings (in wt mode, the standard post-delegation placement check applies to the resumed turn like any other delegation). If the resume also returns without working, or malformed, the attempt is consumed — fall through to the routing above (corrective re-spawn if not yet used; otherwise the malformed-fallthrough verdict body).

    Verdict body for malformed-fallthrough (pipe it into quality-review-write-verdict.sh on stdin, populated per this section's opening rule). An example of the shape:

    Verdict: terminated-with-open-items
    Cycles: 2 (the cycle-2 re-review was malformed; one corrective re-spawn also malformed)
    Findings resolved: 3 (CRIT/impl: null-pointer in handler; HIGH/impl: race in retry loop; MED/test: unchecked cast)
    Deferred fixed in-session: none
    Deferred filed as issues: none
    Deferred dropped: none
    Open items: agent output malformed across two attempts; manual review required (see chat above for raw outputs)
    
  • Reviewer agent unavailable (the quality-reviewer or quality-verifier subagent type missing, infrastructure error, or re-spawn from the malformed branch failed to return): surface the failure to the user, then terminate with a body of the shape below, populated per this section's opening rule. Cycles: counts completed review cycles (Step 3's initial review plus each Step 5 item 4 re-review) only, and a dispatch that produced no review at all — unavailable, or a zero-tool return whose resume and corrective re-spawn also failed to produce one — is not a completed cycle, so an initial spawn that never returned leaves Cycles: 0.

    Verdict: terminated-with-open-items
    Cycles: 2 (initial + 1 re-review)
    Findings resolved: 1 (HIGH/impl: unclosed file handle on the error path)
    Deferred fixed in-session: none
    Deferred filed as issues: none
    Deferred dropped: none
    Open items: review could not complete; quality-verifier agent unavailable on the cycle-3 re-review (see chat above for failure details)
    
  • quality-review-write-verdict.sh itself fails (disk full, perms, mktemp/mv error, missing tmp/ parent unreachable): the persistence layer cannot record the verdict, so /finish Step 1.5 would see VERDICT=none-found and fall back to a generic no-artifact refusal — losing the open items the fallthrough verdict was meant to put in front of the gate. Exit 3 is not this case: the verdict IS persisted, just off-schema — correct the block per the stderr's named fields and re-run the same call instead of entering the recovery below. On detection (script exit code non-zero and not 3):

    1. Surface the script's stderr to the user immediately.
    2. Do NOT silently proceed. Print an explicit warning: WARNING: /quality-review verdict could not be persisted (see above). /finish for this issue will not be gated by this verdict. Either resolve the persistence failure and re-run /quality-review, or run /finish with explicit awareness that Step 8's gate cannot read the verdict you just produced.
    3. Continue with the in-memory verdict (still surface the verdict block to the user / caller) so /start Step 10 has something to render, but understand that /finish cannot enforce it. The user must decide whether to re-run or proceed manually.
Quality Review Skill | Agent Skills