Skip to content

premerge: Phase 1 re-reviews the whole stack every attempt, so the loop re-discovers instead of burning down #796

Open

Description

@TheFJK

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:

  1. Attempt N raises a blocker in a.sh. Its fixer returns NO_CHANGE or
    REFUSED (a legitimate outcome the prompt explicitly allows).
  2. a.sh is therefore absent from fix-scope-<NN>.modified.
  3. Attempt N+1's review is scoped to that delta, so nobody looks at a.sh.
  4. The blocker is not re-raised → blockers_remaining=0 → assert-green returns
    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 plan so 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 what critic-loops calls
    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_CHANGE still leaves its blocker
    counted in blockers_remaining at the next attempt.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions