fix(3597): make markdown-fence regex CRLF-tolerant in 3 windows-failing tests

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) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-16 11:55:55 -04:00
parent 1be0e4e2ee
commit aac6d3635b
3 changed files with 6 additions and 6 deletions

View File

@@ -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));

View File

@@ -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;

View File

@@ -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',