The coupling to Claude Code's built-in run skill (wave 4, order A2,
decisions 54 and 64). The plan's "run the thing" step, the executor's
End to end part and the plan template each say in one sentence that the
app is seen and driven with run, and that the verdict is still the
End-to-end: command, because run gives no exit code. The template quotes
the name instead of backticking it: every backticked span on a template
step bullet is read as a runnable example.
R2: lib/skills/routing.mjs keeps a short fixed list of Claude Code's own
skills a quiet case may name as owner (BUILTIN_OWNERS, only run, seen in
2.1.303). For such an owner only "not taken" is checked, and the report
says so; ROUTING_NEGATIVE_WON stands, and an owner on no list is still
ROUTING_UNKNOWN_OWNER. skill-writing-criteria-quiet-2 gets owner=run.
The trekplan.md paragraph is reflowed to its old line count so the stats
line_ref there does not move (order B edits the same file); the
trekexecute Phase 9 line_ref fixture moves by two lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- park-attempt-files: parks in the git dir (outside the work tree, so Sub-step C.2 never registers a parked file again), reads a path with a space, names a new file under a header it cannot read, parks a symlink, never overwrites a parked file, and exits 1 while a file the attempt made is still in the tree; the upward diff is saved in one form whatever the git config.
- brief-validator: BRIEF_SC_NOT_RUNNABLE only when a later backticked span is a command; a criterion that only names a flag or a path stays prose (Handover 1 says so now).
- review ids: counted over the final list (runContract), as fill-finding-ids counts review.md; trimmed_count matches rows by id; engine-findings and fidelity-diff use computeFindingIds; the --workflow dedup keeps one source's different findings apart, the decision-23 put-back stays one copy per triplet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The revert of a step sent back to the plan leaves its new files on disk, and --resume built the revised step on top of them: a stale tests/batch.sh holding the rejected line would have passed Verify, the manifest and the success criterion. lib/util/park-attempt-files.mjs moves every still-untracked file the attempt created (from step-N-upward.diff) into the report dir, never deleting it; /trekexecute runs it before the fresh build helper (voyage-lekerepo trial 5, finding 4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decision 41's rerun is scoped to below plan_version 1.8 in the executor and Handover 4 (as the helper and the exit-2 row already say), the decision-30 sentence names the one exception, and the Fixed and Known-issues slicers share one subsection helper.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 7 (516dffa..45aa9e1, WARN: 12 MAJOR, 0 BLOCKER) and the fence defect
found while writing its review.md. Each fix began with a test that failed on
0f400d3 (v6.3.0); suite 2208 (2206/0/2), 14 tests more.
- Tests-only (operator 2026-10-11): the plan's Files: alone decides, and the
text says so (red-proof, trekexecute Sub-step B, step-implementer,
HANDOVER-CONTRACTS). red-proof no longer reads the step's diff, so a change
already in the tree when the step began no longer counts against it, and a
plan placed outside .claude/ reads the same. The round-6 test that had the
diff overrule the plan is rewritten to the new rule.
- The Files: reader keeps a plain file beside a backticked path
(", Dockerfile", "the CLI entry src/cli.mjs", "and Makefile"). Measured on
the 520 Files: fields in 182 plans: no field reads differently.
- Red proof reports a command its timeout killed as killed after the timeout,
not "could not start"; the release check tells a timeout from output past
its buffer and from another signal. One reading of a stop
(criteria-runner stoppedBy) for red proof, the criteria runner and the
release check.
- A trailing `# comment` is dropped in a backticked check too, and in a
command passed as written, so the words screened are the words run.
- The release check reads a relative --review in --repo.
- The 15-minute timeout is defined once (criteria-runner.mjs).
- The findings check reads each cited file once per validation.
- One isMain helper (lib/util/is-main.mjs) for every CLI: the 31 copies of
the realpath guard and the 20 modules still on the file:// guard, which
exited 0 having done nothing under a symlink or a path with a space.
- The Bash guard writes its no-one-to-ask refusal once, on a direct condition.
- The findings check ends a ```json block only at a fence on its own line, so
three backticks inside a JSON string no longer end it (also in 6.2.0).
- Brief-conformance notes: a test for test-lock green on exit 127 (it
passed at once; it fails when 127 is read as could-not-run); the prose now
names the git revision `@~1` as the one leading-@ exception.
Not changed: the example plan's steps 1-2 keep their tests-only warning
(operator decision 2026-10-09; the r5 test records it). Brief note 4 (a
Regression: whose runner is not installed goes to the fix rounds) is a
routing question for the operator.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 6 (v6.2.0..c3641ff, WARN: 11 MAJOR, 3 MINOR; 0 BLOCKER). The round-5
fixes hold (code-correctness-reviewer); three of the new MAJOR were side
effects of running checks with no shell. Each fixed behind a test that
failed first on c3641ff.
- git checks stay in the repo too: no path that climbs out with .., starts
at / or reads <rev>:../x, and no --no-index (both reviewers).
- A trailing `# comment` on a shell-fence line is dropped: with no shell,
its words made an honest check NOT RUN, among them the first check of the
shipped example plan.
- A glob reaches `node --test` only on Node 21 and later, which expands it
itself; on an older Node it is refused with that reason (engines >=18).
- With no shell, exit 126/127 is the test program's own: red proof reads it
as a red (the control run still tells an environment fault apart), the
done line and the test lock as a failure.
- A runner that could not start (not installed) is NOT RUN in the criteria
runner and not-run (exit 2) in regression-check, as red-proof and
done-line already said; never a red suite.
- Tests-only: a backticked `Makefile` alone in its entry is a path, and red
proof confirms the plan's tests-only reading against the step's own diff
(the altitude finding: the diff is a fact, the Files: prose is not).
- release-check stops a suite that never ends (15 minutes) and reads it as
not measured (exit 2).
- The two lines this release added to pre-bash-executor.mjs are ASCII.
- MINOR: git and the other runners share one plain-word pattern.
Not in this round: MINOR f00a2eb8 (shared norm/ticked-path and isMain
helpers) and MINOR on plan-review-dedup's repeated tokenizing.
Suite 2194 (2192/0/2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 3 (v6.2.0..96cb977, BLOCK: 1 BLOCKER, 8 MAJOR, 2 MINOR), each fixed
behind a test that failed first.
- Every allowlisted runner, not only git, takes plain words and a short list
of options that only read: an option that writes a file or loads code
(--test-reporter-destination, --outputFile, --junitxml, --basetemp,
--import, --config, -p, -o, -exec, a reporter path) is on no list; after
`npm test --` only paths follow; `make test` takes no VAR=value. A built-in
node test reporter (tap, spec, dot, junit, lcov) stays allowed.
- /trekexecute: a restore the guard refuses (destructive git, in the mode
that shows no prompts) or the operator declines is never recorded as
reverted: simplify = "revert-refused" and the run stops before review; a
refused step revert is reported with its paths.
- stepTestsOnly: a fixture document under tests/ is a test file, "e.g." is
not a path, ./ is dropped; the plan-file sweep reads Files: with the same
reader (fieldPaths); the validator hands its parsed fields over.
- plan-validator --steps-from asks for Verify: on a 1.8 plan too.
- skill frontmatter: a value starting with # is a hazard; a quote after an
escaped backslash closes the value. Routing reads kind tags with hasOwn.
- release-check reads scope_sha_end as written (an all-digit sha keeps its
leading zero), and Voyage's own artifacts under .claude/projects/ and
.claude/voyage/ committed after the review do not count as unreviewed.
Suite 2170 (2168/0/2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 2 (v6.2.0..0d044f6, BLOCK: 2 BLOCKER, 11 MAJOR, 1 MINOR), each fixed
behind a test that failed first.
- The git runner checks only words /bin/sh passes through unchanged (no
quote, backslash, $, glob or brace) and refuses --output and its
abbreviations: round 1's --output refusal was bypassed by quoting.
- fieldPaths: backticked paths are kept as written, parentheses and all; in
plain text every path-like word counts, and a parenthesised note does not.
- plan-validator --steps-from reads the plan's ## Verification for an
appended End-to-end step, and warns instead of failing under --soft.
- regression-check prints progress_status, the value progress.json takes;
/trekexecute copies it.
- release-check: a plugin in a subdirectory is checked in its own directory,
the suite is not run when a cheaper check already makes the answer exit 2,
and a bad argument or a crash is exit 2.
- Skill routing looks a skill up among skills only, and builds the corpus
vectors once.
- /trekexecute Phase 7.2 says plainly that remediation steps in the review
template's own form stop with a re-plan; making them runnable is a later
order (operator 2026-10-10).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of v6.2.0..f669b87 (BLOCK: 1 BLOCKER, 12 MAJOR, 1 MINOR), each
fixed behind a test that failed first, or, for the two missing tests, a test
shown to fail on a stub of the branch it covers.
- Review steps appended by /trekexecute Phase 7.2 are held to the step rules
on any plan version: plan-validator --steps-from <N> (on a plan below 1.8 a
template review step validated and was executed).
- The guard refuses where no one can be asked also when its rules will not
load; the wave merge path names that refusal instead of MERGE_REFUSED.
Tests for the classifier-error refusal and post-approval-log's own check.
- release-check: the version files may change only in their version after
the review (JSON compared without it), README diff lines are read inside
hunks only, an unreadable diff is exit 2, and npm test runs without
NODE_TEST_CONTEXT.
- What counts as a step of only tests: a note in parentheses is not a path,
and a data file under specs/ is not a test. End-to-end: rides on the last
step that builds behaviour (validator hint, trekplan, template).
- A removed line is cited as <rev>:<path>:<line> (findings-schema hint,
trekreview re-ask).
- skill-lint counts a trailing newline as no line; regression-check takes
commandEnv from lib/util/command-env.mjs; CLAUDE.md names four mode-read
exemptions.
- /security-review, older than v6.2.0: the criteria runner refuses git
--output, which writes a file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1. A step of only tests can never be proven red. One reading,
stepTestsOnly (lib/parsers/plan-schema.mjs, test paths from the new
lib/util/test-path.mjs): the plan validator warns PLAN_STEP_TESTS_ONLY,
red-proof --plan/--step adds tests_only to a not-red-at-base, and
/trekexecute and the step helper send such a step straight up to the
plan instead of into fix rounds that cannot succeed. /trekplan, the plan
template and plan-critic (dimension 8, still 12) say the test belongs in
the step that builds the behaviour.
2. Timing tests under load: the hook helper's kill limit is 30 s and per
call overridable; SC #14 passes on the fastest of up to three runs
against the same 800 ms limit (tests/helpers/timing.mjs).
3. The example plan's On failure lines open with a K11 word.
4. /trekreview saves the reviewer's whole answer for the findings check and
names FINDING_PROSE_LINE_OUT_OF_RANGE (error: re-ask; warning: report).
5. The criteria runner's commands run without NODE_TEST_CONTEXT
(commandEnv, moved to lib/util/command-env.mjs).
6. The resolver returns effort only when the brief's phase signal sets one;
the four commands said it always did.
Also: the K2 hook tests used fixed session ids in a per-user temp dir, so
two suites running at once took each other's approval records; the ids are
now unique per run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
voyage-lekerepo trial 2 (2026-10-08), report section A; the operator decided
they are fixed before the next release:
- A1: a /code-review finding whose category is simplification, efficiency,
style or readability enters as CODE_REVIEW_IMPROVEMENT (MINOR), not MAJOR.
- A2: recommended_action is the engine's own fix when transcribed, otherwise
a test-first step; never the failure scenario dressed as a remedy.
- A3: a /code-review finding outside the exact range it was given is declined
and listed, but no longer holds ALLOW back; /security-review's still does.
- A4: the manifest must_contain recipe uses grep -E -e ... -- so a pattern
like --sep is not read as an option.
- A5: indented sub-points belong to their criterion (counted), and a
criterion with sub-points and no command warns BRIEF_SC_SUBPOINTS.
- A6: a brief requiring bash 3.2 whose criterion runs plain bash warns
BRIEF_SC_BASH_NOT_PINNED; /trekbrief proposes /bin/bash.
- A7: the plan template's and /trekplan's checkpoints stage the step's files.
Tests fail on 5f9faee first (tests/lib/trial2-fixes.test.mjs, 11 of 14).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The skills reading (2026-10-06) found two defects the operator decided to fix
once wave 1 was merged:
- the brief template's Success Criteria examples used a pipe and npx, which
the criteria runner refuses (and the brief validator now warns on);
- trekcontinue and trekexecute called lib/ scripts relative to the working
directory, which is the user's repo, not the plugin.
Tests fail on the old text first (tests/lib/reading-defects.test.mjs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Independent read-only review of c56f852..2d29966 (2026-10-08), each defect
reproduced by the reviewer; each new test fails on that code.
- test-lock green ran ANY allowlisted command: `green --cmd "git status"`
marked a red step green and the stop guard let it stop. The lock now
stores the step's test command (`lock --cmd`); `green` runs only that
one (a different --cmd, or a lock without one, exits 2). trekexecute D1
calls `green` with no command, which also removes the quoting hazard.
- plan-gate --accept-open worked at round 1 and passed the gate; it now
refuses while rounds are left.
- findings-schema --repo-root swallowed a following flag, or took a root
that does not exist, and silently skipped the line check: exit 2.
- brief-review-stamp hashed only the body, so a changed `framing` or
`research_topics` stayed "current"; it now covers the reviewed
frontmatter too (only the post-review stamps are left out).
- red-proof red_kind read an assertion message naming SyntaxError as a
load error; the signatures are now specific.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
voyage-lekerepo trial (2026-10-06) point 7, operator decision 2026-10-08
(«grønt merke på låsen»): the stop guard blocked Stop three times while the
executor waited for the controller, with the step's test already green.
- test-lock.mjs green --cmd "<Verify>" runs the step's test command (same
allowlist + denylist as done-line) and records green_at only on exit 0;
pending clears it; a new lock starts without it.
- stop-test-lock lets a green-marked lock stop; the write guard still
refuses the locked test files.
- trekexecute: D1 marks green after a PASS; each fix round marks pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
voyage-lekerepo end-to-end trial (2026-10-06), report points 8, 10, 12, 15, 16:
- approval-log: «Approve (Recommended)» / «Godkjenn (anbefalt)» is the
approval it labels; the suffix never turns a hollow answer into a yes.
- trekexecute Phase 8: the state-file block imports atomic-write through
${CLAUDE_PLUGIN_ROOT}; a relative import only ran with cwd = plugin root
(the test runs the block from another directory).
- trekreview, trekresearch, trekcontinue: stats go through stats-append like
the other commands, and a failed write is reported, never skipped
silently (closes the open STATE finding for all three). The schema
fixture's stale line refs for these and trekplan are re-measured.
- annotate prints an absolute path, so file://{$ANNOT_HTML} is a live link.
- red-proof: `red_kind` = load-error | test-failure on a proven red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
voyage-lekerepo end-to-end trial (2026-10-06), report points 2, 3, 4, 9:
11 of 13 brief criteria were NOT RUN at review with no warning in /trekbrief;
a plan `git grep` criterion passed validator, gate and three critic rounds,
then felled the run in Phase 7; the End-to-end field and Phase 7's text
search never met; a prose Verify left the test command undefined.
- criteria-runner: runnableCheck() screens a declared check without running
it (same allowlist; an npm run script is not looked up at write time);
/bin/bash tests/<script> allowed; the plan report carries end_to_end,
verified when a passed criterion runs a step's End-to-end command (the
older text form still counts).
- plan-validator 1.8: PLAN_VERIFICATION_NOT_RUNNABLE,
PLAN_STEP_VERIFY_NOT_RUNNABLE, PLAN_END_TO_END_NOT_RUNNABLE,
PLAN_END_TO_END_NOT_VERIFIED (the gate inherits them).
- brief-validator: BRIEF_SC_NOT_RUNNABLE warning; /trekbrief offers a
tests/ script; brief-reviewer no longer proposes unrunnable commands.
- trekexecute: Phase 7 copies end_to_end from the runner; Verify's test
command is its first backticked span.
Measured on the trial's own brief and plan: 11 warnings (the 11 NOT RUN),
and the plan stops on V5 git grep and the unlisted End-to-end command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent review of abc3083 found 15 defects, the two worst verified
in scratch repos. Each fix started with a test that failed.
- Proof of red could be "proven" by the environment: a start point
unpacked with git archive had no node_modules and no .git, so any test
failed there. The start point is now a git clone --shared checked out at
start_sha, gitignored top-level entries are linked in, and a control run
(start point + the change) must pass, or the result is not-run.
- The Test first file was never committed (the template lists it apart
from Files:) and leaked into the next step's diff. The step's scope is
now Files: + test files, staged at the checkpoint.
- New files outside Files: were invisible to every check. Every untracked
file is registered with --intent-to-add; a revert unregisters them.
- The floor guard paired renames (foo.test.js -> .bak read as clean) and
missed skipIf and node's t.skip/t.todo; prose files no longer count as
stubs.
- Phase 7.5 audited from the last step's start_sha; it now uses the run's
session_start_sha.
- The done line is written after the checkpoint and checked with --commit.
- fix_rounds never passes 5; attempts are never reset; re-control keeps
the spec/quality verdicts and earlier floor-guard rulings; the closed
stop list maps every existing stop; failed vs stopped made consistent;
the controller's effort is said, not assumed.
Tests: 1470 (1468 pass, 0 fail, 2 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/trekexecute no longer builds steps itself. Each plan step is built by one
fresh helper (new agent step-implementer: opus, effort high, no Agent tool)
and judged by one controller (a general helper on opus) that reads the
diff from a recorded start_sha and gives two verdicts, spec and quality.
Fix rounds go up, maximum 5: three fixes by a fresh helper, then back to
the plan, then to the operator. No cheaper-model ladder.
Proof is a command, never the model's word:
- lib/verification/red-proof.mjs: the step's test must fail at the start
point and pass at the result (exit 0/1/2; 2 is never green). Reproduces
the 02243c6 red-first measurement (6 of 55) through the shim fixture.
- lib/verification/test-integrity.mjs: the floor guard on the diff
(removed asserts, skipped/deleted tests, muted checks, stubs, loosened
thresholds). Over the last 150 commits: 149 clean, 1 real finding.
- lib/verification/done-line.mjs: DONE step N only when the test command
exits 0.
Test first is required from plan_version 1.8. The test lock (K8) is used
through its CLI and reported as not-locked while it is missing. A run
stops only on a closed list of four classes; every other call goes into a
decision log with its cost if wrong. Without a passing run-the-thing
criterion the result is not verified end to end, never green.
Handover 5 gains optional per-step start_sha, report_path, red_proof,
test_lock, fix_rounds (0-5) and run-level decisions and end_to_end; old
progress files still validate and resume.
No isolation on the helper: measured, a worktree-isolated subagent
branches from the default branch (02f320f), not the executor's HEAD
(2c89a6b), so it would miss the earlier steps.
Borrowings credited in commands/trekexecute.md ## Credits (Superpowers,
ai-native-sdlc, Kiro, GitHub Spec Kit, agent-skills, Cherny).
Tests: 1411 -> 1457 (1455 pass, 0 fail, 2 skipped).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/trekreview of 5680591..84a0621 (verdict WARN: 0 BLOCKER, 3 MAJOR, 1 MINOR,
1 MAJOR suppressed as unverified for length only). All closed red -> green:
- Suppressed MAJOR 1d0171a7 (verified live): Phase 8 spliced {plan-path}
into a single-quoted --payload. A lone quote broke the shell parse (event
lost, no written:false to report); a quoted pair ran a command
substitution and still reported written:true. event-emit now reads
`--payload -` from stdin and the three Phase 8 blocks pass the payload
through a quoted heredoc. Checked under bash and zsh.
- 570ba19b: the SC5 set-step extractor read line by line and never saw the
wrapped Step 0 sentinel (`blocked`); it now matches across the break and
the test asserts `blocked` is read.
- c69a865a: the Phase 9 line_ref in jsonl-schemas.md was 9 lines off. Both
trekexecute rows are now pinned to the exact span by a test.
- 0004fa23 (MINOR): the gates_mode_flag_override block says what to do on
written:false (print the reason, continue).
- emit() docstring no longer says failures are swallowed silently.
- 7ffaede5: chosen to KEEP the prose-only legacy `passed` tolerance, because
a public-plugin run started under the 6.1.0 prose would otherwise re-run
every finished step on resume; recorded as an accepted deviation in the
(local-only) brief. No code change.
Suite 1400 (1398/0/2), was 1393.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The progress file /trekexecute's prose described did not pass the command's own validator: run status in-progress and no partial, no plan_version (required), step status running and passed. Measured on the 4 real progress files: 3 used passed, 2 failed on plan_version. The prose now speaks the validator's vocabulary, site by site: schema (plan_version = the value Phase 2 parsed; run status in_progress|completed|failed|stopped|partial), fresh-mode warning, both resume rules, step start in_progress, step finish completed, the escalation staging rule, the Phase 7.5 commit count (status=completed - left alone, every successful run would read as drift and be overridden to partial) and the final write (adds partial; a sandbox-blocked run writes stopped, report result blocked). Untouched on purpose: plan_verification.status, session-state / NEXT-SESSION-PROMPT status, stats and report result, the tolerant session-orders reader list, Phase 2.6's running state.
Valgt: tolerate a legacy passed on resume (one sentence after each resume rule) because Voyage is a public plugin and an in-flight run written under the old prose would otherwise re-execute finished steps. The validator gets no alias. Valgt: fresh mode also warns for stopped and partial - both are resumable, so they now get the --resume hint instead of a silent overwrite (intended behaviour change).
Red: 3 of 5 new tests fail on the pre-fix text - SC4 (documented progress file validates 0/0 per run status), SC5 (offenders at trekexecute.md 1011, 1055, 1062, 1164, 1169, 1326, 1352, 1489), the legacy-sentence count. Regression guard and known-positive control green on arrival.
Verify 125/125 (trekexecute-progress-schema, trekexecute, session-orders, criteria-runner, progress-validator). Suite 1388 (1386/0/2).
Voyage order 20261005T104513Z-1250514346, plan step 4/5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 9 said 'append to ${CLAUDE_PLUGIN_DATA}/trekexecute-stats.jsonl; if it is not set, skip silently' - the Bash tool env never carries it. It now runs stats-append.mjs trekexecute with the record on stdin, the /trekbrief Phase 7 shape, and reports a written:false. The gates_mode_flag_override record was prose with no writer; it now has an event-emit block (fixed literal payload: --gates is a boolean flag) and a documented shape {ts, event, known_event, payload:{flag, signal_effort}}. Phase 8's three event blocks spliced ${PLAN_PATH} (set in an earlier Bash call that does not survive) and ${WAVE_COUNT} (never assigned) into a double-quoted payload, so a literal run printed payload-not-json, and the decline reason was operator free text in a shell argument. Payloads are now single-quoted with {plan-path} / {wave-count} placeholders and the literal label hold-for-review; each call runs on its own and a written:false is printed and passed. docs/architecture.md and the jsonl-schemas rows (writer, measured line_refs) follow.
Red: 7 of 7 new tests fail on the pre-fix text (Phase 9 writer + no 'skip silently', Phase 9 record lands, no bare ${CLAUDE_PLUGIN_DATA}/...jsonl target, no shell expansion in --payload, the three Phase 8 blocks land known events when run as written in a fresh shell, the written:false sentence, the override writer and shape).
Verify 198/198 (stats-landing, main-merge-gate, gates-flag-coverage, profile-stats-fields, plugin-root-resolution, doc-consistency, no-claude-code-launch). Suite 1383 (1381/0/2).
Voyage order 20261005T104513Z-1250514346, plan step 3/5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Check 4, Step 2a' and Step 5 start with
`[ -n "${ZSH_VERSION:-}" ] && setopt nonomatch`, so an unmatched
session-* glob stays literal under zsh as it does under bash and the
existing `[ -d "$wt" ] || continue` guard skips it. null_glob was not
used: it turns `ls -d .../session-*` into `ls -d`, which lists `.`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1. Step 6 cleared the orders dir before Phase 8's main-merge gate asked,
so a declined gate lost the merged-waves record and the next run
prepared wave 1 again on merged code. The clear now sits only under
the gate's "On confirm" (wave path, next-wave none, Step 4 passed);
Step 6 and a declined gate keep the record, so a re-run goes
next-wave none -> Step 4 -> back to the gate.
2. The gate's Decline row and Recovery surface now say what holds on the
wave path: the waves are already merged on the current branch,
nothing is finalized, the record is kept, and re-running the same
command (not --resume, which Phase 2.5 runs as a single session)
re-runs master verification and returns to the gate. The branch name
is trek/{slug}/session-N; the push-before-cleanup note now holds only
where the push succeeded. The single-session --resume line stays.
3. The report footer adds the multi-session way to continue.
4. wave.json stores the branch the wave was prepared on (write
--branch); Step 3's status call passes the current branch and stops,
touching nothing, when the main checkout is on another branch.
npm test: 1277 tests, 1275 pass, 0 fail, 2 skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One invariant closes the data-loss class: Phase 2.55, 2.6 and 7.6 remove
worktrees only with `git worktree remove` without force and branches only
with `git branch -d`. Whatever git refuses (a worktree with changes, an
unmerged or checked-out branch) is kept, reported with what the user can
do, and never retried with force; every unmerged trek/{slug}/* branch
survives every failure path. Step 1 stops on a branch or worktree left
by an earlier attempt instead of deleting it. After a merged wave, a
progress file is dropped from its worktree only when untracked and
byte-identical to its archived copy, so the happy path still cleans up.
B1: Step 3 refuses to merge into a dirty main checkout, and tells a
refused merge (no MERGE_HEAD: stop, keep wave.json) from a real conflict
(abort, Step 5 keeps the branches).
B2: Phase 2.5, Check 4 and Step 0 take the main checkout from the first
`git worktree list` entry and refuse to run from a linked worktree.
B3: wave.json and merged-waves.json carry the plan's sha256 and refuse
another plan or version, naming the run that holds the orders dir and
the clear command; Step 0 stops on that. Step 6 clears the orders dir
when every wave is merged AND Step 4 passed, no longer keyed on the
Phase 8 label the wave path cannot reach.
Also: every session-orders.mjs call runs from the main checkout with a
main-relative plan path; Check 1 no longer counts .claude/trekplan-sessions/
as a dirty tree (it blocked every re-run after a failed wave); a stuck
wave has a documented non-force way out; Hard rule 12 states the
invariant. The test file only narrows one call-site filter that also
matched Step 0's plugin-root existence check.
npm test: 1273 tests, 1271 pass, 0 fail, 2 skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude Code's positional-argument shorthand replaces $N in a command's
text; awk's $(0) is the same field without that collision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven defects from an independent review of the 6.0 session-order flow,
all verified against the text before fixing:
1. Step 3 branched only on pending/failed/stopped/unreadable, so a session
still writing in-progress (or in_progress) next to a failed one had its
live worktree force-removed. status now classifies each session as
completed, running (no file, pending, in-progress, in_progress, or any
unknown status) or ended-bad (failed, stopped, partial, blocked,
unreadable), keeping the raw status; Step 3 stops on any running FIRST
and touches no worktree, branch or wave.json.
2. The Phase 2.5 wave-in-flight guard was cwd-relative; from a subdirectory
it missed wave.json and Check 4 force-removed the wave. The guard is now
anchored at the git toplevel, and Check 4 opens with a block that refuses
to clean while wave.json exists.
3. Step 6 read progress files from worktrees that Step 3/5 had removed. A
new archive subcommand copies each to orders/wave-W-session-N.progress.json
before any worktree goes; Step 6 and Phase 8 read the copies.
4. NO_WAVE always prepared the lowest wave, so a re-run after a wave-2
failure re-ran wave 1. record-merged writes orders/merged-waves.json
after each merged wave (Step 5 keeps it); next-wave returns the lowest
unmerged wave or none (then Step 4). Phase 8 clears the orders dir only
after a completed report; the record refuses another plan's path.
5. Phase 7.6 named a deleted worktree and promised an unwired re-run. The
order now names the main checkout and says the recovery session's own
audit is the check.
6. (test side, previous commit) progress paths are asserted literally.
7. Hard rule 12 now carries the awaiting-sessions exception.
npm test: 1262 tests, 1260 pass, 0 fail, 2 skipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Valgt the literal ${CLAUDE_PLUGIN_ROOT} because that is the form Claude
Code substitutes in a command's text (recorded sessions ran
node <cache>/voyage/5.11.1/lib/...); a :- default is never replaced and
the Bash env is empty. The fallback now picks the highest installed
version under cache/<marketplace>/voyage/<version>/ by version number
(mtime ranked 5.11.0 above 5.11.1 on the measuring machine). Without
this, /trekexecute's multi-session Step 0, plan verification (Phase 7),
/trekreview's criteria checks and /trekresearch's loop gate all exit 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operator decision 27.09.2026: Voyage works the PM way, so nothing in it
starts Claude Code. Work that needs another session becomes an order plus
a starter line the user gives a session they open themselves; Voyage
never starts the session.
/trekexecute's multi-session path (Phase 2.6) now prepares one wave at a
time: worktree per session, then lib/util/session-orders.mjs writes one
order per session (worktree, steps, done-condition, starter) plus
orders/wave.json, and the run stops. Running the same command again reads
each session's progress file (status mode), merges sequentially with
push-before-cleanup as before, and prepares the next wave. Phase 2.5 skips
the destructive pre-flight while a wave is in flight. Phase 7.6 recovery is
handed over the same way. Hard rule 21: never start Claude Code.
Removed with it, because they existed only for launched children: the
ANTHROPIC_API_KEY billing check (and its dry-run line and Hard rule 1
exception), the hooks-fire version precheck, the safety preamble, the
scoped-tool/turn/budget flags, templates/headless-launch-template.md, the
decomposer's launch.sh (now orders.md), /trekplan's "launch", the README
billing and MCP tuning sections, and scripts/q3-cache-prefix-experiment.mjs
(an experiment that spawned the binary; its results stay in docs/).
/trekresearch's deep-research pre-gate no longer runs the binary for its
version; the post-hoc check was already the authoritative guard.
Chose orders + starters over in-session subagents because the order's done
criterion asks for exactly that, and because /trekexecute disallows the
Agent tool on purpose (Hard rule 10); reopening that is a separate change.
Gate: 54 -> 0 hit lines outside EXEMPT. npm test 1250 (1245 pass, 0 fail,
5 skipped: 3 are the retired-reference scan without its local list).
verify.sh 7/0. The fixture run (tests/lib/session-orders.test.mjs) writes
two orders and wave.json for wave 1 and leaves a trap binary untouched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured 2026-09-18: nine ordinary shapes of a plan's `## Verification`
section parsed to zero criteria - an untagged fence, a ```text fence, a
markdown table, `## Verification (acceptance)`, `## Verification:`,
`### Verification`, an unclosed fence earlier in the document. Every one came
out as `0 of 0`, NOT OK, exit 1, and Phase 7 then forbade `result: completed`
without anyone being told that the FORMAT, not the code, was the problem.
"The section is empty" and "I cannot read this format" are different facts.
Three changes, one hole:
- The runner reports `NO_CRITERIA` with a source line (`plan.md:NN`) when the
section is there and nothing in it parsed, and names the two forms it does
read. Same for a brief's `## Success Criteria`, so the evidence block the
conformance reviewer gets says which of the two it is looking at rather than
showing an empty table.
- Phase 7 says it out loud instead of failing silently: report the source line
and the two forms, and say that the plan is what failed there, not the run.
- `/trekplan` now pins what it produces to what the runner reads: the heading
is exactly `## Verification`, the criteria are a bullet whose first
backticked span is the command or a shell-tagged fence, and the command must
be one the allowlist runs. A doc-consistency test holds the writer and the
reader together, so a runner that learns a new form must update the source.
Honest about the round trip: the two round-trip tests were GREEN on arrival -
the template already writes the bullet form the runner reads. What was missing
was not the format but the PIN: `/trekplan` mandated neither the heading string
nor the format, so a plan could satisfy the command's own instructions and
still parse to nothing. The tests now hold that.
Red first: 5 of the 7 new tests failed before the change (3 NO_CRITERIA, 2
doc-consistency); the 2 round-trip tests are guards, and said so above.
Suite 1161 (1159/0/2). Gate unchanged: defects 0 of 7, intact, exit 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A denylist in front of /bin/sh is whack-a-mole. Measured 2026-09-18, end to
end through both screens: 5 of 11 named evasions ran with real effect - a
`command` prefix reached git, an escaped `rm` inside a shell fence deleted a
directory, `find -delete` deleted a file, `>|` and `tee` wrote outside the
working tree, a python one-liner deleted the whole tree - and 19 of 28 got
past the refusal list on its own. Every quoting, aliasing and indirection form
of the shell is another mole.
So the screen is now an ALLOWLIST. A criterion runs only when its first word
is a known test runner (npm test, npm run <script package.json declares>,
node --test, vitest, jest, pytest, python -m pytest, uv run pytest,
cargo test, go test, make test, bash <script under tests/>, a read-only git
subcommand) AND the command carries no shell operator and no newline.
Everything else is NOT RUN with the reason said out loud: never run, and never
reported as a failure either - an absent measurement is not a finding. That
also closes the smaller hole in the same file: a bare word a sentence merely
names (`whoami`, `login`, `package.json`) is no longer executed, because it is
not a runner.
REFUSED_BY_POLICY is gone with the list that produced it; a command outside
the allowlist is `unrunnable`, which in plan mode still fells the run and in
brief mode is reported to the reviewer as an absent measurement.
What the allowlist deliberately does NOT do, said in the file and in the
reviewer's rubric: it is not a sandbox. `npm test`, `npm run <script>` and
`make test` run whatever the repo's own package.json/Makefile says they run,
including a script that pushes - that is the repo's responsibility. And it
rejects honest commands too: an env prefix, a project's own binary, anything
piped. A check that needs one of those is declared through
`bash tests/<script>.sh`, the documented way in.
Red first: 6 of the new tests fail against the previous runner (measured with
an always-allow shim so the module still loads), including the end-to-end one
where the canary directory was deleted and files were written outside the
tree. The fixtures move from `true`/`false` to two allowlisted shell fixtures,
because `false` is no longer a runner - the fail case must still be a real
non-zero exit, not an unrun criterion.
Suite 1148 (1146/0/2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/trekreview passed --cwd and trekexecute Phase 7 did not, so the same criterion
could resolve two ways in the two phases (PM checkpoint 2026-09-18, MINOR).
It stopped being cosmetic with the refusal list: --cwd is the boundary a write
is measured against, so leaving it unset silently moves that boundary to
whatever the process cwd happens to be. Both phases now resolve
CRITERIA_CWD="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
- one form, with a fallback, so a repo-less checkout falls back instead of
passing an empty --cwd. Pinned in tests/lib/doc-consistency.test.mjs.
Red first: the new pin failed on both phases before this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/trekreview now runs the commands a BRIEF declares, and a brief is an artifact
that can arrive from outside the repo. Measured 2026-09-18 on 6cafb4c: the
executor denylist stopped a download piped into a shell, but the remote-writing
git subcommand and a recursive delete of a path both RAN. The denylist screens
catastrophe (root deletion, fork bombs, mkfs); it was never meant to screen an
artifact under review.
A second screen, in the runner and ahead of the denylist, refuses four classes:
- a remote-writing git subcommand. The subcommand is found by walking git's own
options (`-C`, `-c`, `--git-dir`, ... take a value), so `git status` and
`git log` still run and `git -C sub push` does not.
- a recursive delete: any `rm` carrying `-r`/`-rf`/`--recursive`. A plain
`rm build/artifact.txt` still runs.
- a download piped straight into a shell (also caught by the denylist; pinned
here so the runner does not depend on another file for it).
- a write outside the working tree. `/dev/null`-class devices are fine, and so
is anything under the working tree; `~/...`, an absolute path elsewhere, and
a target carrying an unexpanded `$VAR` are refused - the runner cannot know
where a variable points, and guessing is how a screen stops screening.
A refusal is its own outcome, REFUSED_BY_POLICY: the command never reaches a
shell, and `summary.ok` is false in both plan and brief mode. For the reviewer,
REFUSED is like NOT RUN - the absence of a measurement, never on its own a
finding - and the rubric and the evidence block both say so.
Chosen deliberately, and it is stricter than today's habit: writing scratch to
/tmp is refused too. The repo's own example plan does `> /tmp/out`. Verification
output belongs in the working tree; exempting the whole system temp dir would
have made the rule unstatable, since a working tree created under /tmp then
contains its own escape hatch.
NOT covered, stated rather than implied:
- other writing git subcommands (tag, remote, config, gc) - only push is listed
- writes through a wrapper: `sh -c '...'`, `xargs`, `find -exec`, a Makefile
target, a script the criterion invokes. The screen reads the command it is
given, not what that command goes on to do.
- `>` inside a quoted string reads as a redirect, so a criterion echoing a
literal `>` is refused. Fail-closed, on purpose.
- the whole surface still runs with the invoking process's permissions; this is
a refusal list, not a sandbox.
The denylist-layer test now uses a stand-in command with a screen double: the
refusal list catches a recursive delete first, so naming one there would have
stopped exercising the denylist layer at all.
Red first: the 6 new tests failed before this change (`refuseCommand` did not
exist), and the fixture brief's four writes ran.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A trekplan's `## Verification` section is where the brief's success criteria
land. Phase 7 opened with "**Skip for trekplans.**", and only the multi-session
wave path (Phase 2.6 Step 3) ran master verification. A plan executed in ONE
session therefore reported `completed` without ever running the criteria it was
measured against — the executor's own belief was the only evidence.
The check now exists as code, not as an instruction:
- `lib/verification/criteria-runner.mjs` parses the criteria an artifact
DECLARES (a plan's `## Verification`, a brief's `## Success Criteria`), runs
each command, and returns a verdict built from exit codes. Fail-closed
throughout: a placeholder, a prose-only criterion, or an unavailable screen
is `unrunnable`/`blocked`, never `passed`. A plan with no `## Verification`
section exits 1 — a plan that promises no end-to-end check cannot be reported
as verified.
- Every command is screened through the plugin's own PreToolUse denylist
(`hooks/scripts/pre-bash-executor.mjs`) before it reaches a shell. Chose
invoking that hook over its documented stdin protocol rather than copying its
rules, because a command spawned from node never passes through the Bash tool
and so the hook cannot fire by itself — this keeps exactly one denylist.
- Phase 7 is now "Exit / verification check": session specs run the exit
condition, trekplans run the criteria runner. Phase 4's entry-condition skip
for trekplans stands — a plan carries no entry condition; the exit side is
not symmetrical.
- A failing criterion FELLS the run: `plan_verification.status != "passed"`
forbids `result: completed`. That is clause 2 of the stop-signal contract,
now enforced on the single-session path too.
Red first: `tests/lib/criteria-runner.test.mjs` (26 tests) against two committed
fixture plans, one of which declares a criterion that fails on purpose. The
doc pin in `tests/lib/doc-consistency.test.mjs` guards the wiring — a capability
no phase calls is the same defect wearing a lib/ file; verified red against the
pre-fix Phase 7 (skip present, runner absent, no fell-the-run clause).
Suite: 1108 (1106/0/2), up 27 from 1081.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up to 63e78c5. Adversarial review found that the fix had reintroduced,
in a smaller form, the exact defect the order was filed about.
1. The rewritten Check 2 block opened with
`git ls-files --error-unmatch "$PLAN_PATH"` while the assignment lived in
the prose above it ("Set PLAN_PATH to the plan path, then run:"). Nothing
upstream sets it — Check 2 is Phase 2.55, and REPO_ROOT/WORKTREE_DIR are
only created in Phase 2.6 Step 1, which runs after. An agent copying the
block verbatim would have run it with PLAN_PATH empty. The note was prose,
the code was what ran. The block now carries
`PLAN_PATH="{plan-path}"` as its first line, the same placeholder idiom
the rest of the file uses (`BRANCH_NAME="trek/{slug}/session-{N}"`).
The three Check 2 tests could not have caught this: they injected
PLAN_PATH through the environment, supplying what the doc has to supply
itself. They now substitute `{plan-path}` the way an agent does, assert
the placeholder is present, and pass PLAN_PATH="" in the environment so a
block that fails to assign it goes red. Control run with the assignment
line stripped: `fatal: empty string is not a valid pathspec` -> exit 1.
2. Both copied blocks contained an em-dash (one in an `echo` string, one in a
comment). Shell that bash 3.2 executes stays ASCII — a multibyte char
under `set -u` has crashed it before. Replaced with plain `-`; the prose
outside the fences keeps its em-dashes. New test asserts both blocks are
ASCII-clean, with a known-positive proving the detector fires on an
em-dash.
Suite 1022 (1020/0/2) -> 1023 (1021/0/2). No version bump, no release.
63e78c5 stays valid in history: the order archive and two coord messages
point at it.
Co-Authored-By: Claude <claude-opus-5>
Two independent defects took down a real voyage on macOS today
(llm-ingestion-okf, wave 1, zero steps executed). Both paths led to the same
end state: the worktree had no plan.
1. Phase 2.6 Step 2a' derived the project relpath with
`realpath --relative-to`, which is GNU coreutils only. On BSD realpath
(macOS default) the substitution fails silently: PROJECT_REL ends up
empty, `mkdir -p "$wt/"` and `cp ... "$wt//"` both succeed, and
brief.md/plan.md land at the worktree root where no child session looks.
The portable form existed only as a prose note saying an operator "may
substitute" it — the note was prose, the code was what ran. The block now
uses `python3 -c os.path.relpath` (stdlib), resolves both operands with
bare realpath (identical on BSD and GNU, and correct across the macOS
/var -> /private/var symlink), and aborts loudly when the relpath is
empty or escapes the repo instead of dropping files at the root. The
coreutils note is deleted rather than extended: two forms means the next
reader picks, and that pick is what failed.
Measured on this machine 2026-08-31 (Intel Mac, no coreutils):
`realpath --relative-to=...` -> `realpath: illegal option -- -`;
bare `realpath /Users/ktg/.claude` -> correct path, exit 0.
2. Phase 2.55 Check 2 ran `git add {plan-path}` unconditionally. When the
project directory is gitignored (`.claude/projects/` is tool-managed and
local-only — normal, not exotic) the add refuses and the plan never
reaches HEAD. Check 2 now probes with `git check-ignore` and branches:
tracked -> nothing; untracked -> add + commit as before; ignored -> step
aside and let Step 2a' be the delivery path. `git add -f` was rejected as
the fix: forcing operator-local artifacts into history publishes them to
whatever remote the repo pushes to. A `check-ignore` exit code other than
0 or 1 is fatal and stops execution — a probe that failed is not a probe
that answered "not ignored".
Also fixed in the same block: `[ -d research ] && cp -r ...` as the last
statement of the loop body made a project without research/ exit the wave
non-zero.
TDD, Iron Law: tests/commands/trekexecute-parallel-portability.test.mjs
extracts both shell blocks from commands/trekexecute.md by grep-able anchor
and executes them, so the tests bind to what an agent actually copies. The
assertions check FILE PLACEMENT, not exit status — the whole defect is that
the broken form exits 0. Controls, all present before the fix: a
known-positive BSD-realpath stub (bare path resolves, GNU flag rejected with
`illegal option`), a negative control running the old GNU form under that
stub and asserting plan.md lands at the worktree root, a known-positive
Check 2 arm where a non-ignored plan is still added and committed, and a
stubbed fatal `git check-ignore` (128). Red first: 7 failed, 2 passed (the
two controls). Green after: 9/9.
Suite 1013 (1011/0/2) -> 1022 (1020/0/2). No version bump, no release.
Order: 20260831T214411Z-941965142-from-.claude (from .claude).
Co-Authored-By: Claude <claude-opus-5>