From d6d6eb53afa57cc267e5ff337177e00c7d2e92f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=88=98=ED=98=84?= Date: Sun, 11 Oct 2026 15:07:50 +0900 Subject: [PATCH] fix: prevent false CSS class matches in AI guard --- scripts/ai-generation-guard.mjs | 4 +- scripts/ai-generation-guard.test.mjs | 161 ++++++++++++++++ scripts/guard-style-analysis.mjs | 279 ++++++++++++++++++++++++++- 3 files changed, 434 insertions(+), 10 deletions(-) diff --git a/scripts/ai-generation-guard.mjs b/scripts/ai-generation-guard.mjs index 8f96be3..a3996f6 100644 --- a/scripts/ai-generation-guard.mjs +++ b/scripts/ai-generation-guard.mjs @@ -90,7 +90,7 @@ async function inspect(options) { const parseJS = (text, file) => parse(text, { sourceType: 'unambiguous', plugins: [...(/\.[cm]?tsx?$/.test(file) ? ['typescript'] : []), 'jsx'], createImportExpressions: true }); const contracts = new Map(); // Parse public barrels and manifest exports for import contracts. - // Implementation text is only a conservative blocker for CSS scope exclusions. + // Implementation class bindings only conservatively block CSS scope exclusions. for (const [pkg, dir] of [['@hm-design/ui', 'ui'], ['@hm-design/center-graphic', 'center-graphic']]) { const base = path.join(workspace, 'packages', dir); const manifest = JSON.parse(fs.readFileSync(path.join(base, 'package.json'), 'utf8')); @@ -465,7 +465,7 @@ async function inspect(options) { && (records.some(record => normalize(path.relative(target, record.file)) === issue.file) || issue.file === normalize(path.relative(target, entry.file)) || !issue.file)); for (const [required, feature, code, label] of [[runtimeUI || uiComponents, 'theme', 'CSS-001', 'Theme CSS'], [utilities, 'utilities', 'CSS-002', 'Tailwind utility CSS entry'], [status, 'status', 'CSS-003', 'Public @hm-design/ui/dashboard-status.css entry']]) if (required && !cssFeatures.has(feature)) add(incompleteCSSGraph ? 'NOT VERIFIED' : 'FAIL', code, focus[0]?.file ?? entry.file, null, incompleteCSSGraph ? `${label} connection is not proven; resolve the incomplete bootstrap / CSS graph` : `Required ${label} is missing from the resolved bootstrap / CSS graph`, true); } - styleChecks = styleAnalysis({ records: [...graph.values()].filter(r => applicableFiles.has(r.file)), target, postcss, walk, selectedFile, knownSlot, origin, add, storybook, scopeRecords: [...graph.values()], implementationFiles: contractFiles.filter(f => f.startsWith('packages/')).map(f => path.join(workspace, f)), entries: entries.map(e => e.file), scopeIncomplete: storybook || issues.some(i => /^(?:ENTRY-|CORE-|CSS-005|SB-004)/.test(i.code)) }); + styleChecks = styleAnalysis({ records: [...graph.values()].filter(r => applicableFiles.has(r.file)), target, postcss, walk, parseJS, selectedFile, knownSlot, origin, add, storybook, scopeRecords: [...graph.values()], implementationFiles: contractFiles.filter(f => f.startsWith('packages/')).map(f => path.join(workspace, f)), entries: entries.map(e => e.file), scopeIncomplete: storybook || issues.some(i => /^(?:ENTRY-|CORE-|CSS-005|SB-004)/.test(i.code)) }); return finish(); function finish() { const styleEvidence = { staticStatus: verdict(styleChecks.map(c => c.staticStatus)), runtimeStatus: 'NOT VERIFIED', designIntentStatus: verdict(styleChecks.map(c => c.designIntentStatus)), finalStatus: styleChecks.some(c => c.staticStatus === 'FAIL') ? 'FAIL' : 'NOT VERIFIED', checks: styleChecks }; diff --git a/scripts/ai-generation-guard.test.mjs b/scripts/ai-generation-guard.test.mjs index 2883bf5..bfae4ab 100644 --- a/scripts/ai-generation-guard.test.mjs +++ b/scripts/ai-generation-guard.test.mjs @@ -7,6 +7,9 @@ import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { performance } from 'node:perf_hooks'; import test from 'node:test'; +import { parse } from '@babel/parser'; +import postcss from 'postcss'; +import { styleAnalysis } from './guard-style-analysis.mjs'; test('64. finite conditional templates and concatenation validate every Token branch', () => { fixture({ 'src/App.tsx': 'import { Button } from "@hm-design/ui"; const name = location.hash ? "fg-primary" : "bg-content"; export default () =>