#!/usr/bin/env node import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; const projectRoot = process.cwd(); const scopedFiles = [ 'src/App.tsx', 'src/components/MissionControlPanel.tsx', 'src/components/ui/primitives.tsx', 'src/components/ErrorBoundary.tsx', 'src/pages/NotFoundPage.tsx', 'src/pages/RecipeListPage.tsx', 'src/components/RecipeCard.tsx', 'src/components/Toast.tsx', ].map((file) => path.resolve(projectRoot, file)); const bannedPatterns = [ { name: 'raw-tailwind-palette', regex: /\b(?:bg|text|border|ring|from|to|via|outline|decoration)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d{2,3}\b/g, message: 'Use tokenized classes/variables instead of direct Tailwind palette utilities (e.g. bg-slate-200, text-blue-600).', }, { name: 'hex-in-arbitrary-class', regex: /\b(?:bg|text|border|ring|from|to|via)-\[#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\]\b/g, message: 'Avoid hardcoded hex colors in className. Add/consume a semantic token in tokens.css instead.', }, ]; function readFile(filePath) { return fs.readFileSync(filePath, 'utf8'); } function lineNumberAt(text, index) { return text.slice(0, index).split('\n').length; } const findings = []; for (const filePath of scopedFiles) { if (!fs.existsSync(filePath)) continue; const content = readFile(filePath); for (const rule of bannedPatterns) { for (const match of content.matchAll(rule.regex)) { findings.push({ filePath, line: lineNumberAt(content, match.index ?? 0), value: match[0], rule: rule.name, message: rule.message, }); } } } if (findings.length === 0) { console.log('✅ style-guardrails: no blocked raw palette patterns found in scoped files.'); process.exit(0); } console.error('❌ style-guardrails: found styling drift patterns in guarded files:\n'); for (const finding of findings) { const relativePath = path.relative(projectRoot, finding.filePath); console.error(`- ${relativePath}:${finding.line} [${finding.rule}] ${finding.value}`); console.error(` ${finding.message}`); } console.error('\nSee .harness/docs/styling-governance.md for the contract and escalation path.'); process.exit(1);