Add mechanical pre-scan for typeset and layout (#345)

* Add mechanical pre-scan for typeset and layout commands.

Introduce --scope filtering, layout/type rule scopes, DESIGN.md font-size validation, and pre-scan steps in the skill references so agents run detect before LLM judgment.

Fixes #149

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add isolated sub-agent orchestration for typeset and layout pre-scans.

Run the mechanical detector and visual assessment in parallel sub-agents so deterministic findings cannot anchor LLM judgment, matching the critique pattern Paul requested on PR #345.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: reject bare --scope so detect never scans unscoped by mistake.

When --scope had no value, the CLI dropped the flag and ran a full scan instead of failing, which could silently use the wrong rule set during typeset/layout pre-scans.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix: require both typeset and layout assessments in sub-agents.

Close a loophole where agents ran only the mechanical pre-scan inline by interpreting "running both" as permitting one inline assessment.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Abdul Wahab <abdulwahab@Abduls-MacBook-Pro-2.local>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Abdul Wahab
2026-07-09 08:29:21 -07:00
committed by GitHub
co-authored by Abdul Wahab Cursor
parent c11cc7b58c
commit f40e2f8f0a
14 changed files with 393 additions and 15 deletions
+55 -3
View File
@@ -15,6 +15,7 @@ import {
isAllowedColorRaw,
isAllowedFont,
isAllowedRadiusRaw,
isAllowedFontSizeRaw,
loadDesignSystemForCwd,
normalizeDesignSystem,
} from '../cli/engine/design-system.mjs';
@@ -31,8 +32,9 @@ function sampleDesignSystem() {
return normalizeDesignSystem({
frontmatter: {
typography: {
display: { fontFamily: 'Avenir Next, Georgia, serif' },
body: { fontFamily: 'IBM Plex Sans, Arial, sans-serif' },
display: { fontFamily: 'Avenir Next, Georgia, serif', fontSize: 'clamp(2.5rem, 6vw, 4rem)' },
body: { fontFamily: 'IBM Plex Sans, Arial, sans-serif', fontSize: '16px' },
label: { fontFamily: 'IBM Plex Sans, Arial, sans-serif', fontSize: '0.875rem' },
},
colors: {
ink: '#241f1a',
@@ -96,6 +98,15 @@ describe('normalizeDesignSystem()', () => {
assert.equal(isAllowedRadiusRaw('100px', designSystem), true);
assert.equal(isAllowedRadiusRaw('9999px', designSystem), true);
assert.equal(isAllowedRadiusRaw('18px', designSystem), false);
assert.equal(isAllowedFontSizeRaw('16px', designSystem), true);
assert.equal(isAllowedFontSizeRaw('1rem', designSystem), true);
assert.equal(isAllowedFontSizeRaw('0.875rem', designSystem), true);
assert.equal(isAllowedFontSizeRaw('14px', designSystem), true);
assert.equal(isAllowedFontSizeRaw('12.5px', designSystem), false);
assert.equal(isAllowedFontSizeRaw('1.2em', designSystem), true);
assert.equal(isAllowedFontSizeRaw('clamp(1rem, 2vw, 2rem)', designSystem), true);
assert.equal(isAllowedFontSizeRaw('var(--text-body)', designSystem), true);
});
});
@@ -220,9 +231,50 @@ scale.style.cssText = 'font-family:' + MONO + '; font-size: 10px;';
assert.deepEqual(
findings.map((item) => item.ignoreValue),
['#ff00aa', 'Poppins', '#cc00ff'],
['10px', '#ff00aa', 'Poppins', '#cc00ff'],
);
});
it('reports literal font sizes outside the DESIGN.md type ramp', () => {
const designSystem = sampleDesignSystem();
const source = `.off-ramp {
font-size: 12.5px;
}
const label = { fontSize: "11px" };
const badge = { className: "text-[10px]" };
/* font-size: 9px; */
.on-ramp {
font-size: 1rem;
}
`;
const findings = checkSourceDesignSystem(source, '/tmp/sizes.css', { designSystem });
const fontSizeFindings = findings.filter((item) => item.antipattern === 'design-system-font-size');
assert.equal(fontSizeFindings.length, 3);
assert.deepEqual(
fontSizeFindings.map((item) => item.ignoreValue),
['12.5px', '11px', '10px'],
);
assert.deepEqual(
fontSizeFindings.map((item) => item.line),
[2, 4, 5],
);
});
it('abstains on font-size checks when DESIGN.md has no literal ramp steps', () => {
const designSystem = normalizeDesignSystem({
frontmatter: {
typography: {
display: { fontFamily: 'Avenir Next, Georgia, serif', fontSize: 'clamp(2.5rem, 6vw, 4rem)' },
body: { fontFamily: 'IBM Plex Sans, Arial, sans-serif', fontSize: 'clamp(1rem, 2vw, 1.125rem)' },
},
},
});
assert.equal(designSystem.hasFontSizes, false);
const findings = checkSourceDesignSystem('.bad { font-size: 12.5px; }', '/tmp/clamp-only.css', { designSystem });
assert.equal(findings.some((item) => item.antipattern === 'design-system-font-size'), false);
});
});
describe('collectStaticDesignSystemFindings()', () => {