@stayicon/drift-guard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/index.ts","../src/core/snapshot.ts","../src/parsers/css-parser.ts","../src/parsers/html-parser.ts","../src/core/drift.ts","../src/core/rules-generator.ts"],"sourcesContent":["// drift-guard type definitions\n\n/**\n * A single design token extracted from CSS/HTML\n */\nexport interface DesignToken {\n /** Token category: color, font, spacing, shadow, radius, layout */\n category: TokenCategory;\n /** CSS property name (e.g., 'color', 'font-family', 'padding') */\n property: string;\n /** Resolved value (e.g., '#1a73e8', '16px', 'Inter') */\n value: string;\n /** CSS selector where this token was found */\n selector: string;\n /** Source file path */\n file: string;\n /** Line number in source file */\n line?: number;\n}\n\nexport type TokenCategory =\n | 'color'\n | 'font'\n | 'spacing'\n | 'shadow'\n | 'radius'\n | 'layout'\n | 'other';\n\n/**\n * Design snapshot — frozen state of all design tokens\n */\nexport interface DesignSnapshot {\n /** Snapshot version */\n version: string;\n /** ISO timestamp when snapshot was created */\n createdAt: string;\n /** Project root directory */\n projectRoot: string;\n /** Source files that were scanned */\n sourceFiles: string[];\n /** All extracted design tokens */\n tokens: DesignToken[];\n /** Token count by category */\n summary: Record<TokenCategory, number>;\n}\n\n/**\n * A single drift item — one token that changed\n */\nexport interface DriftItem {\n /** The original token from the snapshot */\n original: DesignToken;\n /** The current token value (null if token was deleted) */\n current: DesignToken | null;\n /** Type of change */\n changeType: 'modified' | 'deleted' | 'added';\n}\n\n/**\n * Drift detection report\n */\nexport interface DriftReport {\n /** ISO timestamp of the check */\n checkedAt: string;\n /** Snapshot used as baseline */\n snapshotCreatedAt: string;\n /** Total tokens in snapshot */\n totalTokens: number;\n /** Number of changed tokens */\n changedTokens: number;\n /** Drift score: (changed / total) * 100 */\n driftScore: number;\n /** Threshold used for pass/fail */\n threshold: number;\n /** Whether the check passed */\n passed: boolean;\n /** Individual drift items */\n items: DriftItem[];\n /** Summary by category */\n categorySummary: Record<TokenCategory, {\n total: number;\n changed: number;\n driftPercent: number;\n }>;\n}\n\n/**\n * Supported AI rule file formats\n */\nexport type RuleFormat =\n | 'cursorrules'\n | 'claude-md'\n | 'agents-md'\n | 'copilot'\n | 'clinerules';\n\n/**\n * Configuration stored in .design-guard/config.json\n */\nexport interface DriftGuardConfig {\n /** Glob patterns for CSS files to scan */\n cssFiles: string[];\n /** Glob patterns for HTML files to scan */\n htmlFiles: string[];\n /** Default drift threshold (percentage) */\n threshold: number;\n /** Token categories to track */\n trackCategories: TokenCategory[];\n /** Files/patterns to ignore */\n ignore: string[];\n}\n\n/**\n * Default configuration\n */\nexport const DEFAULT_CONFIG: DriftGuardConfig = {\n cssFiles: [\n 'src/**/*.css',\n 'app/**/*.css',\n 'styles/**/*.css',\n '**/*.module.css',\n '**/*.css',\n ],\n htmlFiles: [\n '**/*.html',\n '!node_modules/**',\n '!dist/**',\n '!build/**',\n ],\n threshold: 10,\n trackCategories: ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'],\n ignore: [\n 'node_modules/**',\n 'dist/**',\n 'build/**',\n '.next/**',\n 'coverage/**',\n ],\n};\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport fg from 'fast-glob';\nimport { parseCss, extractCssVariables } from '../parsers/css-parser.js';\nimport { parseHtml, extractStyleBlocks } from '../parsers/html-parser.js';\nimport type {\n DesignSnapshot,\n DesignToken,\n TokenCategory,\n DriftGuardConfig,\n} from '../types/index.js';\nimport { DEFAULT_CONFIG } from '../types/index.js';\n\nconst SNAPSHOT_DIR = '.design-guard';\nconst SNAPSHOT_FILE = 'snapshot.json';\nconst CONFIG_FILE = 'config.json';\n\n/**\n * Get the full path to the snapshot file\n */\nexport function getSnapshotPath(projectRoot: string): string {\n return path.join(projectRoot, SNAPSHOT_DIR, SNAPSHOT_FILE);\n}\n\n/**\n * Get the config path\n */\nexport function getConfigPath(projectRoot: string): string {\n return path.join(projectRoot, SNAPSHOT_DIR, CONFIG_FILE);\n}\n\n/**\n * Load or create config\n */\nexport function loadConfig(projectRoot: string): DriftGuardConfig {\n const configPath = getConfigPath(projectRoot);\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };\n }\n return { ...DEFAULT_CONFIG };\n}\n\n/**\n * Save config\n */\nexport function saveConfig(projectRoot: string, config: DriftGuardConfig): void {\n const dir = path.join(projectRoot, SNAPSHOT_DIR);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(getConfigPath(projectRoot), JSON.stringify(config, null, 2), 'utf-8');\n}\n\n/**\n * Scan the project and extract all design tokens\n */\nexport async function scanProject(\n projectRoot: string,\n config: DriftGuardConfig,\n stitchHtmlPath?: string,\n): Promise<{ tokens: DesignToken[]; files: string[] }> {\n const allTokens: DesignToken[] = [];\n const scannedFiles: string[] = [];\n\n // 1. If a Stitch HTML file is provided, parse it first\n if (stitchHtmlPath) {\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (fs.existsSync(absPath)) {\n const htmlContent = fs.readFileSync(absPath, 'utf-8');\n const htmlTokens = parseHtml(htmlContent, stitchHtmlPath);\n allTokens.push(...htmlTokens);\n\n // Also parse <style> blocks within the HTML\n const styleBlocks = extractStyleBlocks(htmlContent);\n for (const block of styleBlocks) {\n const cssTokens = parseCss(block, stitchHtmlPath);\n allTokens.push(...cssTokens);\n const vars = extractCssVariables(block, stitchHtmlPath);\n allTokens.push(...vars);\n }\n scannedFiles.push(stitchHtmlPath);\n }\n }\n\n // 2. Scan CSS files\n const cssFiles = await fg(config.cssFiles, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of cssFiles) {\n const absPath = path.join(projectRoot, file);\n const content = fs.readFileSync(absPath, 'utf-8');\n const tokens = parseCss(content, file);\n allTokens.push(...tokens);\n const vars = extractCssVariables(content, file);\n allTokens.push(...vars);\n scannedFiles.push(file);\n }\n\n // 3. Scan HTML files (for inline styles)\n const htmlFiles = await fg(config.htmlFiles, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of htmlFiles) {\n if (scannedFiles.includes(file)) continue; // Skip if already scanned (e.g., Stitch HTML)\n const absPath = path.join(projectRoot, file);\n const content = fs.readFileSync(absPath, 'utf-8');\n const tokens = parseHtml(content, file);\n allTokens.push(...tokens);\n\n // Parse embedded <style> blocks\n const styleBlocks = extractStyleBlocks(content);\n for (const block of styleBlocks) {\n const cssTokens = parseCss(block, file);\n allTokens.push(...cssTokens);\n }\n scannedFiles.push(file);\n }\n\n // Filter by tracked categories\n const filtered = allTokens.filter(t =>\n config.trackCategories.includes(t.category),\n );\n\n // Deduplicate: same property + selector + file = keep first\n const seen = new Set<string>();\n const deduplicated = filtered.filter(t => {\n const key = `${t.file}:${t.selector}:${t.property}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { tokens: deduplicated, files: scannedFiles };\n}\n\n/**\n * Build category summary from tokens\n */\nfunction buildSummary(tokens: DesignToken[]): Record<TokenCategory, number> {\n const summary: Record<TokenCategory, number> = {\n color: 0,\n font: 0,\n spacing: 0,\n shadow: 0,\n radius: 0,\n layout: 0,\n other: 0,\n };\n\n for (const token of tokens) {\n summary[token.category]++;\n }\n\n return summary;\n}\n\n/**\n * Create a snapshot from the current project state\n */\nexport async function createSnapshot(\n projectRoot: string,\n stitchHtmlPath?: string,\n): Promise<DesignSnapshot> {\n const config = loadConfig(projectRoot);\n const { tokens, files } = await scanProject(projectRoot, config, stitchHtmlPath);\n\n const snapshot: DesignSnapshot = {\n version: '1.0.0',\n createdAt: new Date().toISOString(),\n projectRoot,\n sourceFiles: files,\n tokens,\n summary: buildSummary(tokens),\n };\n\n return snapshot;\n}\n\n/**\n * Save a snapshot to disk\n */\nexport function saveSnapshot(projectRoot: string, snapshot: DesignSnapshot): string {\n const dir = path.join(projectRoot, SNAPSHOT_DIR);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n const snapshotPath = getSnapshotPath(projectRoot);\n fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), 'utf-8');\n return snapshotPath;\n}\n\n/**\n * Load an existing snapshot from disk\n */\nexport function loadSnapshot(projectRoot: string): DesignSnapshot | null {\n const snapshotPath = getSnapshotPath(projectRoot);\n if (!fs.existsSync(snapshotPath)) {\n return null;\n }\n\n const raw = fs.readFileSync(snapshotPath, 'utf-8');\n return JSON.parse(raw) as DesignSnapshot;\n}\n","import * as csstree from 'css-tree';\nimport type { DesignToken, TokenCategory } from '../types/index.js';\n\n/**\n * CSS properties that map to each token category\n */\nconst CATEGORY_MAP: Record<string, TokenCategory> = {\n // Colors\n 'color': 'color',\n 'background-color': 'color',\n 'background': 'color',\n 'border-color': 'color',\n 'outline-color': 'color',\n 'fill': 'color',\n 'stroke': 'color',\n '--color': 'color',\n\n // Fonts\n 'font-family': 'font',\n 'font-size': 'font',\n 'font-weight': 'font',\n 'line-height': 'font',\n 'letter-spacing': 'font',\n\n // Spacing\n 'margin': 'spacing',\n 'margin-top': 'spacing',\n 'margin-right': 'spacing',\n 'margin-bottom': 'spacing',\n 'margin-left': 'spacing',\n 'padding': 'spacing',\n 'padding-top': 'spacing',\n 'padding-right': 'spacing',\n 'padding-bottom': 'spacing',\n 'padding-left': 'spacing',\n 'gap': 'spacing',\n 'row-gap': 'spacing',\n 'column-gap': 'spacing',\n\n // Shadows\n 'box-shadow': 'shadow',\n 'text-shadow': 'shadow',\n\n // Radius\n 'border-radius': 'radius',\n 'border-top-left-radius': 'radius',\n 'border-top-right-radius': 'radius',\n 'border-bottom-left-radius': 'radius',\n 'border-bottom-right-radius': 'radius',\n\n // Layout\n 'display': 'layout',\n 'flex-direction': 'layout',\n 'justify-content': 'layout',\n 'align-items': 'layout',\n 'grid-template-columns': 'layout',\n 'grid-template-rows': 'layout',\n 'position': 'layout',\n};\n\n/**\n * Determine the category for a CSS property\n */\nfunction getCategory(property: string): TokenCategory | null {\n // Exact match\n if (CATEGORY_MAP[property]) {\n return CATEGORY_MAP[property];\n }\n\n // CSS custom properties (variables)\n if (property.startsWith('--')) {\n // Try to infer category from variable name\n const lower = property.toLowerCase();\n if (lower.includes('color') || lower.includes('bg') || lower.includes('text')) return 'color';\n if (lower.includes('font') || lower.includes('size') || lower.includes('weight')) return 'font';\n if (lower.includes('spacing') || lower.includes('margin') || lower.includes('padding') || lower.includes('gap')) return 'spacing';\n if (lower.includes('shadow')) return 'shadow';\n if (lower.includes('radius') || lower.includes('rounded')) return 'radius';\n return 'other';\n }\n\n return null;\n}\n\n/**\n * Parse CSS content and extract design tokens\n */\nexport function parseCss(\n cssContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n\n try {\n const ast = csstree.parse(cssContent, {\n filename: filePath,\n positions: true,\n });\n\n csstree.walk(ast, {\n visit: 'Declaration',\n enter(node) {\n const property = node.property;\n const category = getCategory(property);\n\n if (!category) return;\n\n const value = csstree.generate(node.value);\n\n // Skip empty, inherit, initial, unset\n if (!value || ['inherit', 'initial', 'unset', 'revert'].includes(value)) return;\n\n // Find the parent selector\n let selector = ':root';\n let parent = this.atrule ?? this.rule;\n if (parent && parent.type === 'Rule' && parent.prelude) {\n selector = csstree.generate(parent.prelude);\n }\n\n tokens.push({\n category,\n property,\n value: value.trim(),\n selector,\n file: filePath,\n line: node.loc?.start?.line,\n });\n },\n });\n } catch (error) {\n // If parsing fails, return what we have so far\n console.warn(`Warning: Failed to parse CSS in ${filePath}: ${(error as Error).message}`);\n }\n\n return tokens;\n}\n\n/**\n * Extract CSS custom properties (variables) specifically\n */\nexport function extractCssVariables(\n cssContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n // Match --variable: value patterns\n const varRegex = /--([\\w-]+)\\s*:\\s*([^;]+)/g;\n let match;\n\n while ((match = varRegex.exec(cssContent)) !== null) {\n const property = `--${match[1]}`;\n const value = match[2].trim();\n const category = getCategory(property) ?? 'other';\n\n tokens.push({\n category,\n property,\n value,\n selector: ':root',\n file: filePath,\n line: cssContent.substring(0, match.index).split('\\n').length,\n });\n }\n\n return tokens;\n}\n","import * as cheerio from 'cheerio';\nimport type { DesignToken, TokenCategory } from '../types/index.js';\n\n/**\n * Style properties we want to extract from inline styles\n */\nconst TRACKED_PROPERTIES: Record<string, TokenCategory> = {\n 'color': 'color',\n 'background-color': 'color',\n 'background': 'color',\n 'border-color': 'color',\n 'font-family': 'font',\n 'font-size': 'font',\n 'font-weight': 'font',\n 'line-height': 'font',\n 'margin': 'spacing',\n 'margin-top': 'spacing',\n 'margin-right': 'spacing',\n 'margin-bottom': 'spacing',\n 'margin-left': 'spacing',\n 'padding': 'spacing',\n 'padding-top': 'spacing',\n 'padding-right': 'spacing',\n 'padding-bottom': 'spacing',\n 'padding-left': 'spacing',\n 'gap': 'spacing',\n 'box-shadow': 'shadow',\n 'text-shadow': 'shadow',\n 'border-radius': 'radius',\n 'display': 'layout',\n 'flex-direction': 'layout',\n 'justify-content': 'layout',\n 'align-items': 'layout',\n};\n\n/**\n * Parse inline styles from a style attribute string\n */\nfunction parseInlineStyle(styleStr: string): Record<string, string> {\n const result: Record<string, string> = {};\n const declarations = styleStr.split(';').filter(Boolean);\n\n for (const decl of declarations) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n\n const prop = decl.substring(0, colonIdx).trim().toLowerCase();\n const val = decl.substring(colonIdx + 1).trim();\n\n if (prop && val) {\n result[prop] = val;\n }\n }\n\n return result;\n}\n\n/**\n * Parse HTML content and extract design tokens from:\n * 1. <style> blocks\n * 2. Inline style attributes\n */\nexport function parseHtml(\n htmlContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n const $ = cheerio.load(htmlContent);\n\n // 1. Extract from <style> blocks (will be handled by CSS parser upstream)\n // We return them separately as raw CSS strings\n const styleBlocks: string[] = [];\n $('style').each((_, el) => {\n const text = $(el).text();\n if (text) {\n styleBlocks.push(text);\n }\n });\n\n // 2. Extract from inline style attributes\n $('[style]').each((_, el) => {\n const element = $(el);\n const styleStr = element.attr('style');\n if (!styleStr) return;\n\n const selector = buildSelectorPath($, element);\n const styles = parseInlineStyle(styleStr);\n\n for (const [prop, value] of Object.entries(styles)) {\n const category = TRACKED_PROPERTIES[prop];\n if (!category) continue;\n\n tokens.push({\n category,\n property: prop,\n value,\n selector: `[inline] ${selector}`,\n file: filePath,\n });\n }\n });\n\n return tokens;\n}\n\n/**\n * Extract raw CSS from <style> blocks in HTML\n */\nexport function extractStyleBlocks(htmlContent: string): string[] {\n const $ = cheerio.load(htmlContent);\n const blocks: string[] = [];\n\n $('style').each((_, el) => {\n const text = $(el).text();\n if (text) {\n blocks.push(text);\n }\n });\n\n return blocks;\n}\n\n/**\n * Build a human-readable selector path for an element\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildSelectorPath($: cheerio.CheerioAPI, element: any): string {\n const parts: string[] = [];\n const tagName = element.prop('tagName')?.toLowerCase() ?? 'div';\n const id = element.attr('id');\n const className = element.attr('class');\n\n let sel = tagName;\n if (id) {\n sel += `#${id}`;\n } else if (className) {\n const classList = className.split(/\\s+/).slice(0, 2).join('.');\n sel += `.${classList}`;\n }\n\n parts.push(sel);\n return parts.join(' > ');\n}\n","import type {\n DesignSnapshot,\n DesignToken,\n DriftItem,\n DriftReport,\n TokenCategory,\n} from '../types/index.js';\nimport { scanProject, loadConfig } from './snapshot.js';\n\n/**\n * Compare two tokens by their key (file + selector + property)\n */\nfunction tokenKey(token: DesignToken): string {\n return `${token.file}::${token.selector}::${token.property}`;\n}\n\n/**\n * Detect design drift between a snapshot and the current project state\n */\nexport async function detectDrift(\n projectRoot: string,\n snapshot: DesignSnapshot,\n threshold: number = 10,\n): Promise<DriftReport> {\n const config = loadConfig(projectRoot);\n const { tokens: currentTokens } = await scanProject(projectRoot, config);\n\n // Build lookup maps\n const snapshotMap = new Map<string, DesignToken>();\n for (const token of snapshot.tokens) {\n snapshotMap.set(tokenKey(token), token);\n }\n\n const currentMap = new Map<string, DesignToken>();\n for (const token of currentTokens) {\n currentMap.set(tokenKey(token), token);\n }\n\n const driftItems: DriftItem[] = [];\n\n // Check for modified and deleted tokens\n for (const [key, original] of snapshotMap) {\n const current = currentMap.get(key);\n\n if (!current) {\n // Token was deleted\n driftItems.push({\n original,\n current: null,\n changeType: 'deleted',\n });\n } else if (current.value !== original.value) {\n // Token was modified\n driftItems.push({\n original,\n current,\n changeType: 'modified',\n });\n }\n }\n\n // Check for added tokens (in current but not in snapshot)\n // Note: Added tokens are tracked but don't count as \"drift\" by default\n // because adding new styles is expected behavior during feature development\n\n const totalTokens = snapshot.tokens.length;\n const changedTokens = driftItems.length;\n const driftScore = totalTokens > 0\n ? Math.round((changedTokens / totalTokens) * 100 * 100) / 100\n : 0;\n\n // Build category summary\n const categorySummary: DriftReport['categorySummary'] = {} as DriftReport['categorySummary'];\n const categories: TokenCategory[] = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout', 'other'];\n\n for (const cat of categories) {\n const total = snapshot.tokens.filter(t => t.category === cat).length;\n const changed = driftItems.filter(d => d.original.category === cat).length;\n categorySummary[cat] = {\n total,\n changed,\n driftPercent: total > 0 ? Math.round((changed / total) * 100 * 100) / 100 : 0,\n };\n }\n\n return {\n checkedAt: new Date().toISOString(),\n snapshotCreatedAt: snapshot.createdAt,\n totalTokens,\n changedTokens,\n driftScore,\n threshold,\n passed: driftScore <= threshold,\n items: driftItems,\n categorySummary,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { DesignSnapshot, RuleFormat } from '../types/index.js';\n\n/**\n * Generate AI agent rules from a design snapshot\n */\nexport function generateRules(\n snapshot: DesignSnapshot,\n format: RuleFormat,\n): string {\n const generators: Record<RuleFormat, () => string> = {\n 'cursorrules': () => generateCursorRules(snapshot),\n 'claude-md': () => generateClaudeMd(snapshot),\n 'agents-md': () => generateAgentsMd(snapshot),\n 'copilot': () => generateCopilotInstructions(snapshot),\n 'clinerules': () => generateClineRules(snapshot),\n };\n\n return generators[format]();\n}\n\n/**\n * Save rules to the appropriate file\n */\nexport function saveRules(\n projectRoot: string,\n format: RuleFormat,\n content: string,\n append: boolean = false,\n): string {\n const fileMap: Record<RuleFormat, string> = {\n 'cursorrules': '.cursorrules',\n 'claude-md': 'CLAUDE.md',\n 'agents-md': 'AGENTS.md',\n 'copilot': '.github/copilot-instructions.md',\n 'clinerules': '.clinerules',\n };\n\n const filePath = path.join(projectRoot, fileMap[format]);\n const dir = path.dirname(filePath);\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n if (append && fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, 'utf-8');\n fs.writeFileSync(filePath, existing + '\\n\\n' + content, 'utf-8');\n } else {\n fs.writeFileSync(filePath, content, 'utf-8');\n }\n\n return filePath;\n}\n\n// ─── Template generators ───────────────────────────────────\n\nfunction buildTokenList(snapshot: DesignSnapshot): string {\n const colorTokens = snapshot.tokens.filter(t => t.category === 'color');\n const fontTokens = snapshot.tokens.filter(t => t.category === 'font');\n const spacingTokens = snapshot.tokens.filter(t => t.category === 'spacing');\n const radiusTokens = snapshot.tokens.filter(t => t.category === 'radius');\n\n const lines: string[] = [];\n\n if (colorTokens.length > 0) {\n lines.push('### Colors (DO NOT CHANGE)');\n const unique = [...new Set(colorTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 30)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (fontTokens.length > 0) {\n lines.push('\\n### Fonts (DO NOT CHANGE)');\n const unique = [...new Set(fontTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 15)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (spacingTokens.length > 0) {\n lines.push('\\n### Spacing (DO NOT CHANGE)');\n const unique = [...new Set(spacingTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 20)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (radiusTokens.length > 0) {\n lines.push('\\n### Border Radius (DO NOT CHANGE)');\n const unique = [...new Set(radiusTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 10)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction generateCursorRules(snapshot: DesignSnapshot): string {\n return `# Design Guard — Locked Design Tokens\n# Generated by drift-guard on ${snapshot.createdAt}\n# DO NOT modify these values when adding features or fixing bugs.\n\n## CRITICAL: Design Drift Prevention\n\nWhen modifying this codebase, you MUST preserve the following design tokens exactly as they are.\nDo NOT change colors, fonts, spacing, or border-radius values unless explicitly asked to update the design.\n\nIf you need to add new styles, use the existing design tokens as reference.\nIf a design change is needed, tell the user to run \\`drift-guard snapshot update\\` after making the change.\n\n${buildTokenList(snapshot)}\n\n## Rules\n1. NEVER change existing CSS color values\n2. NEVER change font-family, font-size, or font-weight values\n3. NEVER change margin, padding, or gap values on existing elements\n4. NEVER change border-radius values\n5. When adding new components, use the SAME design tokens listed above\n6. If you must change a design token, warn the user first\n`;\n}\n\nfunction generateClaudeMd(snapshot: DesignSnapshot): string {\n return `# Design Guard — Locked Design Tokens\n\n> Generated by drift-guard on ${snapshot.createdAt}\n> Run \\`npx drift-guard check\\` to verify design consistency\n\n## Design Drift Prevention Rules\n\nWhen working on this codebase, preserve the existing design exactly as-is.\nThe following design tokens are LOCKED and should NOT be modified:\n\n${buildTokenList(snapshot)}\n\n## Important\n- Adding new features should NOT change existing visual styles\n- If a CSS change affects existing components, flag it to the user\n- New components should follow the same design language (colors, fonts, spacing)\n- Run \\`npx drift-guard check\\` before committing to verify no design drift\n`;\n}\n\nfunction generateAgentsMd(snapshot: DesignSnapshot): string {\n return `# AGENTS.md — Design Guard Rules\n\n> Auto-generated by drift-guard. Do not edit manually.\n> Snapshot: ${snapshot.createdAt} | Tokens: ${snapshot.tokens.length}\n\n## Design Token Protection\n\nThis project uses drift-guard to prevent design drift during AI-assisted development.\nThe following design tokens are protected:\n\n${buildTokenList(snapshot)}\n\n## Agent Instructions\n1. Do NOT modify existing CSS values for colors, fonts, spacing, or border-radius\n2. New components MUST use the same design tokens\n3. Run \\`npx drift-guard check\\` before suggesting commits\n4. If design changes are intentional, inform the user to update the snapshot\n`;\n}\n\nfunction generateCopilotInstructions(snapshot: DesignSnapshot): string {\n return `# Copilot Instructions — Design Guard\n\nThis project has locked design tokens. When suggesting code changes:\n\n1. Preserve all existing CSS color, font, spacing, and border-radius values\n2. Use the project's design tokens for new components\n3. Do not override CSS custom properties (variables starting with --)\n\nProtected tokens (${snapshot.tokens.length} total):\n\n${buildTokenList(snapshot)}\n`;\n}\n\nfunction generateClineRules(snapshot: DesignSnapshot): string {\n return `# Design Guard — Protected Design Tokens\n# Generated: ${snapshot.createdAt}\n\nDESIGN_PROTECTION=true\n\n## Rules\n- Do NOT change existing CSS values\n- Preserve colors: ${snapshot.summary.color} tokens locked\n- Preserve fonts: ${snapshot.summary.font} tokens locked\n- Preserve spacing: ${snapshot.summary.spacing} tokens locked\n- Preserve radius: ${snapshot.summary.radius} tokens locked\n\n## Verification\nRun: npx drift-guard check\nThreshold: Changes to more than 10% of design tokens will be flagged\n\n${buildTokenList(snapshot)}\n`;\n}\n"],"mappings":"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","names":["fs","path"]}
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CONFIG,
4
+ createSnapshot,
5
+ detectDrift,
6
+ generateRules,
7
+ loadConfig,
8
+ loadSnapshot,
9
+ saveConfig,
10
+ saveRules,
11
+ saveSnapshot
12
+ } from "../chunk-27T45SVD.js";
13
+
14
+ // src/cli/index.ts
15
+ import { Command } from "commander";
16
+ import chalk5 from "chalk";
17
+
18
+ // src/cli/init.ts
19
+ import chalk from "chalk";
20
+ async function initCommand(options) {
21
+ const projectRoot = process.cwd();
22
+ const threshold = parseInt(options.threshold ?? "10", 10);
23
+ console.log(chalk.bold("\n\u{1F6E1}\uFE0F drift-guard init\n"));
24
+ console.log(chalk.dim("Scanning project for design tokens...\n"));
25
+ const config = { ...DEFAULT_CONFIG, threshold };
26
+ saveConfig(projectRoot, config);
27
+ const snapshot = await createSnapshot(projectRoot, options.from);
28
+ if (snapshot.tokens.length === 0) {
29
+ console.log(chalk.yellow("\u26A0\uFE0F No design tokens found."));
30
+ console.log(chalk.dim(" Make sure you have CSS files or use --from <stitch.html>"));
31
+ console.log(chalk.dim(" Supported patterns: src/**/*.css, app/**/*.css, styles/**/*.css\n"));
32
+ return;
33
+ }
34
+ const snapshotPath = saveSnapshot(projectRoot, snapshot);
35
+ console.log(chalk.green("\u2705 Design snapshot created!\n"));
36
+ console.log(chalk.dim(" Snapshot: ") + chalk.white(snapshotPath));
37
+ console.log(chalk.dim(" Files scanned: ") + chalk.white(snapshot.sourceFiles.length.toString()));
38
+ console.log(chalk.dim(" Tokens locked: ") + chalk.white(snapshot.tokens.length.toString()));
39
+ console.log(chalk.dim(" Threshold: ") + chalk.white(`${threshold}%`));
40
+ console.log();
41
+ console.log(chalk.bold(" Token Summary:"));
42
+ const categories = ["color", "font", "spacing", "shadow", "radius", "layout"];
43
+ for (const cat of categories) {
44
+ const count = snapshot.summary[cat];
45
+ if (count > 0) {
46
+ const icon = { color: "\u{1F3A8}", font: "\u{1F4DD}", spacing: "\u{1F4CF}", shadow: "\u{1F32B}\uFE0F", radius: "\u2B55", layout: "\u{1F4D0}" }[cat];
47
+ console.log(chalk.dim(` ${icon} ${cat}: `) + chalk.white(count.toString()));
48
+ }
49
+ }
50
+ console.log();
51
+ console.log(chalk.dim("Next steps:"));
52
+ console.log(chalk.cyan(" 1. ") + "Add .design-guard/ to .gitignore (optional)");
53
+ console.log(chalk.cyan(" 2. ") + chalk.bold("npx drift-guard rules") + " \u2014 Generate AI agent protection rules");
54
+ console.log(chalk.cyan(" 3. ") + chalk.bold("npx drift-guard check") + " \u2014 Check for design drift anytime");
55
+ console.log();
56
+ }
57
+
58
+ // src/cli/check.ts
59
+ import chalk2 from "chalk";
60
+ async function checkCommand(options) {
61
+ const projectRoot = process.cwd();
62
+ const snapshot = loadSnapshot(projectRoot);
63
+ if (!snapshot) {
64
+ console.log(chalk2.red("\n\u274C No snapshot found."));
65
+ console.log(chalk2.dim(" Run ") + chalk2.cyan("npx drift-guard init") + chalk2.dim(" first.\n"));
66
+ process.exit(1);
67
+ }
68
+ const config = loadConfig(projectRoot);
69
+ const threshold = options.threshold ? parseInt(options.threshold, 10) : config.threshold;
70
+ console.log(chalk2.bold("\n\u{1F6E1}\uFE0F drift-guard check\n"));
71
+ console.log(chalk2.dim(`Comparing against snapshot from ${snapshot.createdAt}...
72
+ `));
73
+ const report = await detectDrift(projectRoot, snapshot, threshold);
74
+ if (options.output === "json") {
75
+ console.log(JSON.stringify(report, null, 2));
76
+ } else {
77
+ printTextReport(report);
78
+ }
79
+ if (!report.passed && (options.ci || process.env["CI"])) {
80
+ process.exit(1);
81
+ }
82
+ if (!report.passed) {
83
+ process.exitCode = 1;
84
+ }
85
+ }
86
+ function printTextReport(report) {
87
+ const scoreColor = report.passed ? chalk2.green : chalk2.red;
88
+ const icon = report.passed ? "\u2705" : "\u{1F6A8}";
89
+ console.log(`${icon} ${chalk2.bold("Drift Score:")} ${scoreColor(`${report.driftScore}%`)} (threshold: ${report.threshold}%)`);
90
+ console.log(chalk2.dim(` ${report.changedTokens} of ${report.totalTokens} tokens changed
91
+ `));
92
+ if (report.items.length === 0) {
93
+ console.log(chalk2.green(" No design drift detected. Your design is intact! \u{1F389}\n"));
94
+ return;
95
+ }
96
+ console.log(chalk2.bold(" Category Breakdown:"));
97
+ const categories = ["color", "font", "spacing", "shadow", "radius", "layout"];
98
+ for (const cat of categories) {
99
+ const summary = report.categorySummary[cat];
100
+ if (summary.total === 0) continue;
101
+ const catIcon = { color: "\u{1F3A8}", font: "\u{1F4DD}", spacing: "\u{1F4CF}", shadow: "\u{1F32B}\uFE0F", radius: "\u2B55", layout: "\u{1F4D0}" }[cat];
102
+ const catColor = summary.changed > 0 ? chalk2.red : chalk2.green;
103
+ console.log(` ${catIcon} ${cat}: ${catColor(`${summary.changed}/${summary.total}`)} (${summary.driftPercent}%)`);
104
+ }
105
+ console.log();
106
+ const itemsToShow = report.items.slice(0, 20);
107
+ if (itemsToShow.length > 0) {
108
+ console.log(chalk2.bold(" Changes:"));
109
+ for (const item of itemsToShow) {
110
+ printDriftItem(item);
111
+ }
112
+ if (report.items.length > 20) {
113
+ console.log(chalk2.dim(` ... and ${report.items.length - 20} more changes
114
+ `));
115
+ }
116
+ }
117
+ console.log();
118
+ if (!report.passed) {
119
+ console.log(chalk2.yellow(" \u{1F4A1} To accept these changes, run:"));
120
+ console.log(chalk2.cyan(" npx drift-guard snapshot update\n"));
121
+ }
122
+ }
123
+ function printDriftItem(item) {
124
+ const { original, current, changeType } = item;
125
+ switch (changeType) {
126
+ case "modified":
127
+ console.log(
128
+ chalk2.yellow(" ~") + chalk2.dim(` ${original.file} `) + chalk2.white(`${original.property}: `) + chalk2.red(original.value) + chalk2.dim(" \u2192 ") + chalk2.green(current?.value ?? "removed")
129
+ );
130
+ break;
131
+ case "deleted":
132
+ console.log(
133
+ chalk2.red(" -") + chalk2.dim(` ${original.file} `) + chalk2.white(`${original.property}: `) + chalk2.red(original.value) + chalk2.dim(" [deleted]")
134
+ );
135
+ break;
136
+ case "added":
137
+ console.log(
138
+ chalk2.green(" +") + chalk2.dim(` ${original.file} `) + chalk2.white(`${original.property}: `) + chalk2.green(original.value)
139
+ );
140
+ break;
141
+ }
142
+ }
143
+
144
+ // src/cli/rules.ts
145
+ import chalk3 from "chalk";
146
+ var ALL_FORMATS = ["cursorrules", "claude-md", "agents-md", "copilot", "clinerules"];
147
+ async function rulesCommand(options) {
148
+ const projectRoot = process.cwd();
149
+ const snapshot = loadSnapshot(projectRoot);
150
+ if (!snapshot) {
151
+ console.log(chalk3.red("\n\u274C No snapshot found."));
152
+ console.log(chalk3.dim(" Run ") + chalk3.cyan("npx drift-guard init") + chalk3.dim(" first.\n"));
153
+ process.exit(1);
154
+ }
155
+ const formats = options.format === "all" || !options.format ? ALL_FORMATS : [options.format];
156
+ console.log(chalk3.bold("\n\u{1F6E1}\uFE0F drift-guard rules\n"));
157
+ console.log(chalk3.dim(`Generating AI protection rules from ${snapshot.tokens.length} locked tokens...
158
+ `));
159
+ for (const format of formats) {
160
+ try {
161
+ const content = generateRules(snapshot, format);
162
+ const filePath = saveRules(projectRoot, format, content, options.append ?? false);
163
+ console.log(chalk3.green(" \u2705 ") + chalk3.white(filePath));
164
+ } catch (error) {
165
+ console.log(chalk3.red(" \u274C ") + chalk3.white(format) + chalk3.dim(`: ${error.message}`));
166
+ }
167
+ }
168
+ console.log();
169
+ console.log(chalk3.dim("Your AI coding agents will now protect these design tokens."));
170
+ console.log(chalk3.dim("Supported tools: Cursor, Claude Code, Codex, GitHub Copilot, Cline\n"));
171
+ }
172
+
173
+ // src/cli/snapshot-cmd.ts
174
+ import chalk4 from "chalk";
175
+ async function snapshotCommand(options) {
176
+ const projectRoot = process.cwd();
177
+ console.log(chalk4.bold("\n\u{1F6E1}\uFE0F drift-guard snapshot update\n"));
178
+ console.log(chalk4.dim("Re-scanning project and updating snapshot...\n"));
179
+ const snapshot = await createSnapshot(projectRoot, options.from);
180
+ if (snapshot.tokens.length === 0) {
181
+ console.log(chalk4.yellow("\u26A0\uFE0F No design tokens found.\n"));
182
+ return;
183
+ }
184
+ const snapshotPath = saveSnapshot(projectRoot, snapshot);
185
+ console.log(chalk4.green("\u2705 Snapshot updated!\n"));
186
+ console.log(chalk4.dim(" File: ") + chalk4.white(snapshotPath));
187
+ console.log(chalk4.dim(" Tokens: ") + chalk4.white(snapshot.tokens.length.toString()));
188
+ console.log(chalk4.dim(" Updated: ") + chalk4.white(snapshot.createdAt));
189
+ console.log();
190
+ console.log(chalk4.dim("\u{1F4A1} Remember to regenerate rules: ") + chalk4.cyan("npx drift-guard rules\n"));
191
+ }
192
+
193
+ // src/cli/index.ts
194
+ var program = new Command();
195
+ program.name("drift-guard").description(
196
+ chalk5.bold("\u{1F6E1}\uFE0F drift-guard") + " \u2014 Protect your UI from AI coding agents' design drift.\n\n Detect and prevent design token changes during AI-assisted development.\n Lock your colors, fonts, spacing, and layout before AI agents touch your code."
197
+ ).version("0.1.0");
198
+ program.command("init").description("Initialize drift-guard and create a design snapshot").option("--from <path>", "Create snapshot from a Stitch/HTML file").option("--threshold <number>", "Set default drift threshold percentage", "10").action(initCommand);
199
+ program.command("check").description("Check for design drift against the saved snapshot").option("--threshold <number>", "Override drift threshold percentage").option("--output <format>", "Output format: text or json", "text").option("--ci", "CI mode: exit with code 1 on drift exceeding threshold").action(checkCommand);
200
+ program.command("rules").description("Generate AI agent rule files from the design snapshot").option("--format <type>", "Rule format: cursorrules, claude-md, agents-md, copilot, clinerules, all", "all").option("--append", "Append to existing rule files instead of overwriting").action(rulesCommand);
201
+ program.command("snapshot").description("Manage design snapshots").command("update").description("Update the snapshot to reflect current design (after intentional changes)").option("--from <path>", "Update from a specific Stitch/HTML file").action(snapshotCommand);
202
+ program.parse();
203
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/index.ts","../../src/cli/init.ts","../../src/cli/check.ts","../../src/cli/rules.ts","../../src/cli/snapshot-cmd.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { initCommand } from './init.js';\nimport { checkCommand } from './check.js';\nimport { rulesCommand } from './rules.js';\nimport { snapshotCommand } from './snapshot-cmd.js';\n\nconst program = new Command();\n\nprogram\n .name('drift-guard')\n .description(\n chalk.bold('🛡️ drift-guard') +\n ' — Protect your UI from AI coding agents\\' design drift.\\n\\n' +\n ' Detect and prevent design token changes during AI-assisted development.\\n' +\n ' Lock your colors, fonts, spacing, and layout before AI agents touch your code.'\n )\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Initialize drift-guard and create a design snapshot')\n .option('--from <path>', 'Create snapshot from a Stitch/HTML file')\n .option('--threshold <number>', 'Set default drift threshold percentage', '10')\n .action(initCommand);\n\nprogram\n .command('check')\n .description('Check for design drift against the saved snapshot')\n .option('--threshold <number>', 'Override drift threshold percentage')\n .option('--output <format>', 'Output format: text or json', 'text')\n .option('--ci', 'CI mode: exit with code 1 on drift exceeding threshold')\n .action(checkCommand);\n\nprogram\n .command('rules')\n .description('Generate AI agent rule files from the design snapshot')\n .option('--format <type>', 'Rule format: cursorrules, claude-md, agents-md, copilot, clinerules, all', 'all')\n .option('--append', 'Append to existing rule files instead of overwriting')\n .action(rulesCommand);\n\nprogram\n .command('snapshot')\n .description('Manage design snapshots')\n .command('update')\n .description('Update the snapshot to reflect current design (after intentional changes)')\n .option('--from <path>', 'Update from a specific Stitch/HTML file')\n .action(snapshotCommand);\n\nprogram.parse();\n","import chalk from 'chalk';\nimport { createSnapshot, saveSnapshot, saveConfig } from '../core/snapshot.js';\nimport { DEFAULT_CONFIG } from '../types/index.js';\n\ninterface InitOptions {\n from?: string;\n threshold?: string;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n const projectRoot = process.cwd();\n const threshold = parseInt(options.threshold ?? '10', 10);\n\n console.log(chalk.bold('\\n🛡️ drift-guard init\\n'));\n console.log(chalk.dim('Scanning project for design tokens...\\n'));\n\n // Save config\n const config = { ...DEFAULT_CONFIG, threshold };\n saveConfig(projectRoot, config);\n\n // Create snapshot\n const snapshot = await createSnapshot(projectRoot, options.from);\n\n if (snapshot.tokens.length === 0) {\n console.log(chalk.yellow('⚠️ No design tokens found.'));\n console.log(chalk.dim(' Make sure you have CSS files or use --from <stitch.html>'));\n console.log(chalk.dim(' Supported patterns: src/**/*.css, app/**/*.css, styles/**/*.css\\n'));\n return;\n }\n\n // Save snapshot\n const snapshotPath = saveSnapshot(projectRoot, snapshot);\n\n // Report\n console.log(chalk.green('✅ Design snapshot created!\\n'));\n console.log(chalk.dim(' Snapshot: ') + chalk.white(snapshotPath));\n console.log(chalk.dim(' Files scanned: ') + chalk.white(snapshot.sourceFiles.length.toString()));\n console.log(chalk.dim(' Tokens locked: ') + chalk.white(snapshot.tokens.length.toString()));\n console.log(chalk.dim(' Threshold: ') + chalk.white(`${threshold}%`));\n console.log();\n\n // Token summary\n console.log(chalk.bold(' Token Summary:'));\n const categories = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'] as const;\n for (const cat of categories) {\n const count = snapshot.summary[cat];\n if (count > 0) {\n const icon = { color: '🎨', font: '📝', spacing: '📏', shadow: '🌫️', radius: '⭕', layout: '📐' }[cat];\n console.log(chalk.dim(` ${icon} ${cat}: `) + chalk.white(count.toString()));\n }\n }\n\n console.log();\n console.log(chalk.dim('Next steps:'));\n console.log(chalk.cyan(' 1. ') + 'Add .design-guard/ to .gitignore (optional)');\n console.log(chalk.cyan(' 2. ') + chalk.bold('npx drift-guard rules') + ' — Generate AI agent protection rules');\n console.log(chalk.cyan(' 3. ') + chalk.bold('npx drift-guard check') + ' — Check for design drift anytime');\n console.log();\n}\n","import chalk from 'chalk';\nimport { loadSnapshot, loadConfig } from '../core/snapshot.js';\nimport { detectDrift } from '../core/drift.js';\nimport type { DriftReport, DriftItem } from '../types/index.js';\n\ninterface CheckOptions {\n threshold?: string;\n output?: string;\n ci?: boolean;\n}\n\nexport async function checkCommand(options: CheckOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n // Load snapshot\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.log(chalk.red('\\n❌ No snapshot found.'));\n console.log(chalk.dim(' Run ') + chalk.cyan('npx drift-guard init') + chalk.dim(' first.\\n'));\n process.exit(1);\n }\n\n const config = loadConfig(projectRoot);\n const threshold = options.threshold\n ? parseInt(options.threshold, 10)\n : config.threshold;\n\n console.log(chalk.bold('\\n🛡️ drift-guard check\\n'));\n console.log(chalk.dim(`Comparing against snapshot from ${snapshot.createdAt}...\\n`));\n\n // Detect drift\n const report = await detectDrift(projectRoot, snapshot, threshold);\n\n if (options.output === 'json') {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printTextReport(report);\n }\n\n // Exit code for CI\n if (!report.passed && (options.ci || process.env['CI'])) {\n process.exit(1);\n }\n\n if (!report.passed) {\n process.exitCode = 1;\n }\n}\n\nfunction printTextReport(report: DriftReport): void {\n // Score display\n const scoreColor = report.passed ? chalk.green : chalk.red;\n const icon = report.passed ? '✅' : '🚨';\n\n console.log(`${icon} ${chalk.bold('Drift Score:')} ${scoreColor(`${report.driftScore}%`)} (threshold: ${report.threshold}%)`);\n console.log(chalk.dim(` ${report.changedTokens} of ${report.totalTokens} tokens changed\\n`));\n\n if (report.items.length === 0) {\n console.log(chalk.green(' No design drift detected. Your design is intact! 🎉\\n'));\n return;\n }\n\n // Category breakdown\n console.log(chalk.bold(' Category Breakdown:'));\n const categories = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'] as const;\n for (const cat of categories) {\n const summary = report.categorySummary[cat];\n if (summary.total === 0) continue;\n\n const catIcon = { color: '🎨', font: '📝', spacing: '📏', shadow: '🌫️', radius: '⭕', layout: '📐' }[cat];\n const catColor = summary.changed > 0 ? chalk.red : chalk.green;\n console.log(` ${catIcon} ${cat}: ${catColor(`${summary.changed}/${summary.total}`)} (${summary.driftPercent}%)`);\n }\n console.log();\n\n // Detailed changes (max 20)\n const itemsToShow = report.items.slice(0, 20);\n if (itemsToShow.length > 0) {\n console.log(chalk.bold(' Changes:'));\n for (const item of itemsToShow) {\n printDriftItem(item);\n }\n\n if (report.items.length > 20) {\n console.log(chalk.dim(` ... and ${report.items.length - 20} more changes\\n`));\n }\n }\n\n console.log();\n\n if (!report.passed) {\n console.log(chalk.yellow(' 💡 To accept these changes, run:'));\n console.log(chalk.cyan(' npx drift-guard snapshot update\\n'));\n }\n}\n\nfunction printDriftItem(item: DriftItem): void {\n const { original, current, changeType } = item;\n\n switch (changeType) {\n case 'modified':\n console.log(\n chalk.yellow(' ~') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.red(original.value) +\n chalk.dim(' → ') +\n chalk.green(current?.value ?? 'removed')\n );\n break;\n case 'deleted':\n console.log(\n chalk.red(' -') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.red(original.value) +\n chalk.dim(' [deleted]')\n );\n break;\n case 'added':\n console.log(\n chalk.green(' +') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.green(original.value)\n );\n break;\n }\n}\n","import chalk from 'chalk';\nimport { loadSnapshot } from '../core/snapshot.js';\nimport { generateRules, saveRules } from '../core/rules-generator.js';\nimport type { RuleFormat } from '../types/index.js';\n\ninterface RulesOptions {\n format?: string;\n append?: boolean;\n}\n\nconst ALL_FORMATS: RuleFormat[] = ['cursorrules', 'claude-md', 'agents-md', 'copilot', 'clinerules'];\n\nexport async function rulesCommand(options: RulesOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.log(chalk.red('\\n❌ No snapshot found.'));\n console.log(chalk.dim(' Run ') + chalk.cyan('npx drift-guard init') + chalk.dim(' first.\\n'));\n process.exit(1);\n }\n\n const formats: RuleFormat[] = options.format === 'all' || !options.format\n ? ALL_FORMATS\n : [options.format as RuleFormat];\n\n console.log(chalk.bold('\\n🛡️ drift-guard rules\\n'));\n console.log(chalk.dim(`Generating AI protection rules from ${snapshot.tokens.length} locked tokens...\\n`));\n\n for (const format of formats) {\n try {\n const content = generateRules(snapshot, format);\n const filePath = saveRules(projectRoot, format, content, options.append ?? false);\n console.log(chalk.green(' ✅ ') + chalk.white(filePath));\n } catch (error) {\n console.log(chalk.red(' ❌ ') + chalk.white(format) + chalk.dim(`: ${(error as Error).message}`));\n }\n }\n\n console.log();\n console.log(chalk.dim('Your AI coding agents will now protect these design tokens.'));\n console.log(chalk.dim('Supported tools: Cursor, Claude Code, Codex, GitHub Copilot, Cline\\n'));\n}\n","import chalk from 'chalk';\nimport { createSnapshot, saveSnapshot } from '../core/snapshot.js';\n\ninterface SnapshotOptions {\n from?: string;\n}\n\nexport async function snapshotCommand(options: SnapshotOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n console.log(chalk.bold('\\n🛡️ drift-guard snapshot update\\n'));\n console.log(chalk.dim('Re-scanning project and updating snapshot...\\n'));\n\n const snapshot = await createSnapshot(projectRoot, options.from);\n\n if (snapshot.tokens.length === 0) {\n console.log(chalk.yellow('⚠️ No design tokens found.\\n'));\n return;\n }\n\n const snapshotPath = saveSnapshot(projectRoot, snapshot);\n\n console.log(chalk.green('✅ Snapshot updated!\\n'));\n console.log(chalk.dim(' File: ') + chalk.white(snapshotPath));\n console.log(chalk.dim(' Tokens: ') + chalk.white(snapshot.tokens.length.toString()));\n console.log(chalk.dim(' Updated: ') + chalk.white(snapshot.createdAt));\n console.log();\n console.log(chalk.dim('💡 Remember to regenerate rules: ') + chalk.cyan('npx drift-guard rules\\n'));\n}\n"],"mappings":"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","names":["chalk","chalk","chalk","chalk","chalk","chalk","chalk","chalk"]}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * A single design token extracted from CSS/HTML
3
+ */
4
+ interface DesignToken {
5
+ /** Token category: color, font, spacing, shadow, radius, layout */
6
+ category: TokenCategory;
7
+ /** CSS property name (e.g., 'color', 'font-family', 'padding') */
8
+ property: string;
9
+ /** Resolved value (e.g., '#1a73e8', '16px', 'Inter') */
10
+ value: string;
11
+ /** CSS selector where this token was found */
12
+ selector: string;
13
+ /** Source file path */
14
+ file: string;
15
+ /** Line number in source file */
16
+ line?: number;
17
+ }
18
+ type TokenCategory = 'color' | 'font' | 'spacing' | 'shadow' | 'radius' | 'layout' | 'other';
19
+ /**
20
+ * Design snapshot — frozen state of all design tokens
21
+ */
22
+ interface DesignSnapshot {
23
+ /** Snapshot version */
24
+ version: string;
25
+ /** ISO timestamp when snapshot was created */
26
+ createdAt: string;
27
+ /** Project root directory */
28
+ projectRoot: string;
29
+ /** Source files that were scanned */
30
+ sourceFiles: string[];
31
+ /** All extracted design tokens */
32
+ tokens: DesignToken[];
33
+ /** Token count by category */
34
+ summary: Record<TokenCategory, number>;
35
+ }
36
+ /**
37
+ * A single drift item — one token that changed
38
+ */
39
+ interface DriftItem {
40
+ /** The original token from the snapshot */
41
+ original: DesignToken;
42
+ /** The current token value (null if token was deleted) */
43
+ current: DesignToken | null;
44
+ /** Type of change */
45
+ changeType: 'modified' | 'deleted' | 'added';
46
+ }
47
+ /**
48
+ * Drift detection report
49
+ */
50
+ interface DriftReport {
51
+ /** ISO timestamp of the check */
52
+ checkedAt: string;
53
+ /** Snapshot used as baseline */
54
+ snapshotCreatedAt: string;
55
+ /** Total tokens in snapshot */
56
+ totalTokens: number;
57
+ /** Number of changed tokens */
58
+ changedTokens: number;
59
+ /** Drift score: (changed / total) * 100 */
60
+ driftScore: number;
61
+ /** Threshold used for pass/fail */
62
+ threshold: number;
63
+ /** Whether the check passed */
64
+ passed: boolean;
65
+ /** Individual drift items */
66
+ items: DriftItem[];
67
+ /** Summary by category */
68
+ categorySummary: Record<TokenCategory, {
69
+ total: number;
70
+ changed: number;
71
+ driftPercent: number;
72
+ }>;
73
+ }
74
+ /**
75
+ * Supported AI rule file formats
76
+ */
77
+ type RuleFormat = 'cursorrules' | 'claude-md' | 'agents-md' | 'copilot' | 'clinerules';
78
+ /**
79
+ * Configuration stored in .design-guard/config.json
80
+ */
81
+ interface DriftGuardConfig {
82
+ /** Glob patterns for CSS files to scan */
83
+ cssFiles: string[];
84
+ /** Glob patterns for HTML files to scan */
85
+ htmlFiles: string[];
86
+ /** Default drift threshold (percentage) */
87
+ threshold: number;
88
+ /** Token categories to track */
89
+ trackCategories: TokenCategory[];
90
+ /** Files/patterns to ignore */
91
+ ignore: string[];
92
+ }
93
+ /**
94
+ * Default configuration
95
+ */
96
+ declare const DEFAULT_CONFIG: DriftGuardConfig;
97
+
98
+ /**
99
+ * Scan the project and extract all design tokens
100
+ */
101
+ declare function scanProject(projectRoot: string, config: DriftGuardConfig, stitchHtmlPath?: string): Promise<{
102
+ tokens: DesignToken[];
103
+ files: string[];
104
+ }>;
105
+ /**
106
+ * Create a snapshot from the current project state
107
+ */
108
+ declare function createSnapshot(projectRoot: string, stitchHtmlPath?: string): Promise<DesignSnapshot>;
109
+ /**
110
+ * Save a snapshot to disk
111
+ */
112
+ declare function saveSnapshot(projectRoot: string, snapshot: DesignSnapshot): string;
113
+ /**
114
+ * Load an existing snapshot from disk
115
+ */
116
+ declare function loadSnapshot(projectRoot: string): DesignSnapshot | null;
117
+
118
+ /**
119
+ * Detect design drift between a snapshot and the current project state
120
+ */
121
+ declare function detectDrift(projectRoot: string, snapshot: DesignSnapshot, threshold?: number): Promise<DriftReport>;
122
+
123
+ /**
124
+ * Generate AI agent rules from a design snapshot
125
+ */
126
+ declare function generateRules(snapshot: DesignSnapshot, format: RuleFormat): string;
127
+ /**
128
+ * Save rules to the appropriate file
129
+ */
130
+ declare function saveRules(projectRoot: string, format: RuleFormat, content: string, append?: boolean): string;
131
+
132
+ export { DEFAULT_CONFIG, type DesignSnapshot, type DesignToken, type DriftGuardConfig, type DriftItem, type DriftReport, type RuleFormat, type TokenCategory, createSnapshot, detectDrift, generateRules, loadSnapshot, saveRules, saveSnapshot, scanProject };
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ import {
2
+ DEFAULT_CONFIG,
3
+ createSnapshot,
4
+ detectDrift,
5
+ generateRules,
6
+ loadSnapshot,
7
+ saveRules,
8
+ saveSnapshot,
9
+ scanProject
10
+ } from "./chunk-27T45SVD.js";
11
+ export {
12
+ DEFAULT_CONFIG,
13
+ createSnapshot,
14
+ detectDrift,
15
+ generateRules,
16
+ loadSnapshot,
17
+ saveRules,
18
+ saveSnapshot,
19
+ scanProject
20
+ };
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@stayicon/drift-guard",
3
+ "version": "0.1.0",
4
+ "description": "Protect your UI from AI coding agents' design drift. Detect, prevent, and lock design tokens during AI-assisted development.",
5
+ "keywords": [
6
+ "design-drift",
7
+ "ai-coding",
8
+ "css",
9
+ "design-tokens",
10
+ "design-system",
11
+ "visual-regression",
12
+ "cursor",
13
+ "claude-code",
14
+ "codex",
15
+ "copilot",
16
+ "pre-commit",
17
+ "vibe-coding",
18
+ "stitch",
19
+ "figma"
20
+ ],
21
+ "license": "MIT",
22
+ "author": "drift-guard contributors",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/Hwani-Net/drift-guard.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/Hwani-Net/drift-guard/issues"
29
+ },
30
+ "homepage": "https://github.com/Hwani-Net/drift-guard#readme",
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "bin": {
35
+ "drift-guard": "./bin/drift-guard.mjs"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "bin",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "dev": "tsup --watch",
46
+ "test": "vitest run",
47
+ "test:watch": "vitest",
48
+ "lint": "eslint src/",
49
+ "typecheck": "tsc --noEmit",
50
+ "prepublishOnly": "npm run build"
51
+ },
52
+ "dependencies": {
53
+ "chalk": "^5.4.1",
54
+ "cheerio": "^1.0.0",
55
+ "commander": "^13.1.0",
56
+ "css-tree": "^3.1.0",
57
+ "fast-glob": "^3.3.3"
58
+ },
59
+ "devDependencies": {
60
+ "@types/css-tree": "^2.3.11",
61
+ "@types/node": "^22.13.0",
62
+ "eslint": "^9.20.0",
63
+ "tsup": "^8.4.0",
64
+ "typescript": "^5.7.3",
65
+ "vitest": "^3.0.5"
66
+ },
67
+ "engines": {
68
+ "node": ">=18"
69
+ }
70
+ }