diff --git a/tools/outcome-coverage.mjs b/tools/outcome-coverage.mjs index baea19e..7ae10db 100644 --- a/tools/outcome-coverage.mjs +++ b/tools/outcome-coverage.mjs @@ -37,6 +37,17 @@ export const BASELINE = path.join(ROOT, "tools", "outcome-coverage-baseline.json /* ------------------------------------------------------------------ the beats */ +/* ONE PATTERN FOR BOTH HALVES OF THIS FILE, and it agrees with check-scenarios' `tagsIn` + deliberately. R-291 measured the two apart: `tagsIn` was widened to tolerate case and + spacing around the colon, this file's strict half was not, and `[cus: Spot — x]` was then + READ by one guard and reported unparseable by the other. The build failed, which is the + safe direction, but it failed saying the tag could not be read while another guard had + just read it. Widened to match; `beatLikeIn` below still refuses `[CUS Spot]` and + `[CUS= Spot]`, which neither strict reader accepts, so the canary keeps its teeth. + check-behaviour asserts the two readers agree — that assertion is the thing that must + not be deleted, not this comment. */ +const TAG = /\[\s*CUS\s*:\s*([^\]—]+?)\s*(?:—|\])/gi; + const indentOf = l => { const m = l.match(/^(\s*)(?:[-*]|\d+\.)\s/); return m ? m[1].length : null; }; const isHeading = l => /^#{1,6}\s/.test(l); @@ -55,7 +66,7 @@ export function beatsIn(text) { if (/^\s*```/.test(l)) { inFence = !inFence; return; } if (inFence) return; const quoted = /^\s*>/.test(l); - for (const m of l.matchAll(/\[CUS:\s*([^\]—]+?)\s*(?:—|\])/g)) { + for (const m of l.matchAll(TAG)) { if (quoted) { out.push({ line: i + 1, skill: m[1].trim(), quoted: true }); continue; } let owner = i; for (let j = i; j >= 0; j--) { @@ -84,7 +95,7 @@ export function beatsIn(text) { /** A `[CUS …]` that this reader cannot parse is worse than no tag: somebody thinks it is one. Asymmetric with beatsIn on purpose — R-286's castLikeIn, same reasoning. */ export function beatLikeIn(text) { - const good = [...text.matchAll(/\[CUS:\s*([^\]—]+?)\s*(?:—|\])/g)].map(m => [m.index, m.index + m[0].length]); + const good = [...text.matchAll(TAG)].map(m => [m.index, m.index + m[0].length]); return [...text.matchAll(/\[\s*CUS\b[^\]]*\]/gi)] .filter(m => !good.some(([a, b]) => m.index >= a && m.index < b)) .map(m => ({ raw: m[0].slice(0, 70), line: text.slice(0, m.index).split("\n").length }));