diff --git a/scripts/check-doc-metrics.mjs b/scripts/check-doc-metrics.mjs index 0e817c48..eec622b6 100644 --- a/scripts/check-doc-metrics.mjs +++ b/scripts/check-doc-metrics.mjs @@ -39,6 +39,8 @@ const ANY_HEADING = /^#{1,6}\s+/; const HISTORICAL_MARKER = /(✅|\bRELEASED\b|\bDELIVERED\b|\bCompleted\b|\[\d+\.\d+\.\d+\]|\(\d{4}-\d{2}-\d{2})/i; const DONE_BULLET = /^\s*-\s*✅/; +// QNBS-v3: mirrors DONE_BULLET's "always present-tense regardless of section" rule for open bullets, so a stale ⬜ can't hide in a historical section +const OPEN_BULLET = /^\s*-\s*⬜/; // QNBS-v3: how many lines below a heading to look for a "**Status:** ✅ Released …" marker that // applies to the whole section (this repo puts it on its own line, not in the heading text). const STATUS_LOOKAHEAD = 5; @@ -65,7 +67,12 @@ export function stripHistoricalSections(markdown) { } } - return lines.map((line, i) => (historical[i] || DONE_BULLET.test(line) ? '' : line)).join('\n'); + return lines + .map((line, i) => { + if (OPEN_BULLET.test(line)) return line; // never strip — see OPEN_BULLET comment above + return historical[i] || DONE_BULLET.test(line) ? '' : line; + }) + .join('\n'); } /** Actual locale count = directories under locales/ (translation-glossary.json is a file, not a locale). */ @@ -187,6 +194,20 @@ export function scanForDrift(content, filePath, { localeCount, keyCount, latestV ); } } + // QNBS-v3: catches a stale "⬜ Tag/Release/publish vX.Y.Z" bullet that should have flipped to ✅ once that version shipped + if ( + OPEN_BULLET.test(line) && + /\b(?:tag|tagging|release|releasing|publish|publishing)\b/i.test(line) + ) { + for (const m of line.matchAll(/v(\d+\.\d+\.\d+)/gi)) { + const mentioned = m[1]; + if (semverLte(mentioned, latestVersion)) { + findings.push( + `${filePath}:${i + 1} — open "⬜" bullet mentions tag/release/publish of v${mentioned}, but v${mentioned} <= latest released v${latestVersion}: "${line.trim()}"`, + ); + } + } + } } }); diff --git a/tests/unit/checkDocMetrics.test.ts b/tests/unit/checkDocMetrics.test.ts index ff0b5c6c..004612dd 100644 --- a/tests/unit/checkDocMetrics.test.ts +++ b/tests/unit/checkDocMetrics.test.ts @@ -62,6 +62,25 @@ describe('stripHistoricalSections', () => { expect(stripped).not.toMatch(/Historical/); expect(stripped).toContain('Present: 17 locales.'); }); + + // QNBS-v3: regression guard for the dc14bc0-shaped drift — a stale open bullet was invisible to the gate inside a historical section + it('preserves an open "⬜" bullet even inside a dated/historical section', () => { + const md = [ + '## v1.24.2 — CSP/crypto/doc-truth hardening (2026-07-29)', + '', + '- ⬜ **Tag `v1.24.2` + publish the GitHub Release** — maintainer action.', + ].join('\n'); + const stripped = stripHistoricalSections(md); + expect(stripped).toContain('⬜ **Tag `v1.24.2`'); + }); + + it('still blanks a "✅" bullet even inside a live, non-historical section', () => { + const md = ['## Current status', '', '- ✅ Already-done item that should not be scanned.'].join( + '\n', + ); + const stripped = stripHistoricalSections(md); + expect(stripped).not.toContain('Already-done item'); + }); }); describe('scanForDrift', () => { @@ -102,6 +121,37 @@ describe('scanForDrift', () => { const findings = scanForDrift(content, 'FAKE.md', actual); expect(findings).toHaveLength(0); }); + + // QNBS-v3: the OPEN_BULLET_VERSION check — a stale open tag/release bullet for an already-released version must be flagged + it('flags an open "⬜" bullet for tagging/releasing a version <= the latest release', () => { + const content = '- ⬜ **Tag `v1.24.1` + publish the GitHub Release** — maintainer action.'; + const findings = scanForDrift(content, 'FAKE.md', actual); + expect(findings.some((f) => f.includes('v1.24.1'))).toBe(true); + }); + + it.each(['Tagging', 'Releasing', 'Publishing'])( + 'flags an open "⬜" bullet using the inflected form "%s"', + (verb) => { + const content = `- ⬜ **${verb} \`v1.24.1\`** — maintainer action.`; + const findings = scanForDrift(content, 'FAKE.md', actual); + expect(findings.some((f) => f.includes('v1.24.1'))).toBe(true); + }, + ); + + it('does not flag an open "⬜" bullet for tagging a version newer than the latest release', () => { + const content = '- ⬜ **Tag `v1.25.0` + publish the GitHub Release** — maintainer action.'; + const findings = scanForDrift(content, 'FAKE.md', actual); + expect(findings).toHaveLength(0); + }); + + it('does not flag any version-based drift when latestVersion is null (shallow/tagless checkout)', () => { + const content = [ + '- ⬜ **Tag `v1.24.1` + publish the GitHub Release** — maintainer action.', + '## Upcoming — v1.0 (PLANNED)', + ].join('\n'); + const findings = scanForDrift(content, 'FAKE.md', { ...actual, latestVersion: null }); + expect(findings).toHaveLength(0); + }); }); // QNBS-v3 (F-10): regression guard for the dead worldscript-studio-indol.vercel.app URL that had