voyage/tests/commands/trekplan.test.mjs
Kjell Tore Guttormsen 9dc50a2047 refactor(voyage): S31 — V15 trim plan-export to headless-only (decompose alias)
Drop the pr/issue/markdown variants from `/trekplan --export`. Claude Code
reformats a plan into a PR body, issue comment, or stripped markdown ad-hoc
on request, so a dedicated export path added maintenance without value.
Keep `--export headless` as a backwards-compatible alias for `--decompose`
and relabel it as the decomposition entry it actually is.

- commands/trekplan.md: Phase 1 parse rejects non-headless formats and sets
  mode = decompose for headless; delete the Export phase; rename Phase 1.6 →
  Phase 1.5 (Decompose); update the mode enum, argument-hint, and usage block.
- docs/command-modes.md, README.md: export row relabeled as a --decompose alias.
- tests/commands/trekplan.test.mjs: +2 V15 pins (variants gone, headless kept).

Non-breaking (plan D-register). Tests 731 (729 pass / 2 skip / 0 fail), bar
`node --test`; `claude plugin validate` green (1 accepted warning).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
2026-06-20 09:23:21 +02:00

92 lines
4 KiB
JavaScript

// tests/commands/trekplan.test.mjs
// v5.1 prose-pin tests + v5.1.1 runtime SC4 + SC7 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 ---
test('trekplan — V15: pr/issue/markdown export variants are removed', () => {
const text = read();
// No remaining `--export pr` usage/example anywhere in the command.
assert.equal(/--export\s+pr\b/.test(text), false, '`--export pr` must be gone');
// The per-format subsections of the old Export phase must be deleted.
assert.ok(!text.includes('Format: `pr`'), 'pr export-format section must be gone');
assert.ok(!text.includes('Format: `issue`'), 'issue export-format section must be gone');
assert.ok(!text.includes('Format: `markdown`'), 'markdown export-format section must be gone');
});
test('trekplan — V15: --export headless survives as a --decompose alias', () => {
const text = read();
assert.match(text, /--export\s+headless/, '`--export headless` must remain documented');
assert.match(text, /alias for [`*]*--decompose/i,
'`--export headless` must be labeled an alias for --decompose');
});