fix(execute): run the brief check in Phase 1, and say what effort really changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-10-22 16:07:27 +02:00
commit 2fe6feb74d
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
6 changed files with 134 additions and 61 deletions

View file

@ -17,7 +17,8 @@
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { copyFileSync, existsSync, mkdtempSync, readFileSync, readdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
@ -698,3 +699,89 @@ test('[#30] sibling: README.md /trekexecute Profile row keeps the helper and the
assert.doesNotMatch(rows[0], /Pin model profile for execution/);
assert.ok(rows[0].includes(`| (v4.1.0) Profile for this run's resolver; ${SAME_ON_OPUS}.`), rows[0]);
});
// --- #33 ---------------------------------------------------------------------------
// /trekexecute promised a brief check and an effort-driven gates mode that no
// line ran. Phase 1's --project branch now runs the two lines /trekplan runs,
// for the execute phase, and halts only on BRIEF_V51_MISSING_SIGNALS (a brief
// edited since planning does not stop a --resume on any other error). Low
// effort means one session, step after step; high effort changes nothing.
/** Phase 1's rule 1 (the --project branch), as lines. */
function projectRule() {
const sec = section(read(EXEC), '## Phase 1 — Parse mode and validate input').split('\n');
const start = sec.findIndex((l) => l.startsWith('1. If arguments contain `--project <dir>`'));
assert.ok(start !== -1, 'Phase 1 must still open with the --project rule');
const end = sec.findIndex((l, i) => i > start && /^2\. /.test(l));
assert.ok(end !== -1, 'Phase 1 must still have a rule 2');
return sec.slice(start, end).join('\n');
}
test('[#33] Phase 1 --project runs brief-validator --soft --json and the execute resolver, and halts only on BRIEF_V51_MISSING_SIGNALS', () => {
const rule = projectRule();
const validator = line(rule, 'validators/brief-validator.mjs', '--soft --json');
const resolver = line(rule, 'profiles/resolver.mjs', '--resolve-phase-model --phase execute');
assert.equal(validator, 'node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{dir}/brief.md"');
assert.equal(resolver,
'node ${CLAUDE_PLUGIN_ROOT}/lib/profiles/resolver.mjs --resolve-phase-model --phase execute --brief-path "{dir}/brief.md" [--profile {profile}] --json');
const f = flat(rule);
for (const s of [
'The validator exits 1 when its `errors` are not empty; read its JSON either way.',
'Halt only when the validator\'s `errors` hold `BRIEF_V51_MISSING_SIGNALS`, with the message from "Sequencing gate surface" below.',
'Any other error is printed in one line and the run continues.',
'A validator or resolver that cannot run (no JSON) is "not measured" in one line, and the run continues.',
'Capture the resolver\'s JSON as `phase_signal_result` (Composition rule below).',
'No `{dir}/brief.md`: skip both lines.',
]) assert.ok(f.includes(s), `the --project rule is missing: ${s}`);
// The two lines run as printed: the validator names the one halting code for a
// 2.1 brief without signals, and the resolver hands Phase 2.5 its low effort.
const dir = mkdtempSync(join(tmpdir(), 'fix-33-'));
try {
copyFileSync(join(ROOT, 'tests', 'fixtures', 'brief-v21-no-signals.md'), join(dir, 'brief.md'));
const v = bash(validator.split('{dir}').join(dir), ROOT);
assert.equal(v.status, 1, `validator: ${v.stdout}${v.stderr}`);
assert.ok(JSON.parse(v.stdout).errors.some((e) => e.code === 'BRIEF_V51_MISSING_SIGNALS'), v.stdout);
copyFileSync(join(ROOT, 'tests', 'fixtures', 'brief-effort-low.md'), join(dir, 'brief.md'));
const r = bash(resolver.split('{dir}').join(dir).replace(' [--profile {profile}]', ''), ROOT);
assert.equal(r.status, 0, `resolver: ${r.stdout}${r.stderr}`);
assert.equal(JSON.parse(r.stdout).effort, 'low');
} finally { rmSync(dir, { recursive: true, force: true }); }
});
test('[#33] the Composition rule says the resolver runs in Phase 1, and no line says Phase 2.4 runs it', () => {
assert.doesNotMatch(flat(read(EXEC)), /invoked in Phase 2\.4/);
assert.ok(flat(section(read(EXEC), '## Composition rule (v5.1)')).includes('invoked in Phase 1 alongside the sequencing-gate brief-validator call'),
'the Composition rule does not say the resolver is invoked in Phase 1');
});
test('[#33] sibling: Composition rule has no "uniformity and stats" and no gates auto-escalation', () => {
const f = flat(section(read(EXEC), '## Composition rule (v5.1)'));
assert.doesNotMatch(f, /uniformity and stats/, 'the Composition rule still says the model is returned for stats');
assert.ok(f.includes('it is returned for cross-command uniformity; no stats record carries it.'),
'the Composition rule does not say no stats record carries the model');
assert.doesNotMatch(f, /gates auto-escalation/);
});
test('[#33] Phase 2.5 names low effort as one session, steps in order, never while a wave is in flight', () => {
const sec = section(read(EXEC), '## Phase 2.5 — Execution strategy decision').split('\n');
const start = sec.findIndex((l) => l.startsWith('**Run as single session (foreground)** when ANY of these are true:'));
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`)"),
`the single-session list does not name low effort: ${bullets.join(' | ')}`);
});
test('[#33] no gates_mode = \'closed\', "strictest" or --event gates_mode_flag_override is left, and high effort says it changes nothing', () => {
const text = read(EXEC);
const f = flat(text);
assert.doesNotMatch(f, /gates_mode = 'closed'/);
assert.doesNotMatch(f, /strictest/);
assert.doesNotMatch(f, /halts immediately/);
assert.doesNotMatch(text, /--event gates_mode_flag_override/);
assert.doesNotMatch(f, /gates_mode = '(adaptive|open)'/);
assert.equal(flat(section(text, '### High-effort behavior (v5.1.1)')),
'### High-effort behavior (v5.1.1) High effort changes nothing in `/trekexecute`: the step helper and the controller '
+ 'already run opus, and every gate already runs. No `gates_mode` is set from the effort.');
});