@stayicon/drift-guard 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -32,6 +32,8 @@ interface DesignSnapshot {
32
32
  tokens: DesignToken[];
33
33
  /** Token count by category */
34
34
  summary: Record<TokenCategory, number>;
35
+ /** DOM structure fingerprint (v0.2.0+) */
36
+ structure?: StructureFingerprint;
35
37
  }
36
38
  /**
37
39
  * A single drift item — one token that changed
@@ -70,6 +72,75 @@ interface DriftReport {
70
72
  changed: number;
71
73
  driftPercent: number;
72
74
  }>;
75
+ /** DOM structure drift report (v0.2.0+) */
76
+ structureDrift?: StructureDriftReport;
77
+ }
78
+ /**
79
+ * DOM structure fingerprint — tracks HTML layout structure
80
+ */
81
+ interface StructureFingerprint {
82
+ /** Semantic tag counts: { header: 1, nav: 1, main: 1, section: 3, footer: 1 } */
83
+ semanticTags: Record<string, number>;
84
+ /** Maximum DOM nesting depth */
85
+ maxDepth: number;
86
+ /** Hash of layout elements (display:flex/grid tags+classes) */
87
+ layoutHash: string;
88
+ /** Hash of body's direct child tag sequence */
89
+ childSequenceHash: string;
90
+ }
91
+ /**
92
+ * Structure drift detection result
93
+ */
94
+ interface StructureDriftReport {
95
+ /** Whether structure changed */
96
+ changed: boolean;
97
+ /** Human-readable change descriptions */
98
+ details: string[];
99
+ }
100
+ /**
101
+ * Sync direction for Stitch ↔ Code synchronization
102
+ */
103
+ type SyncDirection = 'to-stitch' | 'to-code';
104
+ /**
105
+ * A single sync change between Stitch and Code
106
+ */
107
+ interface SyncChange {
108
+ /** Token category */
109
+ category: TokenCategory;
110
+ /** CSS property or token name */
111
+ property: string;
112
+ /** Previous value */
113
+ fromValue: string;
114
+ /** New value */
115
+ toValue: string;
116
+ /** Type of change */
117
+ action: 'update' | 'add' | 'remove';
118
+ }
119
+ /**
120
+ * Result of a sync operation
121
+ */
122
+ interface SyncResult {
123
+ /** Which direction the sync goes */
124
+ direction: SyncDirection;
125
+ /** All changes detected */
126
+ changes: SyncChange[];
127
+ /** Natural language prompt for edit_screens (to-stitch only) */
128
+ prompt?: string;
129
+ /** CSS patch content (to-code only) */
130
+ patchFile?: string;
131
+ /** ISO timestamp */
132
+ timestamp: string;
133
+ }
134
+ /**
135
+ * Stitch project/screen configuration
136
+ */
137
+ interface StitchConfig {
138
+ /** Stitch project ID */
139
+ projectId?: string;
140
+ /** Stitch screen ID */
141
+ screenId?: string;
142
+ /** Local path to downloaded Stitch HTML */
143
+ htmlPath?: string;
73
144
  }
74
145
  /**
75
146
  * Supported AI rule file formats
@@ -89,6 +160,8 @@ interface DriftGuardConfig {
89
160
  trackCategories: TokenCategory[];
90
161
  /** Files/patterns to ignore */
91
162
  ignore: string[];
163
+ /** Stitch project configuration (optional) */
164
+ stitch?: StitchConfig;
92
165
  }
93
166
  /**
94
167
  * Default configuration
@@ -129,4 +202,48 @@ declare function generateRules(snapshot: DesignSnapshot, format: RuleFormat): st
129
202
  */
130
203
  declare function saveRules(projectRoot: string, format: RuleFormat, content: string, append?: boolean): string;
131
204
 
132
- export { DEFAULT_CONFIG, type DesignSnapshot, type DesignToken, type DriftGuardConfig, type DriftItem, type DriftReport, type RuleFormat, type TokenCategory, createSnapshot, detectDrift, generateRules, loadSnapshot, saveRules, saveSnapshot, scanProject };
205
+ /**
206
+ * Convert DriftItems into SyncChanges
207
+ */
208
+ declare function driftItemsToSyncChanges(items: DriftItem[]): SyncChange[];
209
+ /**
210
+ * Generate a natural language prompt from sync changes.
211
+ * This prompt is designed for Stitch's `edit_screens` API.
212
+ */
213
+ declare function generateSyncPrompt(changes: SyncChange[]): string;
214
+ /**
215
+ * Build a SyncResult for pushing code changes to Stitch.
216
+ */
217
+ declare function syncToStitch(driftItems: DriftItem[]): SyncResult;
218
+ /**
219
+ * Build a SyncResult for pulling Stitch changes to code.
220
+ *
221
+ * ONLY compares design tokens that exist in BOTH Stitch and snapshot.
222
+ * Code-only tokens (e.g., Shadcn --background, --sidebar) are NOT
223
+ * flagged as "removed" — Stitch doesn't own them.
224
+ */
225
+ declare function syncFromStitch(stitchTokens: DesignToken[], snapshotTokens: DesignToken[]): SyncResult;
226
+ /**
227
+ * Apply sync changes to actual CSS files in the project.
228
+ */
229
+ declare function applySyncChanges(changes: SyncChange[], cssFiles: Map<string, string>): {
230
+ modifiedFiles: Map<string, string>;
231
+ appliedCount: number;
232
+ };
233
+
234
+ /**
235
+ * Compute a DOM structure fingerprint from HTML content.
236
+ *
237
+ * Captures:
238
+ * 1. Semantic tag counts (header, nav, main, section, etc.)
239
+ * 2. Maximum nesting depth
240
+ * 3. Layout element hash (elements with display:flex/grid)
241
+ * 4. Body direct child tag sequence hash
242
+ */
243
+ declare function computeStructureFingerprint(htmlContent: string): StructureFingerprint;
244
+ /**
245
+ * Compare two structure fingerprints and return human-readable differences
246
+ */
247
+ declare function compareStructure(original: StructureFingerprint, current: StructureFingerprint): string[];
248
+
249
+ export { DEFAULT_CONFIG, type DesignSnapshot, type DesignToken, type DriftGuardConfig, type DriftItem, type DriftReport, type RuleFormat, type StitchConfig, type StructureDriftReport, type StructureFingerprint, type SyncChange, type SyncDirection, type SyncResult, type TokenCategory, applySyncChanges, compareStructure, computeStructureFingerprint, createSnapshot, detectDrift, driftItemsToSyncChanges, generateRules, generateSyncPrompt, loadSnapshot, saveRules, saveSnapshot, scanProject, syncFromStitch, syncToStitch };
package/dist/index.js CHANGED
@@ -1,21 +1,35 @@
1
1
  import {
2
2
  DEFAULT_CONFIG,
3
+ applySyncChanges,
4
+ compareStructure,
5
+ computeStructureFingerprint,
3
6
  createSnapshot,
4
7
  detectDrift,
8
+ driftItemsToSyncChanges,
5
9
  generateRules,
10
+ generateSyncPrompt,
6
11
  loadSnapshot,
7
12
  saveRules,
8
13
  saveSnapshot,
9
- scanProject
10
- } from "./chunk-27T45SVD.js";
14
+ scanProject,
15
+ syncFromStitch,
16
+ syncToStitch
17
+ } from "./chunk-HI6H6PCS.js";
11
18
  export {
12
19
  DEFAULT_CONFIG,
20
+ applySyncChanges,
21
+ compareStructure,
22
+ computeStructureFingerprint,
13
23
  createSnapshot,
14
24
  detectDrift,
25
+ driftItemsToSyncChanges,
15
26
  generateRules,
27
+ generateSyncPrompt,
16
28
  loadSnapshot,
17
29
  saveRules,
18
30
  saveSnapshot,
19
- scanProject
31
+ scanProject,
32
+ syncFromStitch,
33
+ syncToStitch
20
34
  };
21
35
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stayicon/drift-guard",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Protect your UI from AI coding agents' design drift. Detect, prevent, and lock design tokens during AI-assisted development.",
5
5
  "keywords": [
6
6
  "design-drift",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/types/index.ts","../src/core/snapshot.ts","../src/parsers/css-parser.ts","../src/parsers/html-parser.ts","../src/core/drift.ts","../src/core/rules-generator.ts"],"sourcesContent":["// drift-guard type definitions\n\n/**\n * A single design token extracted from CSS/HTML\n */\nexport interface DesignToken {\n /** Token category: color, font, spacing, shadow, radius, layout */\n category: TokenCategory;\n /** CSS property name (e.g., 'color', 'font-family', 'padding') */\n property: string;\n /** Resolved value (e.g., '#1a73e8', '16px', 'Inter') */\n value: string;\n /** CSS selector where this token was found */\n selector: string;\n /** Source file path */\n file: string;\n /** Line number in source file */\n line?: number;\n}\n\nexport type TokenCategory =\n | 'color'\n | 'font'\n | 'spacing'\n | 'shadow'\n | 'radius'\n | 'layout'\n | 'other';\n\n/**\n * Design snapshot — frozen state of all design tokens\n */\nexport interface DesignSnapshot {\n /** Snapshot version */\n version: string;\n /** ISO timestamp when snapshot was created */\n createdAt: string;\n /** Project root directory */\n projectRoot: string;\n /** Source files that were scanned */\n sourceFiles: string[];\n /** All extracted design tokens */\n tokens: DesignToken[];\n /** Token count by category */\n summary: Record<TokenCategory, number>;\n}\n\n/**\n * A single drift item — one token that changed\n */\nexport interface DriftItem {\n /** The original token from the snapshot */\n original: DesignToken;\n /** The current token value (null if token was deleted) */\n current: DesignToken | null;\n /** Type of change */\n changeType: 'modified' | 'deleted' | 'added';\n}\n\n/**\n * Drift detection report\n */\nexport interface DriftReport {\n /** ISO timestamp of the check */\n checkedAt: string;\n /** Snapshot used as baseline */\n snapshotCreatedAt: string;\n /** Total tokens in snapshot */\n totalTokens: number;\n /** Number of changed tokens */\n changedTokens: number;\n /** Drift score: (changed / total) * 100 */\n driftScore: number;\n /** Threshold used for pass/fail */\n threshold: number;\n /** Whether the check passed */\n passed: boolean;\n /** Individual drift items */\n items: DriftItem[];\n /** Summary by category */\n categorySummary: Record<TokenCategory, {\n total: number;\n changed: number;\n driftPercent: number;\n }>;\n}\n\n/**\n * Supported AI rule file formats\n */\nexport type RuleFormat =\n | 'cursorrules'\n | 'claude-md'\n | 'agents-md'\n | 'copilot'\n | 'clinerules';\n\n/**\n * Configuration stored in .design-guard/config.json\n */\nexport interface DriftGuardConfig {\n /** Glob patterns for CSS files to scan */\n cssFiles: string[];\n /** Glob patterns for HTML files to scan */\n htmlFiles: string[];\n /** Default drift threshold (percentage) */\n threshold: number;\n /** Token categories to track */\n trackCategories: TokenCategory[];\n /** Files/patterns to ignore */\n ignore: string[];\n}\n\n/**\n * Default configuration\n */\nexport const DEFAULT_CONFIG: DriftGuardConfig = {\n cssFiles: [\n 'src/**/*.css',\n 'app/**/*.css',\n 'styles/**/*.css',\n '**/*.module.css',\n '**/*.css',\n ],\n htmlFiles: [\n '**/*.html',\n '!node_modules/**',\n '!dist/**',\n '!build/**',\n ],\n threshold: 10,\n trackCategories: ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'],\n ignore: [\n 'node_modules/**',\n 'dist/**',\n 'build/**',\n '.next/**',\n 'coverage/**',\n ],\n};\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport fg from 'fast-glob';\nimport { parseCss, extractCssVariables } from '../parsers/css-parser.js';\nimport { parseHtml, extractStyleBlocks } from '../parsers/html-parser.js';\nimport type {\n DesignSnapshot,\n DesignToken,\n TokenCategory,\n DriftGuardConfig,\n} from '../types/index.js';\nimport { DEFAULT_CONFIG } from '../types/index.js';\n\nconst SNAPSHOT_DIR = '.design-guard';\nconst SNAPSHOT_FILE = 'snapshot.json';\nconst CONFIG_FILE = 'config.json';\n\n/**\n * Get the full path to the snapshot file\n */\nexport function getSnapshotPath(projectRoot: string): string {\n return path.join(projectRoot, SNAPSHOT_DIR, SNAPSHOT_FILE);\n}\n\n/**\n * Get the config path\n */\nexport function getConfigPath(projectRoot: string): string {\n return path.join(projectRoot, SNAPSHOT_DIR, CONFIG_FILE);\n}\n\n/**\n * Load or create config\n */\nexport function loadConfig(projectRoot: string): DriftGuardConfig {\n const configPath = getConfigPath(projectRoot);\n if (fs.existsSync(configPath)) {\n const raw = fs.readFileSync(configPath, 'utf-8');\n return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };\n }\n return { ...DEFAULT_CONFIG };\n}\n\n/**\n * Save config\n */\nexport function saveConfig(projectRoot: string, config: DriftGuardConfig): void {\n const dir = path.join(projectRoot, SNAPSHOT_DIR);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(getConfigPath(projectRoot), JSON.stringify(config, null, 2), 'utf-8');\n}\n\n/**\n * Scan the project and extract all design tokens\n */\nexport async function scanProject(\n projectRoot: string,\n config: DriftGuardConfig,\n stitchHtmlPath?: string,\n): Promise<{ tokens: DesignToken[]; files: string[] }> {\n const allTokens: DesignToken[] = [];\n const scannedFiles: string[] = [];\n\n // 1. If a Stitch HTML file is provided, parse it first\n if (stitchHtmlPath) {\n const absPath = path.resolve(projectRoot, stitchHtmlPath);\n if (fs.existsSync(absPath)) {\n const htmlContent = fs.readFileSync(absPath, 'utf-8');\n const htmlTokens = parseHtml(htmlContent, stitchHtmlPath);\n allTokens.push(...htmlTokens);\n\n // Also parse <style> blocks within the HTML\n const styleBlocks = extractStyleBlocks(htmlContent);\n for (const block of styleBlocks) {\n const cssTokens = parseCss(block, stitchHtmlPath);\n allTokens.push(...cssTokens);\n const vars = extractCssVariables(block, stitchHtmlPath);\n allTokens.push(...vars);\n }\n scannedFiles.push(stitchHtmlPath);\n }\n }\n\n // 2. Scan CSS files\n const cssFiles = await fg(config.cssFiles, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of cssFiles) {\n const absPath = path.join(projectRoot, file);\n const content = fs.readFileSync(absPath, 'utf-8');\n const tokens = parseCss(content, file);\n allTokens.push(...tokens);\n const vars = extractCssVariables(content, file);\n allTokens.push(...vars);\n scannedFiles.push(file);\n }\n\n // 3. Scan HTML files (for inline styles)\n const htmlFiles = await fg(config.htmlFiles, {\n cwd: projectRoot,\n ignore: config.ignore,\n absolute: false,\n });\n\n for (const file of htmlFiles) {\n if (scannedFiles.includes(file)) continue; // Skip if already scanned (e.g., Stitch HTML)\n const absPath = path.join(projectRoot, file);\n const content = fs.readFileSync(absPath, 'utf-8');\n const tokens = parseHtml(content, file);\n allTokens.push(...tokens);\n\n // Parse embedded <style> blocks\n const styleBlocks = extractStyleBlocks(content);\n for (const block of styleBlocks) {\n const cssTokens = parseCss(block, file);\n allTokens.push(...cssTokens);\n }\n scannedFiles.push(file);\n }\n\n // Filter by tracked categories\n const filtered = allTokens.filter(t =>\n config.trackCategories.includes(t.category),\n );\n\n // Deduplicate: same property + selector + file = keep first\n const seen = new Set<string>();\n const deduplicated = filtered.filter(t => {\n const key = `${t.file}:${t.selector}:${t.property}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { tokens: deduplicated, files: scannedFiles };\n}\n\n/**\n * Build category summary from tokens\n */\nfunction buildSummary(tokens: DesignToken[]): Record<TokenCategory, number> {\n const summary: Record<TokenCategory, number> = {\n color: 0,\n font: 0,\n spacing: 0,\n shadow: 0,\n radius: 0,\n layout: 0,\n other: 0,\n };\n\n for (const token of tokens) {\n summary[token.category]++;\n }\n\n return summary;\n}\n\n/**\n * Create a snapshot from the current project state\n */\nexport async function createSnapshot(\n projectRoot: string,\n stitchHtmlPath?: string,\n): Promise<DesignSnapshot> {\n const config = loadConfig(projectRoot);\n const { tokens, files } = await scanProject(projectRoot, config, stitchHtmlPath);\n\n const snapshot: DesignSnapshot = {\n version: '1.0.0',\n createdAt: new Date().toISOString(),\n projectRoot,\n sourceFiles: files,\n tokens,\n summary: buildSummary(tokens),\n };\n\n return snapshot;\n}\n\n/**\n * Save a snapshot to disk\n */\nexport function saveSnapshot(projectRoot: string, snapshot: DesignSnapshot): string {\n const dir = path.join(projectRoot, SNAPSHOT_DIR);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n const snapshotPath = getSnapshotPath(projectRoot);\n fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), 'utf-8');\n return snapshotPath;\n}\n\n/**\n * Load an existing snapshot from disk\n */\nexport function loadSnapshot(projectRoot: string): DesignSnapshot | null {\n const snapshotPath = getSnapshotPath(projectRoot);\n if (!fs.existsSync(snapshotPath)) {\n return null;\n }\n\n const raw = fs.readFileSync(snapshotPath, 'utf-8');\n return JSON.parse(raw) as DesignSnapshot;\n}\n","import * as csstree from 'css-tree';\nimport type { DesignToken, TokenCategory } from '../types/index.js';\n\n/**\n * CSS properties that map to each token category\n */\nconst CATEGORY_MAP: Record<string, TokenCategory> = {\n // Colors\n 'color': 'color',\n 'background-color': 'color',\n 'background': 'color',\n 'border-color': 'color',\n 'outline-color': 'color',\n 'fill': 'color',\n 'stroke': 'color',\n '--color': 'color',\n\n // Fonts\n 'font-family': 'font',\n 'font-size': 'font',\n 'font-weight': 'font',\n 'line-height': 'font',\n 'letter-spacing': 'font',\n\n // Spacing\n 'margin': 'spacing',\n 'margin-top': 'spacing',\n 'margin-right': 'spacing',\n 'margin-bottom': 'spacing',\n 'margin-left': 'spacing',\n 'padding': 'spacing',\n 'padding-top': 'spacing',\n 'padding-right': 'spacing',\n 'padding-bottom': 'spacing',\n 'padding-left': 'spacing',\n 'gap': 'spacing',\n 'row-gap': 'spacing',\n 'column-gap': 'spacing',\n\n // Shadows\n 'box-shadow': 'shadow',\n 'text-shadow': 'shadow',\n\n // Radius\n 'border-radius': 'radius',\n 'border-top-left-radius': 'radius',\n 'border-top-right-radius': 'radius',\n 'border-bottom-left-radius': 'radius',\n 'border-bottom-right-radius': 'radius',\n\n // Layout\n 'display': 'layout',\n 'flex-direction': 'layout',\n 'justify-content': 'layout',\n 'align-items': 'layout',\n 'grid-template-columns': 'layout',\n 'grid-template-rows': 'layout',\n 'position': 'layout',\n};\n\n/**\n * Determine the category for a CSS property\n */\nfunction getCategory(property: string): TokenCategory | null {\n // Exact match\n if (CATEGORY_MAP[property]) {\n return CATEGORY_MAP[property];\n }\n\n // CSS custom properties (variables)\n if (property.startsWith('--')) {\n // Try to infer category from variable name\n const lower = property.toLowerCase();\n if (lower.includes('color') || lower.includes('bg') || lower.includes('text')) return 'color';\n if (lower.includes('font') || lower.includes('size') || lower.includes('weight')) return 'font';\n if (lower.includes('spacing') || lower.includes('margin') || lower.includes('padding') || lower.includes('gap')) return 'spacing';\n if (lower.includes('shadow')) return 'shadow';\n if (lower.includes('radius') || lower.includes('rounded')) return 'radius';\n return 'other';\n }\n\n return null;\n}\n\n/**\n * Parse CSS content and extract design tokens\n */\nexport function parseCss(\n cssContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n\n try {\n const ast = csstree.parse(cssContent, {\n filename: filePath,\n positions: true,\n });\n\n csstree.walk(ast, {\n visit: 'Declaration',\n enter(node) {\n const property = node.property;\n const category = getCategory(property);\n\n if (!category) return;\n\n const value = csstree.generate(node.value);\n\n // Skip empty, inherit, initial, unset\n if (!value || ['inherit', 'initial', 'unset', 'revert'].includes(value)) return;\n\n // Find the parent selector\n let selector = ':root';\n let parent = this.atrule ?? this.rule;\n if (parent && parent.type === 'Rule' && parent.prelude) {\n selector = csstree.generate(parent.prelude);\n }\n\n tokens.push({\n category,\n property,\n value: value.trim(),\n selector,\n file: filePath,\n line: node.loc?.start?.line,\n });\n },\n });\n } catch (error) {\n // If parsing fails, return what we have so far\n console.warn(`Warning: Failed to parse CSS in ${filePath}: ${(error as Error).message}`);\n }\n\n return tokens;\n}\n\n/**\n * Extract CSS custom properties (variables) specifically\n */\nexport function extractCssVariables(\n cssContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n // Match --variable: value patterns\n const varRegex = /--([\\w-]+)\\s*:\\s*([^;]+)/g;\n let match;\n\n while ((match = varRegex.exec(cssContent)) !== null) {\n const property = `--${match[1]}`;\n const value = match[2].trim();\n const category = getCategory(property) ?? 'other';\n\n tokens.push({\n category,\n property,\n value,\n selector: ':root',\n file: filePath,\n line: cssContent.substring(0, match.index).split('\\n').length,\n });\n }\n\n return tokens;\n}\n","import * as cheerio from 'cheerio';\nimport type { DesignToken, TokenCategory } from '../types/index.js';\n\n/**\n * Style properties we want to extract from inline styles\n */\nconst TRACKED_PROPERTIES: Record<string, TokenCategory> = {\n 'color': 'color',\n 'background-color': 'color',\n 'background': 'color',\n 'border-color': 'color',\n 'font-family': 'font',\n 'font-size': 'font',\n 'font-weight': 'font',\n 'line-height': 'font',\n 'margin': 'spacing',\n 'margin-top': 'spacing',\n 'margin-right': 'spacing',\n 'margin-bottom': 'spacing',\n 'margin-left': 'spacing',\n 'padding': 'spacing',\n 'padding-top': 'spacing',\n 'padding-right': 'spacing',\n 'padding-bottom': 'spacing',\n 'padding-left': 'spacing',\n 'gap': 'spacing',\n 'box-shadow': 'shadow',\n 'text-shadow': 'shadow',\n 'border-radius': 'radius',\n 'display': 'layout',\n 'flex-direction': 'layout',\n 'justify-content': 'layout',\n 'align-items': 'layout',\n};\n\n/**\n * Parse inline styles from a style attribute string\n */\nfunction parseInlineStyle(styleStr: string): Record<string, string> {\n const result: Record<string, string> = {};\n const declarations = styleStr.split(';').filter(Boolean);\n\n for (const decl of declarations) {\n const colonIdx = decl.indexOf(':');\n if (colonIdx === -1) continue;\n\n const prop = decl.substring(0, colonIdx).trim().toLowerCase();\n const val = decl.substring(colonIdx + 1).trim();\n\n if (prop && val) {\n result[prop] = val;\n }\n }\n\n return result;\n}\n\n/**\n * Parse HTML content and extract design tokens from:\n * 1. <style> blocks\n * 2. Inline style attributes\n */\nexport function parseHtml(\n htmlContent: string,\n filePath: string,\n): DesignToken[] {\n const tokens: DesignToken[] = [];\n const $ = cheerio.load(htmlContent);\n\n // 1. Extract from <style> blocks (will be handled by CSS parser upstream)\n // We return them separately as raw CSS strings\n const styleBlocks: string[] = [];\n $('style').each((_, el) => {\n const text = $(el).text();\n if (text) {\n styleBlocks.push(text);\n }\n });\n\n // 2. Extract from inline style attributes\n $('[style]').each((_, el) => {\n const element = $(el);\n const styleStr = element.attr('style');\n if (!styleStr) return;\n\n const selector = buildSelectorPath($, element);\n const styles = parseInlineStyle(styleStr);\n\n for (const [prop, value] of Object.entries(styles)) {\n const category = TRACKED_PROPERTIES[prop];\n if (!category) continue;\n\n tokens.push({\n category,\n property: prop,\n value,\n selector: `[inline] ${selector}`,\n file: filePath,\n });\n }\n });\n\n return tokens;\n}\n\n/**\n * Extract raw CSS from <style> blocks in HTML\n */\nexport function extractStyleBlocks(htmlContent: string): string[] {\n const $ = cheerio.load(htmlContent);\n const blocks: string[] = [];\n\n $('style').each((_, el) => {\n const text = $(el).text();\n if (text) {\n blocks.push(text);\n }\n });\n\n return blocks;\n}\n\n/**\n * Build a human-readable selector path for an element\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildSelectorPath($: cheerio.CheerioAPI, element: any): string {\n const parts: string[] = [];\n const tagName = element.prop('tagName')?.toLowerCase() ?? 'div';\n const id = element.attr('id');\n const className = element.attr('class');\n\n let sel = tagName;\n if (id) {\n sel += `#${id}`;\n } else if (className) {\n const classList = className.split(/\\s+/).slice(0, 2).join('.');\n sel += `.${classList}`;\n }\n\n parts.push(sel);\n return parts.join(' > ');\n}\n","import type {\n DesignSnapshot,\n DesignToken,\n DriftItem,\n DriftReport,\n TokenCategory,\n} from '../types/index.js';\nimport { scanProject, loadConfig } from './snapshot.js';\n\n/**\n * Compare two tokens by their key (file + selector + property)\n */\nfunction tokenKey(token: DesignToken): string {\n return `${token.file}::${token.selector}::${token.property}`;\n}\n\n/**\n * Detect design drift between a snapshot and the current project state\n */\nexport async function detectDrift(\n projectRoot: string,\n snapshot: DesignSnapshot,\n threshold: number = 10,\n): Promise<DriftReport> {\n const config = loadConfig(projectRoot);\n const { tokens: currentTokens } = await scanProject(projectRoot, config);\n\n // Build lookup maps\n const snapshotMap = new Map<string, DesignToken>();\n for (const token of snapshot.tokens) {\n snapshotMap.set(tokenKey(token), token);\n }\n\n const currentMap = new Map<string, DesignToken>();\n for (const token of currentTokens) {\n currentMap.set(tokenKey(token), token);\n }\n\n const driftItems: DriftItem[] = [];\n\n // Check for modified and deleted tokens\n for (const [key, original] of snapshotMap) {\n const current = currentMap.get(key);\n\n if (!current) {\n // Token was deleted\n driftItems.push({\n original,\n current: null,\n changeType: 'deleted',\n });\n } else if (current.value !== original.value) {\n // Token was modified\n driftItems.push({\n original,\n current,\n changeType: 'modified',\n });\n }\n }\n\n // Check for added tokens (in current but not in snapshot)\n // Note: Added tokens are tracked but don't count as \"drift\" by default\n // because adding new styles is expected behavior during feature development\n\n const totalTokens = snapshot.tokens.length;\n const changedTokens = driftItems.length;\n const driftScore = totalTokens > 0\n ? Math.round((changedTokens / totalTokens) * 100 * 100) / 100\n : 0;\n\n // Build category summary\n const categorySummary: DriftReport['categorySummary'] = {} as DriftReport['categorySummary'];\n const categories: TokenCategory[] = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout', 'other'];\n\n for (const cat of categories) {\n const total = snapshot.tokens.filter(t => t.category === cat).length;\n const changed = driftItems.filter(d => d.original.category === cat).length;\n categorySummary[cat] = {\n total,\n changed,\n driftPercent: total > 0 ? Math.round((changed / total) * 100 * 100) / 100 : 0,\n };\n }\n\n return {\n checkedAt: new Date().toISOString(),\n snapshotCreatedAt: snapshot.createdAt,\n totalTokens,\n changedTokens,\n driftScore,\n threshold,\n passed: driftScore <= threshold,\n items: driftItems,\n categorySummary,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { DesignSnapshot, RuleFormat } from '../types/index.js';\n\n/**\n * Generate AI agent rules from a design snapshot\n */\nexport function generateRules(\n snapshot: DesignSnapshot,\n format: RuleFormat,\n): string {\n const generators: Record<RuleFormat, () => string> = {\n 'cursorrules': () => generateCursorRules(snapshot),\n 'claude-md': () => generateClaudeMd(snapshot),\n 'agents-md': () => generateAgentsMd(snapshot),\n 'copilot': () => generateCopilotInstructions(snapshot),\n 'clinerules': () => generateClineRules(snapshot),\n };\n\n return generators[format]();\n}\n\n/**\n * Save rules to the appropriate file\n */\nexport function saveRules(\n projectRoot: string,\n format: RuleFormat,\n content: string,\n append: boolean = false,\n): string {\n const fileMap: Record<RuleFormat, string> = {\n 'cursorrules': '.cursorrules',\n 'claude-md': 'CLAUDE.md',\n 'agents-md': 'AGENTS.md',\n 'copilot': '.github/copilot-instructions.md',\n 'clinerules': '.clinerules',\n };\n\n const filePath = path.join(projectRoot, fileMap[format]);\n const dir = path.dirname(filePath);\n\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n\n if (append && fs.existsSync(filePath)) {\n const existing = fs.readFileSync(filePath, 'utf-8');\n fs.writeFileSync(filePath, existing + '\\n\\n' + content, 'utf-8');\n } else {\n fs.writeFileSync(filePath, content, 'utf-8');\n }\n\n return filePath;\n}\n\n// ─── Template generators ───────────────────────────────────\n\nfunction buildTokenList(snapshot: DesignSnapshot): string {\n const colorTokens = snapshot.tokens.filter(t => t.category === 'color');\n const fontTokens = snapshot.tokens.filter(t => t.category === 'font');\n const spacingTokens = snapshot.tokens.filter(t => t.category === 'spacing');\n const radiusTokens = snapshot.tokens.filter(t => t.category === 'radius');\n\n const lines: string[] = [];\n\n if (colorTokens.length > 0) {\n lines.push('### Colors (DO NOT CHANGE)');\n const unique = [...new Set(colorTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 30)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (fontTokens.length > 0) {\n lines.push('\\n### Fonts (DO NOT CHANGE)');\n const unique = [...new Set(fontTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 15)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (spacingTokens.length > 0) {\n lines.push('\\n### Spacing (DO NOT CHANGE)');\n const unique = [...new Set(spacingTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 20)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n if (radiusTokens.length > 0) {\n lines.push('\\n### Border Radius (DO NOT CHANGE)');\n const unique = [...new Set(radiusTokens.map(t => `${t.property}: ${t.value}`))];\n for (const t of unique.slice(0, 10)) {\n lines.push(`- \\`${t}\\``);\n }\n }\n\n return lines.join('\\n');\n}\n\nfunction generateCursorRules(snapshot: DesignSnapshot): string {\n return `# Design Guard — Locked Design Tokens\n# Generated by drift-guard on ${snapshot.createdAt}\n# DO NOT modify these values when adding features or fixing bugs.\n\n## CRITICAL: Design Drift Prevention\n\nWhen modifying this codebase, you MUST preserve the following design tokens exactly as they are.\nDo NOT change colors, fonts, spacing, or border-radius values unless explicitly asked to update the design.\n\nIf you need to add new styles, use the existing design tokens as reference.\nIf a design change is needed, tell the user to run \\`drift-guard snapshot update\\` after making the change.\n\n${buildTokenList(snapshot)}\n\n## Rules\n1. NEVER change existing CSS color values\n2. NEVER change font-family, font-size, or font-weight values\n3. NEVER change margin, padding, or gap values on existing elements\n4. NEVER change border-radius values\n5. When adding new components, use the SAME design tokens listed above\n6. If you must change a design token, warn the user first\n`;\n}\n\nfunction generateClaudeMd(snapshot: DesignSnapshot): string {\n return `# Design Guard — Locked Design Tokens\n\n> Generated by drift-guard on ${snapshot.createdAt}\n> Run \\`npx drift-guard check\\` to verify design consistency\n\n## Design Drift Prevention Rules\n\nWhen working on this codebase, preserve the existing design exactly as-is.\nThe following design tokens are LOCKED and should NOT be modified:\n\n${buildTokenList(snapshot)}\n\n## Important\n- Adding new features should NOT change existing visual styles\n- If a CSS change affects existing components, flag it to the user\n- New components should follow the same design language (colors, fonts, spacing)\n- Run \\`npx drift-guard check\\` before committing to verify no design drift\n`;\n}\n\nfunction generateAgentsMd(snapshot: DesignSnapshot): string {\n return `# AGENTS.md — Design Guard Rules\n\n> Auto-generated by drift-guard. Do not edit manually.\n> Snapshot: ${snapshot.createdAt} | Tokens: ${snapshot.tokens.length}\n\n## Design Token Protection\n\nThis project uses drift-guard to prevent design drift during AI-assisted development.\nThe following design tokens are protected:\n\n${buildTokenList(snapshot)}\n\n## Agent Instructions\n1. Do NOT modify existing CSS values for colors, fonts, spacing, or border-radius\n2. New components MUST use the same design tokens\n3. Run \\`npx drift-guard check\\` before suggesting commits\n4. If design changes are intentional, inform the user to update the snapshot\n`;\n}\n\nfunction generateCopilotInstructions(snapshot: DesignSnapshot): string {\n return `# Copilot Instructions — Design Guard\n\nThis project has locked design tokens. When suggesting code changes:\n\n1. Preserve all existing CSS color, font, spacing, and border-radius values\n2. Use the project's design tokens for new components\n3. Do not override CSS custom properties (variables starting with --)\n\nProtected tokens (${snapshot.tokens.length} total):\n\n${buildTokenList(snapshot)}\n`;\n}\n\nfunction generateClineRules(snapshot: DesignSnapshot): string {\n return `# Design Guard — Protected Design Tokens\n# Generated: ${snapshot.createdAt}\n\nDESIGN_PROTECTION=true\n\n## Rules\n- Do NOT change existing CSS values\n- Preserve colors: ${snapshot.summary.color} tokens locked\n- Preserve fonts: ${snapshot.summary.font} tokens locked\n- Preserve spacing: ${snapshot.summary.spacing} tokens locked\n- Preserve radius: ${snapshot.summary.radius} tokens locked\n\n## Verification\nRun: npx drift-guard check\nThreshold: Changes to more than 10% of design tokens will be flagged\n\n${buildTokenList(snapshot)}\n`;\n}\n"],"mappings":"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","names":["fs","path"]}