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:
parent
e351e7c29d
commit
8a2a989f79
18 changed files with 284 additions and 71 deletions
10
tests/fixtures/jsonl-schemas.md
vendored
10
tests/fixtures/jsonl-schemas.md
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(?!\.)/ },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue