* 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:
@@ -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(''), '');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user