Skip to content

Review triage economics: drop the finding quota, redraw patch vs defer, one ledger contract - #2805

Draft
pbean wants to merge 9 commits into
mainfrom
fix/build-review-economics
Draft

Review triage economics: drop the finding quota, redraw patch vs defer, one ledger contract#2805
pbean wants to merge 9 commits into
mainfrom
fix/build-review-economics

Conversation

@pbean

@pbean pbean commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Why

Every review layer in bmad-build, bmad-build-auto and bmad-code-review ran a Blind Hunter subagent whose prompt carried a floor: find at least ten issues, and never stop with an empty list. A reviewer that must produce ten items on a clean forty-line diff produces ten items. Triage then paid to verify each one, the spec's triage log recorded each one, and the ones triage could not disprove but did not want landed in the deferred-work ledger — where the next sweep read them back as work.

The floor came from #2675, which credited the question — look for what is missing, not only what is wrong — and got implemented as a count. This branch keeps the question and drops the count, then redraws the line between patch and defer so that what the change caused gets fixed in the run instead of filed.

What changed

Everything here is instruction text. The two step-04 review files stay in lockstep: shared sentences are byte-identical between bmad-build and bmad-build-auto, and only the human-present branches differ.

1 — The hunter stops counting (f8ee78e4, b8012952, 24bc95c4, 81229443)
All five copies of the Blind Hunter prompt lose the ten-finding floor and the never-stop-empty clause, and gain an evidence bar: file, line, the input or state that reaches it, the bad outcome. The what-is-missing second pass survives intact. Four copies are the review layers in bmad-build, bmad-build-auto and bmad-code-review; the fifth is references/lens-adversarial.md in bmad-review, which states the same bar in its own field shape — location, trigger_condition, potential_consequence rather than file-and-line — and drops "or improvement" from guard_snippet, the exact category the bar excludes. The bmad-review SKILL.md lens summary no longer advertises the old ten-finding contract.

2 — Triage verifies cheaper and routes wider (f8ee78e4, b8012952, 606f502d)

  • Carry forward: on a loopback, resumed review, or follow-up pass, a finding matching a logged row keeps that row's verdict and route when the code still reads as the row describes — written back as carried, never re-verified, never patched or deferred twice.
  • Verify from the evidence a finding cites; trace from scratch only when it cites nothing. Findings naming the same site and outcome get one verification and separate rows.
  • A finding with no demonstrated outcome is false. Triage does not build the claim on the reviewer's behalf.
  • patch becomes a locality test, not a size test — the blast radius is named explicitly (files in the diff, their tests, code a test in the diff exercises, direct callers of changed symbols, sibling sites of a pattern the diff fixed).
  • defer becomes a five-part positive test. All-maybe-false groups are rejected with what would settle them, not deferred. Out-of-scope routes on merits, never to defer.
  • Verification gaps are patches: a test that would still pass with the change reverted, or a touched site that skips the helper the change introduced. Step-03 carries the same bar as its definition of done.

3 — One ledger contract (24bc95c4)
New references/deferred-work-entry.md, byte-identical in bmad-build and bmad-code-review and pinned by test-template-sync, becomes the only way to write deferred-work.md. Read the whole ledger first; an open match on location and substance gets a seen-again: line instead of a new entry; a closed match gets a new entry naming the closed id. Entries are ### DW-<n> blocks; ids mint from the highest existing and nothing is renumbered. Five write sites point at it.

4 — The loop stops paying twice (2bafb3fb, 3933e297, 14215d54)

  • Re-engagement is defined: continue the step-03 implementer, addressed by the name or id its launch returned. A fresh launch is not re-engagement. The patcher runs only the tests covering the files it edits and returns; full verification belongs to step-04.
  • One ## Review Triage Log section per spec, ever — append to the one that exists; if two exist, merge into the earlier heading first, preserving every row.
  • Step-01 sets followup_pass on every re-dispatch of a done spec; a follow-up recommends another pass only when it patched a high, and must name the unverified risk. Patch volume is never grounds.
  • Step-04 rewrites the diff file after patching so step-05 reads it instead of rebuilding it.

Docs and tests move with the skills: build-a-change describes the ledger contract, dedupe and carry-forward; review-a-change describes the five-way verdict; test-template-sync generalises to a cross-skill vendored-copy check; the renderer test asserts the reference is published and referenced from every write site.

Verification

A/B on the hunter prompt — 20 context-free runs

Old and new prompts against two diffs: a real one, and the same diff with exactly one line changed (a proof-of-work ternary inverted — a defect that turns nine tests red). Five runs per cell, all claude-opus-5, model confirmed per transcript. Old prompt read from git show main:, new from the branch working tree.

Condition Runs Mean Plant found
Old · clean diff 21, 24, 20, 19, 16 20.0
New · clean diff 2, 0, 2, 4, 1 1.8
Old · planted defect 20, 19, 21, 22, 16 19.6 5 / 5
New · planted defect 1, 1, 1, 1, 1 1.0 5 / 5

The planted defect was found in all ten runs that saw it. Under the old prompt it arrived first in a list of 16–22 items; under the new prompt it arrived alone, and four of five runs proved it by running the suite (nine tests red as written, 333 green with the operands swapped). The new prompt's clean-diff findings were real defects, not noise — one was independently rediscovered and landed later on the downstream repo's main. One run returned an empty list and said it had rejected stale-prose near-findings. Contamination screen clean: no reviewer read the planting script's body or cross-compared the diffs.

Finding quality — from the sweep specs' own triage logs

19 review passes under the old skill, 12 under the new, across ten stories.

Measure Old New
Findings graded per pass ≈ 21 ≈ 19
Verified real per pass (high+med+low) 7.9 (150) 14.2 (171)
Real share of graded findings 38% 74%
Rejected per pass 13.2 5.0
high verdicts per pass 0.63 (12) 0.25 (3)
Entries routed defer 51 0

Volume barely moved; composition flipped. The new skill surfaces and fixes more verified-real defects per pass while triage spends far less on refutation. Deferrals per story fell from ~7 to 0.5, and the follow-up pass converges — recommended at most once per story, every one resolving to false.

Session cost

Measured from the loop journal's session-start / session-end pairs across 42 sessions:

  • Review session, median: 24m41s → 26m56s (n=15 then 7) — sessions did not get shorter.
  • Review session, median weighted tokens: 545k → 382k (−30%).
  • Second review pass per dispatch (review-2): 6 sessions → 0 — median 23m01s each, 2.3 h and 3.6 M weighted tokens the new era never spent.
  • Patch phase inside a review session: the fresh-subagent case that prompted 3933e297 cost 20 of that run's 38 review minutes; after the change, three sessions ran 3m55s, 5m12s and 9m21s, and 3 of 3 continued the live implementer rather than launching a patch agent.

The saving is a session that stops being scheduled and a third of the review session's tokens — not a shorter session.

Limits

  • The A/B is five runs per cell on one diff pair, one repo, one model, and tests recall against exactly one seeded defect. The old prompt's 16–24-item lists were never graded item by item, so a real defect the new prompt is silent about would not show up.
  • Field numbers come from one sweep run whose stories differ in size and subject; the eras are not matched work. Seven new-era review sessions can say no large duration saving appeared, not resolve a small change either way.
  • The high rate fell 12 → 3. The counts are too small to separate from the stories, and these commits also changed what high means (a vague "this is messy" with no named harm can no longer be graded at all), so part of that is definitional. When highs were present the new skill caught them — the rearm pass patched three verified high findings.
  • The quality tally counts what triage concluded, not what was true. A false verdict is a refutation by the same step these commits rewrote; nothing here audits those refutations independently.
  • The two eras log differently: the old format grades only surviving findings and writes rejects as prose, the new grades everything. Counting a dismissed bullet as the rejected finding it is makes the share comparable, but it is not a verdict-for-verdict match.

Testing

Full npm test green, including the generalised test-template-sync (both vendored copies byte-identical to their sources) and the renderer assertion that references/deferred-work-entry.md is published into the snapshot and referenced from every ledger write site.

pbean added 9 commits August 30, 2026 16:38
…eview triage

Blind Hunter: keep the what-is-missing second pass, drop the ten-finding
floor and the never-stop-empty clause; every finding must name file, line,
the input or state that reaches it, and the bad outcome. #2675 credited
the question, not the count.

step-04 Classify: carry forward logged verdicts on a loopback or follow-up
pass; verify from the evidence a finding cites; a finding with no
demonstrated outcome is false. Out-of-scope routes on merits, never to
defer. patch is a locality test, not a size test; bad_spec is for design
defects and doubt resolves to patch; defer is a five-part positive test and
maybe-false groups are rejected, not deferred. Deferred items are
deduplicated against the spec before append, summary is one clause of at
most 100 characters, location and severity are required.

Finalize: a follow-up pass recommends another only when it patched a high;
step-01 sets followup_pass when it starts one.

step-03: a test the change wrote must discriminate and a helper it
introduced must be adopted at every touched site before done.
Lockstep with the bmad-build-auto change: both Blind Hunter layers drop
the ten-finding floor and the never-stop-empty clause and demand file,
line, reaching input, and bad outcome per finding.

step-04 Classify carries forward logged verdicts on a loopback or resumed
review, verifies from the cited evidence, treats a finding with no
demonstrated outcome as false, routes out-of-scope findings on their
merits, makes patch a locality test and defer a five-part positive test,
and resolves bad_spec doubt to patch. With a human present: a patch that
crosses a scope line or changes an agent-context rule is confirmed once,
all together, before it is applied, and a declined one becomes a defer
with the human's reason; intent_gap asks before reverting working code.
The ledger write sites read the file before appending and skip an entry
naming the same location and substance.

step-oneshot gains the out-of-scope guard, the spec-edit rejection, the
locality patch test, and the five-part defer test. step-03 adds the
discriminating-test and helper-adoption done bar. step-04 rewrites
{diff_file} after patches so step-05 reads it instead of rebuilding the
diff.
…code review

Add references/deferred-work-entry.md — byte-identical in bmad-build and
bmad-code-review, pinned by test-template-sync — as the single way to
write the deferred-work ledger: read the whole file first; an open match
on location and substance gets a seen-again line instead of a new entry;
a closed match gets a new entry naming the closed id; entries are native
### DW-<n> blocks with origin, location, source_spec, severity, a
two-sentence reason, and status: open; ids are minted from the highest
existing and existing lines are never rewritten. The four bmad-build
write sites and bmad-code-review's present step point at it; the
"## Deferred from: code review" heading shape is gone.

bmad-code-review: the Blind Hunter drops its ten-finding floor; step-01
loads the spec's prior review record and open ledger entries as triage
input; step-03 carries forward verdicts for unchanged code, verifies from
cited evidence, treats undemonstrated findings as false, routes
out-of-scope findings on their merits, makes patch a locality test and
defer a five-part test, and keeps an ambiguous fix at decision_needed
even without a spec — the user is present. Story-file defer bullets carry
their DW id.

Docs: build-a-change describes the ledger contract, dedupe, and
carry-forward and no longer suggests one fresh run per entry;
review-a-change describes the five-way verdict instead of the retired
dismiss and assign-severity steps. Renderer test asserts the reference is
published and referenced from every write site.
…rior recommendation

A dry run of the new rules against a real ledger found four places the
text under-specified the route. At every lockstep site (build-auto and
build step-04, build oneshot, code-review step-03):

- the patch blast radius includes the code a test in the diff exercises,
  so a tests-only story can patch the code under test;
- the verification-gap sentence also fires for a test that would pass
  under the wrong behavior it exists to catch, not only under a revert
  (step-03's done bar says the same);
- a new record of a kind a format already carries is not a new external
  contract; a new field or file is;
- a direct correction outside the diff names its file in the triage row.

build-auto step-01 sets followup_pass only when the done spec's
followup_review_recommended is true; a plain re-review is a first pass.
Restore the plan's reading of followup_pass: step-01 sets it whenever a
done spec re-enters review, so any re-review runs the strict convergence
rule — another pass only when this one patched a high. The previous
commit gated it on the spec's followup_review_recommended, which let a
manual re-review count as a first pass and recommend again on two
patched mediums. followup_review_recommended itself is unchanged: it
defaults to false.
…er tests only what it edits

The first live run of the new step-04 (bmad-automator2, spec-dw-65-66-82) spent 20 of its 38
review minutes inside a fresh "Apply review patches" subagent: five minutes re-reading the tree
before its first edit, then ablations, linters and the full suite that the step re-ran anyway.
Tool wall time was under three minutes; the rest was generation. Step-04 asked for the step-03
implementer to be "re-engaged with its context intact" but never said what that meant, so the
reviewer launched a new subagent with a prompt that opened "You previously implemented".

Both step-04 files now say it: re-engagement is continuing the same subagent, addressed by the
name or id its launch returned; a fresh launch is not re-engagement and discards the context the
implementer holds; apply the patches yourself only when the implementer cannot be continued. The
patch message also tells the patcher to run only the tests covering the files it edits and then
return - the step owns the full verification. The shared sentence stays byte-identical between
bmad-build and bmad-build-auto; the enumerated human-present differences are untouched.
Observed live (bmad-automator2, spec-dw-79-83-85-rearm-transaction-window): the follow-up review
pass appended a fresh "## Review Triage Log" section at the end of the file instead of the one the
spec already carried between the Spec Change Log and Design Notes. Both sections sat outside the
intent contract, so nothing forbade writing to the first - the reviewer just did not look for it.
A split log weakens the carried-row comparison on later passes, which reads "the" triage log.

Both step-04 files now carry the same sentence at their triage-log write site: append to the
section the spec already has - never create a second; if two exist, merge them into the earlier
heading first, preserving every row. bmad-build hosts it on its one-row-per-finding bullet,
bmad-build-auto on its append-format item; the sentence itself is byte-identical.
This branch took the quota out of the Blind Hunter prompt in three ship skills, four instances
across their customize.toml files. references/lens-adversarial.md is a fifth copy of that same
prompt and kept both lines, so a review run through the adversarial lens was still told to find
at least ten issues and still read an empty list as a signal to keep hunting - the quota the rest
of the branch removed, surviving on the one path that reaches it from bmad-review rather than
from a ship skill. Upstream #2801 updated this lens directory alongside the other three copies of
the edge-case hunter, so the lenses already move in lockstep with the ship prompts by convention.

The lens now carries the same evidence bar, adapted rather than pasted: it addresses "the
provided content" instead of CONTENT, and asks each finding to name where in the content it
lives, the condition that triggers it, and the bad outcome that follows - its own location /
trigger_condition / potential_consequence fields, not the file-and-line phrasing the diff-only
copies use. The closing line is the shared one: an empty list is a valid result, and you do not
fill it by lowering the evidence bar.

guard_snippet was defined as "the concrete fix or improvement". Improvement is the exact category
the evidence bar excludes, so a lens that no longer reports improvements should not offer a field
that invites one; it now means the concrete fix. SKILL.md advertised the old contract in its lens
summary and now describes the current one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant