merge: wave 1 order 1 (executor) into main

CLAUDE.md command table: each row from its owner (trekplan from order 3,
trekexecute from order 1, trekreview from order 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-06 20:42:47 +02:00
commit ca6ee8779a
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
28 changed files with 2384 additions and 116 deletions

View file

@ -2,8 +2,8 @@
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
disallowed-tools: Agent, TeamCreate
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
disallowed-tools: TeamCreate
---
# Ultraexecute Local
@ -12,9 +12,17 @@ Disciplined executor for trekplan plans. Reads a plan file, detects if it has
an Execution Strategy (multi-session), and either executes directly or
prepares one session order per session, wave by wave — all to realize one plan.
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).
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
themselves (Phase 2.6).
themselves (Phase 2.6). Helpers inside this session are subagents, and helpers
never start helpers.
## Phase 1 — Parse mode and validate input
@ -120,15 +128,34 @@ Extract every `### Step N: {description}` heading (in order). For each step, ext
- **Files** — file paths to create or modify
- **Changes** — what to modify
- **Reuses** — existing code to leverage (informational)
- **Test first** — test to run before implementation (optional)
- **Verify** — command to run after implementation
- **Test first** — the test the step writes before it builds: its `File:`
(existing | new), what it `Verifies:`, an optional `Pattern:` (required from
plan_version ≥ 1.8, see below)
- **Verify** — command to run after implementation. 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)
- **Checkpoint** — git commit command after success
- **Manifest** — YAML block following Checkpoint (v1.7+)
- **Interface in** / **Interface out** / **Review focus** — optional (K4):
what the step consumes and produces, and what its controller must look at
hardest. Read when present and passed to the helper and the controller;
never required, and the executor tolerates their absence in every plan
version.
If a step is missing `On failure`, default to `escalate` and record a parse warning.
If a step is missing `Verify`, record a parse warning.
**`Test first:` is required from plan_version ≥ 1.8.** A ≥ 1.8 step without a
`Test first:` field (or whose `File:` is missing) is not executable: STOP with
```
Error: plan_version={v} but step {N} has no Test first: field.
Re-run /trekplan - from plan_version 1.8 every step proves red before it builds.
```
For plans below 1.8 the field stays optional: a step without it runs, its
`red_proof` is recorded as `"not-run"`, and the final report counts it among
the steps without proof of red (never hidden, never called proven).
### Parse the Manifest block (v1.7+)
Read the plan header for a `plan_version` marker. Treat ≥ `1.7` as strict
@ -1021,18 +1048,39 @@ progress file.
"mode": "{execute | resume | step}",
"total_steps": 0,
"current_step": 0,
"session_start_sha": null,
"status": "{in_progress | completed | failed | stopped | partial}",
"steps": {
"1": { "status": "pending", "attempts": 0, "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, "error": null, "completed_at": null, "commit": null }
},
"iterations_remaining": 25,
"entry_condition_checked": false,
"exit_condition_checked": false,
"plan_verification": null,
"end_to_end": null,
"decisions": [],
"summary": null
}
```
**The step-loop fields (Handover 5, additive-optional).** Written by Phase 6;
a progress file without them still validates and resumes:
| Field | Written | Meaning |
|---|---|---|
| `session_start_sha` | Phase 3 | the commit the run started from; Phase 7.5 audits the whole run from it |
| `steps.{N}.start_sha` | Sub-step 0 | the commit the step started from; every check of the step reads `git diff {start_sha}` |
| `steps.{N}.report_path` | Sub-step 0 | the step's report file (helper rounds + the done line) |
| `steps.{N}.red_proof` | Sub-step B | `"proven" \| "rejected" \| "not-run"` — the exit of `red-proof.mjs` |
| `steps.{N}.test_lock` | Sub-steps B, G | `"locked" \| "released" \| "not-locked"` (K8) |
| `steps.{N}.fix_rounds` | Sub-step F | 0–5: 0 = passed its first control, 1–3 = fix rounds, 4 = sent up to the plan, 5 = sent up to the operator |
| `steps.{N}.attempts` | Sub-steps C, F | helpers launched for the step: 1 + the fix rounds that launched one |
| `end_to_end` | Phase 7 | `"verified" \| "not-verified"` (P9 (6)) |
| `decisions` | any phase | `[{step, decision, cost_if_wrong}]` — every call the executor made on its own (Phase 5.5) |
`progress-validator.mjs` rejects `fix_rounds` outside 0–5 (there is no sixth
round) and a `decisions` entry without its `cost_if_wrong`.
`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.
@ -1047,8 +1095,8 @@ condition instead) and on early stop. Unknown keys are tolerated by
recovery/retry budget against the global hard cap (unit = **iterations**, per the
brief — token/cost budgeting is out of scope). Seeded to
`TREKEXECUTE_MAX_RECOVERY_ITERATIONS` (default 25) at start. **Single writer:** the
executor decrements it by 1 on **every recovery/retry iteration** — each step
attempt beyond the first, and each Phase 7.6 recovery dispatch. **Backfill-on-absent:**
executor decrements it by 1 on **every recovery/retry iteration** — each fix
round of a step (Sub-step F), and each Phase 7.6 recovery dispatch. **Backfill-on-absent:**
when resuming a legacy `progress.json` that lacks the field, seed it to the cap
rather than hard-failing (forward/backward-compatible; shape-validated by
`progress-validator.mjs` — non-negative integer, never required-top). The field is
@ -1056,6 +1104,12 @@ present in the spec at minimum — there is **no `(if wired)` conditional**.
### Mode-specific behavior
**Run start point (every mode that executes):** a fresh progress file records
`session_start_sha` = `git rev-parse HEAD` before step 1; a resumed one keeps
the value it has (a legacy file without it is backfilled with the parent of
the first completed step's `commit`, or `HEAD` when none is completed). Phase
7.5 audits the whole run from it — never from a step's own `start_sha`.
**mode = execute (fresh):**
- If a progress file exists with status `in_progress`, `failed`, `stopped` or `partial`: warn that
`--resume` is available, then wait 3 seconds (`sleep 3`) and start fresh.
@ -1160,6 +1214,55 @@ If `has_execution_strategy = true`, show a preview of multi-session orchestratio
Stop after the dry-run report. Do not execute anything.
## Phase 5.5 — Autonomy: what stops a run, and the decision log
A run is long and mostly unattended. It does not stop to ask: what it decides
on its own it writes down, and it stops only for the four reasons below.
### Closed stop list
The list is **closed**: a run stops on these four classes and on nothing else.
Every stop names its class in the final report (Phase 8 `### Stops`).
1. **Something that cannot be undone.** A step or command whose effect cannot
be reverted from git: deleting data outside the repo's tracked files, a
migration against a live store, anything the plan marks irreversible — and
a dirty main checkout before a multi-session wave (Phase 2.55 Check 1:
merging would lose the uncommitted work).
2. **Something security-sensitive.** A command the executor denylist blocks
(Phase 2.4, Sub-step D1), a write to a security-sensitive path (Hard
rule 16), credentials or secrets in a diff.
3. **Something outside the working copy.** Merge to the main branch, push,
tag, publish, a write outside the repository (Hard rule 15). The main-merge
gate (Phase 8) is this class: it always pauses.
4. **A plan so broken that everything after it is guesswork.** The plan fails
to parse, a ≥ 1.8 step has no `Test first:`, a step's own `On failure:
escalate` fired (the plan's author declared that past this point is
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).
Every stop the executor can make maps to one of these four; `stop_class` in
the summary is that number.
Not on the list, and therefore never a stop: a failing test (that is a fix
round), an ambiguity the plan leaves open (decide, and log it), a style
choice, a missing optional field.
### Decision log
Every decision the executor (or a helper, through its report) takes that the
plan did not dictate goes into progress `decisions` as one entry:
`{"step": N, "decision": "...", "cost_if_wrong": "..."}`. `cost_if_wrong` is
concrete: what has to be redone, by whom, how big — "one rename commit",
"step 4's API shape; steps 5–7 build on it". The final report prints the log
(Phase 8 `### Decisions taken`), so the operator reviews the calls instead of
being asked them one by one.
Borrowed: Superpowers subagent-driven-development (MIT) — the stop classes and
the log of choices made while unattended.
## Phase 6 — Step execution loop
The core execution phase. Runs for modes: `execute`, `resume`, `step`.
@ -1179,6 +1282,33 @@ Update progress: `steps.{N}.status = "in_progress"`, `current_step = N`, `update
--- Step {N}/{total}: {description} ---
```
One step at a time. Inside one session the steps run in order in one working
tree; parallel steps are the multi-session wave path (Phase 2.6), never two
helpers in one tree.
#### Sub-step 0 — Record the start point
Before anything touches the tree, record where the step begins:
```bash
git rev-parse HEAD
git status --porcelain
```
- `steps.{N}.start_sha` = the `HEAD` sha. Every later check of this step reads
`git diff {start_sha}`: the review package is the diff from this recorded
start point, not anything the helper says. On `--resume` of a step that
already has a `start_sha`, keep it.
- `steps.{N}.report_path` = `{progress-dir}/steps/step-{N}.report.md` (create
the directory). Every helper round of the step appends to it, and the done
line lands in it (Sub-step G). Hand-over between the executor, the helper
and the controller is in files: the plan, this report, the diff.
- A non-empty `git status --porcelain` (beyond Voyage's own progress and report
files) means work the plan does not know about would mix into this step's
diff. Not a stop (Phase 5.5): log it as a decision
(`cost_if_wrong`: "the controller judges unrelated changes as part of step N")
and name the paths in the report.
#### Sub-step A — Scope fence check (session specs only)
Before touching any file, verify that every file in the step's `Files:` field is
@ -1192,24 +1322,118 @@ Escalating — this step cannot be executed within this session's scope.
Treat this as an automatic `escalate`. Jump to the stop-and-report logic.
#### Sub-step B — Test first (if present)
#### Sub-step B — Test first and proof of red
If the step has a `Test first:` field:
1. If test file is marked `(new)`: note it will be created during implementation.
2. If test file exists: run it. Expect failure (RED state).
3. If test unexpectedly passes: warn but continue — step may already be done.
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.
Do not block on test-first failures — they are expected.
**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
the checkpoint, and neither is a scope finding.
#### Sub-step C — Implement changes
**The test command** is the step's `Verify:` command. It must be ONE
test-runner invocation without shell operators (the allowlist of
`lib/verification/criteria-runner.mjs`); the tools below refuse anything else
with exit 2.
Read the step's `Files:` and `Changes:` fields. Implement exactly as described.
1. **The helper proves red before it builds** (Sub-step C): it writes the test
from `Test first:`, runs the gate
`red-proof.mjs --red-only --base {start_sha} --cmd "{Verify}" --tests {test files, ONE comma-separated argument}`
(the test must FAIL at the start point), locks the test files, and only then
implements.
2. **The executor proves it both ways after the helper returns:**
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/red-proof.mjs --base {start_sha} --cmd "{Verify command}" --tests {test files, ONE comma-separated argument} --json
```
The test must FAIL against the start point and PASS against the result
(the working tree), and a **control run** must PASS: the start-point copy
with the step's whole change laid on top. The start point is a temp
`git clone --shared` checked out at `start_sha` (a real git checkout), with
the step's test files copied in and every gitignored top-level entry of the
working tree (`node_modules`, `.venv`, …) linked in; `--link a,b` adds
deeper dependency paths. A start point that fails for an environment reason
fails the control too, so it is never read as red.
**Rules:**
- Follow `Changes:` exactly — do not improvise, add scope, or optimize
- Use Edit for modifications, Write for new files
- If `Reuses:` references existing code, read that code first for context
- Only touch files listed in `Files:` — nothing else
| Exit | `steps.{N}.red_proof` | Executor |
|------|-----------------------|----------|
| 0 | `"proven"` | continue |
| 1, `not-red-at-base` | `"rejected"` | the test passes without the change, so the TEST is wrong: a finding for Sub-step F, and that fix round is the one exception to the lock — the executor releases the step's test lock for it, the fix helper rewrites the test, passes the `--red-only` gate and locks again |
| 1, `not-green-at-result` | `"rejected"` | the change does not make the test pass: a finding for Sub-step F |
| 2 | `"not-run"` | never a pass and never proven. **plan_version ≥ 1.8:** when the reason is the command (the `Verify:` is not one test-runner invocation), the plan is what failed: the step goes straight up to the plan (Sub-step F, `fix_rounds = 4`, stop class 4); any other reason (a missing test file, an unresolvable start point, a control run that stays red) is a finding for Sub-step F. **Below 1.8:** a step with no `Test first:`, or whose `Verify:` is not a test-runner command, is tolerated as `"not-run"` — counted in the final report among the steps without proof of red, never called proven; any other reason is a finding |
The exit code is the verdict, the output text is not. `base.tap` (when the
runner prints TAP) says what the red was: `fail 1 of 1` on a node test file is
usually a load error, not a named failing test — say so in the step report.
**The test lock (K8).** After its red gate the helper runs
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs lock --files {test files} --step {N}`;
from then on the guard refuses Write/Edit/NotebookEdit on those files, so the
code moves to meet the test and never the reverse. The executor reads
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs status` and records
`steps.{N}.test_lock = "locked"` when it exits 0 and names the step's files.
If `lib/util/test-lock.mjs` is not in this plugin, record `"not-locked"` and
write "not locked: test-lock.mjs missing" in the step report — never silent.
The floor guard (Sub-step D3) still reads every test change in the diff. The
lock is released in Sub-step G, or when the run stops.
#### Sub-step C — Implement through one fresh helper
The executor does not write the step's code. **Launch** one fresh helper for
the step with the Agent tool:
Launch `subagent_type: "voyage:step-implementer"` with a short `description`
("step {N}: {title}") and a prompt that carries, as files and values, not as a
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}`.
**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
changed. A step at `fix_rounds` 4 on `--resume` was re-planned: launch a fresh
`build` helper for the revised step.
- **Never pass the Agent tool's `name` parameter.** A named spawn becomes an
`in_process_teammate`: it does the work, but its result never reaches this
orchestrator (measured: named 0/5 returned, unnamed 3/3 —
`docs/agent-return-channel-defect.md`). The same holds for the controller
(Sub-step E).
- **Never pass `isolation`.** Measured 2026-10-06 (Claude Code 2.1.291): a
worktree-isolated helper branched from the repository's default branch
(`02f320f`), not from the executor's `HEAD` (`2c89a6b`), so it did not see
the earlier steps. Only the operator's `worktree.baseRef: "head"` setting
changes that, and a plugin cannot set it. The helper works in this tree,
from the recorded `start_sha`.
- **Wait for the helper to finish** before anything else; when it runs in the
background, its completion notification is the signal. Never start the next
sub-step or step while a helper runs.
- `steps.{N}.attempts` += 1 for every helper launched for the step — this
one, each fix round's, and the `build` helper after a re-plan. It is never
reset, and every launch after a step's first also takes 1 from
`iterations_remaining`, so the completion gate's reconciliation
(`Σ (attempts − 1)`) holds across re-plans.
The helper's one-line answer is not evidence. After it returns:
1. The report at `report_path` must carry a NEW section for this round
(`## Step {N} - {round label}`) with its `### Ran`, `### Saw` and
`### Did NOT check`; earlier rounds' sections and verdicts stay. A missing
section is a finding for Sub-step F (stale sections do not count).
2. Register EVERY new file so the diff and every check see it — not only the
listed ones: for each path `git ls-files --others --exclude-standard`
prints, run `git add --intent-to-add -- {path}` (stages nothing). A new file
the step did not list is otherwise invisible to `git diff`, to the floor
guard and to the controller. Voyage's own progress file and step reports
are the one exception: never registered.
3. A changed or new path outside the step's scope (`Files:` + test files) is a
scope finding (Hard rule 2) for the controller. The executor does not
revert it on its own.
**Rules the helper is held to** (its agent file carries them; the controller
checks them in the diff): follow `Changes:` exactly — no improvising, no added
scope; touch only the step's scope (`Files:` + test files); read `Reuses:`
first; never weaken a test; never write a stub; never commit.
#### Sub-step D1 — Command verification
@ -1259,7 +1483,9 @@ status = `blocked` and apply `On failure: escalate`. Skip D2 manifest
verification (nothing to verify for a read-only sandbox test). Commit none —
jump straight to Phase 7 with a structured "sandbox-blocked" reason.
If **PASS**: proceed to Sub-step D2 (manifest verification).
If **PASS**: proceed to Sub-step D2 (manifest verification). If **FAIL**: the
failure output is a finding for Sub-step F; still run D2 and D3, so the
controller and the next fix round see every failure at once.
#### Sub-step D2 — Manifest verification
@ -1273,10 +1499,10 @@ holds, regardless of Verify exit code.
file exists in the repo. Count how many exist.
2. **min_file_count satisfied:** count from (1) must be ≥ `min_file_count`.
3. **Forbidden paths untouched:** for each `forbidden_paths` entry, run
`git diff --name-only HEAD~{attempts} HEAD -- {path}` (since this step
`git diff --name-only {start_sha} -- {path}` (since this step
began). Any modified forbidden path = fail.
4. **Bash syntax check:** for each entry in `bash_syntax_check` AND any
`.sh` file that appears in `git diff --name-only HEAD~1 HEAD` (safety
`.sh` file that appears in `git diff --name-only {start_sha}` (safety
net for unlisted scripts), run `bash -n {path}`. Non-zero exit = fail.
5. **must_contain patterns:** for each `{path, pattern}` pair, run
`grep -E "{pattern}" {path}`. No match = fail.
@ -1288,45 +1514,160 @@ Manifest verification FAIL for step {N}:
- {which check failed with detail}
```
Apply the step's `On failure:` clause (same as Sub-step D1 failure). Increment
attempts. Manifest failures count against the retry cap equally with command
failures.
A manifest failure is a finding for Sub-step F, exactly like a command
failure: it counts against the same fix rounds.
If all manifest checks pass: proceed to Sub-step F (checkpoint).
Either way, proceed to Sub-step D3 (floor guard).
Record per-step manifest audit result in progress file:
`steps.{N}.manifest_audit = "pass" | "fail"`,
`steps.{N}.manifest_drift = [{check: reason, ...}]` on fail.
#### Sub-step E — On failure handling
#### Sub-step D3 — Floor guard
If **FAIL**, read the `On failure:` clause. Apply the retry cap: **maximum 2 retries**
(3 total attempts). Track attempts in `steps.{N}.attempts`.
Read the step's diff for weakened tests — by a command:
**`On failure: revert`**
- If attempts < 3: analyze the failure, re-implement with adjustments, re-verify.
```
Attempt {A}/3 failed. Retrying...
```
- If attempts == 3: revert this step's changes:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/test-integrity.mjs --base {start_sha} --json
```
It reports a removed assert, a deleted or skipped test, a muted check
(`|| true`, continue-on-error, an inline lint/type suppression), a stub body
("not implemented") and a threshold loosened in the permissive direction.
| Exit | Meaning | Executor |
|------|---------|----------|
| 0 | clean | continue |
| 1 | findings | each finding goes to the controller (Sub-step E), which rules it `justified` only by quoting the plan line that asks for the change; every other finding is a `violation` |
| 2 | could not run | exit 2 is never green: "floor guard not run: {reason}" is a finding for Sub-step F, and the step cannot pass this round |
A finding is never dropped silently: the step report and the final report list
every one with its ruling.
#### 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:
`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
at the session's effort: run `/trekexecute` at effort `high` or above (the
standard). A session below `high` is a decision-log entry ("controller ran at
effort {level}"), never silent. It reads the diff, not the helper's summary: it is never given
the step report, and it judges what changed, not what the helper says changed.
The prompt (fill in every `{…}`; pass the mechanical results verbatim as JSON):
```text
You are the controller for step {N} of the plan {plan path}. You judge; you
change nothing: no Write, no Edit, no git command that writes.
Read the step's diff yourself: git diff {start_sha} --stat, then
git diff {start_sha}. The diff is the evidence. You are not given the
helper's report and must not look for it.
The step, verbatim:
{every field of the step}
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}.
Give two verdicts:
- "spec": does the diff do what the step's Changes, Files and Interface say -
no more, no less? Name any changed path outside Files.
- "quality": does the code hold - correctness, error handling, edge cases the
step names, no stub, no weakened test, the repo's own patterns?
Rule every floor-guard finding: "justified: <the plan line, quoted>" or
"violation". List what you did not judge.
Answer with exactly one JSON block:
{"spec": "pass|fail", "quality": "pass|fail",
"findings": [{"verdict": "spec|quality", "file": "...", "line": 0, "problem": "...", "fix": "..."}],
"floor_guard": [{"kind": "...", "file": "...", "ruling": "justified: ... | violation"}],
"not_judged": ["..."]}
```
Wait for the controller to finish. Parse its JSON block; an answer without a
parseable block is a failed control (a finding for Sub-step F), never a pass.
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.
Otherwise go to Sub-step F with every finding.
**Re-control after a fix** checks only what was fixed. A fresh controller gets
the previous round's verdicts, findings and floor-guard rulings, and the
round's own change — before the fix round the executor saves
`git diff {start_sha}` to `{report dir}/step-{N}-round-{R}.diff`, and afterwards
hands over `diff -u` of that file against the new `git diff {start_sha}`. It
answers in the SAME JSON block as above:
- per previous finding, fixed or not — a finding not fixed stays in
`findings`; whether the round changed anything the findings did not name
(that is a new finding);
- `"spec"` and `"quality"` for the step as a whole: the previous verdict
carries over for every part the round did not touch, re-judged only where
the round changed something;
- `floor_guard`: a finding identical (same kind, file and text) to one the
previous round already ruled keeps that ruling; only new floor-guard
findings are ruled.
It does not re-judge the rest of the step.
#### Sub-step F — Fix rounds
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
runs `opus` (the step helper pins effort `high`; the controller follows the
session's effort, Sub-step E).
| `fix_rounds` | Goes to | What happens |
|---|---|---|
| 1, 2, 3 | a fresh helper | 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 |
| 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 |
**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`:
the step verbatim, every round's findings and verdicts, and the question for
the planner — which assumption in the step (its Changes, its Interface, its
test) makes it fail. Then stop: step `failed`, run `failed`, and tell the
operator to re-plan the step (`/trekplan` revises it) and run
`/trekexecute --resume`, which launches a fresh `build` helper for the revised
step (`attempts` +1, `iterations_remaining` −1) and keeps `fix_rounds` at 4. A step whose proof of red cannot run on a
≥ 1.8 plan because its `Verify:` is not one test-runner invocation goes here
directly (Sub-step B).
**The plan's `On failure:` clause** decides what happens around the ladder:
**`On failure: revert`** and **`On failure: retry`**
- Fix rounds 1–3 run as above; for `retry`, the clause's alternative approach
is handed to the round-1 fix helper.
- When the step goes up (round 4 or 5), first save the upward files, then
revert this step's changes (nothing of the step is committed yet):
```bash
git checkout -- {files from Files: field}
git checkout -- {the step's tracked files changed since start_sha}
git rm --cached -q -- {the step's new files: registered with --intent-to-add since start_sha}
```
Record failure. **Do NOT proceed to next step.** Jump to Phase 7.
**`On failure: retry`**
- If attempts < 3: use the alternative approach described in the On failure clause.
- If attempts == 3: revert and stop. Jump to Phase 7.
The new files stay on disk, untracked, and `step-{N}-upward.md` names them —
nothing the step made is deleted, and none of it is left registered.
Record failure. **Do NOT proceed to next step.** Jump to Phase 7.5 (Phase 7
runs only when every step passed).
**`On failure: skip`**
- Mark step as skipped regardless of attempt count. Continue to next step.
- Mark step as skipped at its first failed round, without fix rounds. Continue
to next step.
```
Step {N}: SKIPPED (non-critical per plan)
```
Update `steps.{N}.status = "skipped"`.
**`On failure: escalate`**
- Stop immediately regardless of attempt count.
- Stop at the first failed round, without fix rounds (stop class 4: the plan's
author declared that past this point is guesswork).
```
Step {N}: ESCALATED — requires human judgment
```
@ -1336,14 +1677,46 @@ If **FAIL**, read the `On failure:` clause. Apply the retry cap: **maximum 2 ret
```bash
git add {files from completed steps' Files: fields} && git commit -m "wip: trekexecute stopped at step {N} — escalation needed"
```
Jump to Phase 7.
Jump to Phase 7.5 (Phase 7 runs only when every step passed).
#### Sub-step F — Checkpoint
The same wip commit of completed work runs whenever a step goes up (round 4
or 5). Release the step's test lock before stopping
(`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs unlock`, `test_lock` =
`"released"`), so the operator and the planner can edit the test.
Run the `Checkpoint:` git commit command exactly as written in the plan.
#### Sub-step G — Checkpoint, done line, unlock
If the commit fails (nothing to commit, etc.): warn but do NOT fail the step.
The step's verification already passed — the commit is bookkeeping.
1. **Checkpoint.** Stage the step's scope — its `Files:` and its test files,
and only those, never `git add -A` — then run the `Checkpoint:` git commit
command exactly as written in the plan. (The test files are listed apart
from `Files:` in the plan; staging only `Files:` would leave the step's
test uncommitted and charge it to the next step's diff.)
If the commit fails (nothing to commit, etc.): warn but do NOT fail the
step. The step's verification already passed — the commit is bookkeeping.
2. **The done line**, at the step's own commit. A step is done only when a
script says so, and the script writes its line only when the test command
exits 0:
```bash
node ${CLAUDE_PLUGIN_ROOT}/lib/verification/done-line.mjs --step {N} --cmd "{Verify command}" --report {report_path}
```
Exit 0 = `DONE step {N}: … exit 0 at {the checkpoint sha} …` appended to the
step report. Confirm with
`node ${CLAUDE_PLUGIN_ROOT}/lib/verification/done-line.mjs --check --step {N} --report {report_path} --commit {hash}`
(exit 0 = the line names this step's commit). Neither the helper nor the
executor ever types a DONE line.
- Exit 1 = the test went red after the checkpoint (it passed in Sub-step E
moments before: a flaky test or an order dependency). A finding for
Sub-step F; the fix lands as a second commit with the same message, and
Phase 7.5's commit count reports that extra commit as drift — said, not
hidden.
- Exit 2 = it could not run: never green — on a ≥ 1.8 plan that is the
plan's `Verify:` (up to the plan, as in Sub-step B); below 1.8, a step
whose `Verify:` is not a test-runner command records "no done line
(Verify is not a test-runner command)" in its report and rests on
Sub-step D1's exit code.
3. **Release the test lock**:
`node ${CLAUDE_PLUGIN_ROOT}/lib/util/test-lock.mjs unlock` → `test_lock` =
`"released"` (a step that was `"not-locked"` stays so).
```
Step {N}: PASS (committed: {hash})
@ -1474,6 +1847,23 @@ now enforced on the single-session path and not only on the multi-session one.
Continue to Phase 7.5 either way: the manifest audit is independent, and a
verification failure must not hide a manifest drift.
### End to end — "run the thing" (P9 (6))
Tests passing is not the product working. From the runner's JSON, set
`end_to_end`:
- `"verified"` — at least one criterion that **passed** says in its text that
it runs the thing end to end (its text contains `end to end`, `end-to-end`
or `run the thing`, any case);
- `"not-verified"` — otherwise, including a session spec and a plan whose
verification did not pass.
`"not-verified"` is never green: the report says **not verified end to end**
in plain words, and the completion gate (Phase 7.5, clause 4) turns
`completed` into `partial`. The fix is in the plan — a criterion that runs the
built thing — not in the run. (This reads the existing `## Verification`
criteria; it adds no plan field.)
## Phase 7.5 — Manifest audit (independent)
**Runs for all modes except dry-run.** This is the last-line-of-defense
@ -1492,18 +1882,26 @@ produces an independent truth based on objective state, not self-narrative.
2. **Filesystem check:** for each expected_path, confirm the file exists.
3. **Commit-count check:** count commits since session-start
(`git rev-list --count {start_sha}..HEAD`). Expected count =
(`git rev-list --count {session_start_sha}..HEAD`). Expected count =
number of steps with `status=completed` (excludes Step 0 if sandbox_preflight).
4. **Commit-message pattern sweep:** walk `git log {start_sha}..HEAD` and
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).
5. **Bash syntax sweep:** for every `.sh` file in
`git diff --name-only {start_sha} HEAD`, run `bash -n`. Collect failures.
`git diff --name-only {session_start_sha} HEAD`, run `bash -n`. Collect failures.
6. **Forbidden-path sweep:** for each `scope_forbidden` entry, check
`git diff --name-only {start_sha} HEAD -- {path}` is empty.
`git diff --name-only {session_start_sha} HEAD -- {path}` is empty.
7. **Done-line sweep:** for every step with `status=completed` that carries a
`report_path`, run
`node ${CLAUDE_PLUGIN_ROOT}/lib/verification/done-line.mjs --check --step {N} --report {report_path} --commit {steps.N.commit}`
(the line must name the step's own checkpoint commit).
Exit 1 on a plan ≥ 1.8 = drift (`{check: "done-line", expected: "DONE step N", actual: "absent"}`);
below 1.8 it is drift only when the step's report does not say why it has
none (Sub-step G).
Compare the audit result against the executor's `progress.status`:
@ -1548,6 +1946,9 @@ Record in progress file:
> result is OVERRIDDEN `completed → partial`, exactly as the audit-override
> already does (`:1221`). So the counter's liveness is auditable at the gate,
> not merely asserted in prose.
> 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.
>
> Termination authority therefore rests on the deterministic audit + an
> objective stop-signal, never on the model's narrative. Native `/goal` (when
@ -1611,16 +2012,19 @@ Do NOT dispatch a third recovery. Report to the user.
Every recovery/retry loop in this executor has a documented numeric bound.
These are **three distinct axes** — not one nested counter:
- **Per-step retry cap** — `attempts ≤ 3` (initial + **maximum 2 retries**;
Hard Rule 5 / Sub-step E). Bounds the retries of a *single* step.
- **Per-step fix-round cap** — `fix_rounds ≤ 5`: maximum 5 fix rounds per step, escalating upward
(rounds 1–3 fix with a fresh helper, 4 goes to the plan, 5 to the operator;
Hard Rule 5 / Sub-step F). `attempts` counts the helpers a step launched.
Bounds the rounds of a *single* step.
- **Per-session recovery cap** — `recovery_depth < 2` (Phase 7.6 hard cap).
Bounds how deep recovery *dispatch* nests.
- **Global recovery/retry budget** — a new env-overridable constant
`TREKEXECUTE_MAX_RECOVERY_ITERATIONS` (default **25**) — the aggregate ceiling
on the **total count of recovery + retry iterations across the whole
execution**. This is the case the two caps above do NOT bound: many steps each
retrying up to 3× can sum unbounded. It primarily backstops the **retry
aggregate** (recovery dispatch is already hard-capped at 2). Surfaced and
using up to 3 fix rounds can sum unbounded. It primarily backstops the **fix-round
aggregate** (recovery dispatch is already hard-capped at 2). Reaching 0 stops
the run (stop class 4: a plan that needs that many fixes is guesswork). Surfaced and
enforced via the `iterations_remaining` counter (Phase 3 schema; decremented
on every recovery/retry iteration; reconciled at the Phase 7.5 completion
gate).
@ -1787,15 +2191,37 @@ Phase 2.3 (validate exit) and Phase 5 (dry-run) intentionally do not write
**Type:** {plan | session-spec}
**Mode:** {execute | resume | step N}
**Result:** {COMPLETED | FAILED at step N | STOPPED (escalation) | PARTIAL (N/total passed)}
**End to end:** {verified | not verified end to end}
### Step Results
| Step | Description | Result | Attempts | Commit | Manifest |
|------|-------------|--------|----------|--------|----------|
| 0 | Sandbox pre-flight | PASS | 1 | — | n/a |
| 1 | {desc} | PASS | 1 | abc1234 | pass |
| 2 | {desc} | FAIL | 3 | — | — |
| 3 | {desc} | — | 0 | — | — |
| Step | Description | Result | Red proof | Fix rounds | Test lock | Done line | Attempts | Commit | Manifest |
|------|-------------|--------|-----------|------------|-----------|-----------|----------|--------|----------|
| 0 | Sandbox pre-flight | PASS | n/a | 0 | n/a | n/a | 1 | — | n/a |
| 1 | {desc} | PASS | proven | 0 | released | yes | 1 | abc1234 | pass |
| 2 | {desc} | FAIL | rejected | 4 (sent to the plan) | released | no | 4 | — | — |
| 3 | {desc} | — | — | 0 | — | — | 0 | — | — |
Steps without proof of red: {N} ({step numbers}; below plan_version 1.8 only)
### Controller verdicts
Per step and round: spec {pass | fail}, quality {pass | fail}, findings (file:line — problem),
floor-guard rulings, and what the controller did not judge. Report paths: {report_path per step}.
### Stops
{none — the run ended without a stop | the stop that ended the run, with its class from the
closed stop list (Phase 5.5): 1 cannot be undone · 2 security-sensitive · 3 outside the working
copy · 4 plan so broken that everything after it is guesswork, and the file or command that shows it}
### Decisions taken
| Step | Decision | Cost if wrong |
|------|----------|---------------|
| {N} | {what the executor or a helper decided that the plan did not dictate} | {what must be redone, by whom, how big} |
{none, when the plan dictated every call}
### Plan Verification (Phase 7, trekplans only)
@ -1819,8 +2245,12 @@ Phase 2.3 (validate exit) and Phase 5 (dry-run) intentionally do not write
- Not reached: {N}
- Blocked (sandbox): {N}
{if all passed + exit condition passed + plan verification passed}:
All steps completed. Exit condition: PASS. Plan verification: PASS.
{if all passed + exit condition passed + plan verification passed + end to end verified}:
All steps completed. Exit condition: PASS. Plan verification: PASS. End to end: verified.
{if all passed but end_to_end = not-verified}:
All steps passed their checks, but the plan has no passing run-the-thing criterion:
not verified end to end. Result: PARTIAL.
{if failed/stopped}:
### Failure Details
@ -1842,10 +2272,11 @@ To continue (multi-session waves): re-run the same /trekexecute command (not --r
- `completed` — all steps passed AND Phase 7.5 manifest audit passed AND
(for trekplans) Phase 7 plan verification passed
- `partial` — steps passed per executor but Phase 7.5 found drift, OR
Phase 7.6 recovery incomplete
Phase 7.6 recovery incomplete, OR the run is not verified end to end
(Phase 7, gate clause 4)
- `blocked` — Step 0 sandbox pre-flight exited 77; no real work attempted
- `failed` — a step failed and On failure was revert/retry (retry cap hit)
- `stopped` — On failure: escalate triggered
- `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)
**JSON summary block** (always at the end, machine-parseable):
@ -1871,7 +2302,11 @@ To continue (multi-session waves): re-run the same /trekexecute command (not --r
"recovery_depth": 0,
"iterations_remaining": 25,
"legacy_plan": false,
"progress_file": "{path}"
"progress_file": "{path}",
"end_to_end": "{verified | not-verified | n/a}",
"steps_without_red_proof": 0,
"stop_class": "{none | 1 | 2 | 3 | 4}",
"decisions": 0
}
}
```
@ -1968,9 +2403,10 @@ strategy (gates auto-escalation, parallel-wave choice — see High-effort
behavior below). The resolver does NOT control the orchestrator's own
model — that is fixed at invocation time (command frontmatter omits
`model:`, so it follows the session model) and cannot be switched mid-turn.
`/trekexecute` spawns no sub-agent swarm (Hard Rule 10), so
`phase_signal_result.model` has no spawn site here; it is returned for
cross-command uniformity and stats.
`/trekexecute` spawns no swarm: one step helper at a time and one controller
(Hard Rule 10), and both run `opus` — the step agent's pin and the
controller's `model: "opus"` — so `phase_signal_result.model` still has no
spawn site here; it is returned for cross-command uniformity and stats.
For `/trekexecute` specifically: `effort == 'low'` activates `--gates open`
+ sequential-only execution (no worktree-isolated parallel waves — runs
@ -2022,7 +2458,9 @@ code-path.
1. **No AskUserQuestion for execution decisions.** All execution decisions come
from the plan's On failure clauses. If the plan says escalate, stop and
report — never ask.
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).
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
@ -2034,7 +2472,10 @@ code-path.
4. **Fresh verification.** Re-run the Verify command from scratch every time.
Never trust cached or prior results.
5. **Retry cap = 3 attempts.** Initial + 2 retries, then stop. Never loop forever.
5. **Fix rounds go up — maximum 5 fix rounds per step, escalating upward.**
Rounds 1–3 are fixes by a fresh helper, round 4 sends the step back to
planning, round 5 to the operator (Sub-step F). Never a fourth guess at the
same level, never a cheaper model, never a loop.
6. **Never corrupt completed work.** Only revert files from the failing step.
Never touch files from earlier passed steps.
@ -2047,11 +2488,15 @@ code-path.
9. **Progress file is ground truth.** Resume uses the progress file, not git log.
10. **No sub-agents.** The executor reads and implements directly.
No Agent tool, no TeamCreate, no delegation. Enforced (not just
documented) via `disallowed-tools: Agent, TeamCreate` in the frontmatter,
which removes both tools from the pool — `allowed-tools` alone does not,
since omission leaves a tool callable (CC 2.1.152).
10. **One fresh helper per plan step, one controller, nothing more.** The
executor writes no product code: each step is built by a fresh
`voyage:step-implementer` (Sub-step C) and judged by one controller, a
general-purpose helper that reads the diff (Sub-step E).
Helpers never start helpers: the step agent has no Agent tool, and the
controller is told it changes nothing. Never the Agent tool's `name` (a named spawn's
result never returns), never `isolation` (measured: wrong base). No
TeamCreate — kept out via `disallowed-tools: TeamCreate`, since omission
from `allowed-tools` alone leaves a tool callable (CC 2.1.152).
11. **Worktree isolation is mandatory for multi-session execution.** Every
session order names its own git worktree. Never run two or more sessions
@ -2123,14 +2568,49 @@ code-path.
failure (worktree removed, branch deleted, work lost) into a recoverable
one (push succeeded, branch preserved on remote). Source: research/02 R3.
20. **Three distinct iteration axes.** Per-step `attempts` (≤3), per-session
`recovery_depth` (<2), and the global `TREKEXECUTE_MAX_RECOVERY_ITERATIONS`
budget (default 25 — aggregate recovery+retry backstop) are independent
bounds, not one nested counter. Every recovery/retry loop is bounded by at
least one of them. See "Iteration caps + global recovery/retry budget".
20. **Three distinct iteration axes.** Per-step `fix_rounds` (≤5, upward),
per-session `recovery_depth` (<2), and the global
`TREKEXECUTE_MAX_RECOVERY_ITERATIONS` budget (default 25 — aggregate
fix-round + recovery backstop) are independent bounds, not one nested
counter. Every recovery/retry loop is bounded by at least one of them. See
"Iteration caps + global recovery/retry budget".
21. **Never start Claude Code.** No `claude` CLI invocation, no Agent SDK, no
background process that runs a session — not for waves, not for recovery,
not for a test. Work that needs another session becomes a session order
plus a starter line the user gives a session they open themselves
(Phase 2.6, Phase 7.6).
22. **Proof, not word.** Red before green, an unweakened test and "done" are
each a command's exit code — `red-proof.mjs`, `test-integrity.mjs`,
`done-line.mjs` — never the helper's or the executor's statement. Exit 2
from any of them ("could not run") is never green.
## Credits
Every borrowing in this executor is named here, never silent. All sources are
MIT-licensed open source except Kiro (a closed product: only its published
documentation was read) and Cherny (public talks and posts). Read 2026-10-06;
pins and quotes in the local practice analysis.
- **Superpowers** — adapted from obra/superpowers@8ca22db (MIT, © 2025 Jesse
Vincent): `subagent-driven-development` — a fresh helper per task, hand-over
in files, a review package from a recorded start point, a reviewer between
tasks, the stop classes and the decision log of an unattended run (P8, P4);
`executing-plans/scripts/task-done` — the completion line appended only when
the test command exits 0, here `done-line.mjs` (P9 (4));
`systematic-debugging` — after three failed fixes, question the
architecture (P9 (7)). Not borrowed: its ladder of cheaper models.
- **ai-native-sdlc** — bashebr/ai-native-sdlc@11fb8d7 (MIT): the helper's
report contract, ran / saw / did NOT check (P8).
- **Kiro** (documentation only): the task runner takes the diff, not the
agent's account of it, as the reviewer's input (P8). Its background daemon is
not borrowed; only the step loop.
- **GitHub Spec Kit** (MIT), github/spec-kit@2e941fe: test-first as a rule,
mechanized here as `red-proof.mjs` (P9 (1)).
- **agent-skills** — addyosmani/agent-skills@9d0c60d (MIT): the floor guard of
constraint-driven development, as `test-integrity.mjs` (P9 (2)).
- **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).