fix(siblings): the texts found next to round 4.5a's fixes say what the code does

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-24 20:41:53 +02:00
commit 8a2a989f79
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
18 changed files with 284 additions and 71 deletions

View file

@ -95,6 +95,7 @@ Fix round 4.5a (decisions 57, 67 and 68): the round's 30 findings, where a Voyag
**Stats**
- **No command promises a stats key its record does not write** (finding #46). The "Stats records emit `profile` …" sentences left `/trekbrief`, `/trekplan`, `/trekexecute`, `/trekreview` and `/trekcontinue`; `/trekresearch` keeps only `engine`, which its record writes; `docs/profiles.md`, `docs/operations.md` and README no longer say a profile reaches the stats records.
- **The texts next to the fixes say the same** (siblings found during the round): `/trekreview`, the review reference and the Arm B coordinator prompt no longer drop a finding whose rule key is unknown; `/trekbrief` writes its stats record on every exit of its manual and auto paths, says that `--gates` adds one pause before its auto chain, and that the chain ends at the plan; `/trekexecute` says `--gates` changes nothing there, checks the wave marker before a low-effort single session, and names no validator check that does not exist; plan mode goes through the plan approval before it prints the `/trekexecute` line; the gates shim reads `--gates=true` as on.
### Known issues

View file

@ -72,7 +72,7 @@ No cloud dependency. No GitHub requirement. Works on **Mac, Linux, and Windows**
### Autonomy mode (`--gates`, v3.4.0)
All four pipeline commands accept `--gates [true|false|closed|open]`. A bare `--gates`, `true` or `closed` turns gating on; `false`, `open` or no flag (the default) leaves it off. Any other next token is not the flag's value, so `/trekplan --gates --project <dir>` is a bare `--gates`. With gating on, `/trekbrief`, `/trekresearch` and `/trekplan` pause at their own boundaries; `/trekexecute` records the setting and pauses on nothing, because its one stop is the merge into main, which always asks. Details: [`docs/operations.md`](docs/operations.md) § Autonomy mode.
All four pipeline commands accept `--gates [true|false|closed|open]`. A bare `--gates`, `true` or `closed` turns gating on; `false`, `open` or no flag (the default) leaves it off. Any other next token is not the flag's value, so `/trekplan --gates --project <dir>` is a bare `--gates`. With gating on, `/trekbrief`, `/trekresearch` and `/trekplan` pause at their own boundaries; `/trekexecute` accepts the flag and pauses on nothing, because its one stop is the merge into main, which always asks. Details: [`docs/operations.md`](docs/operations.md) § Autonomy mode.
Under the hood, `lib/util/autonomy-gate.mjs` runs a small state machine (`idle → gates_on | auto_running → paused_for_gate → completed`), and `lib/stats/event-emit.mjs` records named lifecycle events (the main-merge gate and its answer, among others) to `trekexecute-stats.jsonl` in the plugin data directory. The new `hooks/scripts/post-compact-flush.mjs` PostCompact hook re-injects `.session-state.local.json` after context compaction so multi-session work survives a compaction boundary.

View file

@ -185,9 +185,12 @@ The coordinator runs a 4-pass process:
2. **HubSpot Judge filters** — Accuracy refutes; Actionability sets aside
as unverified; Succinctness never removes a finding (only MINOR/SUGGESTION
text is trimmed, and counted).
3. **Cloudflare reasonableness** — drop speculative findings without a
`file:line` citation; drop findings whose `rule_key` is not in
`RULE_CATALOGUE`.
3. **Cloudflare reasonableness** — drop a finding with no `file:line`
citation (`no-citation`) or whose file is in neither the working tree nor
the diff (`file-existence:refuted`); route one whose `rule_key` is not in
`RULE_CATALOGUE` (`unknown-rule_key`) or whose file cannot be checked
(`file-existence:indeterminate`) to `unverified`, which forbids `ALLOW`;
reset a severity that does not match its catalogue tier.
4. **Compute verdict** — `BLOCK` if `BLOCKER ≥ 1`, `WARN` if `MAJOR ≥ 1`,
else `ALLOW`.

View file

@ -54,13 +54,11 @@ Parse `$ARGUMENTS`:
false, and nothing else reads the state. On exit 1 or no JSON (a broken
shim; the four values cannot cause it), print the error in one line and
map in prose instead: bare, `true`, `closed` → true; `false`, `open` →
false. The run goes on either way. `gates_mode = true` re-enables
approval pauses at every phase boundary in the downstream pipeline
(research, plan). Default `gates_mode = false`
means auto mode runs continuously until the main-merge gate (which is
the one boundary that ALWAYS pauses, regardless of `gates_mode`). Strip
the flag (and its value, when it has one) from `$ARGUMENTS` before
further parsing.
false. The run goes on either way. `gates_mode = true` adds one pause,
before the auto chain starts (Phase 5); the chain does not pass
`--gates` on to `/trekresearch` or `/trekplan`. Default
`gates_mode = false` skips that pause. Strip the flag (and its value,
when it has one) from `$ARGUMENTS` before further parsing.
4. `--approve <project-dir | brief-file>`: set **mode = approve**. No
interview, no drafting. Set `APPROVE_ARG` to the argument and resolve it:
@ -1014,13 +1012,13 @@ Then:
/trekexecute --project {PROJECT_DIR}
```
Stop. Do not continue to Phase 6.
Run Phase 7 (`auto_result: manual`), then stop. Do not continue to Phase 6.
### Auto path
**Auto requires an approved intent.** If Phase 4h ended "Leave unapproved",
the auto chain would halt at `/trekplan` anyway — print the manual-path
output instead and stop.
output instead, run Phase 7 (`auto_result: manual`) and stop.
Set `auto_research: true` in the brief's frontmatter (edit the file).
@ -1029,12 +1027,12 @@ stamp — on both paths — so it is not emitted again here.
If `gates_mode == true`: pause here via `AskUserQuestion` —
"Auto-mode confirmed. Proceed to research now? (yes/no)". If the user
answers no, fall back to the manual path output and stop. Otherwise
proceed to Phase 6.
answers no, fall back to the manual path output, run Phase 7
(`auto_result: cancelled`) and stop. Otherwise proceed to Phase 6.
If `gates_mode == false` (default in auto): proceed directly to Phase 6.
The chain stops only at the main-merge gate (see `commands/trekexecute.md`
Phase 8).
The chain ends when `/trekplan` has written the plan (Step 6e); it never
starts `/trekexecute`.
Proceed to Phase 6.
@ -1056,7 +1054,8 @@ Continue?"
| **Continue — auto** | Proceed. |
| **Cancel — do manual** | Revert to manual path (print commands, stop). |
If cancelled → fall back to manual path output and stop.
If cancelled → fall back to manual path output, run Phase 7
(`auto_result: cancelled`) and stop.
### Step 6b — Run research topics sequentially (inline)
@ -1093,7 +1092,8 @@ ls -1 {PROJECT_DIR}/research/*.md | wc -l
```
Expected count: N. If any are missing, report and ask the user how to
proceed (retry, skip missing topic, cancel).
proceed (retry, skip missing topic, cancel). On cancel, run Phase 7
(`auto_result: cancelled`) and stop.
Update brief frontmatter: `research_status: complete`.
@ -1107,7 +1107,8 @@ Invoke the planning command inline in this session:
The planning pipeline runs all phases (exploration, synthesis, review) in
main context. Wait for the plan to be written to `{PROJECT_DIR}/plan.md`
before continuing.
before continuing. If `/trekplan` stops before it writes the plan, report
where it stopped, run Phase 7 (`auto_result: failed`) and stop.
### Step 6e — Report completion

View file

@ -86,8 +86,8 @@ Parse `$ARGUMENTS` for mode flags:
shim; the four values cannot cause it), print the error in one line and
map in prose instead: bare, `true`, `closed` → true; `false`, `open` →
false. The run goes on either way. Default `gates_mode = false`.
`/trekexecute` records `gates_mode`; no phase of it pauses on it. The one
stop is the merge into main, which always asks (Phase 8).
`gates_mode` changes nothing in `/trekexecute`: no phase of it pauses on
it. The one stop is the merge into main, which always asks (Phase 8).
It no longer adds a question of the executor's own before a merge. The
wave merge-back (Phase 2.6 Step 3) and the merge into main (Phase 8) are
asked by the guard, which asks before every merge into main and every push,
@ -126,7 +126,7 @@ Modes:
--step N Execute only step N (foreground)
--session N Execute only session N from the plan's Execution Strategy
--profile Profile for this run's resolver; the step helper and the controller stay on opus
--gates Record gates_mode: bare, true or closed = on; false or open = off
--gates Accepted, pauses on nothing: bare, true or closed = on; false or open = off
Examples:
/trekexecute .claude/plans/trekplan-2026-04-06-auth-refactor.md
@ -465,7 +465,7 @@ Determine how to execute this plan:
- `--step N` mode
- `--resume` mode
- `--session N` mode (runs only that session's steps, foreground)
- `phase_signal_result.effort == 'low'` → one session, steps in order — never while a wave is in flight (`WAVE_IN_FLIGHT`)
- `phase_signal_result.effort == 'low'` → one session, steps in order, unless a wave is in flight: when `.claude/trekplan-sessions/{slug}/orders/wave.json` exists in the main checkout (the first entry of `git worktree list`), this bullet does not apply and the multi-session path below collects the wave. The check only reads the file and never stops the run.
- Plan has no `## Execution Strategy` section
- Plan has Execution Strategy with only 1 session
@ -616,9 +616,8 @@ These sessions cannot safely run in parallel.
Fix the Execution Strategy in the plan, or use --fg for sequential execution.
```
Stop execution. This is a defense-in-depth check — /trekplan's validator
should have prevented this, but verifying at execution time catches plans
that were manually edited or have bugs.
Stop execution. This check runs at execution time, so it also catches a plan
that was edited by hand after planning.
### Check 4 — Stale worktree cleanup
@ -2450,10 +2449,11 @@ Record in progress file:
>
> **Commands:** Phase 7.5 step 3's `git rev-list --count`, step 5's `bash -n`,
> step 6's `git diff --name-only` and step 7's `done-line.mjs --check`; the
> criteria runner behind clauses 2 and 4; clause 5's `arch-rules.mjs` and
> `plan-scope-sweep.mjs`. **Executor judgement:** steps 1, 2 and 4, the expected
> count in step 3, and clause 3's formula. A script for the judgement parts is
> part c of the hardening.
> criteria runner behind clauses 2 and 4 for a trekplan (a session spec's
> clause 2 is Phase 7's `## Exit Condition` checklist, and clause 4 is a
> trekplan's only); clause 5's `arch-rules.mjs` and `plan-scope-sweep.mjs`.
> **Executor judgement:** steps 1, 2 and 4, the expected count in step 3, and
> clause 3's formula. The judgement parts are not yet a script.
>
> The review verdict (Phase 7.2) is decided after this gate, by the lock in
> Phase 8: in the closing run, a closed lock or a declined merge turns

View file

@ -325,9 +325,9 @@ Determine the output directory from the plan slug:
Launch the **session-decomposer** agent:
Pass `model: {phase_signal_result.model}` at this spawn; without it (the
`--brief` branch, where the resolver does not run, or a resolver that printed
nothing) the agent's own `model:` applies.
Phase 1 runs the model resolver only for `--project`, so `--decompose` has no
resolved model: this spawn passes no `model:`, and the agent's own `model:`
applies.
```
Plan file: {plan path}
@ -1201,9 +1201,11 @@ plan-approval prompt:
plan back.
3. **Approved:** the session leaves plan mode. Copy the plan file to the plan
destination from Phase 3, then run the gate on the destination — it must
exit 0 again, which also proves the copy is the reviewed text. Then
print the `/trekexecute` line (Phase 11) and stop — leaving plan mode is
not an order to implement.
exit 0 again, which also proves the copy is the reviewed text. Then go
through Phase 11 (its plan approval, then the `/trekexecute` line) and
stop — leaving plan mode is not an order to implement. Phase 11 asks again
because the plan-mode approval is logged under Claude Code's plan file, and
`/trekexecute` checks the copy at the plan destination.
4. **"No, keep planning":** revise per the operator's words, run one Phase 9
round, and present again from step 1.

View file

@ -510,8 +510,12 @@ The coordinator runs the 4-pass process documented in
Succinctness never removes a finding: only MINOR/SUGGESTION text is
trimmed, and every trim is counted. A long BLOCKER or MAJOR keeps every
character.
3. **Cloudflare reasonableness** — remove speculative or catalogue-violating
findings (skipped in `quick` mode).
3. **Cloudflare reasonableness** (skipped in `quick` mode) — drop a finding
with no `file:line` citation (`no-citation`) or whose file is in neither
the working tree nor the diff (`file-existence:refuted`); route one whose
`rule_key` is not in `RULE_CATALOGUE` (`unknown-rule_key`) or whose file
cannot be checked (`file-existence:indeterminate`) to `unverified`, which
forbids `ALLOW`; reset a severity that does not match its catalogue tier.
4. **Verdict** — BLOCK / WARN / ALLOW per the threshold table.
**Fail-closed.** Every removal in Pass 2 and Pass 3 is either

View file

@ -80,7 +80,7 @@ If `{project_dir}/architecture/overview.md` exists (typically produced by an opt
| `--step N` | Execute only step N |
| `--fg` | Force foreground — run all steps sequentially, ignore Execution Strategy |
| `--session N` | Execute only session N from plan's Execution Strategy |
| `--gates [true\|false\|closed\|open]` | (v3.4.0) Autonomy flag: bare, `true` or `closed` = on; `false` or `open` = off; any other next token is not its value. See `## Autonomy mode` in `docs/operations.md`. `/trekexecute` records `gates_mode` and pauses on nothing; it adds no question of its own before a merge: the guard asks there (operator decision 2026-10-08). |
| `--gates [true\|false\|closed\|open]` | (v3.4.0) Autonomy flag: bare, `true` or `closed` = on; `false` or `open` = off; any other next token is not its value. See `## Autonomy mode` in `docs/operations.md`. `/trekexecute` accepts the flag and pauses on nothing; it adds no question of its own before a merge: the guard asks there (operator decision 2026-10-08). |
| `--profile <name>` | (v4.1.0) Profile for this run's resolver; the step helper and the controller stay on opus. Inherited from plan.md frontmatter `profile:` if present. |
Every mode that executes refuses a plan whose approval in the approval log does not cover its current sha256 (K2). A run ends with the review it starts by itself (`/simplify` → `/trekreview` → verdict gate); a BLOCK or an undecided verdict keeps the merge into main closed. `TREKEXECUTE_MAX_REVIEW_ROUNDS` (default 3) caps the reviews per run.

View file

@ -4,7 +4,7 @@ Imported from `CLAUDE.md` via pointer.
## Autonomy mode (`--gates`, v3.4.0)
All four pipeline commands accept `--gates [true|false|closed|open]`. A bare `--gates`, `--gates true` or `--gates closed` turns gating **on**; `--gates false`, `--gates open` or no flag (the default) leaves it off. The next token is the flag's value only when it is one of those four; any other token stays the command's own argument, so `/trekplan --gates --project <dir>` and `/trekexecute --gates plan.md` are a bare `--gates`. Each command maps the value once through `lib/util/autonomy-gate.mjs`; a shim that fails falls back to the same mapping in prose, and the run goes on. With gating on, `/trekbrief` pauses before its auto chain, `/trekresearch` after each topic, and `/trekplan` after exploration, synthesis and adversarial review. `/trekexecute` records `gates_mode` and pauses on nothing: its one stop is the merge into main, which always asks (below).
All four pipeline commands accept `--gates [true|false|closed|open]`. A bare `--gates`, `--gates true` or `--gates closed` turns gating **on**; `--gates false`, `--gates open` or no flag (the default) leaves it off. The next token is the flag's value only when it is one of those four; any other token stays the command's own argument, so `/trekplan --gates --project <dir>` and `/trekexecute --gates plan.md` are a bare `--gates`. Each command maps the value once through `lib/util/autonomy-gate.mjs`; a shim that fails falls back to the same mapping in prose, and the run goes on. With gating on, `/trekbrief` pauses before its auto chain, `/trekresearch` after each topic, and `/trekplan` after exploration, synthesis and adversarial review. `/trekexecute` accepts the flag and pauses on nothing: its one stop is the merge into main, which always asks (below).
Under the hood: `lib/util/autonomy-gate.mjs` runs the state machine `idle → gates_on | auto_running → paused_for_gate → completed`. `start` routes to `gates_on` when `--gates` is bare, `true` or `closed`, else `auto_running`; a `phase_boundary` from `gates_on` pauses at `paused_for_gate` (awaiting `resume`); `finish` reaches the terminal `completed`. The module is pure data with no I/O; the commands run only its `start` transition, to map the flag. Separately, `lib/stats/event-emit.mjs` records named lifecycle events (`brief-approved`, `main-merge-gate`, `main-merge-approved`, `main-merge-declined`, `user_input`) to `trekexecute-stats.jsonl` in the plugin data directory (`CLAUDE_PLUGIN_DATA`, else `~/.claude/plugins/data/voyage-ktg-plugin-marketplace/`, the directory `scripts/yardstick.mjs` reads); a write that fails is reported, never skipped. The main-merge gate is the final autonomy boundary before HEAD lands on `main`.

View file

@ -500,8 +500,9 @@ export function judgeFilter(findings) {
/**
* Pass 3 — Cloudflare reasonableness (deterministic subset): drop findings
* with no citation (empty file / line < 0) or an unknown rule_key; CORRECT a
* severity that does not match the catalogue tier (a correction, not a drop).
* with no citation (empty file / line < 0) and
* route an unknown rule_key to `unverified`; CORRECT a severity that does not
* match the catalogue tier (a correction, not a drop).
* The fs file-existence glob is excluded (I/O) — its indeterminate branch is
* prose-side, tokenised as `file-existence:indeterminate`.
*

View file

@ -18,7 +18,8 @@
// CLI shim:
// node lib/util/autonomy-gate.mjs --state X --event Y [--gates [true|false|closed|open]]
// (bare --gates = on; true|closed = on, false|open = off; any other value
// is a usage error, exit 1; case-sensitive)
// is a usage error, exit 1; case-sensitive. One token --gates=<value> is
// read as --gates <value>; an empty value is the same usage error)
// → JSON: { ok: true, next_state: "..." } (success)
// → JSON: { ok: false, error: "..." } (invalid transition; exit 1)
//
@ -104,27 +105,37 @@ export function isTerminal(state) {
// ---- CLI shim ----------------------------------------------------------------
/** A --gates value: true|closed = on, false|open = off, else undefined. */
function gatesValue(v) {
if (v === 'true' || v === 'closed') return true;
if (v === 'false' || v === 'open') return false;
return undefined;
}
const GATES_USAGE = 'usage: --gates takes true, false, closed or open';
function parseArgs(argv) {
const out = {};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--state') out.state = argv[++i];
else if (a === '--event') out.event = argv[++i];
else if (a === '--gates') {
else if (a.startsWith('--gates=')) {
// The one-token form, read as `--gates <value>`; an empty value is no value.
const g = gatesValue(a.slice('--gates='.length));
if (g === undefined) out.error = GATES_USAGE;
else out.gates = g;
} else if (a === '--gates') {
// Bare `--gates` (last, or followed by another flag) means on and
// consumes nothing. A value is case-sensitive: true|closed = on,
// false|open = off; anything else is a usage error.
const v = argv[i + 1];
if (v === undefined || v.startsWith('--')) {
out.gates = true;
} else if (v === 'true' || v === 'closed') {
out.gates = true;
i++;
} else if (v === 'false' || v === 'open') {
out.gates = false;
i++;
} else {
out.error = 'usage: --gates takes true, false, closed or open';
const g = gatesValue(v);
if (g === undefined) out.error = GATES_USAGE;
else out.gates = g;
i++;
}
}

View file

@ -245,7 +245,7 @@ function coordinatorPrompt(merged, a) {
'(file,line,rule_key) in JS. Do NOT re-dedup. Apply ONLY bounded operations:',
'HubSpot Judge filters (accuracy / actionability; succinctness NEVER removes a',
'finding — trim only MINOR/SUGGESTION text, keep BLOCKER/MAJOR whole), Cloudflare',
'reasonableness (drop speculative or catalogue-violating findings), then compute',
'reasonableness (drop a finding with no file:line citation or whose file is in neither the tree nor the diff; keep, never drop, a finding whose rule_key is not in the catalogue or whose file cannot be checked), then compute',
'the verdict (BLOCK if any BLOCKER; WARN if any MAJOR; else ALLOW).',
'Return {verdict, findings} via StructuredOutput. Synthesis across files is forbidden.',
`The brief (the contract) is at: ${a.briefPath} — Read it if needed for context.`,

View file

@ -19,14 +19,14 @@
| schema_id | fields | writer_path | line_ref | v4.1 additive | PII |
|-----------|--------|-------------|----------|---------------|-----|
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1140-1164 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:806-839 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1305-1333 | profile, phase_models, parallel_agents, profile_source | none |
| trekbrief-stats | ts, task, slug, mode, interview_turns, review_iterations, brief_quality, research_topics, auto_research, auto_result, intent_approved, project_dir, order_id, order_claimed_at, order_reason | commands/trekbrief.md (orchestrator-emit Phase 7; order_* added by lib/stats/stats-append.mjs) | trekbrief.md:1141-1165 | profile, phase_models, profile_source | none |
| trekresearch-stats | ts, question, mode, scope, engine, slug, project_dir, brief_path, dimensions, dimensions_baseline, dimensions_baseline_preserved, effort, conv_turns, empty_turns, unique_sources, agents_local, agents_external, gemini_used, confidence, contradictions, open_questions | lib/stats/stats-append.mjs (trekresearch.md Stats tracking) | trekresearch.md:822-855 | profile, phase_models, parallel_agents, external_research_enabled, profile_source | none |
| trekplan-stats | ts, task, mode, slug, brief_path, project_dir, codebase_size, codebase_files, agents_deployed, deep_dives, research_briefs_used, research_scout_used, critic_verdict, guardian_verdict, outcome | lib/stats/stats-append.mjs (trekplan.md Phase 12) | trekplan.md:1307-1335 | profile, phase_models, parallel_agents, profile_source | none |
| trekexecute-stats (Phase 9 record) | ts, plan, plan_type, mode, result, steps_total, steps_passed, steps_failed, steps_skipped, failed_at_step | lib/stats/stats-append.mjs (trekexecute.md Phase 9) | trekexecute.md:2969-2991 | profile, phase_models, profile_source | none |
| trekexecute-stats (autonomy events) | ts, event, known_event, payload | lib/stats/event-emit.mjs `emit()` | event-emit.mjs:78-99 | payload.profile, payload.phase_models, payload.profile_source | none |
| trekexecute-stats (PostToolUse Bash) | ts, session_id, command_excerpt, duration_ms, success | hooks/scripts/post-bash-stats.mjs (Bash PostToolUse) | post-bash-stats.mjs:42-54 | none (hook is plugin-level, not profile-aware) | command_excerpt (CWE-212) |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:636-686 | profile, phase_models, profile_source | none |
| trekcontinue-stats | ts, project, next_session_label, status | lib/stats/stats-append.mjs (trekcontinue.md Phase 5) | trekcontinue.md:293-297 | profile, profile_source | none |
| trekreview-stats | ts, slug, verdict, counts (BLOCKER/MAJOR/MINOR/SUGGESTION), reviewed_files_count, mode, duration_ms | lib/stats/stats-append.mjs (trekreview.md Phase 8) | trekreview.md:640-690 | profile, phase_models, profile_source | none |
| trekcontinue-stats | ts, project, next_session_label, status | lib/stats/stats-append.mjs (trekcontinue.md Phase 5) | trekcontinue.md:304-308 | profile, profile_source | none |
| token-usage | ts, session_id, scope, model, tokens_input, tokens_output, tokens_cache_creation, tokens_cache_read, cost_usd, is_estimate, price_table_version | lib/stats/token-usage.mjs `captureTokenUsage` (via hooks/scripts/otel-export.mjs Stop hook, opt-in `VOYAGE_TOKEN_METER`) | token-usage.mjs:buildRecord | n/a (SKAL-2 schema) | session_id (stripped at export) |
## Field-allowlist input for Step 11

View file

@ -191,3 +191,18 @@ test('--gates false → auto_running', () => {
test('--gates open → auto_running', () => {
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates', 'open']), 'auto_running');
});
// The `=` form reaches the shim whole as one token; it is read as `--gates <value>`.
test('[#1] sibling: --gates=<value> is read as --gates <value>; an unknown or empty value is the usage error', () => {
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=true']), 'gates_on');
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=closed']), 'gates_on');
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=false']), 'auto_running');
assert.equal(shimState(['--state', 'idle', '--event', 'start', '--gates=open']), 'auto_running');
for (const bad of ['--gates=maybe', '--gates=']) {
const r = runShim(['--state', 'idle', '--event', 'start', bad]);
assert.equal(r.code, 1, `expected exit 1 for ${bad}, got ${r.code}: ${r.out}`);
const parsed = JSON.parse(r.out.trim());
assert.equal(parsed.ok, false);
assert.match(parsed.error, /usage: --gates takes true, false, closed or open/);
}
});

View file

@ -2084,8 +2084,11 @@ function schemaRow(id) {
// Each row is pinned to the span it names: its phase heading to the closing
// fence of its stats-append stdin block. The trekreview anchor is the full
// "## Phase 8 — Validate output" so the Phase 8.5 / 8.7 headings never match.
// The trekresearch anchor is "### Stats tracking", the one such heading in
// commands/trekresearch.md.
const STATS_ROWS = [
{ id: 'trekbrief-stats', cmd: 'trekbrief', heading: /^## Phase 7\b(?!\.)/ },
{ id: 'trekresearch-stats', cmd: 'trekresearch', heading: /^### Stats tracking$/ },
{ id: 'trekplan-stats', cmd: 'trekplan', heading: /^## Phase 12\b(?!\.)/ },
{ id: 'trekreview-stats', cmd: 'trekreview', heading: /^## Phase 8 — Validate output/ },
{ id: 'trekexecute-stats (Phase 9 record)', cmd: 'trekexecute', heading: /^## Phase 9\b(?!\.)/ },

View file

@ -111,10 +111,28 @@ function dropsOnlyWithTrace(part, where) {
assert.match(part, /drop[^.]{0,60}only when you can say why it is wrong[^.]{0,120}Declined to Judge[^.]{0,60}with that reason/i,
`${where}: a drop must carry its reason on a Declined to Judge line`);
assert.match(part, /catalogue('s)? (tier|severity)/i, `${where}: the catalogue tier stays the default`);
// Every sentence that drops (other than "not / never dropped") carries the reason and the trace.
for (const s of part.split(/(?<=\.)\s+/)) {
if (!/\bdrop/i.test(s) || /\b(not|never) dropped\b/i.test(s)) continue;
assert.match(s, /only when you can say why it is wrong/i, `${where}: a drop sentence names no reason: ${s}`);
assert.match(s, /Declined to Judge/i, `${where}: a drop sentence leaves no Declined to Judge line: ${s}`);
}
}
const toBlank = (l) => l.trim() === '';
// Untagged: these test the helper itself, so they pass at ab6b32c by construction.
const COMPLIANT_DROP = 'If a finding feels less severe than its catalogue tier, keep the catalogue tier; drop it only '
+ 'when you can say why it is wrong, and then write one Declined to Judge line with that reason.';
test('step 25: dropsOnlyWithTrace throws on a compliant off-tier sentence followed by " Drop it."', () => {
assert.throws(() => dropsOnlyWithTrace(`${COMPLIANT_DROP} Drop it.`, 'planted'), /a drop sentence names no reason: Drop it\./);
});
test('step 25: dropsOnlyWithTrace does not throw on the compliant sentence alone (control)', () => {
assert.doesNotThrow(() => dropsOnlyWithTrace(COMPLIANT_DROP, 'control'));
});
test('[#111] code-correctness-reviewer dimension 4: a nice-to-have test gap is dropped only with a Declined to Judge line', () => {
dropsOnlyWithTrace(partAt('agents/code-correctness-reviewer.md', 'When the brief explicitly asks for tests', toBlank),
'code-correctness-reviewer dimension 4');
@ -164,6 +182,41 @@ test('[#111] review-coordinator input: a rule_key outside the catalogue is route
'review-coordinator ## Input');
});
// The coordinator's Pass 3 as its 4-pass lists say it (agents/review-coordinator.md ### Pass 3).
const PASS3_ITEM = '**Cloudflare reasonableness** — drop a finding with no `file:line` citation (`no-citation`) or whose '
+ 'file is in neither the working tree nor the diff (`file-existence:refuted`); route one whose `rule_key` is not in '
+ '`RULE_CATALOGUE` (`unknown-rule_key`) or whose file cannot be checked (`file-existence:indeterminate`) to '
+ '`unverified`, which forbids `ALLOW`; reset a severity that does not match its catalogue tier.';
/** Item 3 of a 4-pass list: from "3. **Cloudflare reasonableness**" to item 4. */
const pass3Item = (rel) => partAt(rel, '3. **Cloudflare reasonableness**', (l) => /^4\. /.test(l));
/** No item 3 says an unknown rule_key or a "catalogue-violating" finding is dropped. */
function pass3DropsNoUnknownKey(item, where) {
assert.doesNotMatch(item, /catalogue-violating/, `${where}: item 3 still drops catalogue-violating findings`);
assert.doesNotMatch(item, /drop findings whose `rule_key` is not in/, `${where}: item 3 still drops an unknown rule_key`);
}
test('[#111] sibling: agents/review-orchestrator.md item 3 says what Pass 3 does', () => {
const item = pass3Item('agents/review-orchestrator.md');
assert.ok(item.includes(`3. ${PASS3_ITEM}`), `item 3 is not the Pass 3 sentence: ${item}`);
pass3DropsNoUnknownKey(item, 'agents/review-orchestrator.md');
});
test('[#111] sibling: commands/trekreview.md item 3 says what Pass 3 does, skipped in quick mode', () => {
const item = pass3Item('commands/trekreview.md');
const want = `3. ${PASS3_ITEM.replace('**Cloudflare reasonableness** —', '**Cloudflare reasonableness** (skipped in `quick` mode) —')}`;
assert.ok(item.includes(want), `item 3 is not the Pass 3 sentence: ${item}`);
pass3DropsNoUnknownKey(item, 'commands/trekreview.md');
});
test('[#111] sibling: the reasonablenessFilter docblock routes an unknown rule_key to `unverified`', () => {
const doc = partAt('lib/review/coordinator-contract.mjs', 'Pass 3 — Cloudflare reasonableness (deterministic subset)',
(l) => /^\s*\*\/\s*$/.test(l));
assert.ok(doc.includes('route an unknown rule_key to `unverified`'), `the docblock does not route an unknown rule_key: ${doc}`);
assert.ok(!doc.includes('or an unknown rule_key;'), 'the docblock still drops an unknown rule_key');
});
// Steps 2-24 use these helpers; referenced here so the file states its interface.
test('helpers: line and bash are the shared line-runner pair', () => {
assert.equal(typeof line, 'function');
@ -187,7 +240,8 @@ test('[#2] /trekexecute never sends anyone to re-run the planning reference docu
assert.match(text, /Re-run \/trekplan — the plan is not executable\./);
assert.match(text, /Fix: re-run \/trekplan — the plan must use v1\.7 format/);
assert.match(text, /Fix: re-run \/trekplan — every step needs a Manifest YAML block/);
assert.match(text, /\/trekplan's validator should have prevented this/);
assert.doesNotMatch(text, /validator should have prevented this/, 'Check 3 still names a validator check that does not exist');
assert.match(text, /Stop execution\. This check runs at execution time, so it also catches a plan that was edited by hand after planning\./);
});
test('[#2] sibling: commands/trekbrief.md names /trekplan as what finishes', () => {
@ -282,10 +336,13 @@ test('[#52] Phase 10 menu: "execute" is for the /trekexecute line, and a stopped
assert.match(p10, /`Execution is blocked: \{the gate's reasons\}` and do not print the `\/trekexecute` line, whatever is said next, until the gate exits 0\./);
});
test('[#52] sibling: plan mode step 3 prints the /trekexecute line and stops', () => {
test('[#52] sibling: plan mode step 3 goes through Phase 11 (its plan approval, then the /trekexecute line) and stops', () => {
const pm = flat(section(read('commands/trekplan.md'), '### In plan mode'));
assert.ok(pm.includes('Then go through Phase 11 (its plan approval, then the `/trekexecute` line) and stop — leaving plan mode is not an order to implement. '
+ 'Phase 11 asks again because the plan-mode approval is logged under Claude Code\'s plan file, and `/trekexecute` checks the copy at the plan destination.'),
'plan mode step 3 does not go through Phase 11\'s plan approval');
assert.doesNotMatch(pm, /Then continue with the operator's choice \(Phase 11\)\./, 'plan mode still continues with the operator\'s choice');
assert.match(pm, /Then print the `\/trekexecute` line \(Phase 11\) and stop — leaving plan mode is not an order to implement\./);
assert.ok(!pm.includes('Then print the `/trekexecute` line (Phase 11) and stop'), 'plan mode still skips Phase 11\'s plan approval');
});
test('[#52] hard rule: "No premature execution" holds, and nothing in Phases 10-11 implements the plan', () => {
@ -334,7 +391,6 @@ test('[#48] commands/trekplan.md pins no spawn to `model: "opus"`', () => {
});
for (const [where, marker] of [
['session-decomposer (Phase 1.5)', 'Launch the **session-decomposer** agent:'],
['brief-reviewer (Phase 4b)', 'Launch the **brief-reviewer** agent.'],
['### Always launch (Phase 5)', '### Always launch (all codebase sizes):'],
['research-scout (Phase 5)', 'launch **research-scout** in'],
@ -347,6 +403,14 @@ for (const [where, marker] of [
});
}
test('[#48] sibling: session-decomposer (Phase 1.5): --decompose has no resolved model, so the spawn passes no model:', () => {
const part = paraAfter('commands/trekplan.md', 'Launch the **session-decomposer** agent:');
assert.ok(part.includes('Phase 1 runs the model resolver only for `--project`, so `--decompose` has no resolved model: this spawn '
+ 'passes no `model:`, and the agent\'s own `model:` applies.'), `the session-decomposer paragraph does not say it passes no model: ${part}`);
assert.ok(!part.includes('phase_signal_result'), 'the session-decomposer spawn still names phase_signal_result');
assert.doesNotMatch(part, /model: "opus"/);
});
test('[#48] convention-scanner (Phase 5): the spawn takes phase_signal_result.model, the frontmatter as the fallback', () => {
const part = paraAfter('commands/trekplan.md', 'use the `convention-scanner` plugin agent');
assert.match(part, /\(model: phase_signal_result\.model, the agent's frontmatter as the fallback\)/);
@ -514,7 +578,7 @@ test('[#47] Phase 5: a brief with no research topics prints /trekplan --project
assert.match(opening, /Phase 6 is never entered without the operator's opt-in\./);
const manual = flat(section(text, '### Manual path (default)'));
assert.match(manual, /Then: \/trekplan --project \{PROJECT_DIR\} Then: \/trekexecute --project \{PROJECT_DIR\}/);
assert.match(manual, /Stop\. Do not continue to Phase 6\./);
assert.match(manual, /Run Phase 7 \(`auto_result: manual`\), then stop\. Do not continue to Phase 6\./);
});
test('[#47] Phase 6 runs only after the operator opted into auto mode and Phase 4h approved the intent', () => {
@ -524,7 +588,43 @@ test('[#47] Phase 6 runs only after the operator opted into auto mode and Phase
test('keep: /trekbrief\'s auto path still requires an approved intent', () => {
assert.equal(partAt('commands/trekbrief.md', '**Auto requires an approved intent.**', toBlank),
'**Auto requires an approved intent.** If Phase 4h ended "Leave unapproved", the auto chain would halt at `/trekplan` anyway — print the manual-path output instead and stop.');
'**Auto requires an approved intent.** If Phase 4h ended "Leave unapproved", the auto chain would halt at `/trekplan` anyway — print the manual-path output instead, run Phase 7 (`auto_result: manual`) and stop.');
});
// Every exit of /trekbrief's manual and auto paths before the plan runs Phase 7
// first, with the auto_result Phase 7 lists: manual, cancelled or failed.
const BRIEF_OLD_EXITS = [
'Stop. Do not continue to Phase 6.',
'print the manual-path output instead and stop.',
'fall back to the manual path output and stop.',
'If cancelled → fall back to manual path output and stop.',
];
/** No old exit sentence stands in commands/trekbrief.md (case-sensitive). */
function noOldBriefExit() {
const f = flat(read('commands/trekbrief.md'));
for (const old of BRIEF_OLD_EXITS) assert.ok(!f.includes(old), `an exit still stops without Phase 7: ${old}`);
}
test('[#47] sibling: Phase 5 runs Phase 7 before each of its three exits', () => {
const p5 = flat(section(read('commands/trekbrief.md'), '## Phase 5 — Auto-orchestration opt-in (if research_topics > 0)'));
for (const s of [
'Run Phase 7 (`auto_result: manual`), then stop. Do not continue to Phase 6.',
'print the manual-path output instead, run Phase 7 (`auto_result: manual`) and stop.',
'fall back to the manual path output, run Phase 7 (`auto_result: cancelled`) and stop.',
]) assert.ok(p5.includes(s), `Phase 5 is missing the exit: ${s}`);
noOldBriefExit();
});
test('[#47] sibling: Phase 6 runs Phase 7 before each of its three exits', () => {
const p6 = flat(section(read('commands/trekbrief.md'), '## Phase 6 — Auto research dispatch (auto path only)'));
for (const s of [
'If cancelled → fall back to manual path output, run Phase 7 (`auto_result: cancelled`) and stop.',
'ask the user how to proceed (retry, skip missing topic, cancel). On cancel, run Phase 7 (`auto_result: cancelled`) and stop.',
'Wait for the plan to be written to `{PROJECT_DIR}/plan.md` before continuing. If `/trekplan` stops before it writes the plan, '
+ 'report where it stopped, run Phase 7 (`auto_result: failed`) and stop.',
]) assert.ok(p6.includes(s), `Phase 6 is missing the exit: ${s}`);
noOldBriefExit();
});
// --- #1 ----------------------------------------------------------------------------
@ -533,7 +633,8 @@ test('keep: /trekbrief\'s auto path still requires an approved intent', () => {
// never makes. Each of the four commands now holds one rule: the next token is
// the flag's value only when it is true, false, closed or open; the shim maps
// it, run once; a broken shim falls back to the same mapping in prose and the
// run goes on. /trekexecute records gates_mode and pauses on nothing.
// run goes on. `gates_mode` changes nothing in `/trekexecute`: its one stop is
// the merge into main.
const GATES_COMMANDS = ['trekbrief', 'trekresearch', 'trekplan', 'trekexecute'];
@ -578,17 +679,47 @@ test('keep: every command\'s shim line maps a bare --gates to gates_on and open
}
});
const GATES_ONE_PAUSE = '`gates_mode = true` adds one pause, before the auto chain starts (Phase 5); the chain does not pass '
+ '`--gates` on to `/trekresearch` or `/trekplan`. Default `gates_mode = false` skips that pause.';
test('[#1] sibling: commands/trekbrief.md promises no execute pause and no pause at every wave in the executor', () => {
const f = flat(read('commands/trekbrief.md'));
assert.doesNotMatch(f, /every wave in the executor/, 'trekbrief still promises a pause at every wave in the executor');
assert.doesNotMatch(f, /\(research, plan, execute\)/, 'trekbrief still promises an execute pause');
assert.match(f, /approval pauses at every phase boundary in the downstream pipeline \(research, plan\)\./);
assert.ok(f.includes(GATES_ONE_PAUSE), 'trekbrief does not say --gates adds one pause before its auto chain');
});
test('[#1] sibling: commands/trekexecute.md records gates_mode and pauses only at the merge into main', () => {
test('[#1] sibling: /trekbrief Phase 1 says --gates adds one pause, before the auto chain', () => {
const p1 = flat(section(read('commands/trekbrief.md'), '## Phase 1 — Parse mode and validate input'));
assert.ok(p1.includes(GATES_ONE_PAUSE), 'Phase 1 does not say --gates adds one pause before the auto chain');
assert.ok(!p1.includes('every phase boundary in the downstream pipeline'), 'Phase 1 still promises a pause at every downstream phase boundary');
assert.ok(!p1.includes('runs continuously until the main-merge gate'), 'Phase 1 still says the auto chain runs until the main-merge gate');
});
test('[#1] sibling: /trekbrief Phase 5 says the auto chain ends at the plan, never at the main-merge gate', () => {
const p5 = flat(section(read('commands/trekbrief.md'), '## Phase 5 — Auto-orchestration opt-in (if research_topics > 0)'));
assert.ok(p5.includes('The chain ends when `/trekplan` has written the plan (Step 6e); it never starts `/trekexecute`.'),
'Phase 5 does not say the chain ends when /trekplan has written the plan');
assert.ok(!p5.includes('The chain stops only at the main-merge gate'), 'Phase 5 still says the chain stops only at the main-merge gate');
});
test('[#1] sibling: docs/operations.md, docs/command-modes.md and README.md say /trekexecute accepts the flag and pauses on nothing', () => {
for (const rel of ['docs/operations.md', 'docs/command-modes.md', 'README.md']) {
assert.ok(flat(read(rel)).includes('`/trekexecute` accepts the flag and pauses on nothing'), `${rel}: missing "accepts the flag and pauses on nothing"`);
}
for (const rel of ['docs/operations.md', 'docs/command-modes.md', 'README.md', EXEC]) {
const f = flat(read(rel));
for (const old of ['records `gates_mode`', 'Record gates_mode', 'records the setting']) {
assert.ok(!f.includes(old), `${rel}: still says "${old}"`);
}
}
});
test('[#1] sibling: commands/trekexecute.md: `gates_mode` changes nothing in `/trekexecute`, and its one stop is the merge into main', () => {
const f = flat(read('commands/trekexecute.md'));
assert.doesNotMatch(f, /consumed by the autonomy-gate state machine/);
assert.match(f, /`\/trekexecute` records `gates_mode`; no phase of it pauses on it\. The one stop is the merge into main, which always asks \(Phase 8\)\./);
assert.ok(f.includes('`gates_mode` changes nothing in `/trekexecute`: no phase of it pauses on it. The one stop is the merge into main, which always asks (Phase 8).'),
'trekexecute does not say gates_mode changes nothing there and the one stop is the merge into main');
});
test('[#1] sibling: docs/operations.md gives the four values and the bare form, and no executor strictness table', () => {
@ -675,7 +806,7 @@ test('[#30] the usage block names --profile and --gates, and its --profile line
assert.ok(usage.includes('/trekexecute --profile <name> <plan.md>'), 'the usage block has no --profile line');
assert.ok(usage.includes(`--profile Profile for this run's resolver; ${SAME_ON_OPUS}`),
'the Modes list has no --profile line, or it does not keep the helper and the controller on opus');
assert.ok(usage.includes('--gates Record gates_mode: bare, true or closed = on; false or open = off'),
assert.ok(usage.includes('--gates Accepted, pauses on nothing: bare, true or closed = on; false or open = off'),
'the Modes list has no --gates line');
});
@ -771,7 +902,9 @@ test('[#33] Phase 2.5 names low effort as one session, steps in order, never whi
assert.ok(start !== -1, 'Phase 2.5 must still list when it runs as a single session');
const end = sec.findIndex((l, i) => i > start && l.trim() === '');
const bullets = sec.slice(start + 1, end);
assert.ok(bullets.includes("- `phase_signal_result.effort == 'low'` → one session, steps in order — never while a wave is in flight (`WAVE_IN_FLIGHT`)"),
assert.ok(bullets.includes("- `phase_signal_result.effort == 'low'` → one session, steps in order, unless a wave is in flight: when "
+ '`.claude/trekplan-sessions/{slug}/orders/wave.json` exists in the main checkout (the first entry of `git worktree list`), '
+ 'this bullet does not apply and the multi-session path below collects the wave. The check only reads the file and never stops the run.'),
`the single-session list does not name low effort: ${bullets.join(' | ')}`);
});
@ -825,6 +958,10 @@ test('[#28] the completion gate names its commands and its executor judgement, a
assert.ok(commands.includes(cmd), `the Commands part does not name ${cmd}`);
assert.ok(audit.includes(cmd), `Phase 7.5 does not run ${cmd}, so the gate cannot call it a command`);
}
// `git diff --name-only` in its step-6 form (step 5's line has no `-- {path}`).
assert.ok(commands.includes("step 6's `git diff --name-only`"), 'the Commands part does not name step 6\'s git diff --name-only');
assert.ok(audit.includes('`git diff --name-only {session_start_sha} HEAD -- {path}`'),
'Phase 7.5 step 6 does not run git diff --name-only {session_start_sha} HEAD -- {path}');
for (const s of ['criteria runner', 'arch-rules.mjs', 'plan-scope-sweep.mjs']) {
assert.ok(commands.includes(s), `the Commands part does not name ${s}`);
}
@ -833,6 +970,14 @@ test('[#28] the completion gate names its commands and its executor judgement, a
}
});
test('[#28] sibling: the completion gate says the judgement parts are not yet a script, and qualifies clauses 2 and 4', () => {
const gate = completionGate();
assert.ok(gate.includes('The judgement parts are not yet a script.'), 'the gate does not say the judgement parts are not yet a script');
assert.ok(gate.includes('the criteria runner behind clauses 2 and 4 for a trekplan (a session spec\'s clause 2 is Phase 7\'s '
+ '`## Exit Condition` checklist, and clause 4 is a trekplan\'s only);'), 'the gate does not qualify the criteria runner behind clauses 2 and 4');
assert.ok(!gate.includes('part c of the hardening'), 'the gate still points at part c of the hardening');
});
// ---- Architecture rule (plan, Step 12): lib/ never imports from scripts/ ----
/**
@ -1566,6 +1711,17 @@ test('CHANGELOG: ### Doors has the two-way line for fix round 4.5a', () => {
'the Doors part has no single two-way line for fix round 4.5a');
});
const SIBLINGS_ENTRY = '- **The texts next to the fixes say the same** (siblings found during the round): `/trekreview`, the review '
+ 'reference and the Arm B coordinator prompt no longer drop a finding whose rule key is unknown; `/trekbrief` writes its stats '
+ 'record on every exit of its manual and auto paths, says that `--gates` adds one pause before its auto chain, and that the chain '
+ 'ends at the plan; `/trekexecute` says `--gates` changes nothing there, checks the wave marker before a low-effort single session, '
+ 'and names no validator check that does not exist; plan mode goes through the plan approval before it prints the `/trekexecute` '
+ 'line; the gates shim reads `--gates=true` as on.';
test('CHANGELOG: ### Fixed holds the siblings entry, exactly', () => {
assert.ok(roundBullets('### Fixed').includes(SIBLINGS_ENTRY), '### Fixed has no siblings entry with the exact text');
});
test('CHANGELOG: the released v6.5.0 section is byte-equal to its text at ab6b32c (sha256)', () => {
const part = changelogPart((l) => l.startsWith('## v6.5.0 — '), 'v6.5.0');
assert.equal(createHash('sha256').update(part, 'utf8').digest('hex'), V650_SHA256,

View file

@ -153,9 +153,10 @@ test('resolveStatsPath honors CLAUDE_PLUGIN_DATA, else the stats-append director
assert.equal(resolveStatsPath({ HOME: '/h', CLAUDE_PLUGIN_DATA: '' }), join('/h', DATA_REL, 'trekexecute-stats.jsonl'));
});
// gates_mode_flag_override stays a known event; no command emits it since fix round 4.5a
test('the Phase 8 and override events are known events', () => {
for (const e of ['main-merge-approved', 'main-merge-declined', 'gates_mode_flag_override']) {
assert.equal(buildRecord(e).known_event, true, `${e} is emitted by /trekexecute by design`);
assert.equal(buildRecord(e).known_event, true, `${e} is a known event`);
}
});

View file

@ -75,6 +75,21 @@ test('armB — a coordinator ALLOW over a BLOCKER finding is recomputed to BLOCK
assert.equal(r.verdict, 'BLOCK');
});
test('[#111] sibling: the Arm B coordinator prompt keeps, never drops, a finding whose rule_key is not in the catalogue', async () => {
const f = { severity: 'MAJOR', rule_key: 'MISSING_TEST', file: 'a.mjs', line: 1, title: 't', detail: 'd' };
const s = stubs({
reviewers: [{ reviewer: 'c', findings: [f] }, { reviewer: 'k', findings: [] }],
coordinator: { verdict: 'WARN', findings: [f] },
});
await exec({ ...BASE, enginePayloads: [engine('claude-code-review'), engine('claude-security-review')] }, s);
const prompt = (s.calls.coordinatorPrompt ?? '').replace(/\s+/g, ' ');
assert.ok(prompt.includes('keep, never drop, a finding whose rule_key is not in the catalogue'),
'the coordinator prompt does not keep a finding whose rule_key is not in the catalogue');
assert.ok(prompt.includes('drop a finding with no file:line citation or whose file is in neither the tree nor the diff'),
'the coordinator prompt does not name what Pass 3 drops');
assert.ok(!prompt.includes('catalogue-violating'), 'the coordinator prompt still drops catalogue-violating findings');
});
test('armB — quick mode expects only the /code-review engine', async () => {
const s = stubs({ reviewers: [{ reviewer: 'k', findings: [] }] });
const r = await exec({ ...BASE, quick: true, enginePayloads: [engine('claude-code-review')] }, s);