@stayicon/drift-guard 0.2.0 → 0.2.1
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.
|
@@ -577,12 +577,14 @@ async function createSnapshot(projectRoot, stitchHtmlPath) {
|
|
|
577
577
|
const config = loadConfig(projectRoot);
|
|
578
578
|
const { tokens, files } = await scanProject(projectRoot, config, stitchHtmlPath);
|
|
579
579
|
let structure;
|
|
580
|
+
let structureSourceFile;
|
|
580
581
|
try {
|
|
581
582
|
let htmlForStructure = null;
|
|
582
583
|
if (stitchHtmlPath) {
|
|
583
584
|
const absPath = path.resolve(projectRoot, stitchHtmlPath);
|
|
584
585
|
if (fs.existsSync(absPath)) {
|
|
585
586
|
htmlForStructure = fs.readFileSync(absPath, "utf-8");
|
|
587
|
+
structureSourceFile = stitchHtmlPath;
|
|
586
588
|
}
|
|
587
589
|
} else {
|
|
588
590
|
for (const file of files) {
|
|
@@ -590,6 +592,7 @@ async function createSnapshot(projectRoot, stitchHtmlPath) {
|
|
|
590
592
|
const absPath = path.join(projectRoot, file);
|
|
591
593
|
if (fs.existsSync(absPath)) {
|
|
592
594
|
htmlForStructure = fs.readFileSync(absPath, "utf-8");
|
|
595
|
+
structureSourceFile = file;
|
|
593
596
|
break;
|
|
594
597
|
}
|
|
595
598
|
}
|
|
@@ -607,7 +610,8 @@ async function createSnapshot(projectRoot, stitchHtmlPath) {
|
|
|
607
610
|
sourceFiles: files,
|
|
608
611
|
tokens,
|
|
609
612
|
summary: buildSummary(tokens),
|
|
610
|
-
structure
|
|
613
|
+
structure,
|
|
614
|
+
structureSourceFile
|
|
611
615
|
};
|
|
612
616
|
return snapshot;
|
|
613
617
|
}
|
|
@@ -678,21 +682,29 @@ async function detectDrift(projectRoot, snapshot, threshold = 10) {
|
|
|
678
682
|
let structureDrift;
|
|
679
683
|
if (snapshot.structure) {
|
|
680
684
|
try {
|
|
681
|
-
const config2 = loadConfig(projectRoot);
|
|
682
|
-
const fg2 = (await import("fast-glob")).default;
|
|
683
|
-
const htmlFiles = await fg2(config2.htmlFiles, {
|
|
684
|
-
cwd: projectRoot,
|
|
685
|
-
ignore: config2.ignore,
|
|
686
|
-
absolute: false
|
|
687
|
-
});
|
|
688
|
-
let htmlContent = null;
|
|
689
685
|
const fs3 = await import("fs");
|
|
690
686
|
const path3 = await import("path");
|
|
691
|
-
|
|
692
|
-
|
|
687
|
+
let htmlContent = null;
|
|
688
|
+
if (snapshot.structureSourceFile) {
|
|
689
|
+
const absPath = path3.join(projectRoot, snapshot.structureSourceFile);
|
|
693
690
|
if (fs3.existsSync(absPath)) {
|
|
694
691
|
htmlContent = fs3.readFileSync(absPath, "utf-8");
|
|
695
|
-
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (!htmlContent) {
|
|
695
|
+
const config2 = loadConfig(projectRoot);
|
|
696
|
+
const fg2 = (await import("fast-glob")).default;
|
|
697
|
+
const htmlFiles = await fg2(config2.htmlFiles, {
|
|
698
|
+
cwd: projectRoot,
|
|
699
|
+
ignore: config2.ignore,
|
|
700
|
+
absolute: false
|
|
701
|
+
});
|
|
702
|
+
for (const file of htmlFiles) {
|
|
703
|
+
const absPath = path3.join(projectRoot, file);
|
|
704
|
+
if (fs3.existsSync(absPath)) {
|
|
705
|
+
htmlContent = fs3.readFileSync(absPath, "utf-8");
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
696
708
|
}
|
|
697
709
|
}
|
|
698
710
|
if (htmlContent) {
|
|
@@ -713,7 +725,7 @@ async function detectDrift(projectRoot, snapshot, threshold = 10) {
|
|
|
713
725
|
changedTokens,
|
|
714
726
|
driftScore,
|
|
715
727
|
threshold,
|
|
716
|
-
passed: driftScore <= threshold,
|
|
728
|
+
passed: driftScore <= threshold && !structureDrift?.changed,
|
|
717
729
|
items: driftItems,
|
|
718
730
|
categorySummary,
|
|
719
731
|
structureDrift
|
|
@@ -1134,4 +1146,4 @@ export {
|
|
|
1134
1146
|
syncFromStitch,
|
|
1135
1147
|
applySyncChanges
|
|
1136
1148
|
};
|
|
1137
|
-
//# sourceMappingURL=chunk-
|
|
1149
|
+
//# sourceMappingURL=chunk-6TBPF6U2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/parsers/structure-parser.ts","../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","../src/core/sync.ts"],"sourcesContent":["import * as cheerio from 'cheerio';\nimport { createHash } from 'node:crypto';\nimport type { StructureFingerprint } from '../types/index.js';\n\n/**\n * Semantic HTML tags to track for structure fingerprinting\n */\nconst SEMANTIC_TAGS = [\n 'header', 'nav', 'main', 'section', 'article',\n 'aside', 'footer', 'form', 'table', 'dialog',\n];\n\n/**\n * Compute a short hash (first 8 chars of sha256)\n */\nfunction shortHash(input: string): string {\n return createHash('sha256').update(input).digest('hex').slice(0, 8);\n}\n\n/**\n * Compute the maximum DOM nesting depth via DFS\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction computeMaxDepth($: cheerio.CheerioAPI, el: cheerio.Cheerio<any>, depth: number): number {\n let maxDepth = depth;\n\n el.children().each((_, child) => {\n if ($(child).prop('nodeType') === 1) { // Element node\n const childDepth = computeMaxDepth($, $(child), depth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n });\n\n return maxDepth;\n}\n\n/**\n * Compute a DOM structure fingerprint from HTML content.\n *\n * Captures:\n * 1. Semantic tag counts (header, nav, main, section, etc.)\n * 2. Maximum nesting depth\n * 3. Layout element hash (elements with display:flex/grid)\n * 4. Body direct child tag sequence hash\n */\nexport function computeStructureFingerprint(htmlContent: string): StructureFingerprint {\n const $ = cheerio.load(htmlContent);\n\n // 1. Semantic tag counts\n const semanticTags: Record<string, number> = {};\n for (const tag of SEMANTIC_TAGS) {\n const count = $(tag).length;\n if (count > 0) {\n semanticTags[tag] = count;\n }\n }\n\n // 2. Max nesting depth\n const body = $('body');\n const maxDepth = body.length > 0\n ? computeMaxDepth($, body, 0)\n : 0;\n\n // 3. Layout element hash — elements with display:flex or display:grid in inline styles\n const layoutElements: string[] = [];\n $('[style]').each((_, el) => {\n const style = $(el).attr('style') ?? '';\n if (/display\\s*:\\s*(flex|grid|inline-flex|inline-grid)/i.test(style)) {\n const tag = ($(el).prop('tagName') ?? 'div').toLowerCase();\n const cls = $(el).attr('class') ?? '';\n layoutElements.push(`${tag}.${cls}`);\n }\n });\n\n // Also capture elements with layout-related class names (Tailwind patterns)\n $('[class*=\"flex\"], [class*=\"grid\"]').each((_, el) => {\n const tag = ($(el).prop('tagName') ?? 'div').toLowerCase();\n const cls = $(el).attr('class') ?? '';\n // Only include if it has actual flex/grid class\n if (/\\b(flex|grid|inline-flex|inline-grid)\\b/.test(cls)) {\n const key = `${tag}.${cls}`;\n if (!layoutElements.includes(key)) {\n layoutElements.push(key);\n }\n }\n });\n\n layoutElements.sort();\n const layoutHash = layoutElements.length > 0\n ? shortHash(layoutElements.join('|'))\n : 'empty';\n\n // 4. Body direct child tag sequence\n const childTags: string[] = [];\n body.children().each((_, child) => {\n if ($(child).prop('nodeType') === 1) { // Element node\n const tag = ($(child).prop('tagName') ?? '').toLowerCase();\n if (tag && tag !== 'script' && tag !== 'link') {\n childTags.push(tag);\n }\n }\n });\n\n const childSequenceHash = childTags.length > 0\n ? shortHash(childTags.join(','))\n : 'empty';\n\n return {\n semanticTags,\n maxDepth,\n layoutHash,\n childSequenceHash,\n };\n}\n\n/**\n * Compare two structure fingerprints and return human-readable differences\n */\nexport function compareStructure(\n original: StructureFingerprint,\n current: StructureFingerprint,\n): string[] {\n const details: string[] = [];\n\n // Compare max depth\n if (original.maxDepth !== current.maxDepth) {\n details.push(`maxDepth: ${original.maxDepth} → ${current.maxDepth}`);\n }\n\n // Compare semantic tag counts\n const allTags = new Set([\n ...Object.keys(original.semanticTags),\n ...Object.keys(current.semanticTags),\n ]);\n\n for (const tag of allTags) {\n const origCount = original.semanticTags[tag] ?? 0;\n const currCount = current.semanticTags[tag] ?? 0;\n if (origCount !== currCount) {\n if (origCount === 0) {\n details.push(`<${tag}> added (${currCount})`);\n } else if (currCount === 0) {\n details.push(`<${tag}> removed (was ${origCount})`);\n } else {\n details.push(`<${tag}> count: ${origCount} → ${currCount}`);\n }\n }\n }\n\n // Compare layout hash\n if (original.layoutHash !== current.layoutHash) {\n details.push(`layout elements changed (hash: ${original.layoutHash} → ${current.layoutHash})`);\n }\n\n // Compare child sequence hash\n if (original.childSequenceHash !== current.childSequenceHash) {\n details.push(`body child sequence changed (hash: ${original.childSequenceHash} → ${current.childSequenceHash})`);\n }\n\n return details;\n}\n","// 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 /** DOM structure fingerprint (v0.2.0+) */\n structure?: StructureFingerprint;\n /** File used for structure fingerprint (v0.2.1+) */\n structureSourceFile?: string;\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 /** DOM structure drift report (v0.2.0+) */\n structureDrift?: StructureDriftReport;\n}\n\n/**\n * DOM structure fingerprint — tracks HTML layout structure\n */\nexport interface StructureFingerprint {\n /** Semantic tag counts: { header: 1, nav: 1, main: 1, section: 3, footer: 1 } */\n semanticTags: Record<string, number>;\n /** Maximum DOM nesting depth */\n maxDepth: number;\n /** Hash of layout elements (display:flex/grid tags+classes) */\n layoutHash: string;\n /** Hash of body's direct child tag sequence */\n childSequenceHash: string;\n}\n\n/**\n * Structure drift detection result\n */\nexport interface StructureDriftReport {\n /** Whether structure changed */\n changed: boolean;\n /** Human-readable change descriptions */\n details: string[];\n}\n\n/**\n * Sync direction for Stitch ↔ Code synchronization\n */\nexport type SyncDirection = 'to-stitch' | 'to-code';\n\n/**\n * A single sync change between Stitch and Code\n */\nexport interface SyncChange {\n /** Token category */\n category: TokenCategory;\n /** CSS property or token name */\n property: string;\n /** Previous value */\n fromValue: string;\n /** New value */\n toValue: string;\n /** Type of change */\n action: 'update' | 'add' | 'remove';\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n /** Which direction the sync goes */\n direction: SyncDirection;\n /** All changes detected */\n changes: SyncChange[];\n /** Natural language prompt for edit_screens (to-stitch only) */\n prompt?: string;\n /** CSS patch content (to-code only) */\n patchFile?: string;\n /** ISO timestamp */\n timestamp: string;\n}\n\n/**\n * Stitch project/screen configuration\n */\nexport interface StitchConfig {\n /** Stitch project ID */\n projectId?: string;\n /** Stitch screen ID */\n screenId?: string;\n /** Local path to downloaded Stitch HTML */\n htmlPath?: string;\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 /** Stitch project configuration (optional) */\n stitch?: StitchConfig;\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, extractTailwindConfig } from '../parsers/html-parser.js';\nimport { computeStructureFingerprint } from '../parsers/structure-parser.js';\nimport type {\n DesignSnapshot,\n DesignToken,\n TokenCategory,\n DriftGuardConfig,\n StructureFingerprint,\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 // Also extract Tailwind config from <script> tags\n const twTokens = extractTailwindConfig(htmlContent, stitchHtmlPath);\n allTokens.push(...twTokens);\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\n // Extract Tailwind config from <script> tags\n const twTokens = extractTailwindConfig(content, file);\n allTokens.push(...twTokens);\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 // Compute structure fingerprint from HTML files\n let structure: StructureFingerprint | undefined;\n let structureSourceFile: string | undefined;\n try {\n // Use Stitch HTML if provided, otherwise try to find an HTML file\n let htmlForStructure: string | null = null;\n\n if (stitchHtmlPath) {\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (fs.existsSync(absPath)) {\n htmlForStructure = fs.readFileSync(absPath, 'utf-8');\n structureSourceFile = stitchHtmlPath;\n }\n } else {\n // Try to find any HTML file in the scanned files\n for (const file of files) {\n if (file.endsWith('.html')) {\n const absPath = path.join(projectRoot, file);\n if (fs.existsSync(absPath)) {\n htmlForStructure = fs.readFileSync(absPath, 'utf-8');\n structureSourceFile = file;\n break;\n }\n }\n }\n }\n\n if (htmlForStructure) {\n structure = computeStructureFingerprint(htmlForStructure);\n }\n } catch {\n // Structure fingerprint is optional — don't fail the snapshot\n }\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 structure,\n structureSourceFile,\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 // Visual effects\n 'backdrop-filter': 'other',\n 'filter': 'other',\n 'animation': 'other',\n 'transition': 'other',\n};\n\n/**\n * Determine the category for a CSS property\n */\nfunction getCategory(property: string, value?: 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 const lower = property.toLowerCase();\n\n // Color — keyword-based detection (expanded for Shadcn/Tailwind patterns)\n const colorKeywords = [\n 'color', 'bg', 'text', 'foreground', 'background',\n // Semantic color tokens (Shadcn UI / Tailwind)\n 'primary', 'secondary', 'accent', 'muted', 'destructive',\n 'success', 'warning', 'danger', 'error', 'info',\n // UI component colors\n 'card', 'popover', 'border', 'input', 'ring',\n 'sidebar', 'chart', 'glow',\n // State colors\n 'hover', 'active', 'focus', 'disabled',\n ];\n if (colorKeywords.some(kw => lower.includes(kw))) return 'color';\n\n // Font\n if (lower.includes('font') || lower.includes('size') || lower.includes('weight') || lower.includes('line-height') || lower.includes('letter')) return 'font';\n\n // Spacing\n if (lower.includes('spacing') || lower.includes('margin') || lower.includes('padding') || lower.includes('gap') || lower.includes('inset')) return 'spacing';\n\n // Shadow\n if (lower.includes('shadow')) return 'shadow';\n\n // Radius\n if (lower.includes('radius') || lower.includes('rounded')) return 'radius';\n\n // Layout\n if (lower.includes('width') || lower.includes('height') || lower.includes('sidebar-width')) return 'layout';\n\n // Value-based fallback: detect HSL bare values (e.g., \"217 91% 60%\") or hex/rgb/hsl\n if (value) {\n const trimmed = value.trim();\n // HSL bare format: \"H S% L%\" (common in Tailwind/Shadcn)\n if (/^\\d{1,3}\\s+\\d{1,3}%\\s+\\d{1,3}%$/.test(trimmed)) return 'color';\n // hex, rgb, hsl, oklch, color functions\n if (/^(#|rgb|hsl|oklch|lch|lab|color\\()/.test(trimmed)) return 'color';\n }\n\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 value = csstree.generate(node.value);\n const category = getCategory(property, value);\n\n if (!category) return;\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, value) ?? '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 // Visual effects\n 'backdrop-filter': 'other',\n 'filter': 'other',\n 'animation': 'other',\n 'transition': 'other',\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 * Extract design tokens from Tailwind config in <script> tags.\n * Stitch generates HTML with tailwind config like:\n * <script id=\"tailwind-config\">\n * tailwind.config = { theme: { extend: { colors: { \"primary\": \"#256af4\" }, ... } } }\n * </script>\n */\nexport function extractTailwindConfig(\n htmlContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n const $ = cheerio.load(htmlContent);\n\n // Find script tags that contain tailwind config\n $('script').each((_, el) => {\n const scriptId = $(el).attr('id') ?? '';\n const text = $(el).text();\n if (!text) return;\n\n // Match scripts with id=\"tailwind-config\" or containing \"tailwind.config\"\n const isTailwindConfig =\n scriptId.toLowerCase().includes('tailwind') ||\n text.includes('tailwind.config');\n\n if (!isTailwindConfig) return;\n\n // Extract colors\n const colorsMatch = text.match(/colors\\s*:\\s*\\{([^}]+)\\}/);\n if (colorsMatch) {\n const colorsBlock = colorsMatch[1];\n const colorRegex = /[\"']([^\"']+)[\"']\\s*:\\s*[\"']([^\"']+)[\"']/g;\n let match;\n while ((match = colorRegex.exec(colorsBlock)) !== null) {\n tokens.push({\n category: 'color',\n property: `--tw-${match[1]}`,\n value: match[2],\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n\n // Extract borderRadius\n const radiusMatch = text.match(/borderRadius\\s*:\\s*\\{([^}]+)\\}/);\n if (radiusMatch) {\n const radiusBlock = radiusMatch[1];\n const radiusRegex = /[\"']([^\"']+)[\"']\\s*:\\s*[\"']([^\"']+)[\"']/g;\n let match;\n while ((match = radiusRegex.exec(radiusBlock)) !== null) {\n tokens.push({\n category: 'radius',\n property: `--tw-radius-${match[1]}`,\n value: match[2],\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n\n // Extract fontFamily\n const fontMatch = text.match(/fontFamily\\s*:\\s*\\{([^}]+)\\}/);\n if (fontMatch) {\n const fontBlock = fontMatch[1];\n // Match: \"display\": [\"Inter\", \"sans-serif\"] or \"body\": [\"Roboto\"]\n const fontRegex = /[\"']([^\"']+)[\"']\\s*:\\s*\\[([^\\]]+)\\]/g;\n let match;\n while ((match = fontRegex.exec(fontBlock)) !== null) {\n const familyValues = match[2]\n .split(',')\n .map(v => v.trim().replace(/[\"']/g, ''))\n .join(', ');\n tokens.push({\n category: 'font',\n property: `--tw-font-${match[1]}`,\n value: familyValues,\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n });\n\n return tokens;\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 StructureDriftReport,\n} from '../types/index.js';\nimport { scanProject, loadConfig } from './snapshot.js';\nimport { computeStructureFingerprint, compareStructure } from '../parsers/structure-parser.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 // Structure drift detection (v0.2.0+)\n let structureDrift: StructureDriftReport | undefined;\n\n if (snapshot.structure) {\n try {\n const fs = await import('node:fs');\n const path = await import('node:path');\n let htmlContent: string | null = null;\n\n // Prefer the file recorded during snapshot creation\n if (snapshot.structureSourceFile) {\n const absPath = path.join(projectRoot, snapshot.structureSourceFile);\n if (fs.existsSync(absPath)) {\n htmlContent = fs.readFileSync(absPath, 'utf-8');\n }\n }\n\n // Fallback: scan all HTML files (backward compat with older snapshots)\n if (!htmlContent) {\n const config = loadConfig(projectRoot);\n const fg = (await import('fast-glob')).default;\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 const absPath = path.join(projectRoot, file);\n if (fs.existsSync(absPath)) {\n htmlContent = fs.readFileSync(absPath, 'utf-8');\n break;\n }\n }\n }\n\n if (htmlContent) {\n const currentStructure = computeStructureFingerprint(htmlContent);\n const details = compareStructure(snapshot.structure, currentStructure);\n structureDrift = {\n changed: details.length > 0,\n details,\n };\n }\n } catch {\n // Structure comparison is optional — don't fail the check\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 && !(structureDrift?.changed),\n items: driftItems,\n categorySummary,\n structureDrift,\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","import type {\n DriftItem,\n DesignToken,\n SyncChange,\n SyncResult,\n SyncDirection,\n TokenCategory,\n} from '../types/index.js';\n\n/**\n * Human-readable labels for token categories\n */\nconst CATEGORY_LABELS: Record<TokenCategory, string> = {\n color: 'color',\n font: 'font',\n spacing: 'spacing',\n shadow: 'shadow',\n radius: 'border-radius',\n layout: 'layout',\n other: 'style',\n};\n\n/**\n * Properties that are design tokens (CSS custom properties or Tailwind config).\n * Inline style properties like 'display', 'align-items' are NOT design tokens.\n */\nfunction isDesignTokenProperty(property: string): boolean {\n // CSS custom properties (--*) and Tailwind config tokens (--tw-*)\n if (property.startsWith('--')) return true;\n // font-family is a design token\n if (property === 'font-family') return true;\n return false;\n}\n\n/**\n * Normalize a token key for matching across Stitch HTML and code CSS.\n * Maps between different naming conventions:\n * Stitch Tailwind config: --tw-primary\n * Code CSS variable: --primary\n */\nfunction normalizeTokenKey(property: string): string {\n return property\n .replace(/^--tw-/, '--') // --tw-primary → --primary\n .replace(/^--tw-font-/, '--font-') // --tw-font-display → --font-display\n .toLowerCase();\n}\n\n/**\n * Convert DriftItems into SyncChanges\n */\nexport function driftItemsToSyncChanges(items: DriftItem[]): SyncChange[] {\n return items.map((item) => {\n if (item.changeType === 'deleted') {\n return {\n category: item.original.category,\n property: item.original.property,\n fromValue: item.original.value,\n toValue: '',\n action: 'remove' as const,\n };\n }\n\n if (item.changeType === 'added') {\n return {\n category: (item.current ?? item.original).category,\n property: (item.current ?? item.original).property,\n fromValue: '',\n toValue: (item.current ?? item.original).value,\n action: 'add' as const,\n };\n }\n\n // modified\n return {\n category: item.original.category,\n property: item.original.property,\n fromValue: item.original.value,\n toValue: item.current?.value ?? '',\n action: 'update' as const,\n };\n });\n}\n\n/**\n * Generate a natural language prompt from sync changes.\n * This prompt is designed for Stitch's `edit_screens` API.\n */\nexport function generateSyncPrompt(changes: SyncChange[]): string {\n if (changes.length === 0) {\n return '';\n }\n\n // Group changes by category\n const grouped = new Map<TokenCategory, SyncChange[]>();\n for (const change of changes) {\n const existing = grouped.get(change.category) ?? [];\n existing.push(change);\n grouped.set(change.category, existing);\n }\n\n const lines: string[] = [\n 'Update the following design tokens to match the latest code changes:',\n '',\n ];\n\n for (const [category, categoryChanges] of grouped) {\n const label = CATEGORY_LABELS[category];\n\n for (const change of categoryChanges) {\n const propName = cleanPropertyName(change.property);\n\n if (change.action === 'update') {\n lines.push(\n `- Change ${label} '${propName}' from ${change.fromValue} to ${change.toValue}`,\n );\n } else if (change.action === 'add') {\n lines.push(\n `- Add new ${label} '${propName}' with value ${change.toValue}`,\n );\n } else if (change.action === 'remove') {\n lines.push(\n `- Remove ${label} '${propName}' (was ${change.fromValue})`,\n );\n }\n }\n }\n\n lines.push('');\n lines.push(\n 'Keep all other design elements unchanged. Only modify the specified tokens.',\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Clean up property names for human-readable prompts.\n */\nfunction cleanPropertyName(property: string): string {\n return property\n .replace(/^--tw-/, '')\n .replace(/^--tw-radius-/, '')\n .replace(/^--tw-font-/, '')\n .replace(/^--/, '');\n}\n\n/**\n * Build a SyncResult for pushing code changes to Stitch.\n */\nexport function syncToStitch(driftItems: DriftItem[]): SyncResult {\n const changes = driftItemsToSyncChanges(driftItems);\n const prompt = generateSyncPrompt(changes);\n\n return {\n direction: 'to-stitch' as SyncDirection,\n changes,\n prompt: prompt || undefined,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Build a SyncResult for pulling Stitch changes to code.\n *\n * ONLY compares design tokens that exist in BOTH Stitch and snapshot.\n * Code-only tokens (e.g., Shadcn --background, --sidebar) are NOT\n * flagged as \"removed\" — Stitch doesn't own them.\n */\nexport function syncFromStitch(\n stitchTokens: DesignToken[],\n snapshotTokens: DesignToken[],\n): SyncResult {\n const changes: SyncChange[] = [];\n\n // Filter to design-token-only properties\n const stitchDesignTokens = stitchTokens.filter(t => isDesignTokenProperty(t.property));\n const snapshotDesignTokens = snapshotTokens.filter(t => isDesignTokenProperty(t.property));\n\n // Build normalized lookup maps\n const snapshotMap = new Map<string, DesignToken>();\n for (const token of snapshotDesignTokens) {\n const key = normalizeTokenKey(token.property);\n if (!snapshotMap.has(key)) {\n snapshotMap.set(key, token);\n }\n }\n\n const stitchMap = new Map<string, DesignToken>();\n for (const token of stitchDesignTokens) {\n const key = normalizeTokenKey(token.property);\n if (!stitchMap.has(key)) {\n stitchMap.set(key, token);\n }\n }\n\n // Find updates and adds: stitch token differs or new\n for (const [key, stitchToken] of stitchMap) {\n const snapshotToken = snapshotMap.get(key);\n\n if (!snapshotToken) {\n changes.push({\n category: stitchToken.category,\n property: stitchToken.property,\n fromValue: '',\n toValue: stitchToken.value,\n action: 'add',\n });\n } else if (stitchToken.value !== snapshotToken.value) {\n changes.push({\n category: stitchToken.category,\n property: snapshotToken.property,\n fromValue: snapshotToken.value,\n toValue: stitchToken.value,\n action: 'update',\n });\n }\n }\n\n // Removals: only flag tokens that are clearly Stitch-origin\n // (i.e., --tw-* prefixed in snapshot AND missing from stitch)\n // This avoids false-flagging code-only tokens like --background\n if (stitchDesignTokens.length > 0) {\n for (const [key, snapshotToken] of snapshotMap) {\n const isStitchOrigin = snapshotToken.property.startsWith('--tw-')\n || snapshotToken.selector === '[tailwind.config]';\n if (isStitchOrigin && !stitchMap.has(key)) {\n changes.push({\n category: snapshotToken.category,\n property: snapshotToken.property,\n fromValue: snapshotToken.value,\n toValue: '',\n action: 'remove',\n });\n }\n }\n }\n\n const patchFile = generateCssPatch(changes);\n\n return {\n direction: 'to-code' as SyncDirection,\n changes,\n patchFile: patchFile || undefined,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Apply sync changes to actual CSS files in the project.\n */\nexport function applySyncChanges(\n changes: SyncChange[],\n cssFiles: Map<string, string>,\n): { modifiedFiles: Map<string, string>; appliedCount: number } {\n const modifiedFiles = new Map<string, string>();\n let appliedCount = 0;\n\n const updateChanges = changes.filter(c => c.action === 'update');\n\n for (const [filename, content] of cssFiles) {\n let modified = content;\n let fileChanged = false;\n\n for (const change of updateChanges) {\n // Try CSS custom property replacement\n const propName = change.property.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const escapedFrom = change.fromValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const cssRegex = new RegExp(\n `(${propName}\\\\s*:\\\\s*)${escapedFrom}(\\\\s*[;\\\\n])`,\n 'g',\n );\n\n let newContent = modified.replace(cssRegex, `$1${change.toValue}$2`);\n\n // Also try Tailwind config format in <script> tags:\n // \"primary\": \"#256af4\" → \"primary\": \"#8b5cf6\"\n if (newContent === modified) {\n const twResult = applySyncToHtml(modified, change);\n if (twResult !== modified) {\n newContent = twResult;\n }\n }\n\n if (newContent !== modified) {\n modified = newContent;\n fileChanged = true;\n appliedCount++;\n }\n }\n\n if (fileChanged) {\n modifiedFiles.set(filename, modified);\n }\n }\n\n return { modifiedFiles, appliedCount };\n}\n\n/**\n * Apply a sync change to HTML content by editing Tailwind config <script> values.\n * Handles format: \"primary\": \"#256af4\" → \"primary\": \"#8b5cf6\"\n */\nfunction applySyncToHtml(html: string, change: SyncChange): string {\n // Extract the token name from --tw-primary → primary\n const tokenName = change.property\n .replace(/^--tw-/, '')\n .replace(/^--/, '');\n\n const escapedFrom = change.fromValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Match: \"primary\": \"#256af4\" or \"primary\": \"Inter\"\n const regex = new RegExp(\n `(\"${tokenName}\"\\\\s*:\\\\s*)[\"']${escapedFrom}[\"']`,\n 'g',\n );\n\n return html.replace(regex, `$1\"${change.toValue}\"`);\n}\n\n/**\n * Generate CSS variable patch content from sync changes.\n */\nfunction generateCssPatch(changes: SyncChange[]): string {\n if (changes.length === 0) return '';\n\n const lines: string[] = [\n '/* drift-guard sync patch — apply these changes to your CSS */',\n '/* Generated by: drift-guard sync --direction to-code */',\n '',\n ':root {',\n ];\n\n for (const change of changes) {\n if (change.action === 'remove') {\n lines.push(` /* REMOVED: ${change.property}: ${change.fromValue}; */`);\n } else {\n const comment =\n change.action === 'update'\n ? ` /* was: ${change.fromValue} */`\n : ' /* NEW */';\n lines.push(` ${change.property}: ${change.toValue};${comment}`);\n }\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["cheerio","fs","path","config","fg","fs","path"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
saveSnapshot,
|
|
18
18
|
syncFromStitch,
|
|
19
19
|
syncToStitch
|
|
20
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-6TBPF6U2.js";
|
|
21
21
|
|
|
22
22
|
// src/cli/index.ts
|
|
23
23
|
import { Command as Command2 } from "commander";
|
|
@@ -55,6 +55,15 @@ async function initCommand(options) {
|
|
|
55
55
|
console.log(chalk.dim(` ${icon} ${cat}: `) + chalk.white(count.toString()));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
if (snapshot.structure) {
|
|
59
|
+
const s = snapshot.structure;
|
|
60
|
+
const tagList = Object.entries(s.semanticTags).filter(([, count]) => count > 0).map(([tag, count]) => `${tag}(${count})`).join(", ");
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.bold(" Structure Fingerprint:"));
|
|
63
|
+
console.log(chalk.dim(" \u{1F3D7}\uFE0F semantic tags: ") + chalk.white(tagList));
|
|
64
|
+
console.log(chalk.dim(" \u{1F3D7}\uFE0F max depth: ") + chalk.white(s.maxDepth.toString()));
|
|
65
|
+
console.log(chalk.dim(" \u{1F3D7}\uFE0F layout hash: ") + chalk.white(s.layoutHash));
|
|
66
|
+
}
|
|
58
67
|
console.log();
|
|
59
68
|
console.log(chalk.dim("Next steps:"));
|
|
60
69
|
console.log(chalk.cyan(" 1. ") + "Add .design-guard/ to .gitignore (optional)");
|
|
@@ -97,32 +106,34 @@ function printTextReport(report) {
|
|
|
97
106
|
console.log(`${icon} ${chalk2.bold("Drift Score:")} ${scoreColor(`${report.driftScore}%`)} (threshold: ${report.threshold}%)`);
|
|
98
107
|
console.log(chalk2.dim(` ${report.changedTokens} of ${report.totalTokens} tokens changed
|
|
99
108
|
`));
|
|
100
|
-
if (report.items.length === 0) {
|
|
109
|
+
if (report.items.length === 0 && !report.structureDrift?.changed) {
|
|
101
110
|
console.log(chalk2.green(" No design drift detected. Your design is intact! \u{1F389}\n"));
|
|
102
111
|
return;
|
|
103
112
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
console.log();
|
|
114
|
-
const itemsToShow = report.items.slice(0, 20);
|
|
115
|
-
if (itemsToShow.length > 0) {
|
|
116
|
-
console.log(chalk2.bold(" Changes:"));
|
|
117
|
-
for (const item of itemsToShow) {
|
|
118
|
-
printDriftItem(item);
|
|
113
|
+
if (report.items.length > 0) {
|
|
114
|
+
console.log(chalk2.bold(" Category Breakdown:"));
|
|
115
|
+
const categories = ["color", "font", "spacing", "shadow", "radius", "layout"];
|
|
116
|
+
for (const cat of categories) {
|
|
117
|
+
const summary = report.categorySummary[cat];
|
|
118
|
+
if (summary.total === 0) continue;
|
|
119
|
+
const catIcon = { color: "\u{1F3A8}", font: "\u{1F4DD}", spacing: "\u{1F4CF}", shadow: "\u{1F32B}\uFE0F", radius: "\u2B55", layout: "\u{1F4D0}" }[cat];
|
|
120
|
+
const catColor = summary.changed > 0 ? chalk2.red : chalk2.green;
|
|
121
|
+
console.log(` ${catIcon} ${cat}: ${catColor(`${summary.changed}/${summary.total}`)} (${summary.driftPercent}%)`);
|
|
119
122
|
}
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
console.log();
|
|
124
|
+
const itemsToShow = report.items.slice(0, 20);
|
|
125
|
+
if (itemsToShow.length > 0) {
|
|
126
|
+
console.log(chalk2.bold(" Changes:"));
|
|
127
|
+
for (const item of itemsToShow) {
|
|
128
|
+
printDriftItem(item);
|
|
129
|
+
}
|
|
130
|
+
if (report.items.length > 20) {
|
|
131
|
+
console.log(chalk2.dim(` ... and ${report.items.length - 20} more changes
|
|
122
132
|
`));
|
|
133
|
+
}
|
|
123
134
|
}
|
|
135
|
+
console.log();
|
|
124
136
|
}
|
|
125
|
-
console.log();
|
|
126
137
|
if (report.structureDrift) {
|
|
127
138
|
if (report.structureDrift.changed) {
|
|
128
139
|
console.log(chalk2.bold(" \u{1F3D7}\uFE0F Structure Drift:"));
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +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","../../src/cli/hook.ts","../../src/cli/sync.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';\nimport { hookInstallCommand, hookUninstallCommand } from './hook.js';\nimport { syncCommand } from './sync.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.2.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\nconst hook = program\n .command('hook')\n .description('Manage pre-commit hook for automatic drift checking');\n\nhook\n .command('install')\n .description('Install a pre-commit hook that runs drift-guard check')\n .option('--threshold <number>', 'Drift threshold percentage for the hook', '10')\n .action(hookInstallCommand);\n\nhook\n .command('uninstall')\n .description('Remove the drift-guard pre-commit hook')\n .action(hookUninstallCommand);\n\nprogram.addCommand(syncCommand);\n\nprogram.parse();\n\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 // Structure drift (v0.2.0+)\n if (report.structureDrift) {\n if (report.structureDrift.changed) {\n console.log(chalk.bold(' 🏗️ Structure Drift:'));\n for (const detail of report.structureDrift.details) {\n console.log(chalk.yellow(` ⚠️ ${detail}`));\n }\n console.log();\n } else {\n console.log(chalk.green(' 🏗️ DOM structure: No changes detected ✅\\n'));\n }\n }\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","import fs from 'node:fs';\nimport path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport chalk from 'chalk';\n\n/**\n * Install a pre-commit hook that runs drift-guard check\n */\nexport async function hookInstallCommand(options: { threshold?: string }): Promise<void> {\n const cwd = process.cwd();\n const threshold = options.threshold ?? '10';\n\n // Check if git repo\n if (!fs.existsSync(path.join(cwd, '.git'))) {\n console.error(chalk.red('✗ Not a git repository. Run this from a git project root.'));\n process.exit(1);\n }\n\n // Detect hook manager\n const hasHusky = fs.existsSync(path.join(cwd, '.husky'));\n const hasLefthook = fs.existsSync(path.join(cwd, 'lefthook.yml')) ||\n fs.existsSync(path.join(cwd, '.lefthook.yml'));\n\n if (hasHusky) {\n // Add to existing husky setup\n const hookFile = path.join(cwd, '.husky', 'pre-commit');\n const hookCommand = `npx drift-guard check --threshold ${threshold} --ci`;\n\n if (fs.existsSync(hookFile)) {\n const existing = fs.readFileSync(hookFile, 'utf-8');\n if (existing.includes('drift-guard')) {\n console.log(chalk.yellow('⚠ drift-guard hook already exists in .husky/pre-commit'));\n return;\n }\n fs.appendFileSync(hookFile, `\\n${hookCommand}\\n`);\n } else {\n fs.writeFileSync(hookFile, `#!/usr/bin/env sh\\n. \"$(dirname -- \"$0\")/_/husky.sh\"\\n\\n${hookCommand}\\n`);\n }\n\n console.log(chalk.green('✓ Added drift-guard check to .husky/pre-commit'));\n return;\n }\n\n if (hasLefthook) {\n console.log(chalk.yellow('⚠ lefthook detected. Add this to your lefthook.yml:'));\n console.log(chalk.cyan(`\npre-commit:\n commands:\n drift-guard:\n run: npx drift-guard check --threshold ${threshold} --ci\n`));\n return;\n }\n\n // No hook manager — install via .git/hooks directly\n const hooksDir = path.join(cwd, '.git', 'hooks');\n const hookFile = path.join(hooksDir, 'pre-commit');\n const hookScript = `#!/usr/bin/env sh\n# drift-guard pre-commit hook\n# Checks for design token drift before commits\n# Use --force flag to bypass: git commit --no-verify\n\nnpx drift-guard check --threshold ${threshold} --ci\n\nif [ $? -ne 0 ]; then\n echo \"\"\n echo \"\\\\033[31m✗ Design drift detected! Commit blocked.\\\\033[0m\"\n echo \" Run 'npx drift-guard check' for details.\"\n echo \" If changes are intentional, run 'npx drift-guard snapshot update'\"\n echo \" Or use 'git commit --no-verify' to bypass.\"\n exit 1\nfi\n`;\n\n if (!fs.existsSync(hooksDir)) {\n fs.mkdirSync(hooksDir, { recursive: true });\n }\n\n if (fs.existsSync(hookFile)) {\n const existing = fs.readFileSync(hookFile, 'utf-8');\n if (existing.includes('drift-guard')) {\n console.log(chalk.yellow('⚠ drift-guard hook already installed'));\n return;\n }\n // Append to existing hook\n fs.appendFileSync(hookFile, `\\n${hookScript}`);\n console.log(chalk.green('✓ Appended drift-guard to existing pre-commit hook'));\n } else {\n fs.writeFileSync(hookFile, hookScript);\n // Make executable on Unix\n try {\n fs.chmodSync(hookFile, 0o755);\n } catch {\n // Windows may not support chmod, skip\n }\n console.log(chalk.green('✓ Installed drift-guard pre-commit hook'));\n }\n\n console.log(chalk.dim(` Threshold: ${threshold}%`));\n console.log(chalk.dim(' Bypass: git commit --no-verify'));\n}\n\n/**\n * Uninstall the pre-commit hook\n */\nexport async function hookUninstallCommand(): Promise<void> {\n const cwd = process.cwd();\n\n // Check husky\n const huskyHook = path.join(cwd, '.husky', 'pre-commit');\n if (fs.existsSync(huskyHook)) {\n const content = fs.readFileSync(huskyHook, 'utf-8');\n if (content.includes('drift-guard')) {\n const cleaned = content.replace(/\\n?npx drift-guard.*\\n?/g, '\\n');\n fs.writeFileSync(huskyHook, cleaned);\n console.log(chalk.green('✓ Removed drift-guard from .husky/pre-commit'));\n return;\n }\n }\n\n // Check .git/hooks\n const gitHook = path.join(cwd, '.git', 'hooks', 'pre-commit');\n if (fs.existsSync(gitHook)) {\n const content = fs.readFileSync(gitHook, 'utf-8');\n if (content.includes('drift-guard')) {\n // If the entire file is our hook, remove it\n if (content.includes('# drift-guard pre-commit hook') && !content.includes('\\n#!/usr/bin/env sh\\n')) {\n fs.unlinkSync(gitHook);\n console.log(chalk.green('✓ Removed drift-guard pre-commit hook'));\n } else {\n // Remove just our part\n const cleaned = content.replace(/# drift-guard pre-commit hook[\\s\\S]*?fi\\n?/g, '');\n fs.writeFileSync(gitHook, cleaned);\n console.log(chalk.green('✓ Removed drift-guard from pre-commit hook'));\n }\n return;\n }\n }\n\n console.log(chalk.yellow('⚠ No drift-guard hook found to uninstall'));\n}\n","import { Command } from 'commander';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { loadSnapshot, loadConfig } from '../core/snapshot.js';\nimport { detectDrift } from '../core/drift.js';\nimport {\n syncToStitch,\n syncFromStitch,\n applySyncChanges,\n} from '../core/sync.js';\nimport { parseCss, extractCssVariables } from '../parsers/css-parser.js';\nimport { parseHtml, extractStyleBlocks, extractTailwindConfig } from '../parsers/html-parser.js';\nimport type { DesignToken, SyncDirection } from '../types/index.js';\n\nexport const syncCommand = new Command('sync')\n .description('Synchronize design tokens between Stitch and your codebase')\n .requiredOption(\n '-d, --direction <direction>',\n 'Sync direction: to-stitch (push code changes to Stitch) or to-code (pull Stitch changes)',\n )\n .option(\n '--stitch-html <path>',\n 'Path to Stitch HTML file (for to-code direction)',\n )\n .option(\n '--stitch-project <id>',\n 'Stitch project ID (for to-stitch direction)',\n )\n .option(\n '--stitch-screen <id>',\n 'Stitch screen ID (for to-stitch direction)',\n )\n .option('--apply', 'Auto-apply changes: full HTML replacement + CSS patching (to-code only)', false)\n .option('--target <path>', 'Target local HTML file to replace with Stitch version (to-code --apply)')\n .option('--dry-run', 'Preview changes without applying', false)\n .option('--json', 'Output as JSON', false)\n .action(async (options) => {\n const projectRoot = process.cwd();\n const direction = options.direction as SyncDirection;\n\n if (direction !== 'to-stitch' && direction !== 'to-code') {\n console.error(\n '❌ Invalid direction. Use \"to-stitch\" or \"to-code\".',\n );\n process.exit(1);\n }\n\n if (direction === 'to-stitch') {\n await handleToStitch(projectRoot, options);\n } else {\n await handleToCode(projectRoot, options);\n }\n });\n\n/**\n * Handle to-stitch direction:\n * Compare current code against snapshot → generate Stitch edit prompt\n */\nasync function handleToStitch(\n projectRoot: string,\n options: { dryRun: boolean; json: boolean; stitchProject?: string; stitchScreen?: string },\n): Promise<void> {\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.error('❌ No snapshot found. Run \"drift-guard init\" first.');\n process.exit(1);\n }\n\n // Detect drift (code vs snapshot)\n const report = await detectDrift(projectRoot, snapshot, 0);\n\n if (report.items.length === 0) {\n console.log('✅ No changes detected. Stitch and code are in sync!');\n return;\n }\n\n const result = syncToStitch(report.items);\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n // Pretty output\n console.log('');\n console.log('🔄 drift-guard sync → to-stitch');\n console.log(` ${result.changes.length} change(s) detected\\n`);\n\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n console.log('');\n console.log('━'.repeat(60));\n console.log('📋 Stitch edit_screens prompt:');\n console.log('━'.repeat(60));\n console.log('');\n console.log(result.prompt);\n console.log('');\n console.log('━'.repeat(60));\n\n const projectId = options.stitchProject;\n const screenId = options.stitchScreen;\n\n if (projectId && screenId) {\n console.log('');\n console.log('💡 To apply these changes in Stitch, run:');\n console.log('');\n console.log(` edit_screens({`);\n console.log(` projectId: \"${projectId}\",`);\n console.log(` selectedScreenIds: [\"${screenId}\"],`);\n console.log(` prompt: \"<the prompt above>\"`);\n console.log(` })`);\n } else {\n console.log('');\n console.log(\n '💡 Copy the prompt above and use it with Stitch MCP edit_screens',\n );\n console.log(\n ' or pass --stitch-project and --stitch-screen for a ready-to-use call.',\n );\n }\n}\n\n/**\n * Handle to-code direction:\n * Compare Stitch HTML against snapshot → generate CSS patch → optionally apply.\n *\n * With --apply: Stitch HTML is the SOURCE OF TRUTH.\n * 1. Replace the local Stitch HTML file entirely (fixes text/layout/content diffs)\n * 2. Patch design tokens in other CSS files\n * 3. Update snapshot\n * 4. Verify 0% drift\n */\nasync function handleToCode(\n projectRoot: string,\n options: { dryRun: boolean; json: boolean; apply: boolean; stitchHtml?: string; target?: string },\n): Promise<void> {\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.error('❌ No snapshot found. Run \"drift-guard init\" first.');\n process.exit(1);\n }\n\n // Get Stitch HTML path (new/downloaded version)\n const config = loadConfig(projectRoot);\n const stitchHtmlPath =\n options.stitchHtml ?? config.stitch?.htmlPath;\n\n if (!stitchHtmlPath) {\n console.error(\n '❌ No Stitch HTML file specified.',\n );\n console.error(\n ' Use --stitch-html <path> or set stitch.htmlPath in .design-guard/config.json',\n );\n process.exit(1);\n }\n\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (!fs.existsSync(absPath)) {\n console.error(`❌ Stitch HTML file not found: ${absPath}`);\n process.exit(1);\n }\n\n // Parse Stitch HTML for tokens\n const htmlContent = fs.readFileSync(absPath, 'utf-8');\n const stitchTokens: DesignToken[] = [];\n\n const htmlTokens = parseHtml(htmlContent, stitchHtmlPath);\n stitchTokens.push(...htmlTokens);\n\n const styleBlocks = extractStyleBlocks(htmlContent);\n for (const block of styleBlocks) {\n stitchTokens.push(...parseCss(block, stitchHtmlPath));\n stitchTokens.push(...extractCssVariables(block, stitchHtmlPath));\n }\n\n const twTokens = extractTailwindConfig(htmlContent, stitchHtmlPath);\n stitchTokens.push(...twTokens);\n\n // Compare Stitch tokens against snapshot\n const result = syncFromStitch(stitchTokens, snapshot.tokens);\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n // === FULL SYNC (--apply): Stitch is source of truth ===\n if (options.apply && !options.dryRun) {\n console.log('');\n console.log('🔄 drift-guard sync → to-code (FULL SYNC)');\n console.log(' Stitch HTML is the source of truth.\\n');\n\n // Step 1: Find local Stitch HTML files to replace\n const targetPath = options.target;\n const localStitchFiles = await findLocalStitchHtml(projectRoot, absPath, targetPath);\n let htmlReplaced = false;\n\n if (localStitchFiles.length > 0) {\n console.log('📄 Step 1: Full HTML replacement');\n for (const localFile of localStitchFiles) {\n const localAbsPath = path.resolve(projectRoot, localFile);\n const localContent = fs.readFileSync(localAbsPath, 'utf-8');\n\n if (localContent !== htmlContent) {\n fs.writeFileSync(localAbsPath, htmlContent, 'utf-8');\n console.log(` ✅ Replaced: ${localFile} (Stitch → local)`);\n htmlReplaced = true;\n } else {\n console.log(` ℹ️ Already identical: ${localFile}`);\n }\n }\n } else {\n console.log('📄 Step 1: No local Stitch HTML found to replace.');\n console.log(' 💡 Use --target <path> to specify the target file.');\n }\n\n // Step 2: Patch design tokens in CSS files\n if (result.changes.length > 0) {\n console.log(`\\n📝 Step 2: Patching ${result.changes.length} design token(s) in CSS files`);\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n const cssFileMap = await loadProjectCssFiles(projectRoot, config);\n const { modifiedFiles, appliedCount } = applySyncChanges(result.changes, cssFileMap);\n\n if (appliedCount > 0) {\n for (const [file, content] of modifiedFiles) {\n const fullPath = path.resolve(projectRoot, file);\n fs.writeFileSync(fullPath, content, 'utf-8');\n console.log(` ✅ Patched: ${file}`);\n }\n }\n } else {\n console.log('\\n📝 Step 2: No design token changes — tokens already match.');\n }\n\n // Step 3: Update snapshot to reflect new state\n if (htmlReplaced || result.changes.length > 0) {\n console.log('\\n📸 Step 3: Updating snapshot...');\n\n const { execSync } = await import('node:child_process');\n try {\n execSync(\n `node \"${path.resolve(projectRoot, '..', 'drift-guard', 'dist', 'cli', 'index.js')}\" snapshot update`,\n { cwd: projectRoot, stdio: 'pipe' },\n );\n } catch {\n // Try the global command\n try {\n execSync('npx drift-guard snapshot update', {\n cwd: projectRoot,\n stdio: 'pipe',\n });\n } catch {\n console.log(' ⚠️ Could not auto-update snapshot. Run: drift-guard snapshot update');\n }\n }\n console.log(' ✅ Snapshot updated.');\n }\n\n // Step 4: Verify full sync\n console.log('\\n🔍 Step 4: Verifying full sync...');\n\n // Compare the Stitch source HTML with the local file\n if (localStitchFiles.length > 0) {\n let allMatch = true;\n for (const localFile of localStitchFiles) {\n const localAbsPath = path.resolve(projectRoot, localFile);\n const localContent = fs.readFileSync(localAbsPath, 'utf-8');\n if (localContent === htmlContent) {\n console.log(` ✅ ${localFile} — 100% identical to Stitch`);\n } else {\n console.log(` ❌ ${localFile} — content differs!`);\n allMatch = false;\n }\n }\n if (allMatch) {\n console.log('\\n 🎉 FULL SYNC VERIFIED — Stitch and code are 100% identical!');\n }\n }\n } else if (result.changes.length === 0) {\n console.log('✅ Stitch and code design tokens are already in sync!');\n console.log(' 💡 Use --apply for full HTML content sync (text, layout, structure).');\n } else {\n // Show token diff and patch\n console.log('');\n console.log('🔄 drift-guard sync → to-code');\n console.log(` ${result.changes.length} design token change(s) from Stitch\\n`);\n\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n if (result.patchFile) {\n console.log('');\n console.log('━'.repeat(60));\n console.log('📋 CSS patch to apply:');\n console.log('━'.repeat(60));\n console.log('');\n console.log(result.patchFile);\n console.log('');\n console.log('━'.repeat(60));\n\n if (!options.dryRun) {\n const patchPath = path.join(\n projectRoot,\n '.design-guard',\n 'sync-patch.css',\n );\n fs.writeFileSync(patchPath, result.patchFile, 'utf-8');\n console.log(`\\n💾 Patch saved to: ${patchPath}`);\n console.log(' Use --apply for full HTML content + token sync.');\n } else {\n console.log('\\n🔍 Dry run — no files written.');\n }\n }\n }\n}\n\n/**\n * Find local Stitch HTML files that should be replaced.\n * Searches for stitch-design.html or files matching --target.\n */\nasync function findLocalStitchHtml(\n projectRoot: string,\n sourceAbsPath: string,\n targetPath?: string,\n): Promise<string[]> {\n if (targetPath) {\n return [targetPath];\n }\n\n // Search for stitch-design.html files in the project\n const stitchFiles = await fg(\n ['**/stitch-design.html', '**/stitch*.html'],\n {\n cwd: projectRoot,\n ignore: ['node_modules/**', 'dist/**', 'build/**'],\n absolute: false,\n },\n );\n\n // Exclude the source file itself\n const sourceRelative = path.relative(projectRoot, sourceAbsPath);\n return stitchFiles.filter(f => f !== sourceRelative);\n}\n\n/**\n * Load all CSS files from the project into a map\n */\nasync function loadProjectCssFiles(\n projectRoot: string,\n config: { cssFiles: string[]; ignore: string[] },\n): Promise<Map<string, string>> {\n const cssFileMap = new Map<string, string>();\n\n for (const pattern of config.cssFiles) {\n const matches = await fg(pattern, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of matches) {\n const absPath = path.resolve(projectRoot, file);\n if (fs.existsSync(absPath)) {\n cssFileMap.set(file, fs.readFileSync(absPath, 'utf-8'));\n }\n }\n }\n\n // Also include HTML files that might have Tailwind config\n const htmlPatterns = ['**/*.html'];\n const htmlIgnore = ['node_modules/**', 'dist/**', 'build/**', '.next/**'];\n const htmlMatches = await fg(htmlPatterns, {\n cwd: projectRoot,\n ignore: htmlIgnore,\n absolute: false,\n });\n\n for (const file of htmlMatches) {\n const absPath = path.resolve(projectRoot, file);\n if (fs.existsSync(absPath)) {\n cssFileMap.set(file, fs.readFileSync(absPath, 'utf-8'));\n }\n }\n\n return cssFileMap;\n}\n"],"mappings":"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","names":["Command","chalk","chalk","chalk","chalk","chalk","chalk","chalk","chalk","hookFile","path","fs","path","fs","Command","chalk"]}
|
|
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","../../src/cli/hook.ts","../../src/cli/sync.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';\nimport { hookInstallCommand, hookUninstallCommand } from './hook.js';\nimport { syncCommand } from './sync.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.2.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\nconst hook = program\n .command('hook')\n .description('Manage pre-commit hook for automatic drift checking');\n\nhook\n .command('install')\n .description('Install a pre-commit hook that runs drift-guard check')\n .option('--threshold <number>', 'Drift threshold percentage for the hook', '10')\n .action(hookInstallCommand);\n\nhook\n .command('uninstall')\n .description('Remove the drift-guard pre-commit hook')\n .action(hookUninstallCommand);\n\nprogram.addCommand(syncCommand);\n\nprogram.parse();\n\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 // Structure fingerprint summary (v0.2.0+)\n if (snapshot.structure) {\n const s = snapshot.structure;\n const tagList = Object.entries(s.semanticTags)\n .filter(([, count]) => count > 0)\n .map(([tag, count]) => `${tag}(${count})`)\n .join(', ');\n console.log();\n console.log(chalk.bold(' Structure Fingerprint:'));\n console.log(chalk.dim(' 🏗️ semantic tags: ') + chalk.white(tagList));\n console.log(chalk.dim(' 🏗️ max depth: ') + chalk.white(s.maxDepth.toString()));\n console.log(chalk.dim(' 🏗️ layout hash: ') + chalk.white(s.layoutHash));\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 && !(report.structureDrift?.changed)) {\n console.log(chalk.green(' No design drift detected. Your design is intact! 🎉\\n'));\n return;\n }\n\n if (report.items.length > 0) {\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\n // Structure drift (v0.2.0+)\n if (report.structureDrift) {\n if (report.structureDrift.changed) {\n console.log(chalk.bold(' 🏗️ Structure Drift:'));\n for (const detail of report.structureDrift.details) {\n console.log(chalk.yellow(` ⚠️ ${detail}`));\n }\n console.log();\n } else {\n console.log(chalk.green(' 🏗️ DOM structure: No changes detected ✅\\n'));\n }\n }\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","import fs from 'node:fs';\nimport path from 'node:path';\nimport { execSync } from 'node:child_process';\nimport chalk from 'chalk';\n\n/**\n * Install a pre-commit hook that runs drift-guard check\n */\nexport async function hookInstallCommand(options: { threshold?: string }): Promise<void> {\n const cwd = process.cwd();\n const threshold = options.threshold ?? '10';\n\n // Check if git repo\n if (!fs.existsSync(path.join(cwd, '.git'))) {\n console.error(chalk.red('✗ Not a git repository. Run this from a git project root.'));\n process.exit(1);\n }\n\n // Detect hook manager\n const hasHusky = fs.existsSync(path.join(cwd, '.husky'));\n const hasLefthook = fs.existsSync(path.join(cwd, 'lefthook.yml')) ||\n fs.existsSync(path.join(cwd, '.lefthook.yml'));\n\n if (hasHusky) {\n // Add to existing husky setup\n const hookFile = path.join(cwd, '.husky', 'pre-commit');\n const hookCommand = `npx drift-guard check --threshold ${threshold} --ci`;\n\n if (fs.existsSync(hookFile)) {\n const existing = fs.readFileSync(hookFile, 'utf-8');\n if (existing.includes('drift-guard')) {\n console.log(chalk.yellow('⚠ drift-guard hook already exists in .husky/pre-commit'));\n return;\n }\n fs.appendFileSync(hookFile, `\\n${hookCommand}\\n`);\n } else {\n fs.writeFileSync(hookFile, `#!/usr/bin/env sh\\n. \"$(dirname -- \"$0\")/_/husky.sh\"\\n\\n${hookCommand}\\n`);\n }\n\n console.log(chalk.green('✓ Added drift-guard check to .husky/pre-commit'));\n return;\n }\n\n if (hasLefthook) {\n console.log(chalk.yellow('⚠ lefthook detected. Add this to your lefthook.yml:'));\n console.log(chalk.cyan(`\npre-commit:\n commands:\n drift-guard:\n run: npx drift-guard check --threshold ${threshold} --ci\n`));\n return;\n }\n\n // No hook manager — install via .git/hooks directly\n const hooksDir = path.join(cwd, '.git', 'hooks');\n const hookFile = path.join(hooksDir, 'pre-commit');\n const hookScript = `#!/usr/bin/env sh\n# drift-guard pre-commit hook\n# Checks for design token drift before commits\n# Use --force flag to bypass: git commit --no-verify\n\nnpx drift-guard check --threshold ${threshold} --ci\n\nif [ $? -ne 0 ]; then\n echo \"\"\n echo \"\\\\033[31m✗ Design drift detected! Commit blocked.\\\\033[0m\"\n echo \" Run 'npx drift-guard check' for details.\"\n echo \" If changes are intentional, run 'npx drift-guard snapshot update'\"\n echo \" Or use 'git commit --no-verify' to bypass.\"\n exit 1\nfi\n`;\n\n if (!fs.existsSync(hooksDir)) {\n fs.mkdirSync(hooksDir, { recursive: true });\n }\n\n if (fs.existsSync(hookFile)) {\n const existing = fs.readFileSync(hookFile, 'utf-8');\n if (existing.includes('drift-guard')) {\n console.log(chalk.yellow('⚠ drift-guard hook already installed'));\n return;\n }\n // Append to existing hook\n fs.appendFileSync(hookFile, `\\n${hookScript}`);\n console.log(chalk.green('✓ Appended drift-guard to existing pre-commit hook'));\n } else {\n fs.writeFileSync(hookFile, hookScript);\n // Make executable on Unix\n try {\n fs.chmodSync(hookFile, 0o755);\n } catch {\n // Windows may not support chmod, skip\n }\n console.log(chalk.green('✓ Installed drift-guard pre-commit hook'));\n }\n\n console.log(chalk.dim(` Threshold: ${threshold}%`));\n console.log(chalk.dim(' Bypass: git commit --no-verify'));\n}\n\n/**\n * Uninstall the pre-commit hook\n */\nexport async function hookUninstallCommand(): Promise<void> {\n const cwd = process.cwd();\n\n // Check husky\n const huskyHook = path.join(cwd, '.husky', 'pre-commit');\n if (fs.existsSync(huskyHook)) {\n const content = fs.readFileSync(huskyHook, 'utf-8');\n if (content.includes('drift-guard')) {\n const cleaned = content.replace(/\\n?npx drift-guard.*\\n?/g, '\\n');\n fs.writeFileSync(huskyHook, cleaned);\n console.log(chalk.green('✓ Removed drift-guard from .husky/pre-commit'));\n return;\n }\n }\n\n // Check .git/hooks\n const gitHook = path.join(cwd, '.git', 'hooks', 'pre-commit');\n if (fs.existsSync(gitHook)) {\n const content = fs.readFileSync(gitHook, 'utf-8');\n if (content.includes('drift-guard')) {\n // If the entire file is our hook, remove it\n if (content.includes('# drift-guard pre-commit hook') && !content.includes('\\n#!/usr/bin/env sh\\n')) {\n fs.unlinkSync(gitHook);\n console.log(chalk.green('✓ Removed drift-guard pre-commit hook'));\n } else {\n // Remove just our part\n const cleaned = content.replace(/# drift-guard pre-commit hook[\\s\\S]*?fi\\n?/g, '');\n fs.writeFileSync(gitHook, cleaned);\n console.log(chalk.green('✓ Removed drift-guard from pre-commit hook'));\n }\n return;\n }\n }\n\n console.log(chalk.yellow('⚠ No drift-guard hook found to uninstall'));\n}\n","import { Command } from 'commander';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport fg from 'fast-glob';\nimport { loadSnapshot, loadConfig } from '../core/snapshot.js';\nimport { detectDrift } from '../core/drift.js';\nimport {\n syncToStitch,\n syncFromStitch,\n applySyncChanges,\n} from '../core/sync.js';\nimport { parseCss, extractCssVariables } from '../parsers/css-parser.js';\nimport { parseHtml, extractStyleBlocks, extractTailwindConfig } from '../parsers/html-parser.js';\nimport type { DesignToken, SyncDirection } from '../types/index.js';\n\nexport const syncCommand = new Command('sync')\n .description('Synchronize design tokens between Stitch and your codebase')\n .requiredOption(\n '-d, --direction <direction>',\n 'Sync direction: to-stitch (push code changes to Stitch) or to-code (pull Stitch changes)',\n )\n .option(\n '--stitch-html <path>',\n 'Path to Stitch HTML file (for to-code direction)',\n )\n .option(\n '--stitch-project <id>',\n 'Stitch project ID (for to-stitch direction)',\n )\n .option(\n '--stitch-screen <id>',\n 'Stitch screen ID (for to-stitch direction)',\n )\n .option('--apply', 'Auto-apply changes: full HTML replacement + CSS patching (to-code only)', false)\n .option('--target <path>', 'Target local HTML file to replace with Stitch version (to-code --apply)')\n .option('--dry-run', 'Preview changes without applying', false)\n .option('--json', 'Output as JSON', false)\n .action(async (options) => {\n const projectRoot = process.cwd();\n const direction = options.direction as SyncDirection;\n\n if (direction !== 'to-stitch' && direction !== 'to-code') {\n console.error(\n '❌ Invalid direction. Use \"to-stitch\" or \"to-code\".',\n );\n process.exit(1);\n }\n\n if (direction === 'to-stitch') {\n await handleToStitch(projectRoot, options);\n } else {\n await handleToCode(projectRoot, options);\n }\n });\n\n/**\n * Handle to-stitch direction:\n * Compare current code against snapshot → generate Stitch edit prompt\n */\nasync function handleToStitch(\n projectRoot: string,\n options: { dryRun: boolean; json: boolean; stitchProject?: string; stitchScreen?: string },\n): Promise<void> {\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.error('❌ No snapshot found. Run \"drift-guard init\" first.');\n process.exit(1);\n }\n\n // Detect drift (code vs snapshot)\n const report = await detectDrift(projectRoot, snapshot, 0);\n\n if (report.items.length === 0) {\n console.log('✅ No changes detected. Stitch and code are in sync!');\n return;\n }\n\n const result = syncToStitch(report.items);\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n // Pretty output\n console.log('');\n console.log('🔄 drift-guard sync → to-stitch');\n console.log(` ${result.changes.length} change(s) detected\\n`);\n\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n console.log('');\n console.log('━'.repeat(60));\n console.log('📋 Stitch edit_screens prompt:');\n console.log('━'.repeat(60));\n console.log('');\n console.log(result.prompt);\n console.log('');\n console.log('━'.repeat(60));\n\n const projectId = options.stitchProject;\n const screenId = options.stitchScreen;\n\n if (projectId && screenId) {\n console.log('');\n console.log('💡 To apply these changes in Stitch, run:');\n console.log('');\n console.log(` edit_screens({`);\n console.log(` projectId: \"${projectId}\",`);\n console.log(` selectedScreenIds: [\"${screenId}\"],`);\n console.log(` prompt: \"<the prompt above>\"`);\n console.log(` })`);\n } else {\n console.log('');\n console.log(\n '💡 Copy the prompt above and use it with Stitch MCP edit_screens',\n );\n console.log(\n ' or pass --stitch-project and --stitch-screen for a ready-to-use call.',\n );\n }\n}\n\n/**\n * Handle to-code direction:\n * Compare Stitch HTML against snapshot → generate CSS patch → optionally apply.\n *\n * With --apply: Stitch HTML is the SOURCE OF TRUTH.\n * 1. Replace the local Stitch HTML file entirely (fixes text/layout/content diffs)\n * 2. Patch design tokens in other CSS files\n * 3. Update snapshot\n * 4. Verify 0% drift\n */\nasync function handleToCode(\n projectRoot: string,\n options: { dryRun: boolean; json: boolean; apply: boolean; stitchHtml?: string; target?: string },\n): Promise<void> {\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.error('❌ No snapshot found. Run \"drift-guard init\" first.');\n process.exit(1);\n }\n\n // Get Stitch HTML path (new/downloaded version)\n const config = loadConfig(projectRoot);\n const stitchHtmlPath =\n options.stitchHtml ?? config.stitch?.htmlPath;\n\n if (!stitchHtmlPath) {\n console.error(\n '❌ No Stitch HTML file specified.',\n );\n console.error(\n ' Use --stitch-html <path> or set stitch.htmlPath in .design-guard/config.json',\n );\n process.exit(1);\n }\n\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (!fs.existsSync(absPath)) {\n console.error(`❌ Stitch HTML file not found: ${absPath}`);\n process.exit(1);\n }\n\n // Parse Stitch HTML for tokens\n const htmlContent = fs.readFileSync(absPath, 'utf-8');\n const stitchTokens: DesignToken[] = [];\n\n const htmlTokens = parseHtml(htmlContent, stitchHtmlPath);\n stitchTokens.push(...htmlTokens);\n\n const styleBlocks = extractStyleBlocks(htmlContent);\n for (const block of styleBlocks) {\n stitchTokens.push(...parseCss(block, stitchHtmlPath));\n stitchTokens.push(...extractCssVariables(block, stitchHtmlPath));\n }\n\n const twTokens = extractTailwindConfig(htmlContent, stitchHtmlPath);\n stitchTokens.push(...twTokens);\n\n // Compare Stitch tokens against snapshot\n const result = syncFromStitch(stitchTokens, snapshot.tokens);\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2));\n return;\n }\n\n // === FULL SYNC (--apply): Stitch is source of truth ===\n if (options.apply && !options.dryRun) {\n console.log('');\n console.log('🔄 drift-guard sync → to-code (FULL SYNC)');\n console.log(' Stitch HTML is the source of truth.\\n');\n\n // Step 1: Find local Stitch HTML files to replace\n const targetPath = options.target;\n const localStitchFiles = await findLocalStitchHtml(projectRoot, absPath, targetPath);\n let htmlReplaced = false;\n\n if (localStitchFiles.length > 0) {\n console.log('📄 Step 1: Full HTML replacement');\n for (const localFile of localStitchFiles) {\n const localAbsPath = path.resolve(projectRoot, localFile);\n const localContent = fs.readFileSync(localAbsPath, 'utf-8');\n\n if (localContent !== htmlContent) {\n fs.writeFileSync(localAbsPath, htmlContent, 'utf-8');\n console.log(` ✅ Replaced: ${localFile} (Stitch → local)`);\n htmlReplaced = true;\n } else {\n console.log(` ℹ️ Already identical: ${localFile}`);\n }\n }\n } else {\n console.log('📄 Step 1: No local Stitch HTML found to replace.');\n console.log(' 💡 Use --target <path> to specify the target file.');\n }\n\n // Step 2: Patch design tokens in CSS files\n if (result.changes.length > 0) {\n console.log(`\\n📝 Step 2: Patching ${result.changes.length} design token(s) in CSS files`);\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n const cssFileMap = await loadProjectCssFiles(projectRoot, config);\n const { modifiedFiles, appliedCount } = applySyncChanges(result.changes, cssFileMap);\n\n if (appliedCount > 0) {\n for (const [file, content] of modifiedFiles) {\n const fullPath = path.resolve(projectRoot, file);\n fs.writeFileSync(fullPath, content, 'utf-8');\n console.log(` ✅ Patched: ${file}`);\n }\n }\n } else {\n console.log('\\n📝 Step 2: No design token changes — tokens already match.');\n }\n\n // Step 3: Update snapshot to reflect new state\n if (htmlReplaced || result.changes.length > 0) {\n console.log('\\n📸 Step 3: Updating snapshot...');\n\n const { execSync } = await import('node:child_process');\n try {\n execSync(\n `node \"${path.resolve(projectRoot, '..', 'drift-guard', 'dist', 'cli', 'index.js')}\" snapshot update`,\n { cwd: projectRoot, stdio: 'pipe' },\n );\n } catch {\n // Try the global command\n try {\n execSync('npx drift-guard snapshot update', {\n cwd: projectRoot,\n stdio: 'pipe',\n });\n } catch {\n console.log(' ⚠️ Could not auto-update snapshot. Run: drift-guard snapshot update');\n }\n }\n console.log(' ✅ Snapshot updated.');\n }\n\n // Step 4: Verify full sync\n console.log('\\n🔍 Step 4: Verifying full sync...');\n\n // Compare the Stitch source HTML with the local file\n if (localStitchFiles.length > 0) {\n let allMatch = true;\n for (const localFile of localStitchFiles) {\n const localAbsPath = path.resolve(projectRoot, localFile);\n const localContent = fs.readFileSync(localAbsPath, 'utf-8');\n if (localContent === htmlContent) {\n console.log(` ✅ ${localFile} — 100% identical to Stitch`);\n } else {\n console.log(` ❌ ${localFile} — content differs!`);\n allMatch = false;\n }\n }\n if (allMatch) {\n console.log('\\n 🎉 FULL SYNC VERIFIED — Stitch and code are 100% identical!');\n }\n }\n } else if (result.changes.length === 0) {\n console.log('✅ Stitch and code design tokens are already in sync!');\n console.log(' 💡 Use --apply for full HTML content sync (text, layout, structure).');\n } else {\n // Show token diff and patch\n console.log('');\n console.log('🔄 drift-guard sync → to-code');\n console.log(` ${result.changes.length} design token change(s) from Stitch\\n`);\n\n for (const change of result.changes) {\n const icon =\n change.action === 'update' ? '✏️' :\n change.action === 'add' ? '➕' : '🗑️';\n console.log(\n ` ${icon} ${change.property}: ${change.fromValue || '(none)'} → ${change.toValue || '(removed)'}`,\n );\n }\n\n if (result.patchFile) {\n console.log('');\n console.log('━'.repeat(60));\n console.log('📋 CSS patch to apply:');\n console.log('━'.repeat(60));\n console.log('');\n console.log(result.patchFile);\n console.log('');\n console.log('━'.repeat(60));\n\n if (!options.dryRun) {\n const patchPath = path.join(\n projectRoot,\n '.design-guard',\n 'sync-patch.css',\n );\n fs.writeFileSync(patchPath, result.patchFile, 'utf-8');\n console.log(`\\n💾 Patch saved to: ${patchPath}`);\n console.log(' Use --apply for full HTML content + token sync.');\n } else {\n console.log('\\n🔍 Dry run — no files written.');\n }\n }\n }\n}\n\n/**\n * Find local Stitch HTML files that should be replaced.\n * Searches for stitch-design.html or files matching --target.\n */\nasync function findLocalStitchHtml(\n projectRoot: string,\n sourceAbsPath: string,\n targetPath?: string,\n): Promise<string[]> {\n if (targetPath) {\n return [targetPath];\n }\n\n // Search for stitch-design.html files in the project\n const stitchFiles = await fg(\n ['**/stitch-design.html', '**/stitch*.html'],\n {\n cwd: projectRoot,\n ignore: ['node_modules/**', 'dist/**', 'build/**'],\n absolute: false,\n },\n );\n\n // Exclude the source file itself\n const sourceRelative = path.relative(projectRoot, sourceAbsPath);\n return stitchFiles.filter(f => f !== sourceRelative);\n}\n\n/**\n * Load all CSS files from the project into a map\n */\nasync function loadProjectCssFiles(\n projectRoot: string,\n config: { cssFiles: string[]; ignore: string[] },\n): Promise<Map<string, string>> {\n const cssFileMap = new Map<string, string>();\n\n for (const pattern of config.cssFiles) {\n const matches = await fg(pattern, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of matches) {\n const absPath = path.resolve(projectRoot, file);\n if (fs.existsSync(absPath)) {\n cssFileMap.set(file, fs.readFileSync(absPath, 'utf-8'));\n }\n }\n }\n\n // Also include HTML files that might have Tailwind config\n const htmlPatterns = ['**/*.html'];\n const htmlIgnore = ['node_modules/**', 'dist/**', 'build/**', '.next/**'];\n const htmlMatches = await fg(htmlPatterns, {\n cwd: projectRoot,\n ignore: htmlIgnore,\n absolute: false,\n });\n\n for (const file of htmlMatches) {\n const absPath = path.resolve(projectRoot, file);\n if (fs.existsSync(absPath)) {\n cssFileMap.set(file, fs.readFileSync(absPath, 'utf-8'));\n }\n }\n\n return cssFileMap;\n}\n"],"mappings":"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","names":["Command","chalk","chalk","chalk","chalk","chalk","chalk","chalk","chalk","hookFile","path","fs","path","fs","Command","chalk"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ interface DesignSnapshot {
|
|
|
34
34
|
summary: Record<TokenCategory, number>;
|
|
35
35
|
/** DOM structure fingerprint (v0.2.0+) */
|
|
36
36
|
structure?: StructureFingerprint;
|
|
37
|
+
/** File used for structure fingerprint (v0.2.1+) */
|
|
38
|
+
structureSourceFile?: string;
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* A single drift item — one token that changed
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/parsers/structure-parser.ts","../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","../src/core/sync.ts"],"sourcesContent":["import * as cheerio from 'cheerio';\nimport { createHash } from 'node:crypto';\nimport type { StructureFingerprint } from '../types/index.js';\n\n/**\n * Semantic HTML tags to track for structure fingerprinting\n */\nconst SEMANTIC_TAGS = [\n 'header', 'nav', 'main', 'section', 'article',\n 'aside', 'footer', 'form', 'table', 'dialog',\n];\n\n/**\n * Compute a short hash (first 8 chars of sha256)\n */\nfunction shortHash(input: string): string {\n return createHash('sha256').update(input).digest('hex').slice(0, 8);\n}\n\n/**\n * Compute the maximum DOM nesting depth via DFS\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction computeMaxDepth($: cheerio.CheerioAPI, el: cheerio.Cheerio<any>, depth: number): number {\n let maxDepth = depth;\n\n el.children().each((_, child) => {\n if ($(child).prop('nodeType') === 1) { // Element node\n const childDepth = computeMaxDepth($, $(child), depth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n });\n\n return maxDepth;\n}\n\n/**\n * Compute a DOM structure fingerprint from HTML content.\n *\n * Captures:\n * 1. Semantic tag counts (header, nav, main, section, etc.)\n * 2. Maximum nesting depth\n * 3. Layout element hash (elements with display:flex/grid)\n * 4. Body direct child tag sequence hash\n */\nexport function computeStructureFingerprint(htmlContent: string): StructureFingerprint {\n const $ = cheerio.load(htmlContent);\n\n // 1. Semantic tag counts\n const semanticTags: Record<string, number> = {};\n for (const tag of SEMANTIC_TAGS) {\n const count = $(tag).length;\n if (count > 0) {\n semanticTags[tag] = count;\n }\n }\n\n // 2. Max nesting depth\n const body = $('body');\n const maxDepth = body.length > 0\n ? computeMaxDepth($, body, 0)\n : 0;\n\n // 3. Layout element hash — elements with display:flex or display:grid in inline styles\n const layoutElements: string[] = [];\n $('[style]').each((_, el) => {\n const style = $(el).attr('style') ?? '';\n if (/display\\s*:\\s*(flex|grid|inline-flex|inline-grid)/i.test(style)) {\n const tag = ($(el).prop('tagName') ?? 'div').toLowerCase();\n const cls = $(el).attr('class') ?? '';\n layoutElements.push(`${tag}.${cls}`);\n }\n });\n\n // Also capture elements with layout-related class names (Tailwind patterns)\n $('[class*=\"flex\"], [class*=\"grid\"]').each((_, el) => {\n const tag = ($(el).prop('tagName') ?? 'div').toLowerCase();\n const cls = $(el).attr('class') ?? '';\n // Only include if it has actual flex/grid class\n if (/\\b(flex|grid|inline-flex|inline-grid)\\b/.test(cls)) {\n const key = `${tag}.${cls}`;\n if (!layoutElements.includes(key)) {\n layoutElements.push(key);\n }\n }\n });\n\n layoutElements.sort();\n const layoutHash = layoutElements.length > 0\n ? shortHash(layoutElements.join('|'))\n : 'empty';\n\n // 4. Body direct child tag sequence\n const childTags: string[] = [];\n body.children().each((_, child) => {\n if ($(child).prop('nodeType') === 1) { // Element node\n const tag = ($(child).prop('tagName') ?? '').toLowerCase();\n if (tag && tag !== 'script' && tag !== 'link') {\n childTags.push(tag);\n }\n }\n });\n\n const childSequenceHash = childTags.length > 0\n ? shortHash(childTags.join(','))\n : 'empty';\n\n return {\n semanticTags,\n maxDepth,\n layoutHash,\n childSequenceHash,\n };\n}\n\n/**\n * Compare two structure fingerprints and return human-readable differences\n */\nexport function compareStructure(\n original: StructureFingerprint,\n current: StructureFingerprint,\n): string[] {\n const details: string[] = [];\n\n // Compare max depth\n if (original.maxDepth !== current.maxDepth) {\n details.push(`maxDepth: ${original.maxDepth} → ${current.maxDepth}`);\n }\n\n // Compare semantic tag counts\n const allTags = new Set([\n ...Object.keys(original.semanticTags),\n ...Object.keys(current.semanticTags),\n ]);\n\n for (const tag of allTags) {\n const origCount = original.semanticTags[tag] ?? 0;\n const currCount = current.semanticTags[tag] ?? 0;\n if (origCount !== currCount) {\n if (origCount === 0) {\n details.push(`<${tag}> added (${currCount})`);\n } else if (currCount === 0) {\n details.push(`<${tag}> removed (was ${origCount})`);\n } else {\n details.push(`<${tag}> count: ${origCount} → ${currCount}`);\n }\n }\n }\n\n // Compare layout hash\n if (original.layoutHash !== current.layoutHash) {\n details.push(`layout elements changed (hash: ${original.layoutHash} → ${current.layoutHash})`);\n }\n\n // Compare child sequence hash\n if (original.childSequenceHash !== current.childSequenceHash) {\n details.push(`body child sequence changed (hash: ${original.childSequenceHash} → ${current.childSequenceHash})`);\n }\n\n return details;\n}\n","// 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 /** DOM structure fingerprint (v0.2.0+) */\n structure?: StructureFingerprint;\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 /** DOM structure drift report (v0.2.0+) */\n structureDrift?: StructureDriftReport;\n}\n\n/**\n * DOM structure fingerprint — tracks HTML layout structure\n */\nexport interface StructureFingerprint {\n /** Semantic tag counts: { header: 1, nav: 1, main: 1, section: 3, footer: 1 } */\n semanticTags: Record<string, number>;\n /** Maximum DOM nesting depth */\n maxDepth: number;\n /** Hash of layout elements (display:flex/grid tags+classes) */\n layoutHash: string;\n /** Hash of body's direct child tag sequence */\n childSequenceHash: string;\n}\n\n/**\n * Structure drift detection result\n */\nexport interface StructureDriftReport {\n /** Whether structure changed */\n changed: boolean;\n /** Human-readable change descriptions */\n details: string[];\n}\n\n/**\n * Sync direction for Stitch ↔ Code synchronization\n */\nexport type SyncDirection = 'to-stitch' | 'to-code';\n\n/**\n * A single sync change between Stitch and Code\n */\nexport interface SyncChange {\n /** Token category */\n category: TokenCategory;\n /** CSS property or token name */\n property: string;\n /** Previous value */\n fromValue: string;\n /** New value */\n toValue: string;\n /** Type of change */\n action: 'update' | 'add' | 'remove';\n}\n\n/**\n * Result of a sync operation\n */\nexport interface SyncResult {\n /** Which direction the sync goes */\n direction: SyncDirection;\n /** All changes detected */\n changes: SyncChange[];\n /** Natural language prompt for edit_screens (to-stitch only) */\n prompt?: string;\n /** CSS patch content (to-code only) */\n patchFile?: string;\n /** ISO timestamp */\n timestamp: string;\n}\n\n/**\n * Stitch project/screen configuration\n */\nexport interface StitchConfig {\n /** Stitch project ID */\n projectId?: string;\n /** Stitch screen ID */\n screenId?: string;\n /** Local path to downloaded Stitch HTML */\n htmlPath?: string;\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 /** Stitch project configuration (optional) */\n stitch?: StitchConfig;\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, extractTailwindConfig } from '../parsers/html-parser.js';\nimport { computeStructureFingerprint } from '../parsers/structure-parser.js';\nimport type {\n DesignSnapshot,\n DesignToken,\n TokenCategory,\n DriftGuardConfig,\n StructureFingerprint,\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 // Also extract Tailwind config from <script> tags\n const twTokens = extractTailwindConfig(htmlContent, stitchHtmlPath);\n allTokens.push(...twTokens);\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\n // Extract Tailwind config from <script> tags\n const twTokens = extractTailwindConfig(content, file);\n allTokens.push(...twTokens);\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 // Compute structure fingerprint from HTML files\n let structure: StructureFingerprint | undefined;\n try {\n // Use Stitch HTML if provided, otherwise try to find an HTML file\n let htmlForStructure: string | null = null;\n\n if (stitchHtmlPath) {\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (fs.existsSync(absPath)) {\n htmlForStructure = fs.readFileSync(absPath, 'utf-8');\n }\n } else {\n // Try to find any HTML file in the scanned files\n for (const file of files) {\n if (file.endsWith('.html')) {\n const absPath = path.join(projectRoot, file);\n if (fs.existsSync(absPath)) {\n htmlForStructure = fs.readFileSync(absPath, 'utf-8');\n break;\n }\n }\n }\n }\n\n if (htmlForStructure) {\n structure = computeStructureFingerprint(htmlForStructure);\n }\n } catch {\n // Structure fingerprint is optional — don't fail the snapshot\n }\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 structure,\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 // Visual effects\n 'backdrop-filter': 'other',\n 'filter': 'other',\n 'animation': 'other',\n 'transition': 'other',\n};\n\n/**\n * Determine the category for a CSS property\n */\nfunction getCategory(property: string, value?: 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 const lower = property.toLowerCase();\n\n // Color — keyword-based detection (expanded for Shadcn/Tailwind patterns)\n const colorKeywords = [\n 'color', 'bg', 'text', 'foreground', 'background',\n // Semantic color tokens (Shadcn UI / Tailwind)\n 'primary', 'secondary', 'accent', 'muted', 'destructive',\n 'success', 'warning', 'danger', 'error', 'info',\n // UI component colors\n 'card', 'popover', 'border', 'input', 'ring',\n 'sidebar', 'chart', 'glow',\n // State colors\n 'hover', 'active', 'focus', 'disabled',\n ];\n if (colorKeywords.some(kw => lower.includes(kw))) return 'color';\n\n // Font\n if (lower.includes('font') || lower.includes('size') || lower.includes('weight') || lower.includes('line-height') || lower.includes('letter')) return 'font';\n\n // Spacing\n if (lower.includes('spacing') || lower.includes('margin') || lower.includes('padding') || lower.includes('gap') || lower.includes('inset')) return 'spacing';\n\n // Shadow\n if (lower.includes('shadow')) return 'shadow';\n\n // Radius\n if (lower.includes('radius') || lower.includes('rounded')) return 'radius';\n\n // Layout\n if (lower.includes('width') || lower.includes('height') || lower.includes('sidebar-width')) return 'layout';\n\n // Value-based fallback: detect HSL bare values (e.g., \"217 91% 60%\") or hex/rgb/hsl\n if (value) {\n const trimmed = value.trim();\n // HSL bare format: \"H S% L%\" (common in Tailwind/Shadcn)\n if (/^\\d{1,3}\\s+\\d{1,3}%\\s+\\d{1,3}%$/.test(trimmed)) return 'color';\n // hex, rgb, hsl, oklch, color functions\n if (/^(#|rgb|hsl|oklch|lch|lab|color\\()/.test(trimmed)) return 'color';\n }\n\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 value = csstree.generate(node.value);\n const category = getCategory(property, value);\n\n if (!category) return;\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, value) ?? '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 // Visual effects\n 'backdrop-filter': 'other',\n 'filter': 'other',\n 'animation': 'other',\n 'transition': 'other',\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 * Extract design tokens from Tailwind config in <script> tags.\n * Stitch generates HTML with tailwind config like:\n * <script id=\"tailwind-config\">\n * tailwind.config = { theme: { extend: { colors: { \"primary\": \"#256af4\" }, ... } } }\n * </script>\n */\nexport function extractTailwindConfig(\n htmlContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n const $ = cheerio.load(htmlContent);\n\n // Find script tags that contain tailwind config\n $('script').each((_, el) => {\n const scriptId = $(el).attr('id') ?? '';\n const text = $(el).text();\n if (!text) return;\n\n // Match scripts with id=\"tailwind-config\" or containing \"tailwind.config\"\n const isTailwindConfig =\n scriptId.toLowerCase().includes('tailwind') ||\n text.includes('tailwind.config');\n\n if (!isTailwindConfig) return;\n\n // Extract colors\n const colorsMatch = text.match(/colors\\s*:\\s*\\{([^}]+)\\}/);\n if (colorsMatch) {\n const colorsBlock = colorsMatch[1];\n const colorRegex = /[\"']([^\"']+)[\"']\\s*:\\s*[\"']([^\"']+)[\"']/g;\n let match;\n while ((match = colorRegex.exec(colorsBlock)) !== null) {\n tokens.push({\n category: 'color',\n property: `--tw-${match[1]}`,\n value: match[2],\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n\n // Extract borderRadius\n const radiusMatch = text.match(/borderRadius\\s*:\\s*\\{([^}]+)\\}/);\n if (radiusMatch) {\n const radiusBlock = radiusMatch[1];\n const radiusRegex = /[\"']([^\"']+)[\"']\\s*:\\s*[\"']([^\"']+)[\"']/g;\n let match;\n while ((match = radiusRegex.exec(radiusBlock)) !== null) {\n tokens.push({\n category: 'radius',\n property: `--tw-radius-${match[1]}`,\n value: match[2],\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n\n // Extract fontFamily\n const fontMatch = text.match(/fontFamily\\s*:\\s*\\{([^}]+)\\}/);\n if (fontMatch) {\n const fontBlock = fontMatch[1];\n // Match: \"display\": [\"Inter\", \"sans-serif\"] or \"body\": [\"Roboto\"]\n const fontRegex = /[\"']([^\"']+)[\"']\\s*:\\s*\\[([^\\]]+)\\]/g;\n let match;\n while ((match = fontRegex.exec(fontBlock)) !== null) {\n const familyValues = match[2]\n .split(',')\n .map(v => v.trim().replace(/[\"']/g, ''))\n .join(', ');\n tokens.push({\n category: 'font',\n property: `--tw-font-${match[1]}`,\n value: familyValues,\n selector: '[tailwind.config]',\n file: filePath,\n });\n }\n }\n });\n\n return tokens;\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 StructureDriftReport,\n} from '../types/index.js';\nimport { scanProject, loadConfig } from './snapshot.js';\nimport { computeStructureFingerprint, compareStructure } from '../parsers/structure-parser.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 // Structure drift detection (v0.2.0+)\n let structureDrift: StructureDriftReport | undefined;\n\n if (snapshot.structure) {\n try {\n // Find HTML content to compute current structure\n const config = loadConfig(projectRoot);\n const fg = (await import('fast-glob')).default;\n const htmlFiles = await fg(config.htmlFiles, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n let htmlContent: string | null = null;\n const fs = await import('node:fs');\n const path = await import('node:path');\n\n for (const file of htmlFiles) {\n const absPath = path.join(projectRoot, file);\n if (fs.existsSync(absPath)) {\n htmlContent = fs.readFileSync(absPath, 'utf-8');\n break;\n }\n }\n\n if (htmlContent) {\n const currentStructure = computeStructureFingerprint(htmlContent);\n const details = compareStructure(snapshot.structure, currentStructure);\n structureDrift = {\n changed: details.length > 0,\n details,\n };\n }\n } catch {\n // Structure comparison is optional — don't fail the check\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 structureDrift,\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","import type {\n DriftItem,\n DesignToken,\n SyncChange,\n SyncResult,\n SyncDirection,\n TokenCategory,\n} from '../types/index.js';\n\n/**\n * Human-readable labels for token categories\n */\nconst CATEGORY_LABELS: Record<TokenCategory, string> = {\n color: 'color',\n font: 'font',\n spacing: 'spacing',\n shadow: 'shadow',\n radius: 'border-radius',\n layout: 'layout',\n other: 'style',\n};\n\n/**\n * Properties that are design tokens (CSS custom properties or Tailwind config).\n * Inline style properties like 'display', 'align-items' are NOT design tokens.\n */\nfunction isDesignTokenProperty(property: string): boolean {\n // CSS custom properties (--*) and Tailwind config tokens (--tw-*)\n if (property.startsWith('--')) return true;\n // font-family is a design token\n if (property === 'font-family') return true;\n return false;\n}\n\n/**\n * Normalize a token key for matching across Stitch HTML and code CSS.\n * Maps between different naming conventions:\n * Stitch Tailwind config: --tw-primary\n * Code CSS variable: --primary\n */\nfunction normalizeTokenKey(property: string): string {\n return property\n .replace(/^--tw-/, '--') // --tw-primary → --primary\n .replace(/^--tw-font-/, '--font-') // --tw-font-display → --font-display\n .toLowerCase();\n}\n\n/**\n * Convert DriftItems into SyncChanges\n */\nexport function driftItemsToSyncChanges(items: DriftItem[]): SyncChange[] {\n return items.map((item) => {\n if (item.changeType === 'deleted') {\n return {\n category: item.original.category,\n property: item.original.property,\n fromValue: item.original.value,\n toValue: '',\n action: 'remove' as const,\n };\n }\n\n if (item.changeType === 'added') {\n return {\n category: (item.current ?? item.original).category,\n property: (item.current ?? item.original).property,\n fromValue: '',\n toValue: (item.current ?? item.original).value,\n action: 'add' as const,\n };\n }\n\n // modified\n return {\n category: item.original.category,\n property: item.original.property,\n fromValue: item.original.value,\n toValue: item.current?.value ?? '',\n action: 'update' as const,\n };\n });\n}\n\n/**\n * Generate a natural language prompt from sync changes.\n * This prompt is designed for Stitch's `edit_screens` API.\n */\nexport function generateSyncPrompt(changes: SyncChange[]): string {\n if (changes.length === 0) {\n return '';\n }\n\n // Group changes by category\n const grouped = new Map<TokenCategory, SyncChange[]>();\n for (const change of changes) {\n const existing = grouped.get(change.category) ?? [];\n existing.push(change);\n grouped.set(change.category, existing);\n }\n\n const lines: string[] = [\n 'Update the following design tokens to match the latest code changes:',\n '',\n ];\n\n for (const [category, categoryChanges] of grouped) {\n const label = CATEGORY_LABELS[category];\n\n for (const change of categoryChanges) {\n const propName = cleanPropertyName(change.property);\n\n if (change.action === 'update') {\n lines.push(\n `- Change ${label} '${propName}' from ${change.fromValue} to ${change.toValue}`,\n );\n } else if (change.action === 'add') {\n lines.push(\n `- Add new ${label} '${propName}' with value ${change.toValue}`,\n );\n } else if (change.action === 'remove') {\n lines.push(\n `- Remove ${label} '${propName}' (was ${change.fromValue})`,\n );\n }\n }\n }\n\n lines.push('');\n lines.push(\n 'Keep all other design elements unchanged. Only modify the specified tokens.',\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Clean up property names for human-readable prompts.\n */\nfunction cleanPropertyName(property: string): string {\n return property\n .replace(/^--tw-/, '')\n .replace(/^--tw-radius-/, '')\n .replace(/^--tw-font-/, '')\n .replace(/^--/, '');\n}\n\n/**\n * Build a SyncResult for pushing code changes to Stitch.\n */\nexport function syncToStitch(driftItems: DriftItem[]): SyncResult {\n const changes = driftItemsToSyncChanges(driftItems);\n const prompt = generateSyncPrompt(changes);\n\n return {\n direction: 'to-stitch' as SyncDirection,\n changes,\n prompt: prompt || undefined,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Build a SyncResult for pulling Stitch changes to code.\n *\n * ONLY compares design tokens that exist in BOTH Stitch and snapshot.\n * Code-only tokens (e.g., Shadcn --background, --sidebar) are NOT\n * flagged as \"removed\" — Stitch doesn't own them.\n */\nexport function syncFromStitch(\n stitchTokens: DesignToken[],\n snapshotTokens: DesignToken[],\n): SyncResult {\n const changes: SyncChange[] = [];\n\n // Filter to design-token-only properties\n const stitchDesignTokens = stitchTokens.filter(t => isDesignTokenProperty(t.property));\n const snapshotDesignTokens = snapshotTokens.filter(t => isDesignTokenProperty(t.property));\n\n // Build normalized lookup maps\n const snapshotMap = new Map<string, DesignToken>();\n for (const token of snapshotDesignTokens) {\n const key = normalizeTokenKey(token.property);\n if (!snapshotMap.has(key)) {\n snapshotMap.set(key, token);\n }\n }\n\n const stitchMap = new Map<string, DesignToken>();\n for (const token of stitchDesignTokens) {\n const key = normalizeTokenKey(token.property);\n if (!stitchMap.has(key)) {\n stitchMap.set(key, token);\n }\n }\n\n // Find updates and adds: stitch token differs or new\n for (const [key, stitchToken] of stitchMap) {\n const snapshotToken = snapshotMap.get(key);\n\n if (!snapshotToken) {\n changes.push({\n category: stitchToken.category,\n property: stitchToken.property,\n fromValue: '',\n toValue: stitchToken.value,\n action: 'add',\n });\n } else if (stitchToken.value !== snapshotToken.value) {\n changes.push({\n category: stitchToken.category,\n property: snapshotToken.property,\n fromValue: snapshotToken.value,\n toValue: stitchToken.value,\n action: 'update',\n });\n }\n }\n\n // Removals: only flag tokens that are clearly Stitch-origin\n // (i.e., --tw-* prefixed in snapshot AND missing from stitch)\n // This avoids false-flagging code-only tokens like --background\n if (stitchDesignTokens.length > 0) {\n for (const [key, snapshotToken] of snapshotMap) {\n const isStitchOrigin = snapshotToken.property.startsWith('--tw-')\n || snapshotToken.selector === '[tailwind.config]';\n if (isStitchOrigin && !stitchMap.has(key)) {\n changes.push({\n category: snapshotToken.category,\n property: snapshotToken.property,\n fromValue: snapshotToken.value,\n toValue: '',\n action: 'remove',\n });\n }\n }\n }\n\n const patchFile = generateCssPatch(changes);\n\n return {\n direction: 'to-code' as SyncDirection,\n changes,\n patchFile: patchFile || undefined,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Apply sync changes to actual CSS files in the project.\n */\nexport function applySyncChanges(\n changes: SyncChange[],\n cssFiles: Map<string, string>,\n): { modifiedFiles: Map<string, string>; appliedCount: number } {\n const modifiedFiles = new Map<string, string>();\n let appliedCount = 0;\n\n const updateChanges = changes.filter(c => c.action === 'update');\n\n for (const [filename, content] of cssFiles) {\n let modified = content;\n let fileChanged = false;\n\n for (const change of updateChanges) {\n // Try CSS custom property replacement\n const propName = change.property.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const escapedFrom = change.fromValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const cssRegex = new RegExp(\n `(${propName}\\\\s*:\\\\s*)${escapedFrom}(\\\\s*[;\\\\n])`,\n 'g',\n );\n\n let newContent = modified.replace(cssRegex, `$1${change.toValue}$2`);\n\n // Also try Tailwind config format in <script> tags:\n // \"primary\": \"#256af4\" → \"primary\": \"#8b5cf6\"\n if (newContent === modified) {\n const twResult = applySyncToHtml(modified, change);\n if (twResult !== modified) {\n newContent = twResult;\n }\n }\n\n if (newContent !== modified) {\n modified = newContent;\n fileChanged = true;\n appliedCount++;\n }\n }\n\n if (fileChanged) {\n modifiedFiles.set(filename, modified);\n }\n }\n\n return { modifiedFiles, appliedCount };\n}\n\n/**\n * Apply a sync change to HTML content by editing Tailwind config <script> values.\n * Handles format: \"primary\": \"#256af4\" → \"primary\": \"#8b5cf6\"\n */\nfunction applySyncToHtml(html: string, change: SyncChange): string {\n // Extract the token name from --tw-primary → primary\n const tokenName = change.property\n .replace(/^--tw-/, '')\n .replace(/^--/, '');\n\n const escapedFrom = change.fromValue.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Match: \"primary\": \"#256af4\" or \"primary\": \"Inter\"\n const regex = new RegExp(\n `(\"${tokenName}\"\\\\s*:\\\\s*)[\"']${escapedFrom}[\"']`,\n 'g',\n );\n\n return html.replace(regex, `$1\"${change.toValue}\"`);\n}\n\n/**\n * Generate CSS variable patch content from sync changes.\n */\nfunction generateCssPatch(changes: SyncChange[]): string {\n if (changes.length === 0) return '';\n\n const lines: string[] = [\n '/* drift-guard sync patch — apply these changes to your CSS */',\n '/* Generated by: drift-guard sync --direction to-code */',\n '',\n ':root {',\n ];\n\n for (const change of changes) {\n if (change.action === 'remove') {\n lines.push(` /* REMOVED: ${change.property}: ${change.fromValue}; */`);\n } else {\n const comment =\n change.action === 'update'\n ? ` /* was: ${change.fromValue} */`\n : ' /* NEW */';\n lines.push(` ${change.property}: ${change.toValue};${comment}`);\n }\n }\n\n lines.push('}');\n return lines.join('\\n');\n}\n"],"mappings":"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","names":["cheerio","config","fg","fs","path","fs","path"]}
|