feat(trekexecute): couple the executor to the review gate, the plan approval and the step checks

Wave 2, order 6. /trekexecute now:
- refuses a plan whose logged approval does not cover its current sha256
  (K2, approval-log verify --kind plan), before step 1;
- runs the step check (K9) and the step's optional Regression: (K10, new
  lib/verification/regression-check.mjs through the criteria runner) at step
  end, before the checkpoint; exit 2 is "not measured", never green;
- runs the architecture rules (K5) and the plan-file sweep (K6) after the last
  step; a violation or a file outside the plan turns completed into partial;
- reviews by itself in the closing run (Phase 7.2): run the thing, /simplify,
  /trekreview, verdict-gate; a BLOCK adds the review's remediation steps after
  the plan validator accepts them and the operator re-approves the plan, capped
  at TREKEXECUTE_MAX_REVIEW_ROUNDS (3);
- makes the main-merge gate a lock on the verdict: BLOCK or undecided keeps
  the merge closed; when open, the executor merges and the guard's question is
  the one question (operator decision 2026-10-08). The --gates pause before
  the wave merge-back is gone for the same reason.

The guard refuses a merge into main and a push in the permission mode that
shows no prompts, since no one can be asked (operator decision 2026-10-09; two
mode-read exemptions approved by the operator).

progress.json gains optional coupling fields (Handover 5); the validator
rejects the runner's exitCode in plan_verification.failed_criteria (C15).
Pointer lines name the skills test-first and debugging (S2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-09 19:22:00 +02:00
commit 920fbd3c47
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
15 changed files with 1310 additions and 64 deletions

View file

@ -2,7 +2,7 @@
name: trekexecute
description: Disciplined plan executor — single-session, or multi-session through session orders the user starts, with failure recovery
argument-hint: "[--project <dir>] [--fg | --resume | --dry-run | --validate | --step N | --session N] [plan.md]"
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent, Skill
disallowed-tools: TeamCreate
---
@ -16,8 +16,14 @@ Each plan step is built by **one fresh helper** (`voyage:step-implementer`)
and judged by **one controller** that reads the step's diff. The executor
itself writes no product code: it records the start point, launches the
helper, runs the mechanical checks (proof of red, the floor guard, the
manifest, the done line), and decides from exit codes and the controller's
verdicts (Phase 6).
manifest, the step check, the regression, the done line), and decides from
exit codes and the controller's verdicts (Phase 6).
It starts only on a plan the operator approved as it is now (Phase 1, K2), and
it ends with a review it starts by itself (Phase 7.2): `/simplify`, then
`/trekreview`, then the verdict gate (K7). A BLOCK keeps the merge into main
closed (Phase 8). The guard asks the one question before a merge; the executor
never asks it again (operator decision 2026-10-08).
Voyage never starts a Claude Code session. Work that needs another session
becomes an order plus a starter line the user gives a session they open
@ -50,14 +56,17 @@ Parse `$ARGUMENTS` for mode flags:
Set **mode = step**, **target-step = N**.
7. If arguments contain `--session N` (N is a positive integer): extract N and the file path.
Set **mode = session**, **target-session = N**.
8. If arguments contain `--gates`: set `gates_mode = true`. Pause for
operator confirmation after each wave's worktrees finish but before
merge-back, AND before the main-merge gate. (The MAIN_MERGE_GATE in
Phase 8 ALWAYS pauses regardless of `gates_mode` — `--gates` re-enables
the per-wave pauses that auto mode otherwise skips.) Default
8. If arguments contain `--gates`: set `gates_mode = true`. Default
`gates_mode = false`. The flag is consumed by the autonomy-gate state
machine via the CLI shim:
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/autonomy-gate.mjs --state X --event Y --gates {true|false}`.
It no longer adds a question of the executor's own before a merge. The
wave merge-back (Phase 2.6 Step 3) and the merge into main (Phase 8) are
asked by the guard, which asks before every merge into main and every push,
in auto mode too, and the executor never asks the same thing again
(operator decision 2026-10-08: two questions for one merge became one). The
MAIN_MERGE_GATE in Phase 8 is always on, regardless of `gates_mode`: it is
a lock, and the guard's question is the one question.
9. Otherwise: the entire argument string is the file path. Set **mode = execute**.
If no path is provided (and `--project` was not used to derive one), output
@ -108,6 +117,53 @@ Project: {project_dir or "-"}
File: {path}
```
### Plan approval gate (K2)
**Runs in every mode that executes** (execute, resume, step N, session N).
Dry-run and validate execute nothing and skip it. Before anything is parsed
for execution, check that the operator approved THIS plan as it is now.
`/trekplan` logs the approval bound to the plan file's sha256 (its Phase 11:
`approval-log.mjs append --kind plan --sha256-of`), so an edit after the
approval makes it stale. Nothing reads the log file directly (K2); the log's
own CLI answers:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/util/approval-log.mjs verify --json --kind plan --subject "{plan_path}" --sha256-of "{plan_path}"
```
`{plan_path}` is the file resolved above. Exit 0 (`approved`) → continue and
record `plan_approval = "approved"`; if `uncommitted` is above 0, say in one
line that N approvals are in the log but not committed yet. Exit 1 → **halt**:
no step, no helper, no progress file. Print `status` and `detail`:
| `status` | Meaning | What the operator does |
|----------|---------|------------------------|
| `not-logged` | The plan was never approved, or was approved before the log existed | `/trekplan --project {dir}` and answer "Approve" at its plan approval |
| `mismatch` | The plan changed after its approval (the approval covers other content) | Same: approve the plan as it is now, or restore the approved text. A review loop interrupted between adding its steps and the approval left the approved text in `{report dir}/plan-approved-round-{R}.md` (Phase 7.2): copy it back |
| `expired` | The approval carried an expiry that has passed | Same |
| `broken` | A record in `.claude/voyage/approvals.jsonl` was edited, removed or reordered | Restore the log from git (`git checkout -- .claude/voyage/approvals.jsonl`), then re-run |
| `diverged` | Committed approvals were rewritten | Same: restore from git |
Exit 2, or no JSON at all → **halt** and show the raw error: an approval check
that could not run is never a pass.
**Not checked:** a session spec or a recovery spec (a file with
`## Scope Fence`, Phase 2). Voyage writes those from an approved plan (the
session decomposer, Phase 7.6), and they carry no approval of their own.
Record `plan_approval = "not-checked"` and say "plan approval: not checked
(session spec)" in the final report, never "approved". A `--session N` run of
a trekplan's Execution Strategy executes the plan itself, so it IS checked:
the log is committed, and Phase 2.55 Check 2 brings the plan into the worktree.
The remediation steps of the review loop (Phase 7.2) change the plan, so the
loop asks for the plan's approval again before it builds them. Nothing else
in this command edits the plan.
Borrowed: the form of `lib/validators/intent-approval.mjs` and the `/trekplan`
intent gate (an approval bound to the approved content's hash, checked before
the work starts); the log is ai-native-sdlc's gate ledger (MIT), with its two
flaws corrected (header of `lib/util/approval-log.mjs`).
## Phase 2 — Detect file type and parse structure
Read the file. Determine whether it is an **trekplan** or a **session spec**:
@ -135,7 +191,15 @@ Extract every `### Step N: {description}` heading (in order). For each step, ext
span of the field. It is also the step's **test command**: proof of red
(Sub-step B) and the done line (Sub-step G) run it, so it must be one
test-runner invocation
- **On failure** — recovery action (revert/retry/skip/escalate)
- **On failure** — recovery action: the field starts with one of the words
`revert`, `retry`, `skip`, `escalate` (K11; the plan validator warns
otherwise), and the rest is the clause's detail
- **Regression** — optional (K10): ONE command from the criteria runner's
allowlist, without pipes or `&&`: the frozen regression suite the step must
keep green. Sub-step D4 runs it before the checkpoint; a step without the
field runs as before. The plan validator rejects a `Regression:` that cannot
run once K10 is in it (wave 2, order 9); for a plan validated before that,
Sub-step D4 is where an unrunnable one is caught, never read as green
- **Checkpoint** — git commit command after success
- **Manifest** — YAML block following Checkpoint (v1.7+)
- **Interface in** / **Interface out** / **Review focus** — optional (K4):
@ -666,6 +730,9 @@ Record `REPO_ROOT`, `PLAN_REL`, `SESSION_DIR`, `WORKTREE_DIR`, `ORDERS_DIR`,
```bash
mkdir -p "$WORKTREE_DIR" "$ORDERS_DIR"
# The run's start point, written once (wave 1): Step 6's whole-run checks and
# the review (Phase 7.2) read it. Phase 8's clear removes it with the orders.
[ -f "$ORDERS_DIR/run-base.sha" ] || git rev-parse HEAD > "$ORDERS_DIR/run-base.sha"
```
**2a. Create worktrees for this wave's sessions:**
@ -842,8 +909,13 @@ in this order:
```
Do NOT merge. Do NOT prepare later waves. Jump to Step 5 (cleanup; it keeps
every unmerged branch and every worktree git refuses to remove), then Phase 8.
- **`all_completed: true`:** if `gates_mode` is true, pause for operator
confirmation before merge-back. Then continue with the merge.
- **`all_completed: true`:** continue with the merge. The executor asks
nothing here, `gates_mode` or not. Each session's push and merge below run
in one Bash call, and the guard asks the operator before it (a push always,
a merge when the branch is main), so that answer is the one question for the
merge (operator decision 2026-10-08; it was two when `--gates` added a pause
of its own). A declined guard question means the call did not run: stop and
report it like `MERGE_REFUSED`, keeping `wave.json`.
**Merge session branches back (SEQUENTIAL, one at a time):**
@ -864,7 +936,9 @@ If it exits 1, stop and report its message: no merge, no cleanup, and keep
For each session N in the wave (in order), push BEFORE merge so the wave
branch survives even if cleanup races ahead (research/02 R3 — converts an
unrecoverable failure to a recoverable one). Push failure is non-fatal
(no remote, offline, etc.):
(no remote, offline, etc.). Run the whole block as one Bash call, never split
into a push call and a merge call: the guard reads the call and asks once for
both.
```bash
GIT_OPTIONAL_LOCKS=0 git push origin "trek/{slug}/session-{N}" 2>/dev/null || true
if GIT_OPTIONAL_LOCKS=0 git merge --no-ff "trek/{slug}/session-{N}" \
@ -940,7 +1014,12 @@ back to Step 1 — this run prepares that wave's orders and stops again. `none`
### Step 4 — Run master verification
After all waves complete and merge successfully, run the plan's `## Verification`
section commands to verify the integrated result.
section commands to verify the integrated result, through the same criteria
runner block as Phase 7 (`### trekplans — run the plan's ## Verification`),
from `$REPO_ROOT`. Record `plan_verification` and `end_to_end` exactly as
Phase 7 does (the same exit table, the same `exitCode` → `exit_code`
translation): the review (Phase 7.2) and the completion gate read them. Step 4
passed = the runner exited 0.
### Step 5 — Cleanup (ALWAYS runs when the run ends, even on failure)
@ -1000,7 +1079,13 @@ Keep the orders directory here, whatever the outcome. Only Phase 8's
main-merge gate clears it, on confirm, when `next-wave` said `none` (every wave
merged) and Step 4 passed. Until then the next run resumes at the lowest wave
not yet merged (Step 0), or re-runs Step 4 and returns to the gate when every
wave is merged. Jump to Phase 8.
wave is merged.
When every wave is merged and Step 4 passed, the integrated result is checked
and reviewed like a single-session run, from the run's start point
(`session_start_sha` = the contents of `$ORDERS_DIR/run-base.sha`, Step 1):
Phase 7's architecture rules and plan-file sweep (K5, K6), then Phase 7.2
(Step 4 is its "run the thing"). Otherwise skip both. Then jump to Phase 8.
### --session N mode
@ -1052,13 +1137,18 @@ progress file.
"session_start_sha": null,
"status": "{in_progress | completed | failed | stopped | partial}",
"steps": {
"1": { "status": "pending", "attempts": 0, "fix_rounds": 0, "start_sha": null, "report_path": null, "red_proof": null, "test_lock": null, "error": null, "completed_at": null, "commit": null }
"1": { "status": "pending", "attempts": 0, "fix_rounds": 0, "start_sha": null, "report_path": null, "red_proof": null, "test_lock": null, "step_check": null, "regression": null, "error": null, "completed_at": null, "commit": null }
},
"iterations_remaining": 25,
"plan_approval": null,
"entry_condition_checked": false,
"exit_condition_checked": false,
"plan_verification": null,
"end_to_end": null,
"arch_rules": null,
"scope_sweep": null,
"simplify": null,
"review": null,
"decisions": [],
"summary": null
}
@ -1082,6 +1172,24 @@ a progress file without them still validates and resumes:
`progress-validator.mjs` rejects `fix_rounds` outside 0–5 (there is no sixth
round) and a `decisions` entry without its `cost_if_wrong`.
**The coupling fields (Handover 5, wave 2, additive-optional).** Each is a
contract CLI's exit said in words. A progress file without them still
validates and resumes; when present, `progress-validator.mjs` rejects any
other value:
| Field | Written | Values (exit 0 / 1 / 2) |
|---|---|---|
| `plan_approval` | Phase 1 (K2) | `"approved"`, or `"not-checked"` for a session spec |
| `steps.{N}.step_check` | Sub-step D4 (K9) | `"clean"` / `"findings"` / `"not-measured"` |
| `steps.{N}.regression` | Sub-step D4 (K10) | `"passed"` or `"absent"` (no field) / `"failed"` or `"unrunnable"` / `"not-run"` |
| `arch_rules` | Phase 7 (K5) | `"pass"` / `"violation"` / `"not-measured"` |
| `scope_sweep` | Phase 7 (K6) | `{status: "pass" \| "deviation" \| "not-measured", outside: [paths]}` |
| `simplify` | Phase 7.2 | `"applied"` \| `"clean"` \| `"reverted"` \| `"not-run"` |
| `review` | Phase 7.2 (K7) | `{round, path, gate_exit: 0 \| 1 \| 2, verdict}`; `round` counts reviews from 1 |
`"not-measured"` and `"not-run"` are never green: the final report says them
in those words, never "pass".
`plan_version` is the value Phase 2 parsed from the plan header (required by
`progress-validator.mjs`). The status words here are the validator's:
`PROGRESS_VALID_STATUSES` for the run, `PROGRESS_STEP_STATUSES` for a step.
@ -1242,7 +1350,10 @@ Every stop names its class in the final report (Phase 8 `### Stops`).
guesswork), a step went up the fix ladder (Sub-step F), the global
iteration budget reached 0 — and the plan's own preconditions failing: an
unmet entry condition (Phase 4), or the sandbox pre-flight exiting 77
(Sub-step D1: the plan cannot be carried out where it runs).
(Sub-step D1: the plan cannot be carried out where it runs). The review
loop's stops are this class too (Phase 7.2): a BLOCK on the wave path,
remediation steps that are not executable, the plan not approved again,
and a BLOCK at the review cap.
Every stop the executor can make maps to one of these four; `stop_class` in
the summary is that number.
@ -1329,6 +1440,11 @@ The step's test is written first, and a command proves it red — never the
model's word that "it failed first". This sub-step blocks: a step whose test
is not proven red at its start point and green at its result does not pass.
**How to write the test first is the `test-first` skill** (S2, Voyage plugin:
`voyage:test-first`): the build helper is told it by name (Sub-step C). The
skill is the advice; the gate below, `red-proof.mjs`, is the guarantee, and it
stays a command whatever the skill says.
**The step's test files** are the `File:` paths under its `Test first:`. The
plan template lists them apart from `Files:`, so everywhere below **the step's
scope = its `Files:` + its test files**: both are built, both are committed at
@ -1391,10 +1507,12 @@ Launch `subagent_type: "voyage:step-implementer"` with a short `description`
summary: the plan path, `plan_version`, the step number, the step's text
verbatim (every field, including `Interface in:`, `Interface out:` and
`Review focus:` when present), `start_sha`, `report_path`, `mode: build`, the
round label (`build`, or `fix round R`), and `voyage_root: ${CLAUDE_PLUGIN_ROOT}`.
round label (`build`, or `fix round R`), `voyage_root: ${CLAUDE_PLUGIN_ROOT}`,
and the skill to work by: `skill: test-first` for a build round,
`skill: debugging` for a fix round (S2; Sub-steps B and F).
**A step at `fix_rounds` 5 is never built again** (Sub-step F): skip the
launch and run only the checks (B, D1, D2, D3, E) on whatever the operator
launch and run only the checks (B, D1, D2, D3, D4, E) on whatever the operator
changed. A step at `fix_rounds` 4 on `--resume` was re-planned: launch a fresh
`build` helper for the revised step.
@ -1561,10 +1679,62 @@ It reports a removed assert, a deleted or skipped test, a muted check
A finding is never dropped silently: the step report and the final report list
every one with its ruling.
#### Sub-step D4 — Step check and regression
Two more commands at step end, before the controller and before the
checkpoint (Sub-step G). Run them in one Bash call. The plugin root is resolved
first, as in Phase 7: an unresolved root would make `node` exit 1, which reads
as a finding when it is in fact a check that never ran.
```bash
# zsh (the Bash tool runs the user's shell): an unmatched glob stays literal, as in bash.
[ -n "${ZSH_VERSION:-}" ] && setopt nonomatch
VOYAGE_ROOT="${CLAUDE_PLUGIN_ROOT}"
case "$VOYAGE_ROOT" in
/*) ;;
*) VOYAGE_ROOT="$(ls -d "$HOME"/.claude/plugins/cache/*/voyage/[0-9]*/ 2>/dev/null \
| awk -F/ '{ split($(NF-1), v, "."); printf "%09d%09d%09d %s\n", v[1], v[2], v[3], $(0) }' \
| sort | tail -1 | cut -d' ' -f2-)"
VOYAGE_ROOT="${VOYAGE_ROOT%/}" ;;
esac
CHECK_CWD="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
if [ ! -f "$VOYAGE_ROOT/lib/verification/step-check.mjs" ]; then
echo "[voyage] step check not run - plugin root unresolved (exit 2)"
else
node "$VOYAGE_ROOT/lib/verification/step-check.mjs" --cwd "$CHECK_CWD"; echo "step-check exit $?"
fi
if [ ! -f "$VOYAGE_ROOT/lib/verification/regression-check.mjs" ]; then
echo "[voyage] regression not run - plugin root unresolved (exit 2)"
else
node "$VOYAGE_ROOT/lib/verification/regression-check.mjs" --plan "{plan_path}" --step {N} --cwd "$CHECK_CWD" --json; echo "regression exit $?"
fi
```
**The step check (K9, P7)** runs the target repo's OWN formatters, linters and
type checkers over the step's changes (the diff against `HEAD` plus untracked
files: nothing of the step is committed yet) and reads the post-edit guard's
log. It installs nothing.
| Exit | `steps.{N}.step_check` | Executor |
|------|------------------------|----------|
| 0 | `"clean"` | continue |
| 1 | `"findings"` | every finding goes to the controller (Sub-step E) as a quality finding; the round does not pass |
| 2 | `"not-measured"` | the repo has no tool for these files, or one could not run. Write "step check: not measured — {reason}" in the step report. Not a finding, and never green: the final report counts the step among those whose step check was not measured |
**The regression (K10)** runs the step's `Regression:` command through the
criteria runner's two screens (the allowlist of test runners, then the
executor denylist), before the checkpoint:
| Exit | `steps.{N}.regression` | Executor |
|------|------------------------|----------|
| 0 | `"passed"`, or `"absent"` when the step has no `Regression:` (nothing new happens) | continue |
| 1 | `"failed"` (the suite went red) or `"unrunnable"` (not one allowlisted command; `"blocked"` by the denylist counts here too) | the step is not done: a finding for Sub-step F, exactly like a red Verify — it goes into the fix rounds, never to the checkpoint |
| 2 | `"not-run"` | the CLI could not run (plan unreadable, no such step): a finding for Sub-step F, never green |
#### Sub-step E — Control
**One controller, two verdicts.** After the mechanical checks (B, D1, D2, D3)
— whatever they said — launch one controller with the Agent tool:
**One controller, two verdicts.** After the mechanical checks (B, D1, D2, D3,
D4) — whatever they said — launch one controller with the Agent tool:
`subagent_type: "general-purpose"`, `model: "opus"`, no `name` (Sub-step C),
no `isolation`. It is a general helper with the prompt below, not a second
Voyage agent. The Agent tool sets no reasoning effort, so the controller runs
@ -1589,7 +1759,10 @@ The step, verbatim:
Review focus from the plan (look hardest here): {Review focus, or "none"}
Mechanical results: proof of red {red-proof JSON}; Verify exit {code};
manifest {pass | fail + drift}; floor guard {test-integrity JSON}.
manifest {pass | fail + drift}; floor guard {test-integrity JSON};
step check {step-check JSON}; regression {regression-check JSON}.
Every step-check finding is a quality finding unless you show it is not in
this step's diff.
Give two verdicts:
- "spec": does the diff do what the step's Changes, Files and Interface say -
@ -1613,7 +1786,8 @@ Append both verdicts, the findings and `not_judged` to the step report.
**The round passes** only when ALL hold: `red_proof` is `"proven"` (or, below
plan_version 1.8, the tolerated `"not-run"` of Sub-step B), Verify PASS,
manifest pass, every floor-guard finding ruled `justified` (and the guard
ran), `"spec": "pass"` and `"quality": "pass"`. Then go to Sub-step G.
ran), `step_check` is `"clean"` or `"not-measured"` (Sub-step D4), `regression` is `"passed"` or `"absent"`,
`"spec": "pass"` and `"quality": "pass"`. Then go to Sub-step G.
Otherwise go to Sub-step F with every finding.
**Re-control after a fix** checks only what was fixed. A fresh controller gets
@ -1635,6 +1809,11 @@ It does not re-judge the rest of the step.
#### Sub-step F — Fix rounds
**How a fix helper finds the cause is the `debugging` skill** (S2, Voyage
plugin: `voyage:debugging`): it is told it by name (Sub-step C,
`skill: debugging`). The skill is the advice; the ladder below and the checks
that re-run after every round are the guarantee.
A failed round never loops sideways. **The fix rounds are capped at maximum 5 fix rounds per step, and they escalate upward** — to a fresh helper, then to
the plan, then to the operator. The executor does not improvise a new design,
and there is no ladder of cheaper models: every helper and every controller
@ -1643,9 +1822,9 @@ session's effort, Sub-step E).
| `fix_rounds` | Goes to | What happens |
|---|---|---|
| 1, 2, 3 | a fresh helper | First `node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs pending` (the step is no longer seen green; D1 marks it again). Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
| 1, 2, 3 | a fresh helper | First `node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs pending` (the step is no longer seen green; D1 marks it again). Launch `voyage:step-implementer` again (no `name`, no `isolation`), `mode: fix`, with the findings (file, line, problem, fix) and the mechanical failures. It fixes only those; the tests stay locked (except in a `not-red-at-base` round, Sub-step B). Then B (both ways), D1, D2, D3, D4 run again and a fresh controller re-controls only what was fixed (Sub-step E). `attempts` +1; `iterations_remaining` −1 |
| 4 | the plan | **After three failed fix rounds the question is the architecture, not a fourth fix.** The step goes back to planning (below). Stop class 4 |
| 5 | the operator | A step already sent to the plan (`fix_rounds` 4) that fails its control again after re-planning goes to the operator. The executor never builds this step again: on a later `--resume` it only runs the checks (B, D1, D2, D3, E) on whatever the operator changed. If they fail again the step stays at 5 — there is no sixth round, `fix_rounds` never exceeds 5 — and the run stops again. Stop class 4 |
| 5 | the operator | A step already sent to the plan (`fix_rounds` 4) that fails its control again after re-planning goes to the operator. The executor never builds this step again: on a later `--resume` it only runs the checks (B, D1, D2, D3, D4, E) on whatever the operator changed. If they fail again the step stays at 5 — there is no sixth round, `fix_rounds` never exceeds 5 — and the run stops again. Stop class 4 |
**Back to planning** (`fix_rounds = 4`): save `git diff {start_sha}` to
`{report dir}/step-{N}-upward.diff` and write `{report dir}/step-{N}-upward.md`:
@ -1801,6 +1980,8 @@ carrying a second copy of the denylist.)
# indistinguishable from a criterion that failed.
# Only the literal form is substituted (a `:-` default is never replaced); the
# fallback takes the highest installed version under cache/<market>/voyage/.
# zsh (the Bash tool runs the user's shell): an unmatched glob stays literal, as in bash.
[ -n "${ZSH_VERSION:-}" ] && setopt nonomatch
VOYAGE_ROOT="${CLAUDE_PLUGIN_ROOT}"
case "$VOYAGE_ROOT" in
/*) ;;
@ -1854,7 +2035,12 @@ Record in the progress file (additive-optional; unknown keys are tolerated by
- `plan_verification.status` — `"passed" | "failed" | "not-run"`
- `plan_verification.summary` — `{total, passed, failed, blocked, unrunnable}`
- `plan_verification.failed_criteria` — `[{label, command, exit_code}]`
- `plan_verification.failed_criteria` — `[{label, command, exit_code}]`, one
per runner result whose `status` is not `passed`. The runner's JSON says
`exitCode`; progress says `exit_code`, so translate the key explicitly,
`exitCode` → `exit_code`, and copy `null` as `null` (a blocked or
unrunnable criterion never ran). Never write the runner's key into progress:
`progress-validator.mjs` rejects it (`PROGRESS_FAILED_CRITERIA_KEY`, C15).
**A failing criterion FELLS the run.** It is not "recorded and included in the
final report": the executor MUST NOT emit `result: completed` while
@ -1883,6 +2069,188 @@ built thing — not in the run. (A ≥ 1.8 plan cannot reach here with its
`End-to-end:` command missing from `## Verification`: the plan validator
rejects that as `PLAN_END_TO_END_NOT_VERIFIED`.)
### Architecture rules (K5) and the plan-file sweep (K6)
Two whole-run checks, for a trekplan and a session spec alike, in one Bash
call. Shell state does not survive between Bash calls, so the block resolves
the plugin root itself; an unresolved root prints exit 2, never a `node`
exit 1 that would read as a violation.
```bash
# zsh (the Bash tool runs the user's shell): an unmatched glob stays literal, as in bash.
[ -n "${ZSH_VERSION:-}" ] && setopt nonomatch
VOYAGE_ROOT="${CLAUDE_PLUGIN_ROOT}"
case "$VOYAGE_ROOT" in
/*) ;;
*) VOYAGE_ROOT="$(ls -d "$HOME"/.claude/plugins/cache/*/voyage/[0-9]*/ 2>/dev/null \
| awk -F/ '{ split($(NF-1), v, "."); printf "%09d%09d%09d %s\n", v[1], v[2], v[3], $(0) }' \
| sort | tail -1 | cut -d' ' -f2-)"
VOYAGE_ROOT="${VOYAGE_ROOT%/}" ;;
esac
CHECK_CWD="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
if [ ! -f "$VOYAGE_ROOT/lib/verification/arch-rules.mjs" ]; then
echo "[voyage] architecture rules not run - plugin root unresolved"; echo "arch-rules exit 2"
else
node "$VOYAGE_ROOT/lib/verification/arch-rules.mjs" --repo "$CHECK_CWD"; echo "arch-rules exit $?"
fi
if [ ! -f "$VOYAGE_ROOT/lib/verification/plan-scope-sweep.mjs" ]; then
echo "[voyage] plan-file sweep not run - plugin root unresolved"; echo "scope-sweep exit 2"
else
node "$VOYAGE_ROOT/lib/verification/plan-scope-sweep.mjs" --plan "{plan_path}" --base "{session_start_sha}" --repo "$CHECK_CWD" \
--process .claude/voyage/approvals.jsonl --process .claude/rules/voyage-review-lessons.md {--process CLAUDE.md, see below}; echo "scope-sweep exit $?"
fi
```
`{--process CLAUDE.md, see below}` is filled in, or left out, before the call:
it is passed only when every commit since the start point that touches
`CLAUDE.md` is a review-lessons commit (Phase 7.2), which
`git log --format=%s {session_start_sha}..HEAD -- CLAUDE.md` shows. A plan
step's own change to `CLAUDE.md` is never hidden by it.
**K5, the architecture rules (P3/P7)**, runs the target repo's OWN
architecture tool (dependency-cruiser, import-linter) and installs nothing:
| Exit | `arch_rules` | Executor |
|------|--------------|----------|
| 0 | `"pass"` | continue |
| 1 | `"violation"` | a finding in the final report with every violation; the completion gate turns `completed` into `partial` (clause 5) |
| 2 | `"not-measured"` | the repo has no such tool, or it could not run. The report says "architecture rules: not measured — {reason}", never "pass". Not a finding |
**K6, the plan-file sweep (P3)**, from the run's start point
(`session_start_sha`, Phase 3): every file the run changed must be named by
the plan (a step's `Files:`, its `Test first:` file, its manifest) or be a
process file. Voyage's own approval log and the review's project-memory file
(and its one-line `CLAUDE.md` pointer, as above) are passed as process files:
the run writes them, the plan does not name them.
| Exit | `scope_sweep` | Executor |
|------|---------------|----------|
| 0 | `{status: "pass", outside: []}` | continue |
| 1 | `{status: "deviation", outside: [...]}` | a finding in the final report with the file list (`outside`), never a silent warning; the completion gate turns `completed` into `partial` (clause 5). A deviation is cleared only by the plan naming the file in the same commit |
| 2 | `{status: "not-measured", outside: []}` | it could not run (not a git repo, an unknown start point). "plan-file sweep: not measured" in the report, never "pass" |
## Phase 7.2 — Simplify, review, verdict (P11)
**Runs only in the closing run, and only when Phase 7 ran** (every step
passed). The closing run is the one that ends a trekplan: a single-session
execute or resume of the whole plan, or the wave parent once every wave is
merged (Phase 2.6 Step 6). It is never a `--step N` run, a `--session N`
child or a session spec: those deliver a part, the review is of the whole
delivery, and a wave session's work is reviewed once the wave is merged
(Phase 8 leaves their result alone). It starts by itself after the last step,
never typed by the operator, in this order (Cherny's):
1. **run the thing** — Phase 7's `## Verification` and its `End-to-end:`
criterion already ran (on the wave path: Step 4, which runs the same
criteria runner). `end_to_end` must be `"verified"`; otherwise skip the rest
of this phase (`review = {round: 1, path: null, gate_exit: 2, verdict: null}`:
there is nothing proven to review) and go to Phase 7.5.
2. **`/simplify`** — Claude Code's bundled cleanup skill: four agents look at
reuse, simplification, efficiency and altitude, and it applies the fixes.
Measured 2026-10-08: the Skill tool starts it from the session. It reviews
"the changed code": its upstream range, else `main...HEAD` or `HEAD~1`, plus
uncommitted changes, or the PR, branch or path given as its argument. Give
it the run as its argument:
`Skill("simplify", args: "the changes of this run: git diff {session_start_sha}..HEAD — files: {git diff --name-only {session_start_sha} HEAD}")`.
Wait for it to finish. Then, from the working tree:
- **Outside the run's changed files:** every path it changed that is not
in `git diff --name-only {session_start_sha} HEAD` is restored
(`git checkout -- {path}`; a new file it made is removed from the index
with `git rm --cached -q -- {path}` and left on disk), and listed in the
final report. Nothing it did is dropped silently.
- **Nothing changed:** `simplify = "clean"`.
- **Changes:** run Phase 7's criteria runner again (same block, same exit
table). Exit 0 → stage exactly the changed paths and commit them as
`refactor: simplify pass (trekexecute)`; `simplify = "applied"`. Exit 1 or
2 → restore every path it changed as above, `simplify = "reverted"`, and
say why in the report: the cleanup broke the verification.
- **It could not start** (the Skill tool refused, the skill is not there):
`simplify = "not-run"`, with the reason. Not a stop: `/simplify` is a
cleanup pass, not a gate. Continue.
3. **`/trekreview`** — Voyage's review gate: Claude Code's `/code-review` and
`/security-review` as the engine, then Voyage's control against the brief,
which is what stops. Start it through the Skill tool, with the run's range:
`Skill("voyage:trekreview", args: "--project {project_dir} --since {session_start_sha}")`.
A plugin command is a skill the model may start unless it carries
`disable-model-invocation`, which `/trekreview` does not (the Claude Code
skills documentation, read 2026-10-08). Follow its phases to the end in
this session; its own questions (project-memory lines, one by one) are its
own. **Without `--project`** there is no brief to review against:
`review = {round: 1, path: null, gate_exit: 2, verdict: null}`, "review not
run — no project directory", and the merge stays closed (Phase 8).
**Its writes are committed.** `/trekreview` may have written approved
project-memory lines (`.claude/rules/voyage-review-lessons.md`) and a
one-line pointer in `CLAUDE.md`. Commit exactly those paths, when they
changed, as `docs(voyage): review lessons (round {R})`: a process commit
(Phase 7.5 step 3). Nothing the review wrote is left uncommitted for the
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"
```
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.
**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`
(Handover 6: Files, Source finding, Test first, Verify, On failure). The fix is
those steps, not improvised. Every stop below is stop class 4 (Phase 5.5):
the run is `stopped`, and the merge stays closed.
0. **On the wave path there is no loop.** The plan's sha256 binds the
merged-waves record (Phase 2.6), so new steps would strand the waves. Stop,
and tell the operator to re-plan from the review: `/trekplan --brief
{project_dir}/review.md` (Handover 6's remediation plan).
1. Copy the approved plan aside: `{report dir}/plan-approved-round-{R}.md`. It
is the text the log approved, and it is how every stop below leaves the plan
matching its approval.
2. Append each `### Step R<n>` to the plan's `## Implementation Plan` as the
next numbered step (`### Step {total + 1}: …`, then +2, …), its fields
copied as they are, never reworded. Add a `Checkpoint:`
(`git commit -m "fix(review): {title} [{finding id, first 8}]"`), and on a
plan_version ≥ 1.7 plan the manifest the legacy rule synthesizes
(Phase 2: `expected_paths` ← its Files, …): a rule already in this file, not
a new design. Earlier steps are never touched.
3. Check that the new steps can run, before anyone is asked:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json "{plan_path}"
```
Any exit but 0 (a `Verify:` that is not one test-runner call, a
`Test first:` without its `File:`, …): restore the plan from the copy,
save the appended text as `{report dir}/review-round-{R}-steps.md`, and
stop. Say that the review's remediation steps are not executable as plan
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:
**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}"`
and `artifact-commit.mjs --store {artifact_store} --artifact "{plan_path}" --message "docs(voyage): approve plan {slug}"`.
This is an approval of new plan content, not an execution decision
(Hard rule 1), and not a merge: no guard asks it.
5. **Approve** → add the new steps to progress (`steps.{M}` pending,
`total_steps` raised), run Phase 6 for them (the same loop: helper, checks,
controller, fix rounds), then Phase 7, then this phase again with
`review.round` + 1. **Not yet**, or an answer that is not an approval →
restore the plan from the copy, save the appended text as
`{report dir}/review-round-{R}-steps.md`, and stop. A later `--resume`
finds the plan as approved and comes back to this review.
**The cap:** `TREKEXECUTE_MAX_REVIEW_ROUNDS` reviews per run, default **3**
(the first review and two fix loops; env-overridable).
A BLOCK at the cap goes to the operator: stop, and the report names every
BLOCKER still open. Each loop also takes 1 from `iterations_remaining`, which
bounds it a second time (the completion gate's clause 3 counts it).
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
Kiro's `feature-pipeline` gate and Spec Kit's `/converge` loop (MIT); that no
finding is dropped silently is Superpowers' (MIT). Ideas only.
## Phase 7.5 — Manifest audit (independent)
**Runs for all modes except dry-run.** This is the last-line-of-defense
@ -1902,11 +2270,19 @@ produces an independent truth based on objective state, not self-narrative.
3. **Commit-count check:** count commits since session-start
(`git rev-list --count {session_start_sha}..HEAD`). Expected count =
number of steps with `status=completed` (excludes Step 0 if sandbox_preflight).
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
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
Phase 8 merge comes after the audit.)
4. **Commit-message pattern sweep:** walk `git log {session_start_sha}..HEAD` and
confirm each commit matches one of the declared
`commit_message_patterns` (in any order — order is advisory).
`commit_message_patterns` (in any order — order is advisory). The process
commits of step 3 match their own fixed messages above.
5. **Bash syntax sweep:** for every `.sh` file in
`git diff --name-only {session_start_sha} HEAD`, run `bash -n`. Collect failures.
@ -1960,7 +2336,8 @@ Record in progress file:
> 3. **Counter liveness (deterministic, defeats "never decremented")** — the gate
> reconciles the budget counter against the ground-truth counters the Phase 7.5
> audit already derives:
> `iterations_remaining == TREKEXECUTE_MAX_RECOVERY_ITERATIONS − (recovery_depth + Σ steps.*.attempts-beyond-first)`.
> `iterations_remaining == TREKEXECUTE_MAX_RECOVERY_ITERATIONS − (recovery_depth + Σ steps.*.attempts-beyond-first + (review.round − 1))`
> (the last term is 0 without a review; each review loop of Phase 7.2 takes 1).
> A field left un-decremented (or tampered) fails this reconciliation and the
> result is OVERRIDDEN `completed → partial`, exactly as the audit-override
> already does (`:1221`). So the counter's liveness is auditable at the gate,
@ -1968,6 +2345,15 @@ Record in progress file:
> 4. **End to end** — for a trekplan, Phase 7's `end_to_end == "verified"`.
> `"not-verified"` OVERRIDES `completed → partial` with the reason
> "not verified end to end": tests that pass are not the thing running.
> 5. **Architecture and plan scope** — Phase 7's `arch_rules` is not
> `"violation"` and `scope_sweep.status` is not `"deviation"`. Either one
> OVERRIDES `completed → partial`, and the report lists the violations or
> the files outside the plan. `"not-measured"` does not override, and it is
> never reported as a pass.
>
> The review verdict (Phase 7.2) is decided after this gate, by the lock in
> Phase 8: in the closing run, a closed lock or a declined merge turns
> `completed` into `partial`, as a declined gate always has.
>
> Termination authority therefore rests on the deterministic audit + an
> objective stop-signal, never on the model's narrative. Native `/goal` (when
@ -2085,22 +2471,122 @@ Run each event call as its own command, never chained with `&&`. The writer
prints `{"written": true, ...}`. On `"written": false` (exit 1), print its
`reason` in one line and continue — an event failure never blocks the gate.
Pause for operator confirmation via `AskUserQuestion`:
**The gate is a lock, not a question** (operator decision 2026-10-08). The
guard (`pre-bash-executor`) asks the operator before every merge into main and
every push, in auto mode too. When the executor first asked "ready to merge?"
itself, one merge cost two questions. Now the executor decides whether the
merge may run at all, and the guard's question is the one question.
**Question:** "All waves merged. Ready to proceed with the main-merge step
and finalize the run?"
**Only the closing run passes the lock** (Phase 7.2: a single-session run of
the whole plan, or the wave parent after the last wave). A `--session N`
child, a `--step N` run and a session spec deliver a part: they emit no gate
event, merge nothing, and their result is left as Phase 7.5 decided it
(`main_merge: "n/a"`). A wave session's `completed` must reach its parent as
`completed`, or Step 3 would read the wave as failed.
| Option | Action |
|--------|--------|
| **Confirm — main-merge** | Emit `main-merge-approved`, proceed to write progress + state files. |
| **Decline — hold for review** | Emit `main-merge-declined`. Nothing is finalized: the run is recorded as `partial`. The commits stay where they are for review — on the multi-session wave path the waves are already merged on the current branch, and the merged-waves record is kept. |
**1. The verdict.** Decide with the review's verdict as a command, never from
a summary:
This pause is **always on**, regardless of `gates_mode`. The `--gates` flag
re-enables earlier per-wave pauses; this gate is the one boundary that
ALWAYS pauses on every run — it is the safety stop between completed waves
and the merge that publishes the integrated result.
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/review/verdict-gate.mjs --json "{review_path}"
```
`{review_path}` is `review.path` from Phase 7.2. No review (Phase 7.2 did not
run, ran without a project directory, or the run stopped before it) counts as
exit 2 without the call.
| Exit | Verdict | Merge |
|------|---------|-------|
| 0 | ALLOW / WARN | 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 |
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
`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.
**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:
```bash
git rev-parse --abbrev-ref HEAD
for b in main master; do git rev-parse -q --verify "refs/heads/$b" >/dev/null && echo "MAIN=$b" && break; done
git worktree list --porcelain
```
- **No `MAIN=` line** (the repo has neither branch): there is nothing to merge
into. Emit `main-merge-declined` with the reason `no-main-branch`.
- **The run's branch IS main (or master):** there is nothing to merge; the
commits are already on main. Emit `main-merge-approved` and say in the
report that the review ran after the commits landed on main and could not
hold them back: run on a branch when the review should gate the merge.
- **Another branch:** merge it into main in ONE Bash call, with the values
written out as literals (the guard reads the command; it cannot follow a
shell variable). When `git worktree list` shows main checked out in a
worktree, merge there:
```bash
if git -C "{main worktree path}" merge --no-ff "{run branch}" -m "merge: trekexecute {slug}"; then
echo "MERGED"
elif git -C "{main worktree path}" rev-parse -q --verify MERGE_HEAD >/dev/null; then
git -C "{main worktree path}" diff --name-only --diff-filter=U
git -C "{main worktree path}" merge --abort
echo "MERGE_CONFLICT"
else
echo "MERGE_REFUSED"
fi
```
Otherwise switch this tree to main, and back to the run's branch on any
failure:
```bash
git switch "{main}" || { echo "MERGE_REFUSED"; exit 1; }
if git merge --no-ff "{run branch}" -m "merge: trekexecute {slug}"; then
echo "MERGED"
elif git rev-parse -q --verify MERGE_HEAD >/dev/null; then
git diff --name-only --diff-filter=U
git merge --abort
git switch "{run branch}"
echo "MERGE_CONFLICT"
else
git switch "{run branch}"
echo "MERGE_REFUSED"
fi
```
The guard answers "ask" before it runs (measured 2026-10-08/09 against
both blocks: they ask, and a merge into another branch does not). The
operator's answer there is the one question; the post-approval hook logs it
as a `merge` approval (K2). The executor asks nothing before or after it. In
the permission mode where Claude Code asks nothing, the guard refuses the
call instead, because there is no one to ask (operator decision 2026-10-09).
- `MERGED` → emit `main-merge-approved`.
- The call did not run (the operator declined at the guard's question, or
the guard refused it) → emit `main-merge-declined` with the reason
`hold-for-review`; the run is `partial`, and the commits stay on the run's
branch. When the guard refused, say so, and that the operator merges by
hand.
- `MERGE_CONFLICT` → list the conflicting files the block printed (the
merge is already aborted), emit `main-merge-declined` with the reason
`merge-conflict`; the run is `partial`. Never resolve a conflict with
`-X ours/theirs`.
- `MERGE_REFUSED` → git refused to start (local changes in the main tree, or
untracked files in the way: its message says which). Emit
`main-merge-declined` with the reason `merge-refused` and report git's
message; the run is `partial`.
This gate is **always on**, regardless of `gates_mode`: a lock that every run
passes through, the one boundary that ALWAYS pauses on every run, either at
its lock or at the guard's question. It is the safety stop between completed
work and the merge that publishes the integrated result. On the multi-session
wave path the waves are already merged on the current branch, the branch the
run started on (Phase 2.6 Step 3, one guard question per session); when that
branch is not main, this gate merges it into main like a single-session run,
and the merged-waves record is kept until the confirm below.
On confirm:
(the merge ran, or there was nothing to merge)
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/stats/event-emit.mjs --event main-merge-approved --payload - <<'JSON'
{"plan":"{plan-path}"}
@ -2117,21 +2603,25 @@ cleared:
```
On decline:
(a closed lock, or the guard's question declined, or a conflict)
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/stats/event-emit.mjs --event main-merge-declined --payload - <<'JSON'
{"plan":"{plan-path}","reason":"hold-for-review"}
{"plan":"{plan-path}","reason":"{reason}"}
JSON
```
The reason is the literal label of the one decline option, never the
operator's free text (free text breaks the JSON).
The reason is one of the literal words above (`review-block`,
`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.
**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 AskUserQuestion. On the multi-session wave path,
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
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
branch, the kept merged-waves record makes Step 0's `next-wave` say `none`,
@ -2256,6 +2746,17 @@ copy · 4 plan so broken that everything after it is guesswork, and the file or
- **Recovery depth:** {N}
- **Legacy plan:** {true | false}
### Checks and review (wave 2)
- **Plan approval (K2):** {approved | not checked (session spec)}
- **Step checks (K9):** {N clean · N with findings (fixed in rounds) · N not measured: steps {list}}
- **Regressions (K10):** {N passed · N absent · failed: steps {list}}
- **Architecture rules (K5):** {pass | violation: {each one} | not measured — {reason}}
- **Plan-file sweep (K6):** {pass | deviation — files outside the plan: {outside, one per line} | not measured — {reason}}
- **Simplify:** {applied (commit {hash}) | clean | reverted — {why} | not run — {reason}}{; edits outside the run restored: {paths}}
- **Review (K7):** round {R} of {cap} · {review_path} · verdict {ALLOW | WARN | BLOCK | none} · gate exit {0 | 1 | 2}{; open BLOCKERs: {ids}}
- **Merge into main:** {merged ({commit}) after the guard's question | nothing to merge (the run was on main) | closed — {reason} | declined at the guard's question | n/a — a part of the plan (`--session N`, `--step N`, a session spec)}
### Summary
- Passed: {N}/{total}
@ -2292,7 +2793,9 @@ To continue (multi-session waves): re-run the same /trekexecute command (not --r
(for trekplans) Phase 7 plan verification passed
- `partial` — steps passed per executor but Phase 7.5 found drift, OR
Phase 7.6 recovery incomplete, OR the run is not verified end to end
(Phase 7, gate clause 4)
(Phase 7, gate clause 4), OR an architecture violation or a file outside the
plan (gate clause 5), OR — in the closing run — the main-merge lock stayed
closed or the merge was declined (Phase 8)
- `blocked` — Step 0 sandbox pre-flight exited 77; no real work attempted
- `failed` — a step failed and went up the fix ladder (On failure revert/retry: round 4 to the plan, or 5 to the operator)
- `stopped` — On failure: escalate triggered, or another stop on the closed stop list (Phase 5.5)
@ -2325,7 +2828,15 @@ To continue (multi-session waves): re-run the same /trekexecute command (not --r
"end_to_end": "{verified | not-verified | n/a}",
"steps_without_red_proof": 0,
"stop_class": "{none | 1 | 2 | 3 | 4}",
"decisions": 0
"decisions": 0,
"plan_approval": "{approved | not-checked}",
"steps_check_not_measured": 0,
"arch_rules": "{pass | violation | not-measured | n/a}",
"scope_sweep": "{pass | deviation | not-measured | n/a}",
"simplify": "{applied | clean | reverted | not-run | n/a}",
"review_verdict": "{ALLOW | WARN | BLOCK | none}",
"review_gate_exit": "{0 | 1 | 2}",
"main_merge": "{merged | nothing-to-merge | closed | declined | n/a}"
}
}
```
@ -2450,7 +2961,7 @@ When `phase_signal_result.effort == 'high'` for the `execute` phase,
set `gates_mode = 'closed'` automatically (equivalent to passing
`--gates closed` on the command line). All autonomy boundaries become
operator-stopping points: manifest-audit FAIL halts immediately, the
main-merge gate pauses for operator confirm, and Phase 7.5
main-merge gate stays a lock whose one question is the guard's (Phase 8), and Phase 7.5
manifest-audit runs in its strictest form (fails on ANY mismatch, not
just deltas above threshold). Operator explicit `--gates` flag still
wins over the brief signal — if the operator passes `--gates open` on
@ -2479,7 +2990,11 @@ code-path.
from the plan's On failure clauses. If the plan says escalate, stop and
report — never ask. What the plan leaves open, the executor decides and
writes into the decision log with its `cost_if_wrong`; it stops only on the
closed stop list (Phase 5.5).
closed stop list (Phase 5.5). The one question the executor asks itself is
an approval, not an execution decision: the plan's approval after the
review loop added remediation steps (Phase 7.2). A merge or a push is asked
by the guard, and the executor never asks again (operator decision
2026-10-08).
2. **No scope creep.** Only touch files listed in the step's `Files:` field.
If a file outside the list seems to need changing, record it as a finding
@ -2586,6 +3101,8 @@ code-path.
cleanup proceeds regardless. Rationale: this converts an unrecoverable
failure (worktree removed, branch deleted, work lost) into a recoverable
one (push succeeded, branch preserved on remote). Source: research/02 R3.
The push and the merge go in one Bash call, so the guard asks once for
both (Phase 2.6 Step 3).
20. **Three distinct iteration axes.** Per-step `fix_rounds` (≤5, upward),
per-session `recovery_depth` (<2), and the global
@ -2605,6 +3122,17 @@ code-path.
`done-line.mjs` — never the helper's or the executor's statement. Exit 2
from any of them ("could not run") is never green.
23. **The review is a gate, and the guard asks the one question.** A run
starts only on a plan approved as it is now (K2, Phase 1). It ends with
`/simplify`, `/trekreview` and the verdict gate, started by the executor
itself (Phase 7.2). Exit 1 (BLOCK) or 2 (not measured) from the verdict
gate keeps the merge into main closed (Phase 8). A BLOCKER is fixed by new
numbered plan steps the operator approves, in a loop capped at
`TREKEXECUTE_MAX_REVIEW_ROUNDS`, never by improvising. When the lock
opens, the guard asks before the merge, and the executor never asks again.
Exit 2 from the step check (K9), the architecture rules (K5) or the
plan-file sweep (K6) is "not measured", never green.
## Credits
Every borrowing in this executor is named here, never silent. All sources are
@ -2632,4 +3160,17 @@ pins and quotes in the local practice analysis.
- **Cherny** (public primary sources): "run the thing" — a plan is verified
end to end only by a criterion that runs what was built (P9 (6)).
- **Claude Code** itself: subagents for the helper and the controller; the
`PreToolUse` guard behind the test lock (K8).
`PreToolUse` guard behind the test lock (K8); `/simplify`, `/code-review` and
`/security-review` as the review's engine, started through the Skill tool
(P11).
- **The review at the end (P11, wave 2)** — Cherny: the order — run the
thing, simplify, then review.
Kiro (`feature-pipeline`, documentation only): the gate — a verdict in a file.
Spec Kit (MIT, `/converge`): the bounded fix-and-review loop, its
remediation steps appended and never reworded.
Superpowers (MIT): nothing is dropped silently — what the controller did not
judge, and every edit restored, is listed.
- **The checks wired in (P7, P3, P12)** — agent-skills (MIT): the step check's
tiers and "not measured"; ai-native-sdlc (MIT): the sweep for files outside
the plan and the guard + log; Superpowers (MIT): the four classes the guard
asks about. Ideas only.