From aac6d3635bae3bb829e272af4a205813a5280d6f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 16 May 2026 11:55:55 -0400 Subject: [PATCH] fix(3597): make markdown-fence regex CRLF-tolerant in 3 windows-failing tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bug-2801, bug-3072, bug-3321 each read .md workflow files from disk and parse fenced code blocks with /\`\`\`bash\n(...)\`\`\`/ — on a Windows checkout with autocrlf=true the file content is CRLF, the regex never matches, code blocks come back empty, and the assertion fails with "expected bash code blocks in workflow" (and similar). Fix: accept optional \r before each literal \n in the fence delimiter and split lines on /\r?\n/. Same pattern bug-2839 had with its local parseFrontmatter copy (already migrated to the CRLF-aware shared helper in 1be0e4e2). bug-2995 + tests/security-scan.test.cjs also match this grep but use \n in literal-string fixtures (not file-content regexes); not affected. Validated: holodeck (ubuntu docker) 11224/0 pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/bug-2801-ingest-docs-handler.test.cjs | 4 ++-- tests/bug-3072-optional-sketch-findings-guard.test.cjs | 6 +++--- tests/bug-3321-verifier-runs-probes.test.cjs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/bug-2801-ingest-docs-handler.test.cjs b/tests/bug-2801-ingest-docs-handler.test.cjs index 4de0290b2..b06ccd605 100644 --- a/tests/bug-2801-ingest-docs-handler.test.cjs +++ b/tests/bug-2801-ingest-docs-handler.test.cjs @@ -105,7 +105,7 @@ describe('bug-2801: ingest-docs.md workflow calls gsd-tools not gsd-sdk', () => const content = fs.readFileSync(WORKFLOW_FILE, 'utf-8'); // Extract bash fenced code blocks structurally. const bashBlocks = []; - const codeBlockRe = /```bash\n([\s\S]*?)```/g; + const codeBlockRe = /```bash\r?\n([\s\S]*?)```/g; let m; while ((m = codeBlockRe.exec(content)) !== null) { bashBlocks.push(m[1]); @@ -129,7 +129,7 @@ describe('bug-2801: ingest-docs.md workflow calls gsd-tools not gsd-sdk', () => test('ingest-docs.md init step uses canonical node-path gsd-tools.cjs invocation', () => { const content = fs.readFileSync(WORKFLOW_FILE, 'utf-8'); // Parse fenced bash blocks structurally — do not match raw markdown text. - const codeBlockRe = /```bash\n([\s\S]*?)```/g; + const codeBlockRe = /```bash\r?\n([\s\S]*?)```/g; const bashLines = [...content.matchAll(codeBlockRe)] .flatMap((m) => m[1].split('\n')) .filter((l) => !/^\s*#/.test(l)); diff --git a/tests/bug-3072-optional-sketch-findings-guard.test.cjs b/tests/bug-3072-optional-sketch-findings-guard.test.cjs index c22e603d6..50dc1d2f1 100644 --- a/tests/bug-3072-optional-sketch-findings-guard.test.cjs +++ b/tests/bug-3072-optional-sketch-findings-guard.test.cjs @@ -13,13 +13,13 @@ function read(rel) { function extractFindingsProbesFromBashBlocks(markdown) { const probes = []; - const fenceRe = /```bash\n([\s\S]*?)```/g; + const fenceRe = /```bash\r?\n([\s\S]*?)```/g; let fenceMatch; while ((fenceMatch = fenceRe.exec(markdown)) !== null) { const block = fenceMatch[1]; - const baseLine = markdown.slice(0, fenceMatch.index).split('\n').length; - const lines = block.split('\n'); + const baseLine = markdown.slice(0, fenceMatch.index).split(/\r?\n/).length; + const lines = block.split(/\r?\n/); lines.forEach((line, idx) => { if (!line.includes('.claude/skills/')) return; diff --git a/tests/bug-3321-verifier-runs-probes.test.cjs b/tests/bug-3321-verifier-runs-probes.test.cjs index 8caf8be6b..6af5e4ff2 100644 --- a/tests/bug-3321-verifier-runs-probes.test.cjs +++ b/tests/bug-3321-verifier-runs-probes.test.cjs @@ -15,7 +15,7 @@ function verifierProbeContract(content) { assert.notEqual(sectionEnd, -1, 'verifier must close Step 7c before Step 8'); const section = content.slice(sectionStart, sectionEnd); - const codeBlocks = [...section.matchAll(/```bash\n([\s\S]*?)\n```/g)].map((match) => match[1]); + const codeBlocks = [...section.matchAll(/```bash\r?\n([\s\S]*?)\r?\n```/g)].map((match) => match[1].split(/\r?\n/).join('\n')); const executionSteps = [...section.matchAll(/^\d+\.\s+(.+)$/gm)].map((match) => match[1]); return { title: 'Step 7c: Probe Execution',