two-lens-review (v1.2) — the review gate + the whole-codebase audit
ONE protocol, two SCOPES over the SAME factors:
- Diff scope — the repo's mandatory merge gate ("Don't merge a PR without
the two-lens review" — workspace
CLAUDE.md, "Things NOT to do"; PR #23 merged
unreviewed with a critical path-traversal). 2+ differentiated-lens agents on the
diff, disposition in the PR thread.
- Whole-codebase scope — the periodic / pre-release AUDIT. A diff review and
an audit scan DIFFERENT populations (fix-introduced-in-one-change vs existing
code + cross-PR accumulation), so the audit is a SEPARATE pass, not a bigger
PR review — but it runs the same factors, verify contract, and disposition.
The factors, the fill-in-the-slots lens briefs, the five hard requirements, the
escalation triggers, AND the whole-codebase fan-out orchestration are all
canonical in
.github/prompts/pr-review.prompt.md —
read it; fill from THAT file, never a paraphrase here (a copy here is the exact
two-copies-drift class Lens 2 hunts — when the prompt gains a factor or trigger, a
copy here silently lags). This skill owns only when to invoke each scope, how
to orchestrate, and the red-flag self-checks.
When to use
Diff scope:
- Before merging any PR (no exceptions — it's the gate, not a nicety; no size
exemption — lens count can shrink, the gate can't).
- User says "review this branch/PR", "two-lens review", "is this ready to merge".
- After a fix round, to re-review the new head before merge.
Whole-codebase scope:
- User says "whole-codebase review" / "audit the repo"; a pre-release or milestone
sweep; a periodic drift/design-debt pass.
- NOT the per-PR gate — that's the diff scope above.
Two agents MINIMUM (diff scope), lenses differentiated (a shared lens makes
their misses re-correlate); lens/finder count scales with blast radius (or tree
size). The quality lever is never the lens NAME — it's the change-specific
checklist filled into the <...> slots, and the FACTOR COVERAGE (no family
silently dropped).
Convergence contract (diff scope)
From the measured review history (derivation in this section's introducing
commit): under ~1500 lines of churn, PRs converge in 0–1 fix rounds; above
it, rounds 2+ are dominated by defects the PREVIOUS round's fixes introduced
and by reversals of already-settled calls — the loop generates its own work.
Hence:
- Churn budget — a diff whose ADDED + MODIFIED lines exceed ~1500 does
not enter review; split it first (stacked PRs). Pure deletions are exempt
from the count — a removed line ships no behavior for a lens to verify —
but only when the census rule below is satisfied. A change that both adds
and deletes at scale is two PRs.
- Two fix rounds, hard cap. Round 1: full review, all lenses, folded into
ONE commit. Round 2: verify the dispositions + review ONLY the delta since
round 1's head — no full re-sweep (full re-sweeps are where settled calls
get re-litigated). If round 2 confirms a HIGH in round 1's fixes, STOP:
revert the fold and re-land smaller, or re-scope the PR. There is no round
3 of patching patches.
- Round 2's fold is the last commit that may change behavior, and it is
verified, not re-reviewed. Every round-2 fix must be one of three shapes:
a revert, a deletion, or a change that ships a test FAILING without it.
Anything else is not a fix — revert the fold and re-land smaller. (Rounds
2+ are dominated by fix-introduced defects; this keeps the one unreviewed
commit from carrying one.)
- Blocking bar — only a HIGH (correctness / security / invariant)
CONFIRMED BY THE ORCHESTRATOR against the code — never by the finder's own
severity label — blocks merge. A MEDIUM this change INTRODUCED is fixed in
the fold or forces a re-scope; taste findings are optional by default —
drop them; a pre-existing find is SURFACED to the owner in one line. Nothing
spawns another round, and agents never file issues.
- No new gates in a fix round. A fix may not introduce a new bespoke
checker/lint/census — gate-shaped fixes routinely arrive fail-open and feed
the next round. Prefer making the failure IMPOSSIBLE (derive from the one
source of truth) over DETECTED (police two copies); a genuinely wanted new
check becomes its own small PR through the design gate. A check
asserts facts in its own layer — a Rust fact is checked from Rust, never a
Python regex over
.rs files.
- Deletion-shaped PRs enumerate first. Before deleting N members of a
class, the population census (full list + criterion) lands in the FIRST
commit or the PR body, before review starts — reviewers check the census
once instead of restoring survivors one per round (#943).
- The bot's
Findings: 0 is evidence, not the gate — it can be vacuous
(an errored run wearing a clean badge). The gate: every finding (local
lenses + both bots) dispositioned, and zero OPEN confirmed HIGH at the
final head.
Diff scope — how to run (orchestration)
- Isolate: the reviewed branch in a worktree (never the shared checkout —
two sessions on one tree race on HEAD). Note
path, branch, base sha.
- Dispatch both lenses in parallel, in the background, each a subagent with
its brief from
pr-review.prompt.md, <...> slots FILLED with this change's
specific claims (a lazily-filled slot turns both reviewers generic). Give each
the worktree path + git -C <path> diff <base>..HEAD. Then add an escalation
lens for EVERY trigger the prompt's "Escalation triggers" section
matches on this change — that trigger→lens list is canonical THERE; don't
restate it here (a copy would be the two-copies-drift class the header names —
a new trigger added to the prompt must reach reviews without a manual mirror).
- Collect + verify: first read each lens's ACTUAL return before counting it
toward the lens floor — a one-word summary or "test"/placeholder findings is a
STUB (a dispatch, not a review); re-run that lens as a single focused agent
(PR #455's a11y lens stubbed under an APPROVE-WITH-NITS aggregate; its re-run
caught a real AA failure). Then for every MEDIUM+ finding, verify the
premise yourself before coding a fix — reviewers have incomplete design
context; read the doc comment on the declaration the finding names first,
and if a finding is deliberate design, REFUTE it with the MECHANISM that
makes it so — a test, a compile-time constraint, a CI gate.
- Fold accepted findings into ONE review-round commit; record any
reviewer-flagged plan-misses as
plan-miss: lines in its message.
- Disposition sweep (shared, below).
- After a fix round, re-run the gates and watch the NEW head's CI; before
merging, read the online bot review's LATEST COMMENT verdict (
Findings: N)
mergeStateStatus — the review JOB passes even when it posts findings, so
the check table alone can't gate (#448). Judge the verdict against the
convergence contract above: dispositioned findings + zero open confirmed
HIGH, within the two-fix-round cap. If the bot ERRORED or left no
findings comment at HEAD (it can fail on a very large diff — error_max_turns
with no comment — or on a spent quota, which the workflow now states itself in
an <!-- absent-<marker>:<sha> --> comment; do NOT read that as a review),
the gate is unsatisfiable as written: split the PR smaller,
else fall back to one extra differentiated lens + owner merge, recorded in the
PR thread. State the condition behaviorally (errored/absent), never a fixed
LOC ceiling.
Whole-codebase scope — how to run (orchestration)
The full fan-out template (subsystem finders + whole-tree specialist sweeps →
adversarial verify → dedup → ranked report) is the "Whole-codebase scope —
orchestration" section of pr-review.prompt.md. In brief:
- Scout (main loop): map crates / LOC / churn / hot files → the work-list.
- Find: fan out subsystem finders (per crate/module cluster) + whole-tree
specialist sweeps (arch-invariants, concurrency/liveness, security, drift —
the aggregate-only lenses). Each finder carries the FULL factor checklist.
Prefer a
Workflow (pipeline per cell); degrade to parallel Agent fan-out.
- Verify each finding adversarially (default REFUTE; read the doc comment
on the declaration it names; construct a repro or refute) — a separate skeptic per finding, never the
finder self-certifying.
- Dedup + rank survivors; ship a report ranked by corrected severity,
grouped by factor family, KEEPING the refuted-as-deliberate list and the
MECHANISM each was refuted by — that is the coverage proof.
- Disposition sweep (shared, below); end with the repo-wide stale-phrase
grep == 0.
Scale to the ask: "any bugs?" → a few finders, single-vote verify; "thoroughly
audit / be comprehensive" → larger finder pool, multi-vote adversarial verify,
synthesis. Do the involved/cross-crate refactors it surfaces IN-ARC (design-debt
lens); anything bigger is SURFACED in the ranked report for the owner to pick.
Disposition sweep (both scopes)
Drive every reviewer/finder/bot finding to exactly one terminal state:
FIXED · REFUTED-with-trace (cite the MECHANISM that refutes it — a test, a
compile-time constraint, a CI gate; ADD one where none exists, never prose) ·
RE-SCOPED · SURFACED — the four states are defined ONCE in
pr-review.prompt.md's orchestrator notes; fill from there, never a
paraphrase here. Agents never file issues. "Acknowledged, no action" is NOT
a state — #40's ignored finding became a 0.4.1 blocker (#46). Diff scope: in the PR
thread. Whole-codebase scope: in the ranked report. Sweep at the FINAL merge
head — a finding that lands after the local lenses ran is the #283/#383 drop
class; and check WHICH commit a bot re-flag was raised against before
re-litigating (#316's were stale).
Red flags (you're about to skip the gate / short the audit)
| Thought |
Reality |
| "It's a tiny/doc-only PR" |
The gate has no size exemption; run it (lens count can shrink, the gate can't). |
| "CI is green, that's enough" |
CI can't see design, blast radius, drift, or a deliberate-looking real bug. |
| "The reviewer said X, so fix X" |
Verify the premise first — read the declaration's doc comment; a wrong fix contradicts a design decision. |
| "One thorough agent is fine" |
Two differentiated lenses is the floor; one lens's blind spots go uncaught. |
| "I'll note the finding and move on" |
Every finding needs a terminal state — dropped findings become release blockers. |
| "The diff looks clean, we're done" (audit) |
The diff scope can't see drift accumulation / design-debt accretion / arch erosion — those need the whole-codebase pass. |
| "The verdict row shows N lenses ran" |
Count REAL returns, not dispatches — a stubbed lens under a clean aggregate hid a real AA failure (#455). |
| "The bot says it's still broken" |
Check WHICH commit it reviewed — #316's re-flags were raised against an old commit; five were already fixed (REFUTED-STALE). |
| "The finder found it, report it" (audit) |
Findings self-certify nothing — a separate skeptic must try to REFUTE each survivor first. |
| "One more fix round will converge" |
Measured: rounds ≥2 mostly find defects the fixes introduced + re-litigate settled calls. Two fix rounds is the cap — revert or re-scope. |
| "This fix needs its own new checker" |
Gate-shaped fixes arrive fail-open and feed the next round. Derive from the source of truth, or file the checker as its own PR. |
| "Just unify the duplication" |
Some duplication is documented deliberate separation (per-source decoders, per-CLI targets); read the declaration's doc comment before proposing a merge. |
| "The fold is committed — the bots will catch the rest" |
Bots review the PR head, not the dispositions; a fold above ~100 behavioral lines is the one unreviewed commit — dispatch the round-2 delta verify the moment it lands, in parallel with the push. |
1---2name: two-lens-review3description: Run pixtuoid's review protocol at either scope — the mandatory pre-merge DIFF gate (2+ differentiated-lens agents on the diff) or a whole-codebase AUDIT (subsystem × factor fan-out over the whole tree). Both draw ONE shared factor taxonomy + verify contract + disposition; they differ only in population and orchestration. Use before merging ANY PR, on 'review this PR/branch' / 'is this ready to merge' (diff scope), or on 'whole-codebase review' / pre-release / periodic audit (whole-codebase scope). Encodes the convergence contract (churn budget, two-fix-round cap, HIGH-only blocking), the five hard requirements, the escalation triggers, the adversarial finder→verify fan-out, and the disposition sweep the repo learned the hard way.4---56# two-lens-review (v1.2) — the review gate + the whole-codebase audit78ONE protocol, two SCOPES over the SAME factors:910- **Diff scope** — the repo's **mandatory** merge gate ("Don't merge a PR without11 the two-lens review" — workspace `CLAUDE.md`, "Things NOT to do"; PR #23 merged12 unreviewed with a critical path-traversal). 2+ differentiated-lens agents on the13 diff, disposition in the PR thread.14- **Whole-codebase scope** — the periodic / pre-release AUDIT. A diff review and15 an audit scan DIFFERENT populations (fix-introduced-in-one-change vs existing16 code + cross-PR accumulation), so the audit is a SEPARATE pass, not a bigger17 PR review — but it runs the same factors, verify contract, and disposition.1819The factors, the fill-in-the-slots lens briefs, the five hard requirements, the20escalation triggers, AND the whole-codebase fan-out orchestration are all21canonical in22[`.github/prompts/pr-review.prompt.md`](../../../.github/prompts/pr-review.prompt.md) —23**read it; fill from THAT file, never a paraphrase here** (a copy here is the exact24two-copies-drift class Lens 2 hunts — when the prompt gains a factor or trigger, a25copy here silently lags). This skill owns only *when* to invoke each scope, *how*26to orchestrate, and the red-flag self-checks.2728## When to use2930**Diff scope:**31- Before merging any PR (no exceptions — it's the gate, not a nicety; no size32 exemption — lens count can shrink, the gate can't).33- User says "review this branch/PR", "two-lens review", "is this ready to merge".34- After a fix round, to re-review the new head before merge.3536**Whole-codebase scope:**37- User says "whole-codebase review" / "audit the repo"; a pre-release or milestone38 sweep; a periodic drift/design-debt pass.39- NOT the per-PR gate — that's the diff scope above.4041Two agents MINIMUM (diff scope), lenses **differentiated** (a shared lens makes42their misses re-correlate); lens/finder count scales with blast radius (or tree43size). The quality lever is never the lens NAME — it's the change-specific44checklist filled into the `<...>` slots, and the FACTOR COVERAGE (no family45silently dropped).4647## Convergence contract (diff scope)4849From the measured review history (derivation in this section's introducing50commit): under ~1500 lines of churn, PRs converge in 0–1 fix rounds; above51it, rounds 2+ are dominated by defects the PREVIOUS round's fixes introduced52and by reversals of already-settled calls — the loop generates its own work.53Hence:5455- **Churn budget** — a diff whose ADDED + MODIFIED lines exceed ~1500 does56 not enter review; split it first (stacked PRs). Pure deletions are exempt57 from the count — a removed line ships no behavior for a lens to verify —58 but only when the census rule below is satisfied. A change that both adds59 and deletes at scale is two PRs.60- **Two fix rounds, hard cap.** Round 1: full review, all lenses, folded into61 ONE commit. Round 2: verify the dispositions + review ONLY the delta since62 round 1's head — no full re-sweep (full re-sweeps are where settled calls63 get re-litigated). If round 2 confirms a HIGH in round 1's fixes, STOP:64 revert the fold and re-land smaller, or re-scope the PR. There is no round65 3 of patching patches.66- **Round 2's fold is the last commit that may change behavior, and it is67 verified, not re-reviewed.** Every round-2 fix must be one of three shapes:68 a revert, a deletion, or a change that ships a test FAILING without it.69 Anything else is not a fix — revert the fold and re-land smaller. (Rounds70 2+ are dominated by fix-introduced defects; this keeps the one unreviewed71 commit from carrying one.)72- **Blocking bar** — only a HIGH (correctness / security / invariant)73 CONFIRMED BY THE ORCHESTRATOR against the code — never by the finder's own74 severity label — blocks merge. A MEDIUM this change INTRODUCED is fixed in75 the fold or forces a re-scope; taste findings are optional by default —76 drop them; a pre-existing find is SURFACED to the owner in one line. Nothing77 spawns another round, and agents never file issues.78- **No new gates in a fix round.** A fix may not introduce a new bespoke79 checker/lint/census — gate-shaped fixes routinely arrive fail-open and feed80 the next round. Prefer making the failure IMPOSSIBLE (derive from the one81 source of truth) over DETECTED (police two copies); a genuinely wanted new82 check becomes its own small PR through the design gate. A check83 asserts facts in its own layer — a Rust fact is checked from Rust, never a84 Python regex over `.rs` files.85- **Deletion-shaped PRs enumerate first.** Before deleting N members of a86 class, the population census (full list + criterion) lands in the FIRST87 commit or the PR body, before review starts — reviewers check the census88 once instead of restoring survivors one per round (#943).89- **The bot's `Findings: 0` is evidence, not the gate** — it can be vacuous90 (an errored run wearing a clean badge). The gate: every finding (local91 lenses + both bots) dispositioned, and zero OPEN confirmed HIGH at the92 final head.9394## Diff scope — how to run (orchestration)95961. **Isolate**: the reviewed branch in a worktree (never the shared checkout —97 two sessions on one tree race on HEAD). Note `path`, `branch`, `base` sha.982. **Dispatch both lenses in parallel, in the background**, each a subagent with99 its brief from `pr-review.prompt.md`, `<...>` slots FILLED with this change's100 specific claims (a lazily-filled slot turns both reviewers generic). Give each101 the worktree path + `git -C <path> diff <base>..HEAD`. Then add an escalation102 lens for EVERY trigger the prompt's "Escalation triggers" section103 matches on this change — that trigger→lens list is canonical THERE; don't104 restate it here (a copy would be the two-copies-drift class the header names —105 a new trigger added to the prompt must reach reviews without a manual mirror).1063. **Collect + verify**: first read each lens's ACTUAL return before counting it107 toward the lens floor — a one-word summary or "test"/placeholder findings is a108 STUB (a dispatch, not a review); re-run that lens as a single focused agent109 (PR #455's a11y lens stubbed under an APPROVE-WITH-NITS aggregate; its re-run110 caught a real AA failure). Then for every MEDIUM+ finding, **verify the111 premise yourself before coding a fix** — reviewers have incomplete design112 context; read the doc comment on the declaration the finding names first,113 and if a finding is deliberate design, REFUTE it with the MECHANISM that114 makes it so — a test, a compile-time constraint, a CI gate.1154. **Fold** accepted findings into ONE review-round commit; record any116 reviewer-flagged plan-misses as `plan-miss:` lines in its message.1175. **Disposition sweep** (shared, below).1186. **After a fix round**, re-run the gates and watch the NEW head's CI; before119 merging, read the online bot review's LATEST COMMENT verdict (`Findings: N`)120 + `mergeStateStatus` — the review JOB passes even when it posts findings, so121 the check table alone can't gate (#448). Judge the verdict against the122 convergence contract above: dispositioned findings + zero open confirmed123 HIGH, within the two-fix-round cap. If the bot ERRORED or left no124 findings comment at HEAD (it can fail on a very large diff — `error_max_turns`125 with no comment — or on a spent quota, which the workflow now states itself in126 an `<!-- absent-<marker>:<sha> -->` comment; do NOT read that as a review),127 the gate is unsatisfiable as written: split the PR smaller,128 else fall back to one extra differentiated lens + owner merge, recorded in the129 PR thread. State the condition behaviorally (errored/absent), never a fixed130 LOC ceiling.131132## Whole-codebase scope — how to run (orchestration)133134The full fan-out template (subsystem finders + whole-tree specialist sweeps →135adversarial verify → dedup → ranked report) is the "Whole-codebase scope —136orchestration" section of `pr-review.prompt.md`. In brief:1371381. **Scout** (main loop): map crates / LOC / churn / hot files → the work-list.1392. **Find**: fan out subsystem finders (per crate/module cluster) + whole-tree140 specialist sweeps (arch-invariants, concurrency/liveness, security, drift —141 the aggregate-only lenses). Each finder carries the FULL factor checklist.142 Prefer a `Workflow` (pipeline per cell); degrade to parallel `Agent` fan-out.1433. **Verify** each finding adversarially (default REFUTE; read the doc comment144 on the declaration it names; construct a repro or refute) — a separate skeptic per finding, never the145 finder self-certifying.1464. **Dedup + rank** survivors; ship a report ranked by corrected severity,147 grouped by factor family, KEEPING the refuted-as-deliberate list and the148 MECHANISM each was refuted by — that is the coverage proof.1495. **Disposition sweep** (shared, below); end with the repo-wide stale-phrase150 `grep` == 0.151152Scale to the ask: "any bugs?" → a few finders, single-vote verify; "thoroughly153audit / be comprehensive" → larger finder pool, multi-vote adversarial verify,154synthesis. Do the involved/cross-crate refactors it surfaces IN-ARC (design-debt155lens); anything bigger is SURFACED in the ranked report for the owner to pick.156157## Disposition sweep (both scopes)158159Drive every reviewer/finder/bot finding to **exactly one terminal state**:160FIXED · REFUTED-with-trace (cite the MECHANISM that refutes it — a test, a161compile-time constraint, a CI gate; ADD one where none exists, never prose) ·162RE-SCOPED · SURFACED — the four states are defined ONCE in163`pr-review.prompt.md`'s orchestrator notes; fill from there, never a164paraphrase here. Agents never file issues. "Acknowledged, no action" is NOT165a state — #40's ignored finding became a 0.4.1 blocker (#46). Diff scope: in the PR166thread. Whole-codebase scope: in the ranked report. Sweep at the FINAL merge167head — a finding that lands after the local lenses ran is the #283/#383 drop168class; and check WHICH commit a bot re-flag was raised against before169re-litigating (#316's were stale).170171## Red flags (you're about to skip the gate / short the audit)172173| Thought | Reality |174|---------|---------|175| "It's a tiny/doc-only PR" | The gate has no size exemption; run it (lens count can shrink, the gate can't). |176| "CI is green, that's enough" | CI can't see design, blast radius, drift, or a deliberate-looking real bug. |177| "The reviewer said X, so fix X" | Verify the premise first — read the declaration's doc comment; a wrong fix contradicts a design decision. |178| "One thorough agent is fine" | Two differentiated lenses is the floor; one lens's blind spots go uncaught. |179| "I'll note the finding and move on" | Every finding needs a terminal state — dropped findings become release blockers. |180| "The diff looks clean, we're done" (audit) | The diff scope can't see drift accumulation / design-debt accretion / arch erosion — those need the whole-codebase pass. |181| "The verdict row shows N lenses ran" | Count REAL returns, not dispatches — a stubbed lens under a clean aggregate hid a real AA failure (#455). |182| "The bot says it's still broken" | Check WHICH commit it reviewed — #316's re-flags were raised against an old commit; five were already fixed (REFUTED-STALE). |183| "The finder found it, report it" (audit) | Findings self-certify nothing — a separate skeptic must try to REFUTE each survivor first. |184| "One more fix round will converge" | Measured: rounds ≥2 mostly find defects the fixes introduced + re-litigate settled calls. Two fix rounds is the cap — revert or re-scope. |185| "This fix needs its own new checker" | Gate-shaped fixes arrive fail-open and feed the next round. Derive from the source of truth, or file the checker as its own PR. |186| "Just unify the duplication" | Some duplication is documented deliberate separation (per-source decoders, per-CLI targets); read the declaration's doc comment before proposing a merge. |187| "The fold is committed — the bots will catch the rest" | Bots review the PR head, not the dispositions; a fold above ~100 behavioral lines is the one unreviewed commit — dispatch the round-2 delta verify the moment it lands, in parallel with the push. |