feat(trekexecute): every MAJOR answered before merge, fixed runs the fix loop

This commit is contained in:
Kjell Tore Guttormsen 2026-11-02 18:44:42 +01:00
commit 9e7e8d1516
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
15 changed files with 376 additions and 30 deletions

View file

@ -2286,11 +2286,16 @@ never typed by the operator, in this order (Cherny's):
`--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
commit), or only append the review answer records (K2 `review` records,
`/trekreview` Phase 8.8) to the top-level approval log. 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.
Exit 2 with a reason that starts `review-unanswered: fixed-pending` enters
the loop below, as a BLOCK does. The loop's R-steps are then the BLOCKER
steps plus each MAJOR answered "fixed". Any other `review-unanswered:` goes
to Phase 7.5 with the merge closed.
**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`
@ -2332,8 +2337,9 @@ the run is `stopped`, and the merge stays closed.
steps ({first error code}) and that the way on is a re-plan from the review,
`/trekplan --brief {project_dir}/review.md`.
4. The plan changed, so its approval is stale (Phase 1). Ask for it again in
one question, `AskUserQuestion`: **"The review blocked: {N} BLOCKER(s),
added as steps {list}. Approve the plan as it is now?"** Options:
one question, `AskUserQuestion`: **"The review asked for fixes: {N}
BLOCKER(s) and {M} MAJOR(s) answered "fixed", added as steps {list}.
Approve the plan as it is now?"** Options:
**Approve** / **Not yet**. Check the answer with
`approval-log.mjs answer` (stdin) exactly as `/trekplan` Phase 11 does;
exit 0 → `approval-log.mjs append --kind plan --subject "{plan_path}" --sha256-of "{plan_path}"`
@ -2353,8 +2359,10 @@ reviews per run (the first and two fix loops), read with
`node ${CLAUDE_PLUGIN_ROOT}/lib/review/review-rounds.mjs --cap`; `/trekreview`
reads the same constant and refuses a fourth round.
A review at the cap with BLOCKERs or MAJORs answered "fixed" still open goes to the operator: stop, and the report names every one.
Each loop also takes 1 from `iterations_remaining`, which
bounds it a second time (the completion gate's clause 3 counts it).
Each loop, WARN or BLOCK, takes 1 from `iterations_remaining`, which bounds
it a second time (the completion gate's clause 3 counts it), and the next
`/trekreview` is a new round counted against the cap, not a new remediation
round (decision 101).
Borrowed: the order (run the thing, simplify, review) is Cherny's; a verdict in
a file that a command gates on, and the bounded fix-and-review loop, are
@ -2383,7 +2391,8 @@ produces an independent truth based on objective state, not self-narrative.
number of steps with `status=completed` (excludes Step 0 if sandbox_preflight)
+ the run's own process commits, and only these: the simplify commit
(`refactor: simplify pass (trekexecute)`, when `simplify = "applied"`), each
review-lessons commit (`docs(voyage): review lessons (round R)`) and each
review-lessons commit (`docs(voyage): review lessons (round R)`), each
review-answers commit (`docs(voyage): review answers (round {R})`) and each
plan approval the review loop committed (`docs(voyage): approve plan …`),
all from Phase 7.2. Each is named in the audit, never folded in silently.
(A remediation step's commit is a completed step like any other; the
@ -2615,7 +2624,7 @@ exit 2 without the call.
| Exit | Verdict | Merge |
|------|---------|-------|
| 0 | ALLOW / WARN | open, when the run's result is `completed` (Phase 7.5) |
| 0 | ALLOW / WARN (every counting MAJOR answered) | open, when the run's result is `completed` (Phase 7.5) |
| 1 | BLOCK | closed |
| 2 | not measured | closed: a review that could not decide is never green |
@ -2623,19 +2632,23 @@ exit 2 without the call.
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`. Exit 2 with a reason that starts `review-not-measured:`
review lessons and the `CLAUDE.md` pointer changed after it. Exit 2 with a
reason that starts `review-unanswered:` is closed with the decline reason
`review-unanswered`: a MAJOR has no answer, or an answer "fixed" no later round
has confirmed. Any other exit 2 that does not start `review-stale` or
`review-unanswered:` is `review-not-measured`. Exit 2 with a reason that starts `review-not-measured:`
(the `--repo` path is missing, it is no git repository, or a git call failed)
is reported as "not measured", never green, and the merge stays locked with
the decline reason `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-stale`,
`review-not-measured` or `run-not-completed`. Nothing is finalized: a `completed` result becomes
`review-unanswered`, `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, a review
of this commit, or a run that completes.
of this commit, an answer per MAJOR (`/trekreview --answers --project {project_dir}`,
no new round) or a review round that finds it fixed, 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:
@ -2739,7 +2752,7 @@ JSON
```
The reason is one of the literal words above (`review-block`,
`review-stale`, `review-not-measured`, `run-not-completed`, `no-main-branch`, `hold-for-review`,
`review-stale`, `review-unanswered`, `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.

View file

@ -4,7 +4,7 @@ description: |
Independent post-hoc review of delivered code against the brief. Produces
review.md with severity-tagged findings (BLOCKER/MAJOR/MINOR/SUGGESTION)
per Handover 6 (review → plan).
argument-hint: "--project <dir> [--since <ref>] [--quick] [--validate] [--dry-run]"
argument-hint: "--project <dir> [--since <ref>] [--quick] [--validate] [--dry-run] [--answers]"
allowed-tools: Agent, Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion, Skill
---
@ -33,8 +33,9 @@ inference across files is forbidden in v1.0 (Judge Agent pattern).
Claude Code's own `/code-review` and `/security-review` are the engine
(Phase 4.7); Voyage's control against the brief runs after them and is the
part that stops (Phase 5–6); the verdict is written in `review.md` and one
command decides on it (`lib/review/verdict-gate.mjs`, Phase 8: 0 = ALLOW/WARN,
1 = BLOCK, 2 = could not decide, never green). No finding is struck for
command decides on it (`lib/review/verdict-gate.mjs`, Phase 8: 0 = ALLOW, or
WARN with every counting MAJOR answered, 1 = BLOCK, 2 = could not decide,
never green). No finding is struck for
length. A finding class seen in two reviews becomes one proposal for the
project's memory, approved line by line (Phase 8.7). Order credited to Boris
Cherny's public workflow (engine → brief control); the gate to Kiro and Spec
@ -69,6 +70,7 @@ FLAG_SCHEMA `trekreview` entry):
| `--validate` | boolean | Schema-only check on existing `{project_dir}/review.md`. No LLM calls. |
| `--dry-run` | boolean | Print the discovered scope and triage map. Skip writes. |
| `--fg` | boolean | No-op alias (foreground is default). |
| `--answers` | boolean | Run only Phase 8.8 (the operator's answers to the counting MAJORs) on the existing `{project_dir}/review.md`, then the Phase 8 gate line. No `prepare`, no reviewer, no write of findings: it opens no new round and counts nothing against the cap (decision 93). |
| `--workflow` | boolean | **(opt-in, NW2)** Run Phase 5–6 on the bake-off-validated Workflow substrate (`scripts/trekreview-armB.workflow.mjs`) instead of the default prose Agent-tool path. Requires **Claude Code 2.1.154+**. Combines with `--quick`. See *§ Phase 5–6 via the Workflow substrate*. |
Resolution:
@ -87,6 +89,7 @@ Resolution:
Set `mode`:
- `validate` if `--validate` is set (overrides everything else; skip to Phase 8.5).
- `answers` if `--answers` is set: skip to Phase 8.8.
- `dry-run` if `--dry-run` is set.
- `quick` if `--quick` is set.
- `default` otherwise.
@ -733,14 +736,17 @@ If validation fails:
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs "{review_path}"
```
Exit 0 = ALLOW or WARN; exit 1 = BLOCK; exit 2 = the gate could not decide
Exit 0 = ALLOW, or WARN with every counting MAJOR answered; exit 1 = BLOCK;
exit 2 = the gate could not decide
(missing verdict, a verdict the file contradicts — in its JSON block, its
`## Findings (BLOCKER)` section or its finding-id list — a JSON block that is
not the last fence or has the wrong shape, two verdict lines, unreadable
file) — **never green**. A WARN that exists because a reviewer or engine
did not run (`missing-reviewer:` / `unattributable-payload` in
`allow_blocked_by`) is also exit 2: a review not performed in full stops a
merge (operator decision 2026-10-06). It takes exactly one file. On exit 0
merge (operator decision 2026-10-06). An exit 2 with a reason that starts
`review-unanswered:` before Phase 8.8 is expected: the operator's answers come
there. It takes exactly one file. On exit 0
with WARN, `--json` also returns `warn_reasons` (the JSON block's
`allow_blocked_by`); print them. Capture the exit code as `gate_exit` and print it in Phase 9 exactly
as returned. This is the command that later stops a merge or a release on
@ -849,6 +855,67 @@ the managed block (Spec Kit `agent-context`), evidence per proposal and
schema-check before write (Kiro), review → memory and the size budget
(Cherny). Credits in `lib/review/recurrence.mjs`.
## Phase 8.8 — Answers
**Skipped in `dry-run` and `validate`.** Every counting MAJOR needs the
operator's answer before a WARN opens the merge or the release; the verdict
gate decides that, not this command. List the open ones:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/review/review-answers.mjs pending --review {project_dir}/review.md --json
```
- Exit 0: nothing to ask.
- Exit 2: say "answers not measured" in one line; the merge stays closed.
- Exit 1: it lists the open MAJORs (`id`, `title`, `detail`, `security`,
`fixed_pending`). Ask about each one with `AskUserQuestion`, at most four per
call and one finding per question, in plain words with no file or function
names. Each question gives the consequence of each option:
- **Fix it**: a plan step is written, and the merge stays closed until a
later review round no longer finds it.
- **Accept: <a reason in plain words>**: the merge can open with this
finding left as it is. Never offered for a finding `pending` marks
`security: true`. That finding gets one option, Fix it, and its question says a security finding cannot be accepted.
The recommendation is the session's honest judgement per finding. It is
never "accept" by default. The operator may type their own reason; a hollow
answer ("sounds good", "do as you like") is not a reason.
For each answer, a "fixed" first gets its `### Step R<n>` under
`## Remediation Steps`, in the form Phase 7 writes for a BLOCKER (Files,
Source finding, Test first, Verify, On failure). Then record it, with the
reason on stdin:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/review/review-answers.mjs record --review "{project_dir}/review.md" --finding {id} --answer {accepted|fixed} --repo . --round {R} --store {artifact_store} --trailer "Co-Authored-By: Claude {model} <noreply@anthropic.com>"
```
`{R}` is this review's round (Phase 3's `{round}`), `{artifact_store}` the
brief's `artifact_store` (absent = `local`), and `{model}` this session's model
name. It appends a K2 `review` record, writes the `## Answers` row and commits
the log as `docs(voyage): review answers (round {R})`; show the operator the
stored reason it returns. On `"committed": false`, print its reason in one
line and continue: the answer is logged but not committed. On exit 1 (a
hollow or empty reason, a "fixed" without its R-step, an accept on a security
finding), say the refusal in one line and ask that finding again once. A
second refusal leaves it unanswered. On exit 2, say "answer not recorded" with
its reason in one line.
Then run the Phase 8 gate line again and report its result as `gate_exit`.
Run on its own, outside `/trekexecute`, "fixed" leaves the finding open until a
later `/trekreview` round marks it resolved.
**`--answers` mode.** `/trekreview --answers --project <dir>` runs only this
phase on the existing `{project_dir}/review.md`, and then the Phase 8 gate
line. It runs no `prepare`, no reviewer and no write of findings, so it opens
no new round and counts nothing against the cap (decision 93: accepting needs
no new review round). When `{project_dir}/review.md` is missing it stops with
one line: "no review to answer: {project_dir}/review.md is missing". In this
mode `--round` is the review's own `round` field (1 when absent), and
`--store` is the brief's `artifact_store` (absent = `local`), read from the
brief as Phase 1 finds it.
## Phase 9 — Present summary
After the write succeeds, print:
@ -864,7 +931,7 @@ After the write succeeds, print:
**Annotation HTML:** file://{$ANNOT_HTML}
**Scope:** {before_sha}..{after_sha} ({reviewed_files_count} files)
**Verdict:** {BLOCK | WARN | ALLOW}
**Gate:** verdict-gate exit {gate_exit} ({0 = ALLOW/WARN | 1 = BLOCK | 2 = could not decide, or a control did not run — not green})
**Gate:** verdict-gate exit {gate_exit} ({0 = ALLOW, or WARN with every counting MAJOR answered | 1 = BLOCK | 2 = could not decide, a control did not run, or a MAJOR without an answer or answered "fixed" and not yet confirmed — not green})
**Engine:** /code-review {delivered N | not run: reason} · /security-review {delivered N | not run: reason}
### Counts