Files
msd-core/tests/code-review-summary-parser.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
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.
2026-10-06 01:47:40 +02:00

428 lines
17 KiB
JavaScript

'use strict';
// allow-test-rule: structural-regression-guard (#2694)
// The code-review/code-review-fix workflows embed inline `node -e` frontmatter
// one-liners whose boundary regex must normalize CRLF before matching (#2694).
// A behavioral test cannot observe which regex the *shipped workflow text* ships
// (the runtime loads the .md verbatim), so we guard the source text directly:
// every `content.match(/^---\n…/)` site in those two files must be preceded by a
// `\r\n` -> `\n` normalize. This catches a revert of the #2694 fix.
const { describe, it } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
// Replicates the inline node -e parser from msd-core/workflows/code-review.md
// step compute_file_scope, Tier 2 (lines ~172-181).
//
// Bug #2134: the section-reset regex uses \s+ (requires leading whitespace), so
// top-level YAML keys at column 0 (e.g. `decisions:`) never reset inSection.
// Items from subsequent top-level lists are therefore mis-classified as
// key_files.modified entries.
/**
* Extracts files from SUMMARY.md YAML frontmatter using the CURRENT (buggy) logic
* copied verbatim from code-review.md.
*/
function parseFilesWithBuggyLogic(frontmatterYaml) {
const files = [];
let inSection = null;
for (const line of frontmatterYaml.split('\n')) {
if (/^\s+created:/.test(line)) { inSection = 'created'; continue; }
if (/^\s+modified:/.test(line)) { inSection = 'modified'; continue; }
// BUG: \s+ requires leading whitespace — top-level keys like `decisions:` don't match
if (/^\s+\w+:/.test(line) && !/^\s+-/.test(line)) { inSection = null; continue; }
if (inSection && /^\s+-\s+(.+)/.test(line)) {
files.push(line.match(/^\s+-\s+(.+)/)[1].trim());
}
}
return files;
}
/**
* Extracts files using the FIXED logic (\s* instead of \s+).
*/
function parseFilesWithFixedLogic(frontmatterYaml) {
const files = [];
let inSection = null;
for (const line of frontmatterYaml.split('\n')) {
if (/^\s+created:/.test(line)) { inSection = 'created'; continue; }
if (/^\s+modified:/.test(line)) { inSection = 'modified'; continue; }
// FIX: \s* allows zero leading whitespace — handles top-level YAML keys
if (/^\s*\w+:/.test(line) && !/^\s*-/.test(line)) { inSection = null; continue; }
if (inSection && /^\s+-\s+(.+)/.test(line)) {
files.push(line.match(/^\s+-\s+(.+)/)[1].trim());
}
}
return files;
}
// SUMMARY.md YAML frontmatter that mirrors a realistic post-execution artifact.
// key_files.modified has ONE real file; decisions has TWO entries that must NOT
// appear in the extracted file list.
const FRONTMATTER = [
'type: summary',
'phase: "02"',
'key_files:',
' modified:',
' - src/real-file.js',
' created:',
' - src/new-file.js',
'decisions:',
' - Used async/await over callbacks',
' - Kept error handling inline',
'metrics:',
' lines_changed: 42',
'tags:',
' - refactor',
' - async',
].join('\n');
describe('code-review SUMMARY.md YAML parser', () => {
it('RED: buggy parser mis-classifies decisions entries as files (demonstrates the bug)', () => {
const files = parseFilesWithBuggyLogic(FRONTMATTER);
// With the bug, `decisions:` at column 0 never resets inSection, so the
// two decision strings are incorrectly captured as modified files.
// This assertion documents the broken behavior we are fixing.
const hasDecisionContamination = files.some(
(f) => f === 'Used async/await over callbacks' || f === 'Kept error handling inline'
);
assert.ok(
hasDecisionContamination,
'Expected buggy parser to include decision entries in file list, but it did not — ' +
'the bug may already be fixed or the test replication is wrong. Got: ' +
JSON.stringify(files)
);
});
it('GREEN: fixed parser returns only the actual file paths', () => {
const files = parseFilesWithFixedLogic(FRONTMATTER);
assert.deepStrictEqual(
files.sort(),
['src/new-file.js', 'src/real-file.js'],
'Fixed parser should return only the two real file paths, not decision strings'
);
});
it('fixed parser: modified-only frontmatter with top-level sibling keys', () => {
const yaml = [
'key_files:',
' modified:',
' - src/a.ts',
' - src/b.ts',
'decisions:',
' - Some decision',
'metrics:',
' count: 2',
].join('\n');
const files = parseFilesWithFixedLogic(yaml);
assert.deepStrictEqual(files.sort(), ['src/a.ts', 'src/b.ts']);
});
it('fixed parser: created-only frontmatter with top-level sibling keys', () => {
const yaml = [
'key_files:',
' created:',
' - src/brand-new.ts',
'tags:',
' - feature',
].join('\n');
const files = parseFilesWithFixedLogic(yaml);
assert.deepStrictEqual(files, ['src/brand-new.ts']);
});
it('fixed parser: no key_files section returns empty array', () => {
const yaml = [
'type: summary',
'decisions:',
' - A decision',
].join('\n');
const files = parseFilesWithFixedLogic(yaml);
assert.deepStrictEqual(files, []);
});
});
// ---------------------------------------------------------------------------
// #2694: the OUTER frontmatter-boundary extraction (the `node -e` one-liner's
// first step) was never covered by this file — only the inner YAML-line loop
// above was. The shipped boundary regex used a literal `\n` and silently
// returned null on CRLF-saved artifacts, dropping every file in that summary.
// These tests replicate the exact shipped boundary step (the FIXED variant:
// normalize `\r\n` -> `\n` before matching) and lock CRLF == LF at the boundary.
// ---------------------------------------------------------------------------
/**
* The frontmatter boundary regex as it ships in the workflow one-liners
* (`/^---\n([\s\S]*?)\n---/`). Built via `new RegExp` rather than a RegExpLiteral
* so the `local/no-crlf-fragile-split` lint (which flags frontmatter-shape
* RegExpLiterals) does not fire on this faithful replica — the whole point of
* `extractFrontmatterBoundaryBuggy` below is to demonstrate that THIS EXACT
* literal-`\n` regex fails on CRLF input. The fixed path normalizes CRLF first.
*/
const FRONTMATTER_BOUNDARY_RE = new RegExp('^---\\n([\\s\\S]*?)\\n---');
/**
* Replicates the FIXED boundary extraction shipped in
* msd-core/workflows/code-review.md (compute_file_scope) and code-review-fix.md:
* normalize CRLF to LF, then locate the YAML block between the first two `---`.
* Returns the captured YAML body, or null if no frontmatter block is present.
*/
function extractFrontmatterBoundary(content) {
const match = content.replace(/\r\n/g, '\n').match(FRONTMATTER_BOUNDARY_RE);
return match ? match[1] : null;
}
/**
* Replicates the BUGGY boundary extraction (literal `\n`, pre-#2694) to prove RED.
*/
function extractFrontmatterBoundaryBuggy(content) {
const match = content.match(FRONTMATTER_BOUNDARY_RE);
return match ? match[1] : null;
}
// A realistic SUMMARY.md frontmatter + body. Built with array.join('\n') so the
// fixture's indentation is exact (CONTRIBUTING.md "Fixture Data Formatting").
const SUMMARY_LINES = [
'---',
'type: summary',
'phase: "02"',
'key_files:',
' modified:',
' - src/real-file.js',
' created:',
' - src/new-file.js',
'decisions:',
' - Used async/await over callbacks',
'---',
'',
'## Summary',
'',
'Body prose that must NOT be parsed as frontmatter.',
];
// REVIEW.md frontmatter exercises the `status:` field consumed at the other 8
// boundary sites (code-review.md:552/625, code-review-fix.md:*).
const REVIEW_LINES = [
'---',
'status: needs-changes',
'phase: "02"',
'critical: 1',
'warning: 2',
'info: 0',
'total: 3',
'files_reviewed_list: [src/real-file.js, src/new-file.js]',
'---',
'',
'## Review',
];
describe('code-review frontmatter boundary extraction (#2694 CRLF)', () => {
it('RED: buggy literal-\\n boundary returns null on a CRLF SUMMARY.md', () => {
const crlf = SUMMARY_LINES.join('\r\n') + '\r\n';
const yaml = extractFrontmatterBoundaryBuggy(crlf);
assert.strictEqual(
yaml,
null,
'Expected the pre-fix literal-\\n boundary to fail on CRLF input — ' +
'if it matches, the bug reproduction is wrong. Got: ' + JSON.stringify(yaml)
);
});
it('GREEN: fixed boundary extracts a CRLF SUMMARY.md identically to the LF equivalent', () => {
const lf = SUMMARY_LINES.join('\n') + '\n';
const crlf = SUMMARY_LINES.join('\r\n') + '\r\n';
const lfYaml = extractFrontmatterBoundary(lf);
const crlfYaml = extractFrontmatterBoundary(crlf);
assert.ok(lfYaml !== null, 'LF fixture must parse (reference)');
assert.ok(crlfYaml !== null, 'CRLF fixture must parse after the fix');
// The load-bearing assertion: CRLF yields the byte-identical YAML body as LF,
// so every downstream inner-parser / shell grep sees the same text.
assert.strictEqual(crlfYaml, lfYaml);
// And the inner parse yields the same file set from both (acceptance criterion 1).
assert.deepStrictEqual(
parseFilesWithFixedLogic(crlfYaml).sort(),
parseFilesWithFixedLogic(lfYaml).sort()
);
});
it('GREEN: fixed boundary extracts a CRLF REVIEW.md status field identically to LF', () => {
const lf = REVIEW_LINES.join('\n') + '\n';
const crlf = REVIEW_LINES.join('\r\n') + '\r\n';
const lfYaml = extractFrontmatterBoundary(lf);
const crlfYaml = extractFrontmatterBoundary(crlf);
assert.ok(crlfYaml !== null, 'CRLF REVIEW.md frontmatter must parse');
assert.strictEqual(crlfYaml, lfYaml);
// The `status:` field consumed at code-review.md:552 / code-review-fix.md:121.
const crlfStatus = crlfYaml.match(/status:\s*(\S+)/);
assert.ok(crlfStatus, 'status field must be reachable through the CRLF boundary');
assert.strictEqual(crlfStatus[1], 'needs-changes');
});
it('no frontmatter block: fixed boundary returns null (no false extraction from body)', () => {
const noFm = ['## Summary', '', 'No frontmatter here.', '', '---', '', 'a horizontal rule'].join('\n') + '\n';
assert.strictEqual(extractFrontmatterBoundary(noFm), null);
// CRLF variant behaves the same.
const noFmCrlf = noFm.replace(/\n/g, '\r\n');
assert.strictEqual(extractFrontmatterBoundary(noFmCrlf), null);
});
it('lone CR (not part of CRLF) does not defeat the boundary', () => {
// A lone \r inside the body (old Mac line ending remnant) is left untouched by
// the \r\n -> \n normalize; it must not prevent the real \r\n-delimited boundary
// from matching.
const content = SUMMARY_LINES.join('\r\n') + '\r\n' + 'body with a lone\rcarriage\r\n';
const yaml = extractFrontmatterBoundary(content);
assert.ok(yaml !== null, 'lone CR in the body must not break the boundary match');
});
});
// ---------------------------------------------------------------------------
// Acceptance criterion 2 (#2694): a phase with a MIX of one CRLF SUMMARY.md and
// one LF SUMMARY.md must yield the UNION of both artifacts' files — the CRLF
// artifact's contribution must not be silently dropped. This replicates the
// full shipped Tier-2 extractor (boundary + inner key_files parse) and runs it
// across a mixed-ending phase, locking the silent-partial-masking behavior the
// issue's triage named as the more serious half of the defect.
// ---------------------------------------------------------------------------
/**
* Replicates the FULL shipped Tier-2 file-scope extractor from
* msd-core/workflows/code-review.md (compute_file_scope): normalize -> boundary
* -> inner key_files.created/modified parse. Returns the extracted file list
* for one SUMMARY.md document, exactly as the shipped `node -e` one-liner does.
*/
function extractTier2Files(summaryContent) {
const yaml = extractFrontmatterBoundary(summaryContent);
if (yaml === null) return [];
return parseFilesWithFixedLogic(yaml);
}
describe('code-review mixed CRLF/LF phase scope (#2694 criterion 2)', () => {
it('a phase with one CRLF SUMMARY.md and one LF SUMMARY.md yields the union of both', () => {
// Two plans in the same phase. Plan A is saved LF, plan B is saved CRLF
// (Windows checkout / CRLF-saving editor). Each contributes distinct files.
const planA_LF = [
'---',
'type: summary',
'key_files:',
' modified:',
' - src/alpha.ts',
'---',
'',
'Plan A body.',
].join('\n') + '\n';
const planB_CRLF = [
'---',
'type: summary',
'key_files:',
' created:',
' - src/beta.ts',
' - lib/gamma.js',
'---',
'',
'Plan B body.',
].join('\r\n') + '\r\n';
// The shipped loop iterates each summary and accumulates into REVIEW_FILES.
const reviewFiles = [];
for (const doc of [planA_LF, planB_CRLF]) {
for (const f of extractTier2Files(doc)) reviewFiles.push(f);
}
// The union — NOT just plan A's files. Pre-fix, planB_CRLF contributed
// nothing (boundary returned null), so reviewFiles would have been only
// ['src/alpha.ts'] with no warning (the aggregate was non-zero, so the
// Tier-3 eq-zero fallback at code-review.md:217 never fired).
assert.deepStrictEqual(
reviewFiles.sort(),
['lib/gamma.js', 'src/alpha.ts', 'src/beta.ts'],
'A mixed CRLF/LF phase must review the union of both artifacts. ' +
'If planB_CRLF dropped out, the fix regressed: ' + JSON.stringify(reviewFiles)
);
});
it('RED proof: with the buggy boundary, the CRLF artifact contributes nothing (silent partial)', () => {
// Same scenario, but using the BUGGY boundary replica to demonstrate the
// exact silent-partial masking the issue reported: the CRLF plan yields [],
// so the phase scope is just the LF plan's files, with zero warning.
const planA_LF = ['---', 'key_files:', ' modified:', ' - src/alpha.ts', '---', ''].join('\n') + '\n';
const planB_CRLF = ['---', 'key_files:', ' created:', ' - src/beta.ts', '---', ''].join('\r\n') + '\r\n';
const buggyFiles = [];
for (const doc of [planA_LF, planB_CRLF]) {
const yaml = extractFrontmatterBoundaryBuggy(doc);
if (yaml === null) continue; // buggy boundary returns null on CRLF -> skip
for (const f of parseFilesWithFixedLogic(yaml)) buggyFiles.push(f);
}
// The bug: only the LF plan's file survives; the CRLF plan's file is gone,
// and because the aggregate is non-zero (1), no fallback warning fired.
assert.deepStrictEqual(buggyFiles.sort(), ['src/alpha.ts']);
assert.ok(
!buggyFiles.includes('src/beta.ts'),
'The buggy boundary must have dropped the CRLF artifact file (RED demonstration).'
);
});
});
describe('shipped workflow frontmatter boundary is CRLF-safe (#2694 structural guard)', () => {
// The two shipped workflow files whose inline `node -e` one-liners extract
// frontmatter. Resolved relative to the repo root (the test runner's CWD is the
// repo root under msd-test).
const WORKFLOW_FILES = [
path.join('msd-core', 'workflows', 'code-review.md'),
path.join('msd-core', 'workflows', 'code-review-fix.md'),
];
for (const rel of WORKFLOW_FILES) {
it(`${rel}: every frontmatter-boundary site normalizes CRLF before matching`, () => {
const src = fs.readFileSync(rel, 'utf-8');
// Locate every shipped boundary-match site. The shipped line shape is:
// const match = content...match(/^---\n([\s\S]*?)\n---/);
// After #2694 the `content...` part must be `content.replace(/\r\n/g, '\n')`.
// Assert no site uses a raw `content.match(...)` against the boundary regex,
// and that the CRLF normalize is present at each site.
const lines = src.split(/\r?\n/);
const boundarySites = [];
const unsafeSites = [];
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i];
if (!/\/\^---\\n\(\[\\s\\S\]\*\?\)\\n---\/\)/.test(line)) continue;
boundarySites.push(i + 1);
// SAFE: the match is taken on the result of content.replace(/\r\n/g, '\n').
// UNSAFE: a direct content.match(/<boundary>/) with no preceding normalize.
const isNormalized = /content\.replace\(\s*\/\\r\\n\/g\s*,\s*'\\n'\s*\)\.match\(/.test(line);
const isRawContentMatch = /(^|[^.])\bcontent\.match\(\s*\/\^---\\n/.test(line);
if (!isNormalized || isRawContentMatch) {
unsafeSites.push({ line: i + 1, text: line.trim() });
}
}
assert.ok(
boundarySites.length >= 3,
`${rel}: expected several frontmatter-boundary sites; found ${boundarySites.length}. ` +
'If the workflow no longer uses this regex, update this guard.'
);
assert.deepStrictEqual(
unsafeSites,
[],
`${rel}: ${unsafeSites.length} frontmatter-boundary site(s) lack the CRLF normalize ` +
'(regression of #2694): ' + JSON.stringify(unsafeSites, null, 2)
);
});
}
});