Known-positive for case 4, in a scratch copy (never this tree):
brief-validator.mjs with the BRIEF_V51_MISSING_SIGNALS check made
'if (false)'. The case still passed 3 of 3: the child read the rule in
the command prose, halted, named phase_signals, and even found the
planted 'if (false)'. So the halt alone does not show the validator
fired, and no grader checked what expected_outcome already promised
('brief-validator ... reports BRIEF_V51_MISSING_SIGNALS').
New test: case 4 needs a names-code grader reading the validator's JSON
code from the trace (same form as the intent case). Red: 1 of 10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scaffold writes a current intent_approved_hash (sha256 over ## Intent +
## Goal) into the case-4 brief. Chose a literal marker over running
intent-approval.mjs --stamp in the scaffold because --stamp emits a
brief-approved record into the live stats the yardstick reads: every eval
run would count as an approval.
New grader no-intent-halt (regex, trace, not_contains
code\W{1,12}BRIEF_INTENT_): a halt at the intent gate fails the case
instead of passing it for the wrong gate.
says-fail / says-pass take FAILED / PASSED as the verdict word. v2's word
boundary dropped them. The one says-fail failure (PM, 1 of 7) is not
explained: its trace was deleted, and 0 of 13 kept replies used another
spelling than FAIL.
Case 4 alone, 10 runs: 10 of 10. In 3 of 10 the child ran the intent gate
first, passed it and went on to the brief-validator. That order halted the
case before. Before the fix, same day: 5 of 6.
Green: tests/lib/proevesett.test.mjs 9 of 9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Case 4 (plan-halts-without-phase-signals) measured on dc9b480, 6 runs:
5 of 6. The failing run called only intent-approval.mjs, halted on
BRIEF_INTENT_NOT_APPROVED, and so failed validator-ran and
halt-names-signals: the fixture has no intent marker, so the case can
measure the wrong gate. PM: 6 of 12 on dc9b480, 7 of 10 on 66e1fa1.
New tests: the case-4 fixture must pass intent-approval --check and fail
only BRIEF_V51_MISSING_SIGNALS; a not_contains grader must fail the case
on any BRIEF_INTENT_* code in the trace. says-fail / says-pass must take
FAILED / PASSED as the verdict word (v2's word boundary dropped them).
Red: 3 of 9 in tests/lib/proevesett.test.mjs fail, each for its own reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2 = eight cases, expectations committed in 3892a83 before the run.
Run 1: 8/8 cases, 36/36 graders, 65 s, $1.97 (list-price estimate).
Known-positive for the intent-gate case, in a scratch copy: --check made
to accept everything → 0.75 (names-code fails). Removing only the gate's
command line from trekplan.md did NOT fell it (1.00): the child rebuilt the
call from the surrounding prose — the case measures the gate's effect; the
exact line is pinned by the unit test that executes it. One green run of
case 5 does not prove the instability gone; that needs more runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New case plan-halts-without-intent-approval (expectation committed before
its first run): a 2.1 brief WITH phase_signals and no intent marker.
Graders: intent-approval.mjs --check ran; the trace carries the gate's own
JSON code for BRIEF_INTENT_NOT_APPROVED; no Agent; no plan.md.
review-requires-project: the arg parser now has a CLI that checks the
required flag and prints 'Error: --project <dir> is required.' + usage
(exit 1, code ARG_REQUIRED_MISSING). trekreview.md runs it with
$ARGUMENTS (it passed "$@", which the Bash tool never has, so the parser
never ran — 0 Bash calls in the PM's case-5 traces) and relays its stderr
instead of composing the line. The grader project-required (reply regex,
unstable on backticks) is replaced by parser-ran + names-missing-project
(trace, the parser's JSON code), the same form as names-rule.
Graders: no-error-code is bound to the validator's JSON output form and
covers every REVIEW_* code (WRONG_TYPE and VERSION_FORMAT were missing);
says-pass/says-fail reject a preceding NOT and a longer word; every case
with no-write also gets no-bash-write (redirect, tee, touch, cp, mv, rm,
sed -i in the Bash command). Checked on the PM's 10 recorded traces: no
false positive; known-positive/negative still split.
Red 348aa95 6/7 → green 7/7. Suite 1231: 1229 pass / 0 fail / 2 skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Offline structure tests for evals/ (the suite itself needs headless runs):
- no case reaches the steg 1 intent gate (PM: 0 traces touched it). The
new case's scaffold is in: its brief passes brief-validator --soft
(2.1 WITH phase_signals) and --check gives BRIEF_INTENT_NOT_APPROVED —
that test is green, so the fixture reaches the gate.
- review-requires-project: trekreview.md composes 'Error: --project <dir>
is required.' in prose (8/10 in the PM run, backticks broke the regex),
and its arg-parser line passes "$@", which the Bash tool never has.
- no-error-code misses REVIEW_WRONG_TYPE; PASS/FAIL graders are raw
substrings; no-write cannot see a write through Bash.
7 tests, 6 red.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M3: the header claimed "reflowing a paragraph keeps the approval"; joining
lines gives STALE (PM probe iii-c). It now says what keeps the approval
(indentation, in-line spacing, blank lines, CRLF) and that joining or
splitting lines makes it stale — erring safe. Also states the one-section
and code-block/comment rules from M1.
M7: the trekreview exemption is read from the brief's own frontmatter, so a
relabelled brief walks past the gate — the same trust class as self-stamping.
Now said in the module header, HANDOVER-CONTRACTS and /trekplan's gate prose.
HANDOVER-CONTRACTS also names both brief modes the --approve path reaches.
Red 097bae8 (M3, M7) → green. Suite 1224: 1222 pass / 0 fail / 2 skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M4: the halt regex matched any **halt** in a 2500-char window, so the PM's
mutant (first **halt** → "continue anyway") survived. Two new tests pin the
sentence right after the gate command and the could-not-run halt. Mutant
run: old test ok, both new tests not ok.
M5: nothing guarded 2045432's allowlist line. New test: trekbrief keeps
intent_approved, drops task/project_dir. Mutant (line removed): not ok.
M3 (red): the header claims a reflow keeps the approval; joining lines is
STALE. Pins the honest wording + the STALE behaviour.
M7 (red): the trekreview exemption is not documented as a bypass of the
same trust class as self-stamping (header, HANDOVER-CONTRACTS, trekplan).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M1: headings inside fenced code blocks and HTML comments are text, not
headings, and ## Intent / ## Goal must each appear exactly once. A second
section, an example section in a code block or comment, or a '## ' line in
a code block inside Intent no longer carries the old approval: check gives
INVALID (duplicate) or STALE, stamp refuses a duplicate. A plain brief
hashes exactly as before (pinned), so no stamped brief goes stale.
Re-run of the PM's 29 probes: vi-a, vi-b, vii-a, vii-b now stop; the rest
unchanged. The PM's p7c probe was a no-op (its replace string is not in the
base brief); vii-c is covered by its own test, red on 66e1fa1.
M2: /trekbrief --approve takes <project-dir | brief-file>. New
intent-approval.mjs --resolve decides: a directory → <dir>/brief.md and
/trekplan --project; a file → itself and /trekplan --brief. Phase 4h and the
stamp line use {BRIEF_PATH}; /trekplan's halt table names
/trekbrief --approve {brief_path}, which works on both brief modes, and so
does the gate's hint. HANDOVER-CONTRACTS' "open to every producer's brief"
is now true. Chose fixing the path over rewording the contract because two
real briefs (docs/*-brief.md) had no approval path at all.
Red ff760ed 9/29 → green 29/29. Suite 1219: 1217 pass / 0 fail / 2 skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M1 (PM probes vi-a, vi-b, vii-a, vii-b, vii-c): extractSection takes the first
## Intent and does not see code blocks or HTML comments, so an added, hidden
or cut-off section rides on the old approval. 5 red. vii-d (comment inside
Intent) and the plain-brief hash are pinned green so the fix cannot move them.
M2: --approve resolves only <dir>/brief.md; a brief planned with
--brief docs/x-brief.md has no approval path but a raw --stamp. 4 red.
The stamp helper now substitutes {BRIEF_PATH} as well as {PROJECT_DIR}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New lib/stats/stats-append.mjs: one record on stdin, appended to
<data dir>/<kind>-stats.jsonl. The data dir is CLAUDE_PLUGIN_DATA, else
~/.claude/plugins/data/voyage-ktg-plugin-marketplace (the yardstick's
default). A failed write exits 1 with a reason: reported, never silent.
/trekbrief Phase 7 and /trekplan Phase 12 now run it instead of
"append to ${CLAUDE_PLUGIN_DATA}/…; skip silently". intent-approval's
resolveApprovalDataDir delegates to the same resolver.
Chose a stdin heredoc over a --json argument because a JSON record in a
shell argument breaks on quotes in task text. Only trekbrief and trekplan
are rewired (økt 2's countable form needs exactly those two); the other
commands' stats prose is unchanged and docs/architecture.md says so.
Red a358059 7/7 → green 7/7. Suite 1208: 1206 pass / 0 fail / 2 skip.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 7 of /trekbrief and Phase 12 of /trekplan say "skip silently" when
CLAUDE_PLUGIN_DATA is unset, and the Bash tool env never carries it — the
same silent skip that left brief-approved at 0 records. Økt 2's countable
form (brief-approved + intent_approved: true + a trekplan record with the
same slug) cannot be counted until those records land where the yardstick
reads. 7 tests, 7 red: no stats-append line in either command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/proevesett.md records what the suite covers and what it does not, how to
run it, the verbatim pass/fail definition with its source, and the first run:
6 of 7 cases (graders 25 of 26), 7 claude -p children, 54 s.
The red case, review-requires-project, lost only its text grader: the child
paraphrased a guard that lives in prose. A diagnostic re-run passed, so it is
unstable, not broken, and it stays red - loosening the pattern after the run
would fit the grader to the result.
Known-positive shown in a scratch copy: with the finding-ID check disabled in
review-validator.mjs, review-validate-flags-bad-finding-id drops from 1.0 to 0.6.
Also recorded, measured by probe: ${CLAUDE_PLUGIN_ROOT} is substituted in the
expanded command text but empty in the child's Bash env, and the plugin
directory is readable from the child's sandbox.
Veikart step 4 uses the suite as its yardstick: a rule change that lowers n stops.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The trekbrief-stats record gained intent_approved in f5dc08f (prose +
jsonl-schemas row) but not lib/exporters/field-allowlist.mjs, so the
exporter would drop it silently. Boolean, low-cardinality — safe label.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven `claude plugin eval` cases under evals/, runs: 1, deterministic graders
only (regex, tool_used, file_exists), no ablation. Each case tests one thing
Voyage promises and stops within minutes, headless:
- plan-requires-brief, plan-project-not-initialized, plan-rejects-unknown-export,
review-requires-project: argument guards stop before any Agent or Write.
- plan-halts-without-phase-signals: a brief_version 2.1 brief without
phase_signals halts /trekplan at the sequencing gate, before the swarm.
- review-validate-flags-bad-finding-id (known-positive) and
review-validate-passes-clean-review (known-negative): /trekreview --validate
names REVIEW_BAD_FINDING_ID on a planted bad ID and stays clean on a valid file.
Each case's expected_outcome is committed here, before the suite has run.
Fixtures are written by an inline scaffold.sh (needs --scaffold): chose inline
heredocs because a run cannot read the eval directory, and the add_dirs path
mapping is not documented. evals/results/ is ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Veikart steg 1, økt 1 av 2. The three holes measured in the grounds:
(1) ## Intent / ## Goal were only checked for existence, (2) no approval
marker existed, (3) brief-approved was emitted only on the auto path.
- lib/validators/intent-approval.mjs: --check / --stamp. The marker is
intent_approved_hash (sha256 over normalized ## Intent + ## Goal) +
intent_approved_at. Editing either section after approval → STALE.
- /trekplan Phase 1 (Read the brief, both --brief and --project): runs
--check and HALTS on BRIEF_INTENT_NOT_APPROVED / _STALE / _INVALID with
the remedy spelled out; an unrunnable check halts too.
- /trekbrief Phase 4h (before the Phase 5 fork, asked even in --quick):
shows Intent + Goal verbatim, AskUserQuestion Approve / Revise / Leave;
only "Approve" runs --stamp. --stamp emits brief-approved, so the manual
(default) path records it; the auto path's own emission is removed.
New mode /trekbrief --approve <project-dir> = Phase 4h alone.
- README, CLAUDE.md, command-modes, HANDOVER-CONTRACTS §Handover 1,
jsonl-schemas (trekbrief-stats gains intent_approved).
Valgt ingen brief_version-bump fordi skjemaendringen er rent additiv (to
valgfrie felt) og kravet sitter i /trekplan — enhver produsents brief kan
godkjennes via /trekbrief --approve uten produsentendring. Valgt eget
--check-kall i stedet for et flagg på brief-validator fordi --brief-stien
i dag ikke kjører validatoren i det hele tatt; et nytt validatorkall der
ville også stoppe på andre feil. Valgt fallback-datamappe = målestokkens
(plugins/data/voyage-…) fordi CLAUDE_PLUGIN_DATA er tom i Bash-miljøet og
event-emits egen fallback er stille skip — slik ble brief-approved 0 records.
What the marker does NOT prove (module header, command prose, contract):
same user, same machine — any session can stamp. A trace, not a signature.
Suite 1183 → 1201 (1199/0/2). Mutants M1–M5 (stale check off, check
always valid, gate line removed, stamp emits nothing, no normalization)
each fell ≥ 1 test. yardstick unchanged: RED, 1 of 3 countable.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Veikart steg 1, økt 1 av 2. 18 tests, all failing today:
(a) a brief without an approval marker → the gate line in
commands/trekplan.md exits non-zero (today: no gate, planning proceeds);
(b) marker + ## Intent or ## Goal edited after approval → stale, stops;
(c) a valid marker passes (known negative), and edits outside the two
sections or whitespace-only reflow keep it valid;
(d) the stamp runs before the Phase 5 manual/auto fork and emits
brief-approved, so the manual (default) path records it too.
The gate and stamp lines are extracted from the command prose and
executed, so removing the line or stubbing the module fells the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One command, `node scripts/yardstick.mjs`, prints use, trust and
interrupts. Each row gives countable YES/NO, the value, the denominator and
the source it was read from. A fourth row asks whether the end-state gate can
be fooled by a mutant that keeps the module's signature. Exit is non-zero
while any number is not countable, the mutant is not felled, or the verdict
over the first five deliveries is not judged. Nothing counts a delivery yet,
so today it is RED for the right reasons:
use NO no order in the queue carries work-class
trust YES current ALLOW streak 0 of 37 (BLOCK 14, WARN 12, ALLOW 11,
last 2026-09-05 BLOCK, ordered by ts, read as JSON)
interrupts NO 0 user_input records of ~141k lines in 5 files, 0 emitter
call sites outside lib/stats/ and tests/
mutants 0 of 1 signature-preserving mutants felled: on M9 (every export
returns {status:'PASSED'}, every named test only checks typeof)
the gate closes both D-03 and D-04
It only measures. The gate, the emitter and the order writer are untouched.
The mutant row is meant to stay red until the gate learns to fell M9.
Choices, and why:
- Use counts over the order queue (pending, archive/ and claimed/), because
an order is the unit of work. The denominator is orders dated on or after a
cutoff (default 2026-09-21) that carry `work-class: new`. They count as
having gone through Voyage when a Voyage run in the stats has the order's
`slug:`. Both fields are the contract the order writer must meet. No order
carries them today, so the number is NOT COUNTABLE, not 0 %.
- Mailboxes whose names start with a dot are mailboxes too. A shell `*`
glob skips them, which is why a hand count of `coord/*/orders` comes out
lower. The row names them so a re-measure can reconcile.
- M9 must be LIVE before its row can count. Its typeof tests have to pass on
the M9 tree, checked by a runner independent of the gate, so a broken
fixture can never read as "felled".
Also: tests/fixtures/red-first/02243c6-always-allow-shim.mjs, the shim that
makes the red-first claim of 02243c6 reproducible (6 of 55). The recipe is in
its header.
Red first: the new test file failed to load before scripts/yardstick.mjs
existed, and a dot-mailbox test was red before the census learned about
dot-mailboxes. Suite 1161 -> 1183 (1181/0/2), also green on a clean tree
exported from the index.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An earlier pass scored the same 16 rows from README.md and CLAUDE.md alone and
landed on full 4 / partial 5 / none 7. Re-reading the rows in commands/, agents/,
lib/, hooks/ and tests/ moves three of them and corrects five underlying claims:
- P3 full -> partial. plan-critic is prose ("if blockers are found: revise the
plan"), not a gate. The plan is never committed by the pipeline, and execution
starts on the word `execute`. What is hard is the plan_version 1.7 manifest.
- P9 full -> partial. Exit-code-as-truth and the Phase 7.5 audit are real, but
trekexecute says verbatim "do not block on test-first failures", and nothing
protects a test file from being rewritten during a fix.
- P10 none -> partial. The docs-only pass missed tests/: the config regression
suite and a scored gold eval were there all along. The honest remaining gap is
CI (zero workflow files) and a live-agent eval, not "no evals".
Corrected without moving a verdict: --gates is boolean; /trekreview's input is a
SHA-range diff, not a PR; no hook anywhere returns an "ask" decision; nothing in
the pipeline writes to a memory file.
Result: full 2 of 16, partial 8 of 16, none 6 of 16. Every row carries a file
pointer so a later claim of "we closed that" can be checked against a denominator.
Also records what is worth borrowing from a third-party MIT implementation of the
same playbook, with credit, and what is not: its eval runner and CI example both
invoke Claude Code from code, which this repo prohibits.
No behaviour change. Suite unchanged at 1161 (1159/0/2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`origin` is a public mirror. Comments in tracked test files named the internal
review mechanism by its role rather than the measurement they came from; the
same class as the frozen file's overclaim fixed in 83d82f1. Replaced with what
they actually are: a re-measurement on a named commit, with the date.
The one remaining occurrence is the assertion that FORBIDS those phrases in
tests/fixtures/end-state-frozen.json - a guard has to spell out what it guards.
Suite 1161 (1159/0/2). Comment-only change.
Co-Authored-By: Claude Opus 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>
The cosmetic close came back one level up. The probe reads the test file from
the same tree it measures, so a four-line file holding two EMPTY tests with the
two named names closed D-03 and D-04 on a tree where `lib/verification/` did
not exist at all - "defects 0 of 7, registry intact" (measured 2026-09-18).
First a two-line stub exporting the right symbols; then an empty test with the
right name. A name is always forgeable.
So the named test is now run twice. Once on the tree, as before - and once in a
sandbox where the module the condition declares in `stubs` is replaced by a
stub exporting the same names, all inert. If the test still passes there, it
binds the name and not the behaviour, and the condition THROWS: NOT FELLABLE,
counted open. A condition that declares no `stubs`, or names a module that is
not there, cannot fire either.
The sandbox is a symlink overlay: every entry of the tree is symlinked, and
only the test file and the stubbed module are materialised for real - Node
resolves an ESM import through the realpath, so a symlinked test file would
import the original module and never see the mutant. Nothing is ever written
inside the measured tree, and the only directory removed is the one this code
made under the system temp dir (pinned by a test).
M7 is now a permanent mutant beside M6, in two forms: the checkpoint's own
reproduction (unfixed tree + empty named tests) and the harder one (the real
module present, so the stub can be built and the empty test passes against it).
Both report `defects 2 of 7`. A positive control pins that D-03/D-04 still
CLOSE on the real tree, so "not closed" everywhere cannot read as a working
probe.
The frozen denominator moves a third time, deliberately, and its `why` no
longer claims authority it does not have: the second and third amendments were
maintenance decisions by the maintainer, not operator decisions, and the
tracked file now says exactly that.
Measured after:
real tree node scripts/end-state-gate.mjs -> defects 0 of 7, intact, exit 1
M6 (stub) 8d1669e + current gate/registry/frozen + lib/cosmetic/stub.mjs -> 2 of 7
M7 (empty) 8d1669e + current gate/registry/frozen + 3-line
tests/lib/criteria-runner.test.mjs with the two named tests -> 2 of 7
Red first: 4 of the new tests failed before the change. Suite 1154 (1152/0/2).
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>
Measured 2026-09-18 (PM checkpoint on 6cafb4c, mutant M6): a two-line file
export function runPlanVerification() { return { ok: true }; }
export function formatCriteriaEvidence() { return ''; }
dropped into an otherwise UNFIXED tree closed both D-03 and D-04, and the gate
reported "defects 0 of 7, registry integrity: intact". The same checkpoint felled
5 of 5 mutants with the SUITE and 0 of 5 with the GATE. Both probes called
themselves `behaviour` while asking a grep whether an export name was present -
a phrase probe pointed at code.
A check condition may now name a test instead of a pattern:
{ "test": "tests/lib/criteria-runner.test.mjs", "name": "<exact test name>",
"expect": "fails" }
The gate spawns the test runner on that one test and reads its TAP line.
`expect: "fails"` holds - the defect stays open - while the test does not pass.
A missing file, a name that matches nothing, a runner that will not start: all
throw, which counts as NOT FELLABLE and therefore open. A check that cannot fire
is never "fixed".
The child's `NODE_TEST_*` env is stripped. The gate usually runs UNDER the test
runner, and an inherited `NODE_TEST_CONTEXT` makes the grandchild report over
the parent's IPC channel instead of stdout - the TAP line would never arrive and
the probe would silently stop felling anything.
M6 is now a permanent mutant in the gate's own test, so the cosmetic close
cannot come back. Verified end to end against a real stub tree:
git archive 8d1669e | tar -x -C $TMP
cp scripts/end-state-gate.mjs scripts/end-state-registry.json $TMP/scripts/
cp tests/fixtures/end-state-frozen.json $TMP/tests/fixtures/; cp STATE.md $TMP/
printf 'export function runPlanVerification…' > $TMP/lib/cosmetic/stub.mjs
node scripts/end-state-gate.mjs --root $TMP
-> defects 2 of 7, both NOT FELLABLE, exit 1
The gate also now states the limit out loud, as its own line above the table:
a behaviour probe proves the capability WORKS; that Phase 7 and Phase 4.5 CALL
it is pinned by TEXT in doc-consistency, not proven deterministically - real
proof is a headless plugin-eval run against a fixture plan (week 40).
FROZEN DENOMINATOR AMENDED, second time in one day, by the work order that
carries this change: D-03 and D-04 signatures moved because their checks
changed on purpose. tests/fixtures/end-state-frozen.json and the FROZEN literal
in tests/scripts/end-state-gate.test.mjs are updated together, both dated, both
saying why. No entry was removed.
Gate after this change: defects 0 of 7, registry integrity intact, exit 1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"The first backtick span is the command" is right for a plan, whose template
puts the command first, and wrong for a brief, whose criterion usually opens
by NAMING the thing under discussion. Measured 2026-09-18 on the repo's own
example brief: 5 of 6 criteria FAILED, 3 of them parse artifacts - `--verbose`
run as a command gave exit 2 ("invalid option"), `tests/` gave exit 126 ("is a
directory"). The rubric reads a FAILED result as decisive, so each one became
a BROKEN_SUCCESS_CRITERION BLOCKER about prose.
looksLikeCommand() screens the span by SHAPE only - no filesystem lookup, so a
span parses the same everywhere. Refused: a leading flag, a directory, a token
carrying quotes/braces/prose, and a lone relative path with a slash (an
explicit ./, ../, / or ~/ still runs, as do env-var prefixes). A refused span
is `unrunnable` with reason `not-a-command` - its own outcome, never FAILED,
and it never reaches a shell.
It deliberately does NOT scan on to a later span. "The first span that LOOKS
like a command" invents commands out of prose: in that same example brief it
would have run `whoami` and `login`, two real binaries a sentence happens to
name. An absent measurement is honest; a guessed one is not.
The shape check applies to prose spans only. Inside a shell-tagged fence the
author has already declared shell, so `[ -f x ] || exit 1` still runs.
The rubric follows: a NOT RUN result is never on its own a finding. The
Partial row now describes half-built DELIVERED CODE, and the reviewer gets a
table of the three reason strings - no-command, placeholder, not-a-command -
with what each says about the sentence rather than about the code.
Not covered, stated for the record: a multi-token span whose first token is a
non-executable file (`tests/golden/login.stdout --check`) still runs, and a
criterion whose command is real but whose binary is absent still reports the
shell's exit 127 - that is a true measurement of a missing binary, not a
parse artifact.
Red first: 4 runner tests + 1 doc-consistency pin failed before this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The runner read bullet lines only. The repo's own example plan writes its
whole acceptance run as a fenced bash block, so `## Verification` parsed to
ZERO criteria, the runner exited 1, and Phase 7 forbade `result: completed`
- a correct plan felled every single-session run. Measured 2026-09-18 on
6cafb4c: none of the repo's plan artifacts exited 0.
Fences are now read twice over, for two opposite reasons:
- a `## ` heading INSIDE a fence is quoted text and no longer opens a
section. examples/02-real-cli/REGENERATED.md is a report that quotes a
plan outline inside one fence; it used to yield an empty section that
read as "0 of 0", and now yields the honest NO_VERIFICATION_SECTION.
- a shell-tagged fence inside the section holds the commands. The tag list
is closed (bash/sh/shell/zsh/console/shell-session): an untagged fence is
more often expected OUTPUT than input, and inventing a criterion from
output is the failure this file exists to prevent.
Blank and comment-only lines inside the block declare nothing. A `$`/`>`
console prompt is stripped; a `#` root prompt is NOT, because it cannot be
told from a comment and running a comment is the worse mistake.
Measured after the fix (parse only - one example names a fictional CLI):
examples/01 6 criteria, plan-template 2 (both placeholders, correctly NOT
RUN), the two runner fixtures 2 each, plan-run-C 1, REGENERATED.md 0 with
NO_VERIFICATION_SECTION.
Divergence from the order's premise, stated for the record: it said 2 of 3
example plans write `## Verification` as a fenced bash block. Ground truth
is 1 of 3 - REGENERATED.md has no section of its own at all - and the
example plan's block holds 6 command lines, not 5.
Red first: 4 of the 6 new tests failed before this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The operator decided on 2026-09-18 that the two checks must stop measuring a
phrase and start measuring behaviour. The reason is the gate's own caveat: a
phrase probe closes on rewording, so "Phase 7 no longer says 'Skip for
trekplans'" and "the rubric no longer says 'exists and passes'" could both have
been satisfied by an edit that changed no behaviour at all. Both entries now
point at code a test exercises:
D-03 lib/**/*.mjs exports runPlanVerification
D-04 lib/**/*.mjs exports formatCriteriaEvidence
Verified open against the pre-fix tree (8d1669e) and closed against this one —
a check that cannot be seen to fire is not evidence.
What the probes can and cannot prove is now stated by the gate itself rather
than left to the reader. `PROBE_NOTES` carries one note per kind, the detail
line prints a note for every kind PRESENT in the row (and none for a kind no
entry declares), and the open/closed tags name the kind instead of labelling
`phrase` alone and leaving every other kind unlabelled. A behaviour probe
proves the capability exists; it does not prove a prose phase calls it — the
wiring is pinned by tests/lib/doc-consistency.test.mjs, and the gate says so.
THE DENOMINATOR MOVED ON PURPOSE. `tests/fixtures/end-state-frozen.json` and
the `FROZEN` literal in `tests/scripts/end-state-gate.test.mjs` carry new
signatures for D-03 and D-04, amended together and dated in both places. That
is the freeze mechanism working, not a bypass: the integrity check went
VIOLATED the moment the checks changed and stayed red until the amendment was
written down. The list may change — only as a decision said out loud. Nothing
else about the denominator moved: seven defect ids, three experiment ids, and
the agents/decisions/freeze configuration are unchanged, and no entry was
removed.
Gate after: defects 0 of 7, registry intact, still RED and exit 1 on the other
four rows — which is correct.
Suite: 1120 (1118/0/2), up 3.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The rubric required `brief-conformance-reviewer` to classify a Success
Criterion as Full only when "its verification command/test exists and passes".
Its tools are `Read`, `Glob`, `Grep`. It cannot run anything, so "passes" was
either guessed from the command's mere existence or quietly downgraded to
"exists" — a BLOCKER-tier rule key resting on an impression.
The reviewer stays read-only — a reviewer that executes the code it reviews is
not an independent reviewer. The command does the running instead:
- `/trekreview` Phase 4.5 runs the brief's `## Success Criteria` commands
through `lib/verification/criteria-runner.mjs --brief --evidence` and captures
the block as `sc_evidence_block`, pasted verbatim into the reviewer prompt in
Phase 5. The exit code does not stop the review — a failing criterion is
exactly what the review exists to find.
- `formatCriteriaEvidence` builds that block in code: one row per criterion with
the command, the exit code and the first output line. Chose a code-built block
over an orchestrator-written summary so the orchestrator cannot narrate a pass
that never happened.
- The rubric now judges the supplied result: `PASS` supports Full, `FAILED` /
`BLOCKED` is `Broken` with the exit code cited, and `NOT RUN` is the absence
of a measurement — never evidence in either direction.
- Phase 4.5 is skipped in `quick` mode: that mode does not launch the
conformance reviewer, so there is nobody to hand the result to.
Red first: seven tests in `tests/lib/criteria-runner.test.mjs` against a
committed brief fixture whose three criteria pass, fail, and are prose-only.
The two doc pins were verified red against the pre-fix files (rubric asked
"exists and passes"; no Phase 4.5; the block reached nobody).
Suite: 1117 (1115/0/2), up 9.
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>
The delegated-orchestration bake-off (T1 section 5) was specified in June and never run, and
nothing said whether it should be. The operator declined it on 2026-09-18, so section 5 now
carries a STATUS block in the same form section 6 got when its PoC was run and declined.
Why it is a decline and not a postponement: the measurement's positive branch is "adopt
delegated orchestration", which the balance analysis lists under Explicitly NOT recommended
(CC-26 lean-NO). Running it would have reopened a settled decision without saying so. The
end-state direction chosen 2026-09-17 is to finish and freeze the present form. The cheaper
synthetic instrument proposed alongside it is not run either.
Nothing is rewritten: the design stays as the record of what was specified, and reviving it
is a new operator decision. This is the same mechanism the end-state gate measures, so the
experiment closes by the decision being written down rather than by code changing.
End-state gate E-03: open -> closed. Experiments 3 -> 2 of 3 (STORM and the deep-research
opt-in remain). Registry integrity intact. Suite 1081 (1079/0/2), run on a clean export of
the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
D-07 had been closed on CLAUDE.md alone. The same false claim lived on elsewhere:
- README.md's "Switch the planning model" said four commands default to opus.
- The same README section offered a sed recipe for `model: opus` frontmatter lines, which
0 of 7 command files have.
- commands/trekresearch.md said "the orchestrator runs on Opus".
Since v5.9.0 no command pins model:, so the orchestrator follows the session model.
- README: the section now says how it actually works. You pick the orchestrator's model with
the session model (`claude --model sonnet` or `/model`). Sub-agent models are resolved at
the spawn sites: brief phase_signals, then profile phase_models, then the agents/*.md pins
as fallback. `--profile economy` still moves the sub-agents to Sonnet.
- trekresearch Launch rules: "Use model: "opus" for all sub-agents" contradicted the same
file's own three-layer resolution, which v5.9.0 wired into the spawn sites and the file
documents in Phase 1 and Hard rules -> Cost. The rule now injects
`phase_signal_result.model` with agent frontmatter as the fallback, and says the
orchestrator follows the session model. Nothing changes under the default premium
profile: it still resolves to opus. The engine=swarm note now names the resolved-model
injection as the anchor, instead of the removed literal.
- Two doc-consistency pins were written first and failed before this change:
- no opus-orchestrator claim in README/CLAUDE.md/trekresearch, and no `^model: opus$`
recipe;
- the Launch rules name `phase_signal_result.model` and carry no blanket opus rule.
- Not touched: the follow-up agent in the default-off STORM phase still says model: "opus".
That is part of an experiment whose fate is an operator decision.
End-state gate D-07: open -> closed. Defects 3 -> 2 of 7 (D-03, D-04 remain, by design);
registry integrity intact. Suite 1081 (1079/0/2), run on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An independent adversarial review of the end-state gate reproduced a GREEN result, exit 0,
with the gate's 18 tests passing. Only the registry and the STATE markers were edited; every
other file stayed byte-identical. The judged party owned the denominator, and nothing
guarded it. This change closes that finding and three more, and reopens a defect the gate
had closed too early.
Registry integrity (the blocker):
- tests/fixtures/end-state-frozen.json freezes the denominator. It holds the 7 defect ids
and 3 experiment ids, each with a sha256 signature of its check (canonical JSON, key
order irrelevant), plus the exact agents / decisions / freeze configuration.
- The rule: an entry may close; it may never disappear or have its check changed. New
entries are allowed. spawnSites may never include agents/.
- The test file pins the manifest literally, with a comment that the denominator was
frozen on 2026-09-17 and that changing it is a decision. The gate verifies the registry
against the manifest on every run.
- A violation makes the gate red and names what moved. A missing manifest makes integrity
n/a, which is also red.
Dormant means "never spawned", not "never mentioned":
- An agent counts as spawned only when a command names it in a spawn instruction: a row of
a table headed `Agent`, a line that starts with Launch/Spawn, or a **name** block followed
by a Prompt: line.
- Prose, negations, HTML comments and fenced code never count. Block quotes cannot match
either, because every form is anchored at the start of the line.
- Re-measured under this definition: still 1 of 20 (synthesis-agent). Per agent, every other
spawnable agent has a spawn instruction in at least one command.
A fifth tally, feat-after-freeze:
- It counts commits after the `end-state-freeze` tag whose subject matches
^feat(\(|!|:).
- The row is n/a, and therefore red, when there is no tag yet, the root is not a git work
tree, or the root is not the top of its work tree. It is never 0.
Honest output:
- Every registry entry now declares its probe kind (phrase or byte).
- Open phrase probes are labelled as such, and closed ids are listed. The output states that
a closed phrase probe is evidence, not proof of behaviour. D-03 and D-04 stay open and
labelled; how they are fixed is still to be decided.
- The decisions row now says it counts ticks, not verified decisions.
D-07 reopened:
- Its check now also covers README.md and commands/trekresearch.md, where the "orchestrator
runs on opus" claim still lives. README even ships a sed recipe for `model: opus` lines no
command has.
- Defects are 3 of 7 until the separate docs fix lands.
Three surviving review mutants were killed with tests:
- an empty glob over an existing dir is silent;
- the header counts n/a rows as zero;
- a dormant-row error reads as 0.
Measured on a clean export of the index:
- Mutation harness, adapted from the review's: review code mutants 18 of 18 killed. Four of
them were re-targeted at the equivalent new code. M02 survived until a test with an
intact registry plus a missing freeze tag was added.
- New-logic mutants: 12 of 12 killed. One equivalent mutant (block-quote stripping) was
removed together with the dead clause it targeted.
- Registry attacks: 7 of 7 killed.
- The review's combined green attack (registry + STATE only) is now RED, exit 1:
"defects: D-03 is frozen but missing from the registry".
- Gate tests 38/38. Suite 1059 -> 1079 (1077/0/2).
- Gate: RED, defects 3 of 7, experiments 3 of 3, dormant 1 of 20; decisions and
feat-after-freeze are n/a in a clean export.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Since v5.9.0 no command in commands/ pins `model:`, so each /trek* orchestrator runs on
the session model. CLAUDE.md's command table still gave "opus" for all seven commands,
which is wrong in an always-loaded file. The column is now "Orchestrator model" with the
value `session`, and a one-line note under the table explains it and points to Model &
effort for sub-agent models. README already said this correctly; it is unchanged.
End-state gate D-07: open -> closed (defects 3 -> 2 of 7; D-03 and D-04 remain open by
design). Suite 1059 (1057/0/2), run on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lib/review/gold-scorer.mjs held its (file, rule_key) separator as a literal NUL byte
inside a string literal. Git's binary heuristic therefore classed the whole file as
binary. Every diff of the scorer read "Binary files differ", `--numstat` printed "- -",
and plain grep matched nothing in it. The separator is now written as the escape
sequence '\x00', which is the same one-character string at runtime.
Proof that the change is behaviour-neutral:
- A snapshot script recorded every number the scorer produces, before and after:
- the committed run-perfect fixture through the coordinator contract;
- empty, half, spurious (keys containing spaces and colons), duplicated and empty-gold
runs;
- all 9 verdict pairs.
`diff` of the two outputs is empty (141 lines each).
- tests/lib/gold-scorer.test.mjs + tests/lib/gold-eval.test.mjs: 13/13 before and after.
- `git diff --no-index --numstat /dev/null <file>`: the new file reads "89 0" (text); the
old blob, as a control, reads "- -" (binary).
End-state gate D-06: open -> closed (defects 4 -> 3 of 7). Suite 1059 (1057/0/2), run
on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude Code 2.1.233 stopped offering the task/todo tools on Opus 4.8, Sonnet 5, Fable 5
and newer unless CLAUDE_CODE_ENABLE_TODO_TOOLS=1 is set. /trekplan still listed
TaskCreate and TaskUpdate in allowed-tools. So did the two orchestrator reference docs,
which mirror the command but are never spawned. No prose in any command calls either tool.
Runtime effect, measured BEFORE removal on CC 2.1.274, claude-opus-5, with
CLAUDE_CODE_ENABLE_TODO_TOOLS unset:
- Probe: a throwaway project command whose frontmatter is
`allowed-tools: Read, TaskCreate, TaskUpdate`, run headless
(`claude -p --output-format stream-json --verbose`).
- The session had 151 tools. The only Task* names among them were Task, TaskOutput and
TaskStop; neither TaskCreate nor TaskUpdate was there.
- The command loaded (listed in slash_commands) and completed with subtype=success,
is_error=false and 0 tool calls. The model reported the tool as not available.
- `claude plugin validate` does not flag the unknown names either: only the one
accepted warning appears, before and after.
Verdict: harmless dead weight, no error anywhere, so this is a correctness-of-docs fix
rather than a runtime fix.
End-state gate D-05: open -> closed (defects 5 -> 4 of 7). Suite 1059 (1057/0/2), run
on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The engine = swarm instruction told the session to run the `### External agents` and
`### Bridge agent` blocks. The Bridge agent heading was removed in v5.10.1 with the
second-opinion bridge, so half of the instruction pointed at nothing. It now names the
one block that exists (`### External agents`, still present). Nothing replaces the
removed bridge pass.
End-state gate D-02: open -> closed (defects 6 -> 5 of 7). Suite 1059 (1057/0/2), run
on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude Code 2.1.178 removed the TeamCreate and TeamDelete tools, so /trekplan's
"execute with team" handoff could never create a team: it always ended in its own
fallback (sequential execution). The path is removed and the fallback is now the only
behaviour. The handoff no longer offers "execute with team"; if a user asks for it
anyway, /trekplan says there is no team path and executes sequentially. Parallel
implementation stays where it already was: --decompose plus /trekexecute's
multi-session waves. No new orchestration replaces it.
- commands/trekplan.md: TeamCreate/TeamDelete removed from allowed-tools; the offered
option and the team section are gone.
- agents/planning-orchestrator.md (reference doc): the offered option is gone.
- docs/cc-upgrade-2.1.181-decision-matrix.md still names the team path: a historical
record, left as written.
End-state gate D-01: open -> closed (defects 7 -> 6 of 7). Suite 1059 (1057/0/2), run
on a clean export of the index.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Voyage now has a destination that can fail: finish the phase-2 pipeline, close the open
experiments, freeze. This adds the gate that measures the distance to it. Nothing is
fixed or removed here; every defect and every removal is its own change.
scripts/end-state-gate.mjs counts four tallies; finished = all four at 0.
defects listed in scripts/end-state-registry.json, each with a runnable check
experiments listed in the registry, each with a runnable check
dormant-agents spawnable agents no commands/*.md references (structural, word-bounded;
files carrying the reference-document marker are excluded)
decisions open operator decisions in the local STATE.md, by a fixed marker
Exit 0 green, 1 red, 2 usage/registry error. `--json` for machines.
Fail-closed by construction: an entry with no check, or whose check cannot run (missing
file, empty glob, missing section), is NOT FELLABLE and counts as open; a missing STATE.md,
a missing section, or an unmarked list item in it makes the decisions tally n/a, which
keeps the gate red. The decisions row is therefore n/a in any clean clone.
Result today (clean export of the index): RED, 0 of 4 tallies at 0 -
defects 7 of 7, experiments 3 of 3, dormant agents 1 of 20, decisions n/a.
With the local STATE present: decisions 11 of 12.
Verified in both directions: 18 fixture tests pin each tally at 0 and above 0. Each of the
10 registry checks was measured open on this tree and closed after a simulated fix on a
throwaway copy (10 of 10). That run caught a too-broad D-03 check - Phase 4's legitimate
entry-condition skip also matched - which is now scoped to the Phase 7 section.
Suite 1041 -> 1059 (1057/0/2), also run on the clean export.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
origin is a public mirror, and the operator decided on 2026-09-15 that PLAN.md follows
the same remote rule as STATE.md: public remote -> local-only. Before this change
`git check-ignore -v PLAN.md` exited 1 in voyage, so a PLAN.md written here would have
been one `git add` away from the mirror - the exact trap the global rule warns about,
in the repo the rule was written about.
The pattern is anchored (`/PLAN.md`) because core.ignorecase is true here: a bare
`PLAN.md` was measured to also ignore a new `examples/02-new/plan.md` (exit 0), which
would silently drop the pipeline's own lowercase plan files. Measured after the change
with `git check-ignore -v --no-index`: PLAN.md -> 0, examples/02-new/plan.md -> 1,
docs/PLAN.md -> 1; STATE.md still ignored (0).
No PLAN.md is created: the destination is the operator's to choose. Suite unchanged at
1041 (1039/0/2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The two "High-effort behavior (v5.1.1)" pins still described the rules as
"Decision B + gemini-bridge" and "contrarian-researcher + gemini-bridge
always-on". Both became wrong in 1ca48e0: trekplan's Pass 2 is now
"unavailable, skipped" and trekresearch forces only contrarian-researcher.
npm test stayed green throughout, and that is the point. The pins match on the
heading regex, not on their own message, so a stale message can never fail
them — and this file is structurally excluded from the S95 live-surface
scanner, because the S95 block is itself full of the word it bans everywhere
else. The blind spot is now written into the S95 comment rather than left for
the next reader to rediscover: when the high-effort rules change, these two
messages have to be re-read by hand.
Suite 1041 (1039/0/2), unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gemini-bridge is dropped by operator directive (three repetitions). The drop is
permanent, not a wait-for-SDK-upgrade state, so the change clears every LIVE
surface that could still steer a run toward the agent and leaves the historical
record alone.
Live surfaces cleared: agents/gemini-bridge.md deleted; trekresearch (bridge
launch block, --local help, high-effort always-on pair, stats record,
degradation list), trekplan, trekbrief, research-orchestrator (mode table,
agent table, prompting block, summary line), README (feature prose, mode table,
agent prose, mermaid EXTERNAL node, conditional legend, optional-MCP
requirement, --local section), CLAUDE.md, settings.json (the whole
trekresearch.geminiBridge block), both templates, architecture.md,
command-modes.md.
trekplan high-effort Adversarial Pass 2 now degrades EXPLICITLY: it emits its
section with status "unavailable, skipped" instead of failing or vanishing. A
high-effort plan carrying no Pass 2 marker is indistinguishable from one whose
Pass 2 crashed, which is the failure mode this wording exists to prevent.
gemini_used is deliberately KEPT as a vestigial trekresearch stats field pinned
to false. Removing it would break the observability export schema for existing
consumers, and the directive was about the agent, not the field.
Not touched: CHANGELOG history and the measurement/decision docs keep their
gemini references. They record what a past version did or what was measured
then; rewriting them is the same defect class as bumping a version string
inside a measurement doc.
Driven test-first. Five new pins in tests/lib/doc-consistency.test.mjs, verified
RED before the edits, including a KNOWN-POSITIVE CONTROL asserting the
historical records still DO carry gemini references — so the empty result on
live surfaces is a measurement and not a broken query (Verifiseringsloven
ansikt 4). Agent inventory 24 -> 23 (20 spawnable + 3 orchestrator reference
docs); the <example>-block floor moves 34 -> 32 because an agent legitimately
left the inventory, not because examples went missing from a surviving one.
Docs: docs/T1-cc26-delegated-orchestration.md §8 item 3 claimed both the §6
synthesis-agent PoC and the §5 bake-off were "designed but unbuilt". That was
written in S7 and falsified the same afternoon by S12, which ran the §6 PoC and
recorded Δ main-context (faithful flow) = 0.0%, NEGATIVE. The stale wording is
what caused the settled PoC to be re-ordered as new work on 2026-09-02, so it is
struck rather than deleted and §6 gained a RUN AND DECLINED status block. The
finding is structural, not stochastic: Phase 5 spawns the exploration swarm
foreground (trekplan.md:158,338-341), so the outputs are already resident in
main before Phase 7 — delegating only the Phase-7 digest evicts nothing.
Also measured 2026-09-03 (CC 2.1.259): claude -p --output-format stream-json
runs on subscription auth with no ANTHROPIC_API_KEY and now emits a
subagent_stats block, so S12's environment-block premise is half stale. Recorded
in §8 item 4. It lowers the cost of §5; it changes nothing about §6.
Suite 1041 (1039/0/2), up from 1036 by exactly the five tests added.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow-up defect in the reviewer accounting added by e2aec01, found by review
and confirmed by probe before fixing.
validateFindings only WARNS on a missing `reviewer` field, so a payload can
fail schema while carrying no name. ingest then records `reviewer: null`, and
runContract turned that null into the literal reviewer name "unnamed reviewer".
MEASURED before the fix:
runContract([{findings:[{file:'x.mjs',line:1,rule_key:'NOPE',severity:'MAJOR'}]}],
{expectedReviewers:['code-correctness-reviewer']})
-> missing_reviewers = ["unnamed reviewer", "code-correctness-reviewer"]
One failure, two entries, one of them an agent nobody launched. The
`reported.delete(s.reviewer)` line was also inert for that case, since a null
name was never in the set to begin with.
Fix: skipped payloads are split by whether they carry a name. Named ones go to
missing_reviewers as before; anonymous ones increment the new
`unattributable_payloads` count, which forbids ALLOW on its own - so stripping
a reviewer name from a payload cannot restore ALLOW, and the floor does not
depend on the caller passing expectedReviewers. `allow_blocked_by` reports the
two facts separately: `missing-reviewer:<name>` and `unattributable-payload (n)`.
The old behaviour never produced a false ALLOW - it failed in the safe
direction - but it named a reviewer that did not exist, which is the kind of
output an operator would chase.
Iron Law: two failing tests first (double entry; anonymous-payload-alone must
forbid ALLOW), then the fix.
Also verified in this pass, by temporarily adding a fake reason to
UNVERIFIED_REASONS: the prose-vocabulary pin does go red when a reason is
declared in the lib but missing from agents/review-coordinator.md. A pin that
cannot fail is not a pin.
Suite 1034 (1032/0/2) -> 1036 (1034/0/2), 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
computeVerdict counted only the findings handed to it (reasoned.kept), so a
finding removed by Pass 2 or Pass 3, and a reviewer whose payload was thrown
away or never arrived, were arithmetically identical to a finding that never
existed. All three pushed the verdict toward ALLOW.
Measured before the fix (probes, 2026-09-01):
- a BLOCKER with a 101-character title -> ALLOW (Pass 2 succinctness)
- a payload with one ad-hoc rule_key is skipped WHOLE at ingest, taking a
valid BLOCKER sibling with it -> ALLOW
- a reviewer that never reported -> ALLOW
Pass 3's own no-citation / unknown-rule_key branches turned out unreachable
through runContract (validateFindings rejects those payloads first), so the
reachable exposure was Pass 2 plus the skipped/absent reviewer.
THE OPEN DESIGN DECISION, and why it went against the order's default.
The order proposed: indeterminate file-existence YES, plain succinctness NO
("a too-long finding is not an uncertain finding"). I kept the first and
overrode the second, on one principle:
A removal is `dropped` only when the test REFUTED the finding as a claim
about this codebase. Every other removal is `unverified`.
Succinctness and actionability read a `.length`. They never examine the claim,
so they cannot establish the finding is unreal - and dropping a BLOCKER for a
101-character title is precisely the fail-open shape being fixed. Three things
settled it:
1. Under the order's default the fix would have been almost inert. Pass 3's
drop branches are unreachable via runContract, so leaving Pass 2 out would
have left the only reachable finding-level exposure open.
2. Cost asymmetry, priced rather than asserted: the verdict is not a gate.
Handover 6 feeds `findings` filtered to BLOCKER+MAJOR into /trekplan
(commands/trekplan.md:218); `verdict` is optional metadata
(docs/HANDOVER-CONTRACTS.md:353). Nothing loops or re-plans on WARN. So a
false `unverified` costs WARN plus a printed reason; a false drop costs a
silent ALLOW over a live BLOCKER.
3. unknown-rule_key joins them for the same reason: an ad-hoc key is a real
defect wearing the wrong label, and v5.1.1 high-effort mode already KEEPS
those, normalised to PLAN_EXECUTE_DRIFT. Refuting them at normal effort
while keeping them at high effort would be incoherent.
no-citation stays a drop: a finding whose file is empty or whose line is
negative names no location, so it makes no checkable claim at all - the one
deterministic refutation, and what the Pass 3 prose already said it was.
Iron Law: tests/lib/coordinator-contract.test.mjs first, red (missing export +
the three measured ALLOWs), then production code. Two existing assertions were
updated AFTER implementation as contract changes, not to make the red pass.
A known-positive control pins that ALLOW is still reachable - without it,
"no ALLOW" is not a fail-closed contract, only a broken one.
lib/review/coordinator-contract.mjs
+ classifySuppression / REFUTING_REASONS / UNVERIFIED_REASONS - one
vocabulary owned by the lib, including the tokens only the LLM
coordinator emits (accuracy:refuted, file-existence:refuted/indeterminate),
so prose and lib cannot drift. Unclassified reasons default to unverified:
the default fails closed.
~ judgeFilter / reasonablenessFilter return {kept, dropped, unverified}
~ computeVerdict(findings, {unverified, missingReviewers}) -> + allow_blocked_by.
Never raises a verdict, only withholds ALLOW. Unverified findings are NOT
counted into a severity tier: their severity was never substantiated, and
counting it would be invention.
~ runContract(payloads, {expectedReviewers}) -> + unverified,
missing_reviewers, allow_blocked_by. `suppressed` stays the union of
dropped + unverified, so existing consumers (gold-eval) keep their meaning.
agents/review-coordinator.md - Pass 2/3 tables gain a fate column, new
"Suppression is two-valued" section, Pass 4 threshold table gains the two
fail-closed rows, Executive Summary must state a withheld ALLOW, Suppressed
Findings tags each line [dropped]/[unverified]. Pass 3's unknown-rule_key
bullet explicitly says high-effort does not reach that branch, so the same
input never has two documented fates.
commands/trekreview.md - Phase 5 "Reviewer accounting": the expected set is
written down before the spawn, a silent reviewer gets one re-ask and then
STOP. That extends the pattern already in the file (schema failure -> 2
bounded re-asks -> "do not feed unvalidated findings to the coordinator") to
the other two ways a reviewer goes missing, rather than softening it to WARN.
The lib's missing_reviewers stays as belt-and-braces for direct callers.
docs/agent-return-channel-defect.md - the "inferred, not observed" caveat on
the unnamed arm above 66 lines is struck: akashic-intelligence S27
(f168630) measured 2/2 unnamed agents returning against a 4370-line plan,
30449 B and 10989 B, both valid JSON. Recorded with akashic's own two
caveats intact - the measurer owns the finding, and byte-identity between
the returned string and the file on disk was not proven. The separate S25
named-arm figures are left standing; these are two measurements, not a
correction of one by the other.
No release, no version bump, no tag, no catalogue ref, no Workflow port.
Suite 1025 (1023/0/2) -> 1034 (1032/0/2), 0 failures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
D-census (order 6242020304, from .claude, 2026-08-20/21) measured that
registry.npmjs.org/voyage answers 200 with someone else's package: "Advanced
HTTP Routing System for Node.js" (NEURS/voyage, v0.0.1, 2022). This manifest
declared `"name": "voyage"` with no `private` flag, so nothing stopped an
accidental `npm publish` against a name we do not own - and publishing is
one-way.
This manifest is test tooling for a Claude Code plugin distributed through the
marketplace catalogue (`ref: vX.Y.Z`), never through npm. The sibling
non-package manifests (okr, repo-mailbox, repo-standard) all carry
`private: true`; voyage was the exception.
Operator decision S93: option A (set `private: true`), not option B (scope the
name to `@<org>/voyage` for a future publish) - there is no publishing plan,
so B would only defer the same choice.
Tests first (Iron Law): tests/lib/doc-consistency.test.mjs pins
`pkg.private === true`, seen red (actual `undefined`) before the manifest
change.
New baseline: 1025 tests, 1023 pass / 0 fail / 2 skip (was 1024: 1022/0/2).
No `npm publish` in any form, no version bump, no tag, no catalogue change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude Code 2.1.218 changed /deep-research to start only when invoked
manually; from there the Skill tool refuses a model invocation outright
with `disable-model-invocation` (measured in a real `--engine deep-research`
run 2026-09-01 18:32Z - the SC3 fallback to swarm held). The engine was
built against 2.1.196 and the prose still promised a path Claude Code has
removed ("requires Claude Code 2.1.154+").
The flag is KEPT as an additive opt-in that never hard-fails. Prose only:
no lib/ change, no adapter-contract change, no default change, no version
bump.
- commands/trekresearch.md: flag bullet states the closed window
`2.1.154 <= CC < 2.1.218`; the pre-gate gained an UPPER ceiling (still a
numeric comparison, not a string one) that skips straight to the fallback
with reason `disable-model-invocation`; step 3 names the Skill-tool
refusal as a known failure manifestation; step 4 lists the reason tokens.
- docs/command-modes.md + README.md: same window, one sentence each, on the
reference row itself.
- CHANGELOG.md: Unreleased/Docs entry.
Tests first (Iron Law): tests/lib/doc-consistency.test.mjs gained a pin
requiring 2.1.218 + disable-model-invocation on all three surfaces, on both
reference rows, and in both load-bearing regions of the command (flag
bullet + pre-gate). Seen red on all three surfaces before the prose change.
tests/commands/trekresearch-engine.test.mjs unchanged and green.
New baseline: 1024 tests, 1022 pass / 0 fail / 2 skip (was 1023: 1021/0/2).
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>