diff --git a/skill/scripts/lib/staleness-deep.mjs b/skill/scripts/lib/staleness-deep.mjs index bae03bc98..8981b654a 100644 --- a/skill/scripts/lib/staleness-deep.mjs +++ b/skill/scripts/lib/staleness-deep.mjs @@ -117,6 +117,18 @@ export function checkDesignDrift({ designPath, projectRoot, threshold = 25 }) { * a section can be absent because it never applied, so this is reported as a * documentation gap for a human to judge, never as an error. */ +function hasCoverageValue(value) { + if (Array.isArray(value)) return value.some(hasCoverageValue); + if (value && typeof value === 'object') { + return Object.values(value).some(hasCoverageValue); + } + if (typeof value === 'string') { + const trimmed = value.trim(); + return trimmed.length > 0 && !/^(?:\[\s*\]|\{\s*\})$/.test(trimmed); + } + return false; +} + export function checkDesignCoverage({ design, designPath, parseDesignMd }) { if (!design || typeof parseDesignMd !== 'function') return []; let model; @@ -126,7 +138,7 @@ export function checkDesignCoverage({ design, designPath, parseDesignMd }) { return []; } const missing = ['colors', 'typography', 'components'] - .filter((section) => !model[section]); + .filter((section) => !model[section] && !hasCoverageValue(model.frontmatter?.[section])); if (!missing.length) return []; return [finding({ id: 'design-md-coverage', diff --git a/tests/doctor.test.mjs b/tests/doctor.test.mjs index 2e120e4c2..fbd9db19d 100644 --- a/tests/doctor.test.mjs +++ b/tests/doctor.test.mjs @@ -172,6 +172,111 @@ describe('checkDesignCoverage', () => { assert.deepEqual(checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }), []); }); + it('counts machine-readable frontmatter as section coverage', () => { + const design = [ + '---', + 'name: X', + 'colors:', + ' ink: "#111111"', + 'typography:', + ' body:', + ' fontFamily: Inter', + 'components:', + ' button:', + ' backgroundColor: "{colors.ink}"', + '---', + '', + '# Design System: X', + '', + 'See the canonical source for prose guidance.', + '', + ].join('\n'); + assert.deepEqual(checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }), []); + }); + + it('does not count empty frontmatter mappings as section coverage', () => { + const design = [ + '---', + 'name: X', + 'colors:', + 'typography:', + ' body:', + ' fontFamily: Inter', + 'components:', + ' button:', + ' backgroundColor: "#111111"', + '---', + '', + '# Design System: X', + '', + ].join('\n'); + const findings = checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }); + assert.deepEqual(ids(findings), ['design-md-coverage']); + assert.match(findings[0].summary, /no colors section/); + assert.doesNotMatch(findings[0].summary, /typography|components/); + }); + + it('does not count boolean or numeric frontmatter scalars as section coverage', () => { + for (const colors of ['false', '0']) { + const design = [ + '---', + 'name: X', + `colors: ${colors}`, + 'typography:', + ' body:', + ' fontFamily: Inter', + 'components:', + ' button:', + ' backgroundColor: "#111111"', + '---', + '', + '# Design System: X', + '', + ].join('\n'); + const findings = checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }); + assert.deepEqual(ids(findings), ['design-md-coverage']); + assert.match(findings[0].summary, /no colors section/); + } + }); + + it('does not count empty frontmatter collection literals as section coverage', () => { + for (const colors of ['[]', '{}']) { + const design = [ + '---', + 'name: X', + `colors: ${colors}`, + 'typography:', + ' body:', + ' fontFamily: Inter', + 'components:', + ' button:', + ' backgroundColor: "#111111"', + '---', + '', + '# Design System: X', + '', + ].join('\n'); + const findings = checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }); + assert.deepEqual(ids(findings), ['design-md-coverage']); + assert.match(findings[0].summary, /no colors section/); + } + }); + + it('counts populated frontmatter array literals as section coverage', () => { + const design = [ + '---', + 'name: X', + 'colors: ["#111111"]', + 'typography: [Inter]', + 'components: [button]', + '---', + '', + '# Design System: X', + '', + ].join('\n'); + assert.deepEqual(checkDesignCoverage({ design, designPath: 'DESIGN.md', parseDesignMd }), []); + }); + it('reports nothing without a DESIGN.md', () => { assert.deepEqual(checkDesignCoverage({ design: null, parseDesignMd }), []); });