chore(cleanup): R-b R3 — profiles, phase signals and the effort dialog removed, one fixed path per command
The four profiles, the resolver, the profile validator and docs/profiles.md go; /trekbrief Phase 3.5 and the brief's phase_signals checks go (an older brief's key is ignored); MANIFEST_PROFILE_DRIFT and profile_used go. Every Profile, Composition rule, Sequencing gate and High-effort section leaves the commands. Decision 209: /trekresearch runs the full swarm with contrarian-researcher as wave 2 every run; /trekreview and the coordinator run all four passes with no high-effort normalization. Voyage agents are spawned with no model (their own model: opus applies); the two built-in spawns pass model "opus". --profile stays in the parser as a retired flag that swallows its value. §Model & effort moves to docs/architecture.md; the sequencing-gate eval case goes. Files removed: 44 (43 from the R3 list + tests/commands/trekreview.test.mjs, whose every test pinned removed text). Files edited: 43. Tests: 2467 (2466 pass / 0 fail / 1 skipped) before, 2309 (2309 pass / 0 fail / 0 skipped) after. Durable check: all three groups green, none pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
32621dcb63
commit
2a35d067d9
87 changed files with 225 additions and 4967 deletions
|
|
@ -1,118 +1,22 @@
|
|||
// tests/commands/trekbrief.test.mjs
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC1 tests.
|
||||
//
|
||||
// Pattern D prose-pins kept as doc-anchors for the .md file. Runtime tests
|
||||
// added per finding 350853 (BLOCKER SC1) + a7f4f95a (MAJOR Plan Step 5 drift).
|
||||
//
|
||||
// SC1 re-interpretation (per plan Step 10 amendment): "asserts on 4
|
||||
// AskUserQuestion calls" → "asserts resolvePhaseSignal returns non-null for
|
||||
// all 4 entries in PHASE_SIGNAL_PHASES when applied to a brief with a
|
||||
// committed phase_signals block." See brief amendment for full rationale.
|
||||
// Pattern D prose-pins kept as doc-anchors for commands/trekbrief.md.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs';
|
||||
import { validateBriefContent, PHASE_SIGNAL_PHASES, EFFORT_LEVELS } from '../../lib/validators/brief-validator.mjs';
|
||||
import { BASE_ALLOWED_MODELS } from '../../lib/validators/profile-validator.mjs';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'trekbrief.md');
|
||||
const REVIEWER_FILE = join(ROOT, 'agents', 'brief-reviewer.md');
|
||||
const TEMPLATE_FILE = join(ROOT, 'templates', 'trekbrief-template.md');
|
||||
const FIXTURE = (name) => join(ROOT, 'tests', 'fixtures', name);
|
||||
|
||||
function read() {
|
||||
return readFileSync(COMMAND_FILE, 'utf8');
|
||||
}
|
||||
|
||||
function readFixture(name) {
|
||||
return readFileSync(FIXTURE(name), 'utf8');
|
||||
}
|
||||
|
||||
function frontmatterOf(text) {
|
||||
const doc = parseDocument(text);
|
||||
return doc.parsed && doc.parsed.frontmatter;
|
||||
}
|
||||
|
||||
// --- Pattern D prose-pins (doc-anchors) ---
|
||||
|
||||
test('trekbrief — Phase 3.5 heading is present', () => {
|
||||
const text = read();
|
||||
assert.match(text, /^## Phase 3\.5 — Per-phase effort dialog$/m,
|
||||
'Phase 3.5 heading missing from commands/trekbrief.md');
|
||||
});
|
||||
|
||||
test('trekbrief — Phase 3.5 references all 4 downstream phases', () => {
|
||||
const text = read();
|
||||
const startIdx = text.indexOf('## Phase 3.5');
|
||||
assert.ok(startIdx >= 0, 'Phase 3.5 not found');
|
||||
const section = text.slice(startIdx, text.indexOf('## Phase 4', startIdx));
|
||||
for (const phase of ['research', 'plan', 'execute', 'review']) {
|
||||
assert.ok(section.includes(phase),
|
||||
`Phase 3.5 missing reference to "${phase}"`);
|
||||
}
|
||||
});
|
||||
|
||||
test('trekbrief — Phase 3.5 documents phase_signals_partial force-stop', () => {
|
||||
const text = read();
|
||||
assert.ok(text.includes('phase_signals_partial'),
|
||||
'phase_signals_partial not mentioned in /trekbrief command prose');
|
||||
});
|
||||
|
||||
// --- v5.1.1 runtime SC1 tests ---
|
||||
|
||||
test('trekbrief — SC1: resolvePhaseSignal returns non-null for all 4 phases on committed brief (brief-effort-low)', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-low.md'));
|
||||
for (const phase of PHASE_SIGNAL_PHASES) {
|
||||
const r = resolvePhaseSignal(fm, phase);
|
||||
assert.ok(r && typeof r === 'object',
|
||||
`phase=${phase}: resolver must return non-null for committed brief; got ${JSON.stringify(r)}`);
|
||||
assert.ok(typeof r.effort === 'string',
|
||||
`phase=${phase}: resolver result must include effort`);
|
||||
}
|
||||
});
|
||||
|
||||
test('trekbrief — SC1: each of 4 phases has both effort AND model on full-signals fixture', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-with-phase-signals.md'));
|
||||
for (const phase of PHASE_SIGNAL_PHASES) {
|
||||
const r = resolvePhaseSignal(fm, phase);
|
||||
assert.ok(r && typeof r === 'object', `phase=${phase}: must resolve`);
|
||||
assert.ok(EFFORT_LEVELS.includes(r.effort),
|
||||
`phase=${phase}: effort "${r.effort}" not in EFFORT_LEVELS`);
|
||||
if ('model' in r) {
|
||||
assert.ok(BASE_ALLOWED_MODELS.includes(r.model),
|
||||
`phase=${phase}: model "${r.model}" not in [${BASE_ALLOWED_MODELS.join(', ')}]`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('trekbrief — SC1: missing phase_signals + brief_version 2.1 triggers BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true });
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(
|
||||
r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'),
|
||||
`gate must fire; errors=${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// --- v5.9 — fable tier option in the Phase 3.5 loop ---
|
||||
|
||||
test('trekbrief — v5.9 Phase 3.5 canonical mapping contains the fable row and offers 4 options', () => {
|
||||
const text = read();
|
||||
const startIdx = text.indexOf('## Phase 3.5');
|
||||
assert.ok(startIdx >= 0, 'Phase 3.5 not found');
|
||||
const section = text.slice(startIdx, text.indexOf('## Phase 4', startIdx));
|
||||
assert.ok(section.includes('fable → {effort: high, model: fable}'),
|
||||
'Phase 3.5 canonical mapping must contain the fable tier row');
|
||||
assert.ok(section.includes('with 4 options'),
|
||||
'Phase 3.5 loop must offer 4 options (AskUserQuestion maxItems: 4)');
|
||||
});
|
||||
|
||||
// --- v5.5 — framing enforcement + TL;DR + memory-alignment prose-pins ---
|
||||
|
||||
test('trekbrief — v5.5 Phase 2.5 framing declaration heading present', () => {
|
||||
|
|
@ -174,35 +78,3 @@ test('trekbrief — v5.5 template carries framing field, 2.2, and TL;DR section'
|
|||
'template frontmatter must include the framing field');
|
||||
assert.match(tpl, /^## TL;DR$/m, 'template must include the ## TL;DR section');
|
||||
});
|
||||
|
||||
test('trekbrief — SC1: phase_signals_partial: true does NOT trigger the gate', () => {
|
||||
const partial = `---
|
||||
type: trekbrief
|
||||
brief_version: "2.1"
|
||||
created: 2026-05-14
|
||||
task: "Partial brief"
|
||||
slug: partial-brief
|
||||
project_dir: .claude/projects/2026-05-14-partial-brief/
|
||||
research_topics: 0
|
||||
research_status: complete
|
||||
auto_research: false
|
||||
interview_turns: 2
|
||||
source: fixture
|
||||
phase_signals_partial: true
|
||||
---
|
||||
|
||||
# Task
|
||||
|
||||
## Intent
|
||||
Stop early.
|
||||
|
||||
## Goal
|
||||
Test partial mode.
|
||||
|
||||
## Success Criteria
|
||||
- gate does not fire.
|
||||
`;
|
||||
const r = validateBriefContent(partial, { strict: true });
|
||||
assert.equal(r.valid, true, `errors=${JSON.stringify(r.errors)}`);
|
||||
assert.ok(!r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,84 +1,21 @@
|
|||
// tests/commands/trekexecute.test.mjs
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekexecute.
|
||||
// Low effort runs the plan as one session, step after step; it sets no --gates
|
||||
// (fix round 4-5a, #33).
|
||||
// Prose-pin and runtime tests for /trekexecute.
|
||||
|
||||
import { after, test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } 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';
|
||||
import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs';
|
||||
import { validateBriefContent } from '../../lib/validators/brief-validator.mjs';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
import { sectionText } from '../helpers/md-section.mjs';
|
||||
import { childEnv } from '../helpers/child-env.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'trekexecute.md');
|
||||
const PHASE = 'execute';
|
||||
|
||||
function read() { return readFileSync(COMMAND_FILE, 'utf8'); }
|
||||
function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); }
|
||||
function frontmatterOf(text) {
|
||||
const doc = parseDocument(text);
|
||||
return doc.parsed && doc.parsed.frontmatter;
|
||||
}
|
||||
|
||||
// --- Pattern D prose-pins ---
|
||||
|
||||
test('trekexecute — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => {
|
||||
const text = read();
|
||||
assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'),
|
||||
'/trekexecute must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate');
|
||||
assert.ok(text.includes('phase_signals'),
|
||||
'/trekexecute must reference phase_signals (v5.1 composition rule)');
|
||||
});
|
||||
|
||||
test('trekexecute — low-effort path runs one session, step after step, and sets no --gates open', () => {
|
||||
const text = read();
|
||||
const compIdx = text.indexOf('## Composition rule (v5.1)');
|
||||
assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing');
|
||||
const section = text.slice(compIdx, compIdx + 2000);
|
||||
assert.doesNotMatch(section, /--gates open/, 'Low effort must not set --gates open');
|
||||
assert.match(section.replace(/\s+/g, ' '), /`effort == 'low'` runs the plan as one session, step after step \(Phase 2\.5\)/,
|
||||
'Low-effort path must run the plan as one session, step after step');
|
||||
});
|
||||
|
||||
// --- v5.1.1 runtime SC4 + SC7 ---
|
||||
|
||||
test('trekexecute — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-low.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'low');
|
||||
assert.equal(r.model, 'sonnet');
|
||||
});
|
||||
|
||||
test('trekexecute — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-standard.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'standard');
|
||||
assert.equal(r.model, undefined);
|
||||
});
|
||||
|
||||
test('trekexecute — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-high.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'high');
|
||||
assert.equal(r.model, 'opus');
|
||||
});
|
||||
|
||||
test('trekexecute — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true });
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(
|
||||
r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'),
|
||||
`sequencing gate must fire; errors=${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// --- S38 loop-discipline hardening: completion gate (Step 2) ---
|
||||
|
||||
|
|
@ -201,9 +138,9 @@ test('trekexecute Phase 8: the state-file block imports through the plugin root
|
|||
}
|
||||
});
|
||||
|
||||
// --- Phase 1 --project: the brief check and the execute resolver -------------
|
||||
// Phase 1's --project branch runs the two lines /trekplan runs, for the execute
|
||||
// phase, and halts only on BRIEF_V51_MISSING_SIGNALS.
|
||||
// --- Phase 1 --project: the brief check -------------------------------------
|
||||
// Phase 1's --project branch runs the brief-validator line /trekplan runs, and
|
||||
// never halts on what it reports.
|
||||
|
||||
/** Phase 1's rule 1 (the --project branch), as lines. */
|
||||
function projectRule() {
|
||||
|
|
@ -215,35 +152,25 @@ function projectRule() {
|
|||
return sec.slice(start, end).join('\n');
|
||||
}
|
||||
|
||||
test('trekexecute Phase 1 --project runs brief-validator --soft --json and the execute resolver, and halts only on BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
test('trekexecute Phase 1 --project runs brief-validator --soft --json and never halts on its errors', () => {
|
||||
const rule = projectRule();
|
||||
const validator = libLine(rule, 'validators/brief-validator.mjs', '--soft --json');
|
||||
const resolver = libLine(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.',
|
||||
'Any error is printed in one line and the run continues.',
|
||||
'A validator that cannot run (no JSON) is "not measured" in one line, and the run continues.',
|
||||
'No `{dir}/brief.md`: skip the line.',
|
||||
]) 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.
|
||||
// The line runs as printed: a brief with an error exits 1 and still prints JSON.
|
||||
const dir = mkdtempSync(join(tmpdir(), 'trekexecute-project-'));
|
||||
try {
|
||||
copyFileSync(join(ROOT, 'tests', 'fixtures', 'brief-v21-no-signals.md'), join(dir, 'brief.md'));
|
||||
writeFileSync(join(dir, 'brief.md'), '---\ntype: trekbrief\n---\n\n# Task\n');
|
||||
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');
|
||||
assert.ok(JSON.parse(v.stdout).errors.some((e) => e.code === 'BRIEF_MISSING_FIELD'), v.stdout);
|
||||
} finally { rmSync(dir, { recursive: true, force: true }); }
|
||||
});
|
||||
|
||||
|
|
@ -362,36 +289,9 @@ test('trekexecute merge lock: Phase 7.2 step 4 run on a BLOCK review of an older
|
|||
assert.equal(JSON.parse(r.stdout).verdict, 'BLOCK');
|
||||
});
|
||||
|
||||
// --- The resolver runs in Phase 1; low effort is one session; no gates escalation
|
||||
// (fix round 4-5a, #33).
|
||||
// --- No gates escalation (fix round 4-5a, #33).
|
||||
|
||||
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()), /invoked in Phase 2\.4/);
|
||||
assert.ok(flat(sectionText(read(), '## 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(sectionText(read(), '## 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.'),
|
||||
'the Composition rule does not say why the model is returned');
|
||||
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 = sectionText(read(), '## 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, 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(' | ')}`);
|
||||
});
|
||||
|
||||
test('[#33] no gates_mode = \'closed\', "strictest" or --event gates_mode_flag_override is left, and high effort says it changes nothing', () => {
|
||||
test('[#33] no gates_mode = \'closed\', "strictest" or --event gates_mode_flag_override is left', () => {
|
||||
const text = read();
|
||||
const f = flat(text);
|
||||
assert.doesNotMatch(f, /gates_mode = 'closed'/);
|
||||
|
|
@ -399,9 +299,6 @@ test('[#33] no gates_mode = \'closed\', "strictest" or --event gates_mode_flag_o
|
|||
assert.doesNotMatch(f, /halts immediately/);
|
||||
assert.doesNotMatch(text, /--event gates_mode_flag_override/);
|
||||
assert.doesNotMatch(f, /gates_mode = '(adaptive|open)'/);
|
||||
assert.equal(flat(sectionText(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.');
|
||||
});
|
||||
|
||||
// --- Phase 8 writes the merge evidence file before the merge call (4.5b2, MEV) --
|
||||
|
|
|
|||
|
|
@ -1,76 +1,17 @@
|
|||
// tests/commands/trekplan.test.mjs
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekplan.
|
||||
// Prose-pin tests for /trekplan.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs';
|
||||
import { validateBriefContent } from '../../lib/validators/brief-validator.mjs';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'trekplan.md');
|
||||
const PHASE = 'plan';
|
||||
|
||||
function read() { return readFileSync(COMMAND_FILE, 'utf8'); }
|
||||
function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); }
|
||||
function frontmatterOf(text) {
|
||||
const doc = parseDocument(text);
|
||||
return doc.parsed && doc.parsed.frontmatter;
|
||||
}
|
||||
|
||||
// --- Pattern D prose-pins (kept) ---
|
||||
|
||||
test('trekplan — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => {
|
||||
const text = read();
|
||||
assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'),
|
||||
'/trekplan must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate');
|
||||
assert.ok(text.includes('phase_signals'),
|
||||
'/trekplan must reference phase_signals (v5.1 composition rule)');
|
||||
});
|
||||
|
||||
test('trekplan — low-effort path references --quick equivalent', () => {
|
||||
const text = read();
|
||||
const compIdx = text.indexOf('## Composition rule (v5.1)');
|
||||
assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing');
|
||||
const section = text.slice(compIdx, compIdx + 2000);
|
||||
assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent');
|
||||
});
|
||||
|
||||
// --- v5.1.1 runtime SC4 + SC7 tests ---
|
||||
|
||||
test('trekplan — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-low.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'low');
|
||||
assert.equal(r.model, 'sonnet');
|
||||
});
|
||||
|
||||
test('trekplan — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-standard.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'standard');
|
||||
assert.equal(r.model, undefined);
|
||||
});
|
||||
|
||||
test('trekplan — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-high.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'high');
|
||||
assert.equal(r.model, 'opus');
|
||||
});
|
||||
|
||||
test('trekplan — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true });
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(
|
||||
r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'),
|
||||
`sequencing gate must fire; errors=${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
});
|
||||
|
||||
// --- S31 / V15: export trim — drop pr|issue|markdown, keep headless as a --decompose alias ---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,63 +1,21 @@
|
|||
// tests/commands/trekresearch.test.mjs
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekresearch.
|
||||
// Prose-pin tests for /trekresearch.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs';
|
||||
import { validateBriefContent } from '../../lib/validators/brief-validator.mjs';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'trekresearch.md');
|
||||
const PHASE = 'research';
|
||||
|
||||
function read() { return readFileSync(COMMAND_FILE, 'utf8'); }
|
||||
function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); }
|
||||
function frontmatterOf(text) {
|
||||
const doc = parseDocument(text);
|
||||
return doc.parsed && doc.parsed.frontmatter;
|
||||
}
|
||||
|
||||
// --- Pattern D prose-pins ---
|
||||
|
||||
test('trekresearch — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => {
|
||||
const text = read();
|
||||
assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'),
|
||||
'/trekresearch must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate');
|
||||
assert.ok(text.includes('phase_signals'),
|
||||
'/trekresearch must reference phase_signals (v5.1 composition rule)');
|
||||
});
|
||||
|
||||
test('trekresearch — low-effort path references --quick equivalent', () => {
|
||||
const text = read();
|
||||
// Bound the Composition rule section by the next `###` heading rather than a
|
||||
// magic 2000-character window: a fixed count silently drops the match as soon
|
||||
// as prose is inserted above it, turning a real pin into a no-op.
|
||||
const sectionOf = (doc) => {
|
||||
const compIdx = doc.indexOf('## Composition rule (v5.1)');
|
||||
assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing');
|
||||
const nextHeading = doc.indexOf('\n### ', compIdx);
|
||||
return nextHeading > compIdx ? doc.slice(compIdx, nextHeading) : doc.slice(compIdx);
|
||||
};
|
||||
|
||||
// (a) positive: the low-effort path is documented inside the bounded section.
|
||||
assert.match(sectionOf(text), /--quick/, 'Low-effort path must mention --quick equivalent');
|
||||
|
||||
// (b) negative: an actual removal must still be caught — a bound that can
|
||||
// never fail proves nothing.
|
||||
const mutated = text.replace(/--quick/g, '--removed');
|
||||
assert.doesNotMatch(sectionOf(mutated), /--quick/,
|
||||
'heading-bounded slice must still fail on a genuine removal');
|
||||
});
|
||||
|
||||
// --- Phase 5 slice (heading-bounded) ---
|
||||
|
||||
// Same bounding discipline as the Composition-rule pin above: slice from the
|
||||
// phase heading to the NEXT phase heading, never a fixed character window.
|
||||
// Slice from the phase heading to the NEXT phase heading, never a fixed character window.
|
||||
function phaseSlice(doc, startHeading, endHeading) {
|
||||
const start = doc.indexOf(startHeading);
|
||||
assert.ok(start >= 0, `${startHeading} missing`);
|
||||
|
|
@ -70,52 +28,15 @@ function phase5(doc) {
|
|||
return phaseSlice(doc, '## Phase 5 —', '## Phase 6 —');
|
||||
}
|
||||
|
||||
test('trekresearch — High-effort behavior gives standard effort the wave-2 sentence and keeps the low effort sentence verbatim', () => {
|
||||
const text = read();
|
||||
const flatText = text.replace(/\s+/g, ' ');
|
||||
test('trekresearch — Phase 4 runs the full swarm with contrarian-researcher as wave 2 every time (decision 209)', () => {
|
||||
const flatText = read().replace(/\s+/g, ' ');
|
||||
assert.ok(
|
||||
flatText.includes('Every run launches the FULL swarm for its scope: all 5 local agents and all 4 external agents, and `contrarian-researcher` runs every time, as wave 2 (see Launch rules)'),
|
||||
'Phase 4 must state the one fixed path: the full swarm, contrarian-researcher as wave 2 every run',
|
||||
);
|
||||
assert.ok(
|
||||
!flatText.includes('use the existing conditional triggers'),
|
||||
'standard effort must not point at a conditional trigger that nothing defines',
|
||||
);
|
||||
assert.ok(
|
||||
flatText.includes('Standard effort (or absent): the selected agents run as in Phase 4, and `contrarian-researcher`, when selected, runs as wave 2 after the other agents return (see Launch rules).'),
|
||||
'the standard-effort sentence must say the contrarian runs as wave 2 when selected',
|
||||
);
|
||||
assert.ok(
|
||||
text.includes('Low effort: inline research only, no agent swarm'),
|
||||
'the low-effort sentence must survive the Phase 5 rewrite verbatim',
|
||||
);
|
||||
});
|
||||
|
||||
// --- v5.1.1 runtime SC4 + SC7 ---
|
||||
|
||||
test('trekresearch — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-low.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'low');
|
||||
assert.equal(r.model, 'sonnet');
|
||||
});
|
||||
|
||||
test('trekresearch — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-standard.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'standard');
|
||||
assert.equal(r.model, undefined);
|
||||
});
|
||||
|
||||
test('trekresearch — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-high.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'high');
|
||||
assert.equal(r.model, 'opus');
|
||||
});
|
||||
|
||||
test('trekresearch — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true });
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(
|
||||
r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'),
|
||||
`sequencing gate must fire; errors=${JSON.stringify(r.errors)}`,
|
||||
'no effort must point at a conditional trigger that nothing defines',
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -134,17 +55,14 @@ function paragraphAt(text, marker) {
|
|||
return flat(lines.slice(start, end).join(' '));
|
||||
}
|
||||
|
||||
test('trekresearch — Phase 5 follow-ups take phase_signal_result.model, else opus', () => {
|
||||
test('trekresearch — Phase 5 follow-ups: a built-in agent is spawned with model opus, at most 2', () => {
|
||||
const text = read();
|
||||
assert.doesNotMatch(flat(phase5(text)), /model: "opus"/);
|
||||
const part = paragraphAt(phase5(text), 'launch a targeted follow-up agent');
|
||||
assert.match(part, /Maximum 2 follow-ups\./);
|
||||
assert.match(part, /launch a targeted follow-up agent \(model: phase_signal_result\.model, else opus — a follow-up may be one of Claude Code's built-in agents, which have no Voyage frontmatter to fall back to; see Launch rules\)/);
|
||||
assert.match(flat(text), /captured as `phase_signal_result` and used at Agent-spawn sites in Phase 4 and Phase 5 to inject the resolved model/);
|
||||
assert.ok(part.includes("A follow-up may be one of Claude Code's built-in agents: Spawn it with `model: \"opus\"`: a built-in agent has no Voyage pin."), part);
|
||||
});
|
||||
|
||||
test('trekresearch — promises no maxTurns budget', () => {
|
||||
const text = read();
|
||||
assert.doesNotMatch(text, /maxTurns/);
|
||||
assert.match(text, /- convention-scanner: medium\+ codebases only \(50\+ files\)/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
// tests/commands/trekreview.test.mjs
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 tests for /trekreview.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { resolvePhaseSignal } from '../../lib/profiles/phase-signal-resolver.mjs';
|
||||
import { validateBriefContent } from '../../lib/validators/brief-validator.mjs';
|
||||
import { parseDocument } from '../../lib/util/frontmatter.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = join(HERE, '..', '..');
|
||||
const COMMAND_FILE = join(ROOT, 'commands', 'trekreview.md');
|
||||
const PHASE = 'review';
|
||||
|
||||
function read() { return readFileSync(COMMAND_FILE, 'utf8'); }
|
||||
function readFixture(name) { return readFileSync(join(ROOT, 'tests', 'fixtures', name), 'utf8'); }
|
||||
function frontmatterOf(text) {
|
||||
const doc = parseDocument(text);
|
||||
return doc.parsed && doc.parsed.frontmatter;
|
||||
}
|
||||
|
||||
// --- Pattern D prose-pins ---
|
||||
|
||||
test('trekreview — sequencing-gate surface mentions BRIEF_V51_MISSING_SIGNALS + phase_signals', () => {
|
||||
const text = read();
|
||||
assert.ok(text.includes('BRIEF_V51_MISSING_SIGNALS'),
|
||||
'/trekreview must surface the BRIEF_V51_MISSING_SIGNALS sequencing gate');
|
||||
assert.ok(text.includes('phase_signals'),
|
||||
'/trekreview must reference phase_signals (v5.1 composition rule)');
|
||||
});
|
||||
|
||||
test('trekreview — low-effort path references --quick equivalent', () => {
|
||||
const text = read();
|
||||
const compIdx = text.indexOf('## Composition rule (v5.1)');
|
||||
assert.ok(compIdx >= 0, 'Composition rule (v5.1) section missing');
|
||||
const section = text.slice(compIdx, compIdx + 2000);
|
||||
assert.match(section, /--quick/, 'Low-effort path must mention --quick equivalent');
|
||||
});
|
||||
|
||||
// --- v5.1.1 runtime SC4 + SC7 ---
|
||||
|
||||
test('trekreview — SC4: low-effort fixture → resolver returns {effort: low, model: sonnet}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-low.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'low');
|
||||
assert.equal(r.model, 'sonnet');
|
||||
});
|
||||
|
||||
test('trekreview — SC4: standard-effort fixture → resolver returns {effort: standard, model: undefined}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-standard.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'standard');
|
||||
assert.equal(r.model, undefined);
|
||||
});
|
||||
|
||||
test('trekreview — SC4: high-effort fixture → resolver returns {effort: high, model: opus}', () => {
|
||||
const fm = frontmatterOf(readFixture('brief-effort-high.md'));
|
||||
const r = resolvePhaseSignal(fm, PHASE);
|
||||
assert.equal(r.effort, 'high');
|
||||
assert.equal(r.model, 'opus');
|
||||
});
|
||||
|
||||
test('trekreview — SC7: brief_version 2.1 + no phase_signals + no partial → BRIEF_V51_MISSING_SIGNALS', () => {
|
||||
// Falsification via brief-v21-no-signals fixture: validator must catch missing signals.
|
||||
const r = validateBriefContent(readFixture('brief-v21-no-signals.md'), { strict: true });
|
||||
assert.equal(r.valid, false);
|
||||
assert.ok(
|
||||
r.errors.find(e => e.code === 'BRIEF_V51_MISSING_SIGNALS'),
|
||||
`sequencing gate must fire; errors=${JSON.stringify(r.errors)}`,
|
||||
);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue