test(voyage): refactor trekbrief command test to runtime SC1 (closes #1)
This commit is contained in:
parent
1bb6a9d63b
commit
94c696fee6
1 changed files with 91 additions and 3 deletions
|
|
@ -1,23 +1,43 @@
|
|||
// tests/commands/trekbrief.test.mjs
|
||||
// v5.1 — Pattern D prose-pattern regression tests for /trekbrief Phase 3.5.
|
||||
// v5.1 prose-pin tests + v5.1.1 runtime SC1 tests.
|
||||
//
|
||||
// Brief SC1 + SC2: end-of-brief effort dialog covering 4 downstream phases,
|
||||
// with `phase_signals_partial` as the force-stop record.
|
||||
// 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 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,
|
||||
|
|
@ -40,3 +60,71 @@ test('trekbrief — Phase 3.5 documents phase_signals_partial force-stop', () =>
|
|||
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)}`,
|
||||
);
|
||||
});
|
||||
|
||||
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'));
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue