// 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. 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 { 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(['sonnet', 'opus'].includes(r.model), `phase=${phase}: model "${r.model}" not in [sonnet, opus]`); } } }); 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.5 — framing enforcement + TL;DR + memory-alignment prose-pins --- test('trekbrief — v5.5 Phase 2.5 framing declaration heading present', () => { const text = read(); assert.match(text, /^## Phase 2\.5 — Framing declaration/m, 'Phase 2.5 framing-declaration heading missing from commands/trekbrief.md'); }); test('trekbrief — v5.5 Phase 2.5 references all four framing values', () => { const text = read(); const start = text.indexOf('## Phase 2.5'); const section = text.slice(start, text.indexOf('## Phase 3', start)); for (const v of ['preserve', 'refine', 'replace', 'new-direction']) { assert.ok(section.includes(v), `Phase 2.5 missing framing value "${v}"`); } }); test('trekbrief — v5.5 Phase 2.5 runs before any brief prose (precedes Phase 3)', () => { const text = read(); assert.ok(text.indexOf('## Phase 2.5') < text.indexOf('## Phase 3'), 'Phase 2.5 must come before the completeness loop (before prose)'); assert.ok(text.includes('even in `--quick` mode'), 'framing must be non-skippable even in --quick mode'); }); test('trekbrief — v5.5 Step 4a writes framing + brief_version 2.2 + generates TL;DR', () => { const text = read(); assert.ok(/brief_version: "2\.2"/.test(text), 'Step 4a must set brief_version 2.2'); assert.ok(/framing: /.test(text), 'Step 4a must write the committed framing value'); assert.ok(/## TL;DR/.test(text), 'Step 4a must generate the TL;DR section'); }); test('trekbrief — v5.5 Phase 4e gate includes memory_alignment', () => { const text = read(); assert.ok(/memory_alignment\.score ≥ 4/.test(text), 'Phase 4e gate must require memory_alignment.score ≥ 4'); }); test('trekbrief — v5.5 brief-reviewer declares the memory-alignment dimension', () => { const reviewer = readFileSync(REVIEWER_FILE, 'utf8'); assert.match(reviewer, /### 6\. Memory alignment/, 'brief-reviewer.md missing dimension 6 (memory alignment)'); assert.ok(reviewer.includes('"memory_alignment"'), 'brief-reviewer.md JSON schema missing memory_alignment key'); assert.ok(/no memory context (is )?supplied/i.test(reviewer), 'brief-reviewer must define the no-memory-context N/A fallback'); }); test('trekbrief — v5.5 template carries framing field, 2.2, and TL;DR section', () => { const tpl = readFileSync(TEMPLATE_FILE, 'utf8'); assert.ok(/brief_version: "2\.2"/.test(tpl), 'template must declare brief_version 2.2'); assert.match(tpl, /^framing: \{preserve \| refine \| replace \| new-direction\}/m, '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')); });