Repository navigation
premerge: Phase 1 re-reviews the whole stack every attempt, so the loop re-discovers instead of burning down #796
Description
The defect
Phase 1 dispatches Skill("code-review", "<PREMERGE_LEVEL> <PREMERGE_PR>") — the
whole stack PR — on every attempt. So attempt N+1 is a fresh discovery pass
over the entire artifact, not a burn-down of attempt N's work list.
That is the finding pump, and this repo has now measured it three times:
| Stack | Blockers per attempt | Stop |
|---|---|---|
| PR #731 | 10 → 10 → 6 → 8 | circuit breaker |
| PR #768 (#785) | 17 → 11 → 6 → 7 | STOP_EXHAUSTED, every round cleared 100% of its list |
| RFC 0021 A3, on itself | 8 → 6, growth ratio 100% | stopped by hand |
GROWTH= / STOP_SELF_REFERENTIAL (#754) detects this after the fact. It does
not prevent it. The structural fix is to stop re-asking "what else is wrong?".
Why this is not a one-line change
Blockers are not carried across attempts. plan --carry-prior carries
suggestions only (lib/premerge-findings.py:2247 →
_carry_prior_suggestions); each attempt's blocker set is rebuilt entirely from
that attempt's fresh reviewer output. So a naive scope — review only the previous
repair's delta — fails open:
- Attempt N raises a blocker in
a.sh. Its fixer returnsNO_CHANGEor
REFUSED(a legitimate outcome the prompt explicitly allows). a.shis therefore absent fromfix-scope-<NN>.modified.- Attempt N+1's review is scoped to that delta, so nobody looks at
a.sh. - The blocker is not re-raised →
blockers_remaining=0→assert-greenreturns
green with a live blocker in the stack, and Phase 4 then polishes on top.
Gating green on a blocker nobody withdrew is strictly worse than the pump.
Two prerequisites, either of which closes it
- (a) Carry unresolved blockers. Extend
planso a blocker not dispositioned
by its fixer survives into the next attempt's evidence the way suggestions do.
Then the review scope only has to cover new surface, and the carried set keeps
the gate honest. This is the cleaner shape and it is whatcritic-loopscalls
burn-down. - (b) Scope to the ASSIGNED set, not the MODIFIED set.
fix-waves-<NN>.json
names every file that carried a blocker last attempt, whether its fixer touched
it or not — so an unfixed blocker's file stays in the window. Cheaper, but it
needs a multi-path target, and multi-path is not in the reviewer's blessed
target set: the prompt's Phase 0 enumerates "a PR number, branch name, or file
path" (singular). The parser does join all tokens into one target string, so it
would probably work — "probably" is not a basis for a gate that fails open.
Acceptance predicate (write it before the loop, per critic-loops)
Not "the reviewer went quiet". Concretely:
- every blocker fingerprint raised at attempt N is, at attempt N+1, in exactly one
of{fixed, refuted-with-evidence, carried}— executed, not asserted; - a corpus replay of the chore(stack): land #762 #763 #764 #765 #766 (v0.56.0) #768 attempt sequence gates green or stops on a named
disposition, never on a resampled finding; - a mutation that makes a fixer return
NO_CHANGEstill leaves its blocker
counted inblockers_remainingat the next attempt.
Related
- premerge: 4c's verify review targets the whole stack, so a resampled blocker reverts a correct simplify commit #795 — the same class in
### 4c, fixed: the verify review is now scoped to the
simplify commit's range. The 4c fix was safe to ship alone because 4c is a
regression check on one commit, not a discovery pass, so it has no carry problem. - premerge: Phase 4 is gated on an unreachable predicate, so the simplify pass never runs on any stack that needs it #785 — Phase 4 unreachable, same root cause, different downstream cost.
- premerge: no bound on its own output — the issue budget is per-dispatch inside an N-round loop, and the loop reviews its own machinery #718 — the output side of the same pump.
- premerge: converge should compute the growth ratio and stop on it — the loop cannot tell self-generated work from real work #754 / RFC 0021 A3 — the growth-ratio detector.
Activity