Status: RATIFIED, written 2026-08-04 for the Opus driver. WP-A landed with
this document, which is why the plan and its docs/README.md index line
arrive in the same commit as the sweep fix.
This document is written for an AI driver to execute work package by work
package. It plans; it does not code. All CLAUDE.md rules apply on top of
this plan, in particular: one feature at a time, prove before done, and
"verified" means ./verify.sh exited 0, run unpiped.
Definition of done for every work package:
- One WP per diff; no batching.
./verify.shexits 0, run unpiped.- The real flow is driven at runtime, not just typechecked.
- Every doc the WP makes stale is updated in the same diff.
- The WP row below is marked DONE with the commit hash, in the same session.
- Open decisions are asked, or the WP is scoped so the decision is not needed yet. Do not guess.
Standing rails that bind every WP here:
- Prompt changes go through the fake quality gate first, the real model after (T18's rule). A paid run is never the first test of anything.
- AI review stages keep read-only tools. The ultracode effort tier stays withheld at picker and API.
docs/DECISIONS.mdis append-only and dated.review/stays gitignored.- Real-engine spend only on the maintainer's explicit go, and only on
subscription auth (check
readAuthStatusfirst:usesSubscriptiontrue).
- All twelve build-plan work packages are DONE. M4 items U1-U12 are DONE; U13's runs are done and captured. Construction is finished; what remains is judgment, this plan's follow-ups, and one in-flight fix.
- The maintainer imported their real protocol as a project-tier ruleset
(51 rules, 70 directives, 23 sweep patterns, all 2,774 lines mapped,
id
01KZ6K8SAMAXGG0YQ2XCFH5HBFin the live DB). Two importer defects were found and fixed on the way in: CRLF documents parsed as zero rules (c4ca58b, D-63), and five numbered checklist headings collided with rule codes 1-5 (resolved by de-numbering the document; D-63). - The maintainer then drove the first real review of a real project:
feat/supplier-ordering-scheduleintomaster, 61 files, 114 hunks, modelclaude-fable-5[1m], effort high. It FAILED in S0 at $0.0000 because rule 29's sweep patternnew Date(is not a valid regular expression, and the pipeline refuses a partial sweep. That review is resumable and is the FG-3 / G2 attempt in progress. - The failure exposed two defects. First, sweep patterns written as literal
code fragments (which is how real protocol authors write them) kill the
whole review when they do not compile as regexes. Second, the pre-flight
API returns
sweepProblemsand the new-review screen never rendered it, so the warning the RUNBOOK points at was invisible.
The in-flight change (uncommitted, tree red). Three files are modified:
src/lib/review/sweep.ts:compile()now falls back to an escaped literal when the pattern is not a valid regex;SweepOutcomegainedranAsLiteral: { ruleCode, pattern }[];problemsremains for a pattern unrunnable even as literal (practically unreachable, kept for honesty).src/server/review/pipeline.ts: appends akind: "note"run note whenranAsLiteralis non-empty (guarded by!alreadySeededso a resume does not duplicate it). Needsnpx prettier --writebefore commit.src/app/api/reviews/preflight/route.ts:sweepProblemsentries now carry rule code and pattern; newsweepLiteralsfield lists patterns that will run as literal text.
./verify.sh fails on exactly the expected three things: prettier on
pipeline.ts, and two tests that assert the old refusal behaviour:
tests/lib/review/sweep-and-quote.test.ts ("reports an unusable pattern
instead of quietly running fewer checks") and
tests/server/review/pipeline.test.ts ("stops the run when a sweep pattern
could not be executed"). Rewriting those two tests to assert the new
behaviour is WP-A's work, not a nuisance to suppress.
| WP | Scope | Blocked on | Status |
|---|---|---|---|
| WP-A | Finish and land the sweep literal fallback | - | DONE 2026-08-04 |
| WP-B | Resume the maintainer's first real review; capture G2 evidence | WP-A | TODO |
| WP-C | Stop S4 re-raising what S3 already found (D-59) | WP-A | DONE 2026-08-04 (mechanical half; the prompt half waits on a real run's fold count) |
| WP-D | Normalise prose rule codes at the stage boundary | - | DONE 2026-08-04 |
| WP-E | Give the fixture a sweep hit, so the sweep-disposition path stops being vacuous | WP-C | DONE 2026-08-04 |
| WP-F | Small inbox items: demo scorer app/ prefix; clearHunk vs sweep-clear reason mismatch; cache-creation token line | - | DONE 2026-08-04 |
| WP-G | Records on gate verdicts (G1, G2, G3 rows; BUILD-PLAN milestones) | maintainer | GATE |
The code half is done (section 1); what remains is proof and records.
npx prettier --write src/server/review/pipeline.ts.- Rewrite
sweep-and-quote.test.ts's unusable-pattern test: a pattern likenew Date(over an added line containingconst t = new Date();now produces a hit at that line, appears inranAsLiteral, and leavesproblemsempty. Keep a companion assertion that a compilable pattern still runs as a regex (the fixture's ownconsole\.semantics must not change). Mutation-prove: revertingcompile()'s fallback branch must fail the new test by name. - Rewrite
pipeline.test.ts's stop-the-run test: the run now completes, the hit is recorded and dispositioned like any other, and the run notes contain the literal-pattern note naming29-style code and pattern. The note must appear once after a resume, not twice (the!alreadySeededguard is load-bearing; assert it). - New-review screen: render the pre-flight's
sweepLiteralsas a muted informational line and keep renderingsweepProblemsas the existing warning. ThePreflightinterface atsrc/app/reviews/new/page.tsx:58needs the new field. This closes the RUNBOOK's "fix those first" claim pointing at an invisible field. ./verify.shunpiped, exit 0.- Records in the same diff: DECISIONS entry D-64 (pattern semantics are
regex-first with literal fallback; refusal kept only for the unrunnable;
alternatives rejected: refusing outright kills real protocols over
punctuation, always-literal breaks the fixture's escaped patterns and
legitimate regex use). RUNBOOK's sweep-pattern sentence updated.
Commit this plan and its
docs/README.mdindex line in the same commit.
The failed review is resumable at zero cost so far. The maintainer presses Resume (their spend, their go; the standing authorisation covers a first real review on subscription). The driver's half:
- Watch the run. Root-cause any failure before any retry: an anomalous artifact is a defect until root-caused, and this is the first real protocol, first real repo, first 61-file change set through the pipeline.
- WP-C landed first, so each finding should now appear once. Read the run notes for the fold count: it says how many times the deletions stage repeated the adversarial stage, and it is the evidence that decides whether the S4 prompt still needs scoping.
- When the review completes and the maintainer decides findings and
exports: that is FG-3. Record the G2 row in
docs/GATES.mdwith dated evidence (counts, cost, wall time, what the maintainer said), status set only by their words, never by the driver.
Both clean fable runs produced 9 distinct findings as 18 candidates: S4 reads the deleted side of every modification as a deletion and re-reports it. Two mechanisms, in order:
- Mechanical, testable, first: dedup at candidate creation. A stage answer finding whose (path, overlapping line range) matches an existing candidate from an earlier stage of the same run is folded, not inserted; the fold is counted on the review (a run note: "S4 re-raised N finding(s) S3 already held; folded"). This is enforceable in app code and provable in the fake suite: extend the scripted runner so S4 returns a near-duplicate of an S3 finding and assert one candidate, one note. The fake gate is currently blind to this class (ideal answers never duplicate), so the test must be written to fail before the fix.
- Prompt scoping, second: tighten the S4 system prompt to deletions without a surviving replacement. This is a prompt change: fake gate first, then at most one real fixture run on the maintainer's go to confirm the duplication is gone (expect 9 candidates, not 18).
Decision needed before building (section 3, gate 2): fold silently versus keep-and-mark. Recommendation is written there. DECISIONS entry either way; D-59 moves from PROPOSED to DECIDED.
Fable writes ruleCode: "Rule 11" where the ruleset speaks "11"; the
stage schema accepts any string, so the finding's rule-text expansion on the
confirmation screen will not resolve. Normalise at the stage answer
boundary (strip a leading rule/Rule prefix, trim), validate the result
against the frozen ruleset's codes, and leave an unmatched code null rather
than invented. Unit tests on the normaliser; one pipeline test proving a
prose code from a stage answer reaches the finding row bare.
The seeded fixture produces zero sweep hits, so pendingSweepHits === 0
and the whole sweep-disposition path are vacuous on it, while the
maintainer's real protocol carries 23 sweep patterns. Plant one sweepable
pattern (the example protocol's table already names toFixed for rule 8;
an added line using toFixed in a defect file does it) and extend the
ideal answers' sweep dispositions accordingly. The quality gate's sweep
assertions become load-bearing for the first time. Watch the trap recorded
in U12-FIXTURE-GAPS.md: the suites stay byte-identical only while risk
tags are empty; a sweep hit does not disturb that, but verify the fake
service suites still pass unchanged before assuming.
Each is one small diff, in this order, each with its dated inbox line retired in the same commit:
scripts/demo-fixture.ts:264-area scoring: qualify by the defect'srepoinstead of hardcodingapp/, so a future shared-core defect cannot score MISSED forever.clearHunkdemands a reason while the sweep-clear guard does not: align them (require the reason) or record why they differ. Look before deciding; if requiring it breaks real stage answers, that is the answer.- Surface
reviews.usage_cache_creation_tokensin the run screen's usage panel (it accumulates already and is shown nowhere).
Deliberately left in the inbox: the rule-4 retag / rule 15 (touches the maintainer's protocol semantics; theirs to want), and the risk-tagged quality-gate variant (real work, low yield until a prompt change needs it).
When, and only when, the maintainer speaks:
- G1 (FG-2): their verdict per
plans/FG2-CHECKLIST.md. Record indocs/GATES.mdwith the evidence directory named. A "pass with prompt work" verdict naming the D-59 duplication feeds WP-C's real-run confirmation. - G2 (FG-3): from WP-B's evidence.
- G3 (FG-4): design acceptance over
review/2026-08-18-fg4/DESIGN-NOTES.mdplus the engine quality gate evidence and full e2e. On a pass, v1 is closed: updateGATES.md,PROJECT-STATE.md,BUILD-PLAN.mdmilestone lines, and the README's Status section in one records commit.
- G1 verdict (pass / pass with prompt work / fail). Recommendation: pass with prompt work, naming the S4 duplication (D-59) and the prose rule codes (WP-D) as the prompt work; both are already scoped above.
- D-59 fold behaviour. Options: (a) fold duplicates silently into the earlier candidate; (b) fold and note the count on the run (recommended: the fold is evidence about the prompts, and a silent fold hides a regression signal); (c) keep both and let the human dismiss one (rejected: it doubles the confirmation queue, which is the defect).
- G3 acceptance per FG-4. Nothing in this plan assumes it.
A structured review pass after WP-C lands (WP-A through WP-C are the behaviour-changing cluster), and another before G3's records commit. Findings numbered and fixed before new WPs start, per the charter.