fix(execute): the merge lock refuses a review of an older commit (review-stale)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-23 11:06:14 +02:00
commit 050a77f513
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
4 changed files with 157 additions and 16 deletions

View file

@ -2265,12 +2265,16 @@ never typed by the operator, in this order (Cherny's):
next round's checks or for the merge.
4. **`verdict-gate.mjs`** — the review's verdict decides, as a command:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs --json "{project_dir}/review.md"
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs --json "{project_dir}/review.md" --repo .
```
Record `review = {round, path, gate_exit, verdict}`. Exit 0 (ALLOW/WARN) →
Phase 7.5. Exit 2 (could not decide, or a reviewer or engine that did not
run) → Phase 7.5 with the merge closed: never green. Exit 1 (BLOCK) → the
loop below.
`--repo .` checks that an ALLOW/WARN review is of the commit being merged:
its `scope_sha_end` is HEAD, or an ancestor of HEAD whose later commits
change only the review lessons and the `CLAUDE.md` pointer (step 3's
commit). Record `review = {round, path, gate_exit, verdict}`. Exit 0
(ALLOW/WARN) → Phase 7.5. Exit 2 (could not decide, a reviewer or engine
that did not run, or a reason that starts `review-stale`: a review of
another commit) → Phase 7.5 with the merge closed: never green. Exit 1
(BLOCK) → the loop below.
**A BLOCK is fixed by new plan steps, in a bounded loop.** The review writes
every BLOCKER as a numbered `### Step R<n>` under `## Remediation Steps`
@ -2583,7 +2587,7 @@ event, merge nothing, and their result is left as Phase 7.5 decided it
a summary:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs --json "{review_path}"
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs --json "{review_path}" --repo .
```
`{review_path}` is `review.path` from Phase 7.2. No review (Phase 7.2 did not
@ -2596,13 +2600,20 @@ exit 2 without the call.
| 1 | BLOCK | closed |
| 2 | not measured | closed: a review that could not decide is never green |
`--repo .` makes the gate check the review against the commit being merged.
Exit 2 with a reason that starts `review-stale` is closed with the decline
reason `review-stale` and the line "re-run /trekreview on this commit": the
review is of another commit than this one, or something other than the
review lessons and the `CLAUDE.md` pointer changed after it. Any other exit 2
is `review-not-measured`.
The merge runs only on exit 0 and a `completed` result. **Closed:** emit
`main-merge-declined` with the reason `review-block`, `review-not-measured` or
`run-not-completed`. Nothing is finalized: a `completed` result becomes
`main-merge-declined` with the reason `review-block`, `review-stale`,
`review-not-measured` or `run-not-completed`. Nothing is finalized: a `completed` result becomes
`partial` (`failed` and `stopped` stay), and the commits stay where they are
for review. Say in one line why it is closed and what opens it: the
BLOCKERs fixed through Phase 7.2's loop, a review that runs in full, or a run
that completes.
BLOCKERs fixed through Phase 7.2's loop, a review that runs in full, a review
of this commit, or a run that completes.
**2. Open: the merge, and the one question.** Find the run's branch and where
main is checked out, in a Bash call of its own:
@ -2706,7 +2717,7 @@ JSON
```
The reason is one of the literal words above (`review-block`,
`review-not-measured`, `run-not-completed`, `no-main-branch`, `hold-for-review`,
`review-stale`, `review-not-measured`, `run-not-completed`, `no-main-branch`, `hold-for-review`,
`merge-conflict`, `merge-refused`), never the operator's free text (free text breaks the JSON).
On decline, nothing is cleared: the merged-waves record is kept.
@ -2714,8 +2725,9 @@ On decline, nothing is cleared: the merged-waves record is kept.
**Recovery surface:** on the single-session path, if declined,
`--resume re-enters at the gate` — re-running
`/trekexecute --resume --project <dir>` skips the completed steps and returns
to the main-merge gate: the lock reads `review.md` again (a review fixed or
re-run in between counts), and when it opens, the guard asks again. On the
to the main-merge gate: the lock reads the review again (a review fixed or re-run
in between counts) and runs the same gate against this repo, so a review of
an older commit stays refused; when it opens, the guard asks again. On the
multi-session wave path,
re-run the same `/trekexecute` command instead, not `--resume` (Phase 2.5 runs
a `--resume` as a single session): the waves are already merged on the current