* 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:
@@ -642,22 +642,29 @@ const FRONTMATTER_SCHEMAS: Record<string, { required: string[]; requiredValues?:
|
||||
};
|
||||
|
||||
/**
|
||||
* Strip ALL frontmatter blocks from the start of `content`.
|
||||
* Strip frontmatter blocks from the start of `content`.
|
||||
*
|
||||
* Handles CRLF line endings and multiple stacked blocks (corruption
|
||||
* recovery): greedily strips consecutive `---...---` blocks separated by
|
||||
* optional whitespace, so a doubled/tripled frontmatter header (e.g. from a
|
||||
* botched merge) is fully removed, not just the first block.
|
||||
* Handles CRLF line endings and, by default, multiple stacked blocks
|
||||
* (corruption recovery): greedily strips consecutive `---...---` blocks
|
||||
* separated by optional whitespace, so a doubled/tripled frontmatter header
|
||||
* (e.g. from a botched merge) is fully removed, not just the first block.
|
||||
*
|
||||
* Pass `{ once: true }` to stop after the first block. Callers whose input is
|
||||
* an arbitrary user-authored document — rather than a GSD artefact with a
|
||||
* known doubling failure mode — need this: a body that opens with a
|
||||
* thematic-break-delimited section is lexically indistinguishable from a
|
||||
* second frontmatter block, and the greedy loop deletes it silently (#2703).
|
||||
*
|
||||
* Canonical home for this primitive (#2143 audit dedup): previously
|
||||
* duplicated byte-identically in both `state.cts` and `state-transition.cts`.
|
||||
*/
|
||||
function stripFrontmatter(content: string): string {
|
||||
function stripFrontmatter(content: string, opts: { once?: boolean } = {}): string {
|
||||
let result = content;
|
||||
while (true) {
|
||||
const stripped = result.replace(/^\s*---\r?\n[\s\S]*?\r?\n---\s*/, '');
|
||||
if (stripped === result) break;
|
||||
result = stripped;
|
||||
if (opts.once) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user