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:
parent
2b5d5f72bc
commit
2fe6feb74d
6 changed files with 134 additions and 61 deletions
|
|
@ -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.');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue