Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
143 lines
6.6 KiB
JavaScript
143 lines
6.6 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* tests/compact-content-template-variant-parity.test.cjs — ADR-4139, epic #4139, Phase 6 (#4406).
|
|
*
|
|
* Check 5 of `.msd/phase/enhance-4406-lazy-remainder/40-design.md`'s variant-pair checklist:
|
|
* template consumer parity. Only two `msd-core/templates/**` files got a `.compact.md` variant
|
|
* wired to a genuine runtime `Read` this phase — `summary.md` and `user-setup.md` (see
|
|
* `40-design.md`'s "a size-only candidate list was also the wrong test here" for why `spec.md`
|
|
* was dropped and why `summary.md`'s wiring is scoped to one call site only).
|
|
*
|
|
* The actual guarantee that makes compacting these templates safe: downstream consumers parse
|
|
* the GENERATED artifact (a real SUMMARY.md / USER-SETUP.md an agent wrote), never the template
|
|
* file itself. So the only way a compact variant could silently break a real consumer is if it
|
|
* changed the artifact's OUTPUT-FORMAT CONTRACT — the `## File Template` fenced block — relative
|
|
* to the canonical file. Both compact variants were authored to leave that block byte-identical
|
|
* and compact only the surrounding illustrative material (examples, guidelines prose). This test
|
|
* proves that invariant directly, rather than assuming it from the authoring process, and then
|
|
* proves the shared contract really is what the one real deterministic consumer
|
|
* (`msd-core/bin/lib/coverage.cjs`'s `classifyContent`, backing `msd-tools uat classify-coverage`)
|
|
* accepts and classifies correctly.
|
|
*
|
|
* `user-setup.md` has no deterministic content parser anywhere in this repo (confirmed by
|
|
* repo-wide search — the only "consumption" beyond an agent `Read` is a human `grep -r
|
|
* "USER-SETUP" .planning/` search over filenames, insensitive to internal structure). Its parity
|
|
* check is therefore limited to the File Template identity assertion; there is no real parser
|
|
* round trip to run against it.
|
|
*/
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const { classifyContent } = require(path.join(ROOT, 'msd-core', 'bin', 'lib', 'coverage.cjs'));
|
|
|
|
/**
|
|
* Extract the fenced code block immediately following a `## File Template` heading.
|
|
* Returns the block's inner content (without the opening/closing fences), or null if
|
|
* the heading or a following fence isn't found.
|
|
* @param {string} content
|
|
* @returns {string | null}
|
|
*/
|
|
function extractFileTemplateBlock(content) {
|
|
const headingIdx = content.indexOf('## File Template');
|
|
if (headingIdx === -1) return null;
|
|
const fenceStart = content.indexOf('```', headingIdx);
|
|
if (fenceStart === -1) return null;
|
|
const afterOpenFence = content.indexOf('\n', fenceStart) + 1;
|
|
const fenceEnd = content.indexOf('\n```', afterOpenFence);
|
|
if (fenceEnd === -1) return null;
|
|
return content.slice(afterOpenFence, fenceEnd);
|
|
}
|
|
|
|
describe('template consumer parity — File Template contract identity', () => {
|
|
test('summary.md and summary.compact.md share a byte-identical File Template block', () => {
|
|
const canonical = fs.readFileSync(path.join(ROOT, 'msd-core', 'templates', 'summary.md'), 'utf8');
|
|
const compact = fs.readFileSync(path.join(ROOT, 'msd-core', 'templates', 'summary.compact.md'), 'utf8');
|
|
const canonicalBlock = extractFileTemplateBlock(canonical);
|
|
const compactBlock = extractFileTemplateBlock(compact);
|
|
assert.ok(canonicalBlock, 'canonical summary.md must have an extractable File Template block');
|
|
assert.ok(compactBlock, 'summary.compact.md must have an extractable File Template block');
|
|
assert.strictEqual(
|
|
compactBlock,
|
|
canonicalBlock,
|
|
'summary.compact.md must not alter the output-format contract any downstream consumer parses',
|
|
);
|
|
});
|
|
|
|
test('user-setup.md and user-setup.compact.md share a byte-identical File Template block', () => {
|
|
const canonical = fs.readFileSync(path.join(ROOT, 'msd-core', 'templates', 'user-setup.md'), 'utf8');
|
|
const compact = fs.readFileSync(path.join(ROOT, 'msd-core', 'templates', 'user-setup.compact.md'), 'utf8');
|
|
const canonicalBlock = extractFileTemplateBlock(canonical);
|
|
const compactBlock = extractFileTemplateBlock(compact);
|
|
assert.ok(canonicalBlock, 'canonical user-setup.md must have an extractable File Template block');
|
|
assert.ok(compactBlock, 'user-setup.compact.md must have an extractable File Template block');
|
|
assert.strictEqual(
|
|
compactBlock,
|
|
canonicalBlock,
|
|
'user-setup.compact.md must not alter the output-format contract',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('template consumer parity — real classify-coverage round trip (summary.md)', () => {
|
|
const REALISTIC_SUMMARY = `---
|
|
phase: 07-example
|
|
plan: 01
|
|
subsystem: testing
|
|
tags: [example]
|
|
requires: []
|
|
provides: []
|
|
affects: []
|
|
coverage:
|
|
- id: D1
|
|
description: "Deterministic deliverable"
|
|
verification:
|
|
- kind: unit
|
|
ref: "tests/example.test.cjs#does the thing"
|
|
status: pass
|
|
human_judgment: false
|
|
- id: D2
|
|
description: "Deliverable needing a human"
|
|
verification: []
|
|
human_judgment: true
|
|
rationale: "UI screenshot review"
|
|
duration: 5min
|
|
completed: 2026-01-01
|
|
status: complete
|
|
---
|
|
|
|
# Phase 7: Example Summary
|
|
|
|
**A deterministic deliverable and a human-judgment deliverable.**
|
|
`;
|
|
|
|
test('a SUMMARY.md built from the shared File Template coverage schema classifies correctly', () => {
|
|
const result = classifyContent(REALISTIC_SUMMARY, '07-example-01-SUMMARY.md');
|
|
assert.strictEqual(result.mode, 'coverage');
|
|
assert.strictEqual(result.errors.length, 0, `expected no validation errors: ${JSON.stringify(result.errors)}`);
|
|
assert.strictEqual(result.auto_passed.length, 1, 'D1 (human_judgment:false, all verification pass) must auto-pass');
|
|
assert.strictEqual(result.present.length, 1, 'D2 (human_judgment:true) must route to a human');
|
|
assert.strictEqual(result.all_auto_covered, false);
|
|
});
|
|
|
|
test('boundary: a legacy SUMMARY.md with no coverage block still classifies (byte-identical fallback)', () => {
|
|
const legacy = REALISTIC_SUMMARY.replace(/coverage:[\s\S]*?rationale: "UI screenshot review"\n/, '');
|
|
const result = classifyContent(legacy, '07-example-01-SUMMARY.md');
|
|
assert.strictEqual(result.mode, 'legacy');
|
|
});
|
|
|
|
test('negative: a coverage block that fails validation is presented to a human, never silently auto-passed', () => {
|
|
const malformed = REALISTIC_SUMMARY.replace('human_judgment: false', '');
|
|
const result = classifyContent(malformed, '07-example-01-SUMMARY.md');
|
|
assert.strictEqual(result.mode, 'coverage');
|
|
assert.ok(
|
|
result.present.some((p) => p.id === 'D1'),
|
|
'D1 missing human_judgment must route to present, never auto-pass',
|
|
);
|
|
});
|
|
});
|