feat(voyage): S6 — v5.5 brief framing enforcement (brief_version 2.2)
Implements the CLAUDE.md cross-cutting invariant "brief framing must match operator intent" as a controlled brief_version 2.1->2.2 bump (operator option A1). Three defense layers, version-gated at >=2.2 so existing 2.0/2.1 briefs stay valid (forward + backward compatible), mirroring the phase_signals >=2.1 gate: - L1 framing: enum field (preserve|refine|replace|new-direction). Enum-checked on any version when present (BRIEF_INVALID_FRAMING); missing at >=2.2 -> BRIEF_MISSING_FRAMING. /trekbrief Phase 2.5 collects it BEFORE any brief prose (non-skippable, even in --quick). - L2 memory alignment: new brief-reviewer dimension 6 comparing brief Intent/Goal + framing against operator memory for explicit contradictions; degrades to score 5 (N/A) when no memory context is supplied. Wired into Phase 4e gate (memory_alignment.score >= 4). - L3 obligatory ## TL;DR (<=5 content lines) at >=2.2; soft cap -> BRIEF_TLDR_TOO_LONG warning. trekreview briefs are exempt from the framing/TL;DR gate. Handover 1 PUBLIC CONTRACT doc, README "What's new", and the CLAUDE.md invariant + agents table (brief-reviewer 5->6 dimensions) updated to 2.2 (schema axis only; plugin version badge + CHANGELOG remain S10). Iron Law followed: validator tests red->green first. Tests 586 -> 606 (+20, 604 pass / 2 skip). claude plugin validate passes (pre-existing CLAUDE.md root-context warning unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqBYc8Ltrk7LipyJmGxXiB
This commit is contained in:
parent
fa23b16443
commit
736ae55d66
10 changed files with 409 additions and 31 deletions
|
|
@ -21,6 +21,8 @@ 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() {
|
||||
|
|
@ -97,6 +99,62 @@ test('trekbrief — SC1: missing phase_signals + brief_version 2.1 triggers BRIE
|
|||
);
|
||||
});
|
||||
|
||||
// --- 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: <state\.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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue