fix(#2703): strip GSD-2 frontmatter with the canonical parser (#3027)

* test(#2703): failing-first coverage for CRLF frontmatter strip in SUMMARY.md

Drives the exported buildPlanningArtifacts seam. Rows for CRLF/LF parity,
stacked blocks and a leading BOM fail against the current hand-rolled
regex; the negative-space rows pin behavior that must not change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#2703): strip GSD-2 frontmatter with the canonical parser

buildSummaryMd matched the closing delimiter with a hardcoded bare \n, so a
CRLF-authored task summary never matched and fell through to the raw-passthrough
branch. The function then prepended its own block, emitting a SUMMARY.md with two
stacked frontmatter blocks and no warning.

Delegates to stripFrontmatter from frontmatter.cts -- the canonical, line-ending
tolerant primitive this repo already deduplicated once (#2143) -- instead of
adding another hand-rolled variant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(#2703): strip only the first frontmatter block in gsd2 import

Adversarial review caught a regression in the first cut: stripFrontmatter
loops by design, so a summary body opening with a thematic-break-delimited
section (--- / heading / ---) had that section silently deleted. The old
pre-#2703 regex preserved it, so shipping the loop would have traded one
silent corruption for another.

Adds an explicit { once } option to the canonical primitive -- default
behavior and the two existing callers are unchanged -- and has buildSummaryMd
opt in. A GSD-2 summary is an arbitrary user document, not a GSD artifact with
a known doubling failure mode, so a second block there is body content.

This also makes the acceptance criterion exact: CRLF now produces the same
result LF already produced, rather than a new result for both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(#2703): backfill changeset pr number

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-08-03 15:21:30 -04:00
committed by GitHub
parent 8ad7845f16
commit fd64389616
5 changed files with 230 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ const {
extractFrontmatter,
reconstructFrontmatter,
spliceFrontmatter,
stripFrontmatter,
noOpObjectListSetError,
parseMustHavesBlock,
FRONTMATTER_SCHEMAS,
@@ -1357,3 +1358,54 @@ describe('noOpObjectListSetError (#1660)', () => {
assert.ok(msg.includes('Edit the file directly'), msg);
});
});
// ─── stripFrontmatter ─────────────────────────────────────────────────────────
describe('stripFrontmatter', () => {
const stacked = ['---', 'a: 1', '---', '---', 'b: 2', '---', '', 'Real body.'].join('\n');
test('strips a single block', () => {
assert.strictEqual(stripFrontmatter(['---', 'a: 1', '---', '', 'Body.'].join('\n')), 'Body.');
});
test('is CRLF-tolerant', () => {
const crlf = ['---', 'a: 1', '---', '', 'Body.'].join('\r\n');
assert.strictEqual(stripFrontmatter(crlf), 'Body.');
});
test('defaults to stripping every stacked block (corruption recovery)', () => {
assert.strictEqual(stripFrontmatter(stacked), 'Real body.');
});
test('an omitted options argument keeps the greedy default', () => {
// Back-compat: state.cts and state-transition.cts call this with one arg.
assert.strictEqual(stripFrontmatter(stacked, {}), 'Real body.');
});
test('once: true stops after the first block', () => {
assert.strictEqual(
stripFrontmatter(stacked, { once: true }),
['---', 'b: 2', '---', '', 'Real body.'].join('\n'),
);
});
test('once: false is the greedy default', () => {
assert.strictEqual(stripFrontmatter(stacked, { once: false }), 'Real body.');
});
test('returns content unchanged when there is no frontmatter', () => {
const plain = ['Just prose.', '', 'More prose.'].join('\n');
assert.strictEqual(stripFrontmatter(plain), plain);
assert.strictEqual(stripFrontmatter(plain, { once: true }), plain);
});
test('leaves an unterminated block alone under both modes', () => {
const unterminated = ['---', 'a: 1', 'b: 2'].join('\n');
assert.strictEqual(stripFrontmatter(unterminated), unterminated);
assert.strictEqual(stripFrontmatter(unterminated, { once: true }), unterminated);
});
test('empty string round-trips', () => {
assert.strictEqual(stripFrontmatter(''), '');
});
});