@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/cli/index.js CHANGED
@@ -1,19 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_CONFIG,
4
+ applySyncChanges,
4
5
  createSnapshot,
5
6
  detectDrift,
7
+ extractCssVariables,
8
+ extractStyleBlocks,
9
+ extractTailwindConfig,
6
10
  generateRules,
7
11
  loadConfig,
8
12
  loadSnapshot,
13
+ parseCss,
14
+ parseHtml,
9
15
  saveConfig,
10
16
  saveRules,
11
- saveSnapshot
12
- } from "../chunk-27T45SVD.js";
17
+ saveSnapshot,
18
+ syncFromStitch,
19
+ syncToStitch
20
+ } from "../chunk-HI6H6PCS.js";
13
21
 
14
22
  // src/cli/index.ts
15
- import { Command } from "commander";
16
- import chalk5 from "chalk";
23
+ import { Command as Command2 } from "commander";
24
+ import chalk6 from "chalk";
17
25
 
18
26
  // src/cli/init.ts
19
27
  import chalk from "chalk";
@@ -115,6 +123,17 @@ function printTextReport(report) {
115
123
  }
116
124
  }
117
125
  console.log();
126
+ if (report.structureDrift) {
127
+ if (report.structureDrift.changed) {
128
+ console.log(chalk2.bold(" \u{1F3D7}\uFE0F Structure Drift:"));
129
+ for (const detail of report.structureDrift.details) {
130
+ console.log(chalk2.yellow(` \u26A0\uFE0F ${detail}`));
131
+ }
132
+ console.log();
133
+ } else {
134
+ console.log(chalk2.green(" \u{1F3D7}\uFE0F DOM structure: No changes detected \u2705\n"));
135
+ }
136
+ }
118
137
  if (!report.passed) {
119
138
  console.log(chalk2.yellow(" \u{1F4A1} To accept these changes, run:"));
120
139
  console.log(chalk2.cyan(" npx drift-guard snapshot update\n"));
@@ -190,14 +209,426 @@ async function snapshotCommand(options) {
190
209
  console.log(chalk4.dim("\u{1F4A1} Remember to regenerate rules: ") + chalk4.cyan("npx drift-guard rules\n"));
191
210
  }
192
211
 
212
+ // src/cli/hook.ts
213
+ import fs from "fs";
214
+ import path from "path";
215
+ import chalk5 from "chalk";
216
+ async function hookInstallCommand(options) {
217
+ const cwd = process.cwd();
218
+ const threshold = options.threshold ?? "10";
219
+ if (!fs.existsSync(path.join(cwd, ".git"))) {
220
+ console.error(chalk5.red("\u2717 Not a git repository. Run this from a git project root."));
221
+ process.exit(1);
222
+ }
223
+ const hasHusky = fs.existsSync(path.join(cwd, ".husky"));
224
+ const hasLefthook = fs.existsSync(path.join(cwd, "lefthook.yml")) || fs.existsSync(path.join(cwd, ".lefthook.yml"));
225
+ if (hasHusky) {
226
+ const hookFile2 = path.join(cwd, ".husky", "pre-commit");
227
+ const hookCommand = `npx drift-guard check --threshold ${threshold} --ci`;
228
+ if (fs.existsSync(hookFile2)) {
229
+ const existing = fs.readFileSync(hookFile2, "utf-8");
230
+ if (existing.includes("drift-guard")) {
231
+ console.log(chalk5.yellow("\u26A0 drift-guard hook already exists in .husky/pre-commit"));
232
+ return;
233
+ }
234
+ fs.appendFileSync(hookFile2, `
235
+ ${hookCommand}
236
+ `);
237
+ } else {
238
+ fs.writeFileSync(hookFile2, `#!/usr/bin/env sh
239
+ . "$(dirname -- "$0")/_/husky.sh"
240
+
241
+ ${hookCommand}
242
+ `);
243
+ }
244
+ console.log(chalk5.green("\u2713 Added drift-guard check to .husky/pre-commit"));
245
+ return;
246
+ }
247
+ if (hasLefthook) {
248
+ console.log(chalk5.yellow("\u26A0 lefthook detected. Add this to your lefthook.yml:"));
249
+ console.log(chalk5.cyan(`
250
+ pre-commit:
251
+ commands:
252
+ drift-guard:
253
+ run: npx drift-guard check --threshold ${threshold} --ci
254
+ `));
255
+ return;
256
+ }
257
+ const hooksDir = path.join(cwd, ".git", "hooks");
258
+ const hookFile = path.join(hooksDir, "pre-commit");
259
+ const hookScript = `#!/usr/bin/env sh
260
+ # drift-guard pre-commit hook
261
+ # Checks for design token drift before commits
262
+ # Use --force flag to bypass: git commit --no-verify
263
+
264
+ npx drift-guard check --threshold ${threshold} --ci
265
+
266
+ if [ $? -ne 0 ]; then
267
+ echo ""
268
+ echo "\\033[31m\u2717 Design drift detected! Commit blocked.\\033[0m"
269
+ echo " Run 'npx drift-guard check' for details."
270
+ echo " If changes are intentional, run 'npx drift-guard snapshot update'"
271
+ echo " Or use 'git commit --no-verify' to bypass."
272
+ exit 1
273
+ fi
274
+ `;
275
+ if (!fs.existsSync(hooksDir)) {
276
+ fs.mkdirSync(hooksDir, { recursive: true });
277
+ }
278
+ if (fs.existsSync(hookFile)) {
279
+ const existing = fs.readFileSync(hookFile, "utf-8");
280
+ if (existing.includes("drift-guard")) {
281
+ console.log(chalk5.yellow("\u26A0 drift-guard hook already installed"));
282
+ return;
283
+ }
284
+ fs.appendFileSync(hookFile, `
285
+ ${hookScript}`);
286
+ console.log(chalk5.green("\u2713 Appended drift-guard to existing pre-commit hook"));
287
+ } else {
288
+ fs.writeFileSync(hookFile, hookScript);
289
+ try {
290
+ fs.chmodSync(hookFile, 493);
291
+ } catch {
292
+ }
293
+ console.log(chalk5.green("\u2713 Installed drift-guard pre-commit hook"));
294
+ }
295
+ console.log(chalk5.dim(` Threshold: ${threshold}%`));
296
+ console.log(chalk5.dim(" Bypass: git commit --no-verify"));
297
+ }
298
+ async function hookUninstallCommand() {
299
+ const cwd = process.cwd();
300
+ const huskyHook = path.join(cwd, ".husky", "pre-commit");
301
+ if (fs.existsSync(huskyHook)) {
302
+ const content = fs.readFileSync(huskyHook, "utf-8");
303
+ if (content.includes("drift-guard")) {
304
+ const cleaned = content.replace(/\n?npx drift-guard.*\n?/g, "\n");
305
+ fs.writeFileSync(huskyHook, cleaned);
306
+ console.log(chalk5.green("\u2713 Removed drift-guard from .husky/pre-commit"));
307
+ return;
308
+ }
309
+ }
310
+ const gitHook = path.join(cwd, ".git", "hooks", "pre-commit");
311
+ if (fs.existsSync(gitHook)) {
312
+ const content = fs.readFileSync(gitHook, "utf-8");
313
+ if (content.includes("drift-guard")) {
314
+ if (content.includes("# drift-guard pre-commit hook") && !content.includes("\n#!/usr/bin/env sh\n")) {
315
+ fs.unlinkSync(gitHook);
316
+ console.log(chalk5.green("\u2713 Removed drift-guard pre-commit hook"));
317
+ } else {
318
+ const cleaned = content.replace(/# drift-guard pre-commit hook[\s\S]*?fi\n?/g, "");
319
+ fs.writeFileSync(gitHook, cleaned);
320
+ console.log(chalk5.green("\u2713 Removed drift-guard from pre-commit hook"));
321
+ }
322
+ return;
323
+ }
324
+ }
325
+ console.log(chalk5.yellow("\u26A0 No drift-guard hook found to uninstall"));
326
+ }
327
+
328
+ // src/cli/sync.ts
329
+ import { Command } from "commander";
330
+ import path2 from "path";
331
+ import fs2 from "fs";
332
+ import fg from "fast-glob";
333
+ var syncCommand = new Command("sync").description("Synchronize design tokens between Stitch and your codebase").requiredOption(
334
+ "-d, --direction <direction>",
335
+ "Sync direction: to-stitch (push code changes to Stitch) or to-code (pull Stitch changes)"
336
+ ).option(
337
+ "--stitch-html <path>",
338
+ "Path to Stitch HTML file (for to-code direction)"
339
+ ).option(
340
+ "--stitch-project <id>",
341
+ "Stitch project ID (for to-stitch direction)"
342
+ ).option(
343
+ "--stitch-screen <id>",
344
+ "Stitch screen ID (for to-stitch direction)"
345
+ ).option("--apply", "Auto-apply changes: full HTML replacement + CSS patching (to-code only)", false).option("--target <path>", "Target local HTML file to replace with Stitch version (to-code --apply)").option("--dry-run", "Preview changes without applying", false).option("--json", "Output as JSON", false).action(async (options) => {
346
+ const projectRoot = process.cwd();
347
+ const direction = options.direction;
348
+ if (direction !== "to-stitch" && direction !== "to-code") {
349
+ console.error(
350
+ '\u274C Invalid direction. Use "to-stitch" or "to-code".'
351
+ );
352
+ process.exit(1);
353
+ }
354
+ if (direction === "to-stitch") {
355
+ await handleToStitch(projectRoot, options);
356
+ } else {
357
+ await handleToCode(projectRoot, options);
358
+ }
359
+ });
360
+ async function handleToStitch(projectRoot, options) {
361
+ const snapshot = loadSnapshot(projectRoot);
362
+ if (!snapshot) {
363
+ console.error('\u274C No snapshot found. Run "drift-guard init" first.');
364
+ process.exit(1);
365
+ }
366
+ const report = await detectDrift(projectRoot, snapshot, 0);
367
+ if (report.items.length === 0) {
368
+ console.log("\u2705 No changes detected. Stitch and code are in sync!");
369
+ return;
370
+ }
371
+ const result = syncToStitch(report.items);
372
+ if (options.json) {
373
+ console.log(JSON.stringify(result, null, 2));
374
+ return;
375
+ }
376
+ console.log("");
377
+ console.log("\u{1F504} drift-guard sync \u2192 to-stitch");
378
+ console.log(` ${result.changes.length} change(s) detected
379
+ `);
380
+ for (const change of result.changes) {
381
+ const icon = change.action === "update" ? "\u270F\uFE0F" : change.action === "add" ? "\u2795" : "\u{1F5D1}\uFE0F";
382
+ console.log(
383
+ ` ${icon} ${change.property}: ${change.fromValue || "(none)"} \u2192 ${change.toValue || "(removed)"}`
384
+ );
385
+ }
386
+ console.log("");
387
+ console.log("\u2501".repeat(60));
388
+ console.log("\u{1F4CB} Stitch edit_screens prompt:");
389
+ console.log("\u2501".repeat(60));
390
+ console.log("");
391
+ console.log(result.prompt);
392
+ console.log("");
393
+ console.log("\u2501".repeat(60));
394
+ const projectId = options.stitchProject;
395
+ const screenId = options.stitchScreen;
396
+ if (projectId && screenId) {
397
+ console.log("");
398
+ console.log("\u{1F4A1} To apply these changes in Stitch, run:");
399
+ console.log("");
400
+ console.log(` edit_screens({`);
401
+ console.log(` projectId: "${projectId}",`);
402
+ console.log(` selectedScreenIds: ["${screenId}"],`);
403
+ console.log(` prompt: "<the prompt above>"`);
404
+ console.log(` })`);
405
+ } else {
406
+ console.log("");
407
+ console.log(
408
+ "\u{1F4A1} Copy the prompt above and use it with Stitch MCP edit_screens"
409
+ );
410
+ console.log(
411
+ " or pass --stitch-project and --stitch-screen for a ready-to-use call."
412
+ );
413
+ }
414
+ }
415
+ async function handleToCode(projectRoot, options) {
416
+ const snapshot = loadSnapshot(projectRoot);
417
+ if (!snapshot) {
418
+ console.error('\u274C No snapshot found. Run "drift-guard init" first.');
419
+ process.exit(1);
420
+ }
421
+ const config = loadConfig(projectRoot);
422
+ const stitchHtmlPath = options.stitchHtml ?? config.stitch?.htmlPath;
423
+ if (!stitchHtmlPath) {
424
+ console.error(
425
+ "\u274C No Stitch HTML file specified."
426
+ );
427
+ console.error(
428
+ " Use --stitch-html <path> or set stitch.htmlPath in .design-guard/config.json"
429
+ );
430
+ process.exit(1);
431
+ }
432
+ const absPath = path2.resolve(projectRoot, stitchHtmlPath);
433
+ if (!fs2.existsSync(absPath)) {
434
+ console.error(`\u274C Stitch HTML file not found: ${absPath}`);
435
+ process.exit(1);
436
+ }
437
+ const htmlContent = fs2.readFileSync(absPath, "utf-8");
438
+ const stitchTokens = [];
439
+ const htmlTokens = parseHtml(htmlContent, stitchHtmlPath);
440
+ stitchTokens.push(...htmlTokens);
441
+ const styleBlocks = extractStyleBlocks(htmlContent);
442
+ for (const block of styleBlocks) {
443
+ stitchTokens.push(...parseCss(block, stitchHtmlPath));
444
+ stitchTokens.push(...extractCssVariables(block, stitchHtmlPath));
445
+ }
446
+ const twTokens = extractTailwindConfig(htmlContent, stitchHtmlPath);
447
+ stitchTokens.push(...twTokens);
448
+ const result = syncFromStitch(stitchTokens, snapshot.tokens);
449
+ if (options.json) {
450
+ console.log(JSON.stringify(result, null, 2));
451
+ return;
452
+ }
453
+ if (options.apply && !options.dryRun) {
454
+ console.log("");
455
+ console.log("\u{1F504} drift-guard sync \u2192 to-code (FULL SYNC)");
456
+ console.log(" Stitch HTML is the source of truth.\n");
457
+ const targetPath = options.target;
458
+ const localStitchFiles = await findLocalStitchHtml(projectRoot, absPath, targetPath);
459
+ let htmlReplaced = false;
460
+ if (localStitchFiles.length > 0) {
461
+ console.log("\u{1F4C4} Step 1: Full HTML replacement");
462
+ for (const localFile of localStitchFiles) {
463
+ const localAbsPath = path2.resolve(projectRoot, localFile);
464
+ const localContent = fs2.readFileSync(localAbsPath, "utf-8");
465
+ if (localContent !== htmlContent) {
466
+ fs2.writeFileSync(localAbsPath, htmlContent, "utf-8");
467
+ console.log(` \u2705 Replaced: ${localFile} (Stitch \u2192 local)`);
468
+ htmlReplaced = true;
469
+ } else {
470
+ console.log(` \u2139\uFE0F Already identical: ${localFile}`);
471
+ }
472
+ }
473
+ } else {
474
+ console.log("\u{1F4C4} Step 1: No local Stitch HTML found to replace.");
475
+ console.log(" \u{1F4A1} Use --target <path> to specify the target file.");
476
+ }
477
+ if (result.changes.length > 0) {
478
+ console.log(`
479
+ \u{1F4DD} Step 2: Patching ${result.changes.length} design token(s) in CSS files`);
480
+ for (const change of result.changes) {
481
+ const icon = change.action === "update" ? "\u270F\uFE0F" : change.action === "add" ? "\u2795" : "\u{1F5D1}\uFE0F";
482
+ console.log(
483
+ ` ${icon} ${change.property}: ${change.fromValue || "(none)"} \u2192 ${change.toValue || "(removed)"}`
484
+ );
485
+ }
486
+ const cssFileMap = await loadProjectCssFiles(projectRoot, config);
487
+ const { modifiedFiles, appliedCount } = applySyncChanges(result.changes, cssFileMap);
488
+ if (appliedCount > 0) {
489
+ for (const [file, content] of modifiedFiles) {
490
+ const fullPath = path2.resolve(projectRoot, file);
491
+ fs2.writeFileSync(fullPath, content, "utf-8");
492
+ console.log(` \u2705 Patched: ${file}`);
493
+ }
494
+ }
495
+ } else {
496
+ console.log("\n\u{1F4DD} Step 2: No design token changes \u2014 tokens already match.");
497
+ }
498
+ if (htmlReplaced || result.changes.length > 0) {
499
+ console.log("\n\u{1F4F8} Step 3: Updating snapshot...");
500
+ const { execSync } = await import("child_process");
501
+ try {
502
+ execSync(
503
+ `node "${path2.resolve(projectRoot, "..", "drift-guard", "dist", "cli", "index.js")}" snapshot update`,
504
+ { cwd: projectRoot, stdio: "pipe" }
505
+ );
506
+ } catch {
507
+ try {
508
+ execSync("npx drift-guard snapshot update", {
509
+ cwd: projectRoot,
510
+ stdio: "pipe"
511
+ });
512
+ } catch {
513
+ console.log(" \u26A0\uFE0F Could not auto-update snapshot. Run: drift-guard snapshot update");
514
+ }
515
+ }
516
+ console.log(" \u2705 Snapshot updated.");
517
+ }
518
+ console.log("\n\u{1F50D} Step 4: Verifying full sync...");
519
+ if (localStitchFiles.length > 0) {
520
+ let allMatch = true;
521
+ for (const localFile of localStitchFiles) {
522
+ const localAbsPath = path2.resolve(projectRoot, localFile);
523
+ const localContent = fs2.readFileSync(localAbsPath, "utf-8");
524
+ if (localContent === htmlContent) {
525
+ console.log(` \u2705 ${localFile} \u2014 100% identical to Stitch`);
526
+ } else {
527
+ console.log(` \u274C ${localFile} \u2014 content differs!`);
528
+ allMatch = false;
529
+ }
530
+ }
531
+ if (allMatch) {
532
+ console.log("\n \u{1F389} FULL SYNC VERIFIED \u2014 Stitch and code are 100% identical!");
533
+ }
534
+ }
535
+ } else if (result.changes.length === 0) {
536
+ console.log("\u2705 Stitch and code design tokens are already in sync!");
537
+ console.log(" \u{1F4A1} Use --apply for full HTML content sync (text, layout, structure).");
538
+ } else {
539
+ console.log("");
540
+ console.log("\u{1F504} drift-guard sync \u2192 to-code");
541
+ console.log(` ${result.changes.length} design token change(s) from Stitch
542
+ `);
543
+ for (const change of result.changes) {
544
+ const icon = change.action === "update" ? "\u270F\uFE0F" : change.action === "add" ? "\u2795" : "\u{1F5D1}\uFE0F";
545
+ console.log(
546
+ ` ${icon} ${change.property}: ${change.fromValue || "(none)"} \u2192 ${change.toValue || "(removed)"}`
547
+ );
548
+ }
549
+ if (result.patchFile) {
550
+ console.log("");
551
+ console.log("\u2501".repeat(60));
552
+ console.log("\u{1F4CB} CSS patch to apply:");
553
+ console.log("\u2501".repeat(60));
554
+ console.log("");
555
+ console.log(result.patchFile);
556
+ console.log("");
557
+ console.log("\u2501".repeat(60));
558
+ if (!options.dryRun) {
559
+ const patchPath = path2.join(
560
+ projectRoot,
561
+ ".design-guard",
562
+ "sync-patch.css"
563
+ );
564
+ fs2.writeFileSync(patchPath, result.patchFile, "utf-8");
565
+ console.log(`
566
+ \u{1F4BE} Patch saved to: ${patchPath}`);
567
+ console.log(" Use --apply for full HTML content + token sync.");
568
+ } else {
569
+ console.log("\n\u{1F50D} Dry run \u2014 no files written.");
570
+ }
571
+ }
572
+ }
573
+ }
574
+ async function findLocalStitchHtml(projectRoot, sourceAbsPath, targetPath) {
575
+ if (targetPath) {
576
+ return [targetPath];
577
+ }
578
+ const stitchFiles = await fg(
579
+ ["**/stitch-design.html", "**/stitch*.html"],
580
+ {
581
+ cwd: projectRoot,
582
+ ignore: ["node_modules/**", "dist/**", "build/**"],
583
+ absolute: false
584
+ }
585
+ );
586
+ const sourceRelative = path2.relative(projectRoot, sourceAbsPath);
587
+ return stitchFiles.filter((f) => f !== sourceRelative);
588
+ }
589
+ async function loadProjectCssFiles(projectRoot, config) {
590
+ const cssFileMap = /* @__PURE__ */ new Map();
591
+ for (const pattern of config.cssFiles) {
592
+ const matches = await fg(pattern, {
593
+ cwd: projectRoot,
594
+ ignore: config.ignore,
595
+ absolute: false
596
+ });
597
+ for (const file of matches) {
598
+ const absPath = path2.resolve(projectRoot, file);
599
+ if (fs2.existsSync(absPath)) {
600
+ cssFileMap.set(file, fs2.readFileSync(absPath, "utf-8"));
601
+ }
602
+ }
603
+ }
604
+ const htmlPatterns = ["**/*.html"];
605
+ const htmlIgnore = ["node_modules/**", "dist/**", "build/**", ".next/**"];
606
+ const htmlMatches = await fg(htmlPatterns, {
607
+ cwd: projectRoot,
608
+ ignore: htmlIgnore,
609
+ absolute: false
610
+ });
611
+ for (const file of htmlMatches) {
612
+ const absPath = path2.resolve(projectRoot, file);
613
+ if (fs2.existsSync(absPath)) {
614
+ cssFileMap.set(file, fs2.readFileSync(absPath, "utf-8"));
615
+ }
616
+ }
617
+ return cssFileMap;
618
+ }
619
+
193
620
  // src/cli/index.ts
194
- var program = new Command();
621
+ var program = new Command2();
195
622
  program.name("drift-guard").description(
196
- chalk5.bold("\u{1F6E1}\uFE0F drift-guard") + " \u2014 Protect your UI from AI coding agents' design drift.\n\n Detect and prevent design token changes during AI-assisted development.\n Lock your colors, fonts, spacing, and layout before AI agents touch your code."
197
- ).version("0.1.0");
623
+ chalk6.bold("\u{1F6E1}\uFE0F drift-guard") + " \u2014 Protect your UI from AI coding agents' design drift.\n\n Detect and prevent design token changes during AI-assisted development.\n Lock your colors, fonts, spacing, and layout before AI agents touch your code."
624
+ ).version("0.2.0");
198
625
  program.command("init").description("Initialize drift-guard and create a design snapshot").option("--from <path>", "Create snapshot from a Stitch/HTML file").option("--threshold <number>", "Set default drift threshold percentage", "10").action(initCommand);
199
626
  program.command("check").description("Check for design drift against the saved snapshot").option("--threshold <number>", "Override drift threshold percentage").option("--output <format>", "Output format: text or json", "text").option("--ci", "CI mode: exit with code 1 on drift exceeding threshold").action(checkCommand);
200
627
  program.command("rules").description("Generate AI agent rule files from the design snapshot").option("--format <type>", "Rule format: cursorrules, claude-md, agents-md, copilot, clinerules, all", "all").option("--append", "Append to existing rule files instead of overwriting").action(rulesCommand);
201
628
  program.command("snapshot").description("Manage design snapshots").command("update").description("Update the snapshot to reflect current design (after intentional changes)").option("--from <path>", "Update from a specific Stitch/HTML file").action(snapshotCommand);
629
+ var hook = program.command("hook").description("Manage pre-commit hook for automatic drift checking");
630
+ hook.command("install").description("Install a pre-commit hook that runs drift-guard check").option("--threshold <number>", "Drift threshold percentage for the hook", "10").action(hookInstallCommand);
631
+ hook.command("uninstall").description("Remove the drift-guard pre-commit hook").action(hookUninstallCommand);
632
+ program.addCommand(syncCommand);
202
633
  program.parse();
203
634
  //# sourceMappingURL=index.js.map
@@ -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"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { initCommand } from './init.js';\nimport { checkCommand } from './check.js';\nimport { rulesCommand } from './rules.js';\nimport { snapshotCommand } from './snapshot-cmd.js';\n\nconst program = new Command();\n\nprogram\n .name('drift-guard')\n .description(\n chalk.bold('🛡️ drift-guard') +\n ' — Protect your UI from AI coding agents\\' design drift.\\n\\n' +\n ' Detect and prevent design token changes during AI-assisted development.\\n' +\n ' Lock your colors, fonts, spacing, and layout before AI agents touch your code.'\n )\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Initialize drift-guard and create a design snapshot')\n .option('--from <path>', 'Create snapshot from a Stitch/HTML file')\n .option('--threshold <number>', 'Set default drift threshold percentage', '10')\n .action(initCommand);\n\nprogram\n .command('check')\n .description('Check for design drift against the saved snapshot')\n .option('--threshold <number>', 'Override drift threshold percentage')\n .option('--output <format>', 'Output format: text or json', 'text')\n .option('--ci', 'CI mode: exit with code 1 on drift exceeding threshold')\n .action(checkCommand);\n\nprogram\n .command('rules')\n .description('Generate AI agent rule files from the design snapshot')\n .option('--format <type>', 'Rule format: cursorrules, claude-md, agents-md, copilot, clinerules, all', 'all')\n .option('--append', 'Append to existing rule files instead of overwriting')\n .action(rulesCommand);\n\nprogram\n .command('snapshot')\n .description('Manage design snapshots')\n .command('update')\n .description('Update the snapshot to reflect current design (after intentional changes)')\n .option('--from <path>', 'Update from a specific Stitch/HTML file')\n .action(snapshotCommand);\n\nprogram.parse();\n","import chalk from 'chalk';\nimport { createSnapshot, saveSnapshot, saveConfig } from '../core/snapshot.js';\nimport { DEFAULT_CONFIG } from '../types/index.js';\n\ninterface InitOptions {\n from?: string;\n threshold?: string;\n}\n\nexport async function initCommand(options: InitOptions): Promise<void> {\n const projectRoot = process.cwd();\n const threshold = parseInt(options.threshold ?? '10', 10);\n\n console.log(chalk.bold('\\n🛡️ drift-guard init\\n'));\n console.log(chalk.dim('Scanning project for design tokens...\\n'));\n\n // Save config\n const config = { ...DEFAULT_CONFIG, threshold };\n saveConfig(projectRoot, config);\n\n // Create snapshot\n const snapshot = await createSnapshot(projectRoot, options.from);\n\n if (snapshot.tokens.length === 0) {\n console.log(chalk.yellow('⚠️ No design tokens found.'));\n console.log(chalk.dim(' Make sure you have CSS files or use --from <stitch.html>'));\n console.log(chalk.dim(' Supported patterns: src/**/*.css, app/**/*.css, styles/**/*.css\\n'));\n return;\n }\n\n // Save snapshot\n const snapshotPath = saveSnapshot(projectRoot, snapshot);\n\n // Report\n console.log(chalk.green('✅ Design snapshot created!\\n'));\n console.log(chalk.dim(' Snapshot: ') + chalk.white(snapshotPath));\n console.log(chalk.dim(' Files scanned: ') + chalk.white(snapshot.sourceFiles.length.toString()));\n console.log(chalk.dim(' Tokens locked: ') + chalk.white(snapshot.tokens.length.toString()));\n console.log(chalk.dim(' Threshold: ') + chalk.white(`${threshold}%`));\n console.log();\n\n // Token summary\n console.log(chalk.bold(' Token Summary:'));\n const categories = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'] as const;\n for (const cat of categories) {\n const count = snapshot.summary[cat];\n if (count > 0) {\n const icon = { color: '🎨', font: '📝', spacing: '📏', shadow: '🌫️', radius: '⭕', layout: '📐' }[cat];\n console.log(chalk.dim(` ${icon} ${cat}: `) + chalk.white(count.toString()));\n }\n }\n\n console.log();\n console.log(chalk.dim('Next steps:'));\n console.log(chalk.cyan(' 1. ') + 'Add .design-guard/ to .gitignore (optional)');\n console.log(chalk.cyan(' 2. ') + chalk.bold('npx drift-guard rules') + ' — Generate AI agent protection rules');\n console.log(chalk.cyan(' 3. ') + chalk.bold('npx drift-guard check') + ' — Check for design drift anytime');\n console.log();\n}\n","import chalk from 'chalk';\nimport { loadSnapshot, loadConfig } from '../core/snapshot.js';\nimport { detectDrift } from '../core/drift.js';\nimport type { DriftReport, DriftItem } from '../types/index.js';\n\ninterface CheckOptions {\n threshold?: string;\n output?: string;\n ci?: boolean;\n}\n\nexport async function checkCommand(options: CheckOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n // Load snapshot\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.log(chalk.red('\\n❌ No snapshot found.'));\n console.log(chalk.dim(' Run ') + chalk.cyan('npx drift-guard init') + chalk.dim(' first.\\n'));\n process.exit(1);\n }\n\n const config = loadConfig(projectRoot);\n const threshold = options.threshold\n ? parseInt(options.threshold, 10)\n : config.threshold;\n\n console.log(chalk.bold('\\n🛡️ drift-guard check\\n'));\n console.log(chalk.dim(`Comparing against snapshot from ${snapshot.createdAt}...\\n`));\n\n // Detect drift\n const report = await detectDrift(projectRoot, snapshot, threshold);\n\n if (options.output === 'json') {\n console.log(JSON.stringify(report, null, 2));\n } else {\n printTextReport(report);\n }\n\n // Exit code for CI\n if (!report.passed && (options.ci || process.env['CI'])) {\n process.exit(1);\n }\n\n if (!report.passed) {\n process.exitCode = 1;\n }\n}\n\nfunction printTextReport(report: DriftReport): void {\n // Score display\n const scoreColor = report.passed ? chalk.green : chalk.red;\n const icon = report.passed ? '✅' : '🚨';\n\n console.log(`${icon} ${chalk.bold('Drift Score:')} ${scoreColor(`${report.driftScore}%`)} (threshold: ${report.threshold}%)`);\n console.log(chalk.dim(` ${report.changedTokens} of ${report.totalTokens} tokens changed\\n`));\n\n if (report.items.length === 0) {\n console.log(chalk.green(' No design drift detected. Your design is intact! 🎉\\n'));\n return;\n }\n\n // Category breakdown\n console.log(chalk.bold(' Category Breakdown:'));\n const categories = ['color', 'font', 'spacing', 'shadow', 'radius', 'layout'] as const;\n for (const cat of categories) {\n const summary = report.categorySummary[cat];\n if (summary.total === 0) continue;\n\n const catIcon = { color: '🎨', font: '📝', spacing: '📏', shadow: '🌫️', radius: '⭕', layout: '📐' }[cat];\n const catColor = summary.changed > 0 ? chalk.red : chalk.green;\n console.log(` ${catIcon} ${cat}: ${catColor(`${summary.changed}/${summary.total}`)} (${summary.driftPercent}%)`);\n }\n console.log();\n\n // Detailed changes (max 20)\n const itemsToShow = report.items.slice(0, 20);\n if (itemsToShow.length > 0) {\n console.log(chalk.bold(' Changes:'));\n for (const item of itemsToShow) {\n printDriftItem(item);\n }\n\n if (report.items.length > 20) {\n console.log(chalk.dim(` ... and ${report.items.length - 20} more changes\\n`));\n }\n }\n\n console.log();\n\n if (!report.passed) {\n console.log(chalk.yellow(' 💡 To accept these changes, run:'));\n console.log(chalk.cyan(' npx drift-guard snapshot update\\n'));\n }\n}\n\nfunction printDriftItem(item: DriftItem): void {\n const { original, current, changeType } = item;\n\n switch (changeType) {\n case 'modified':\n console.log(\n chalk.yellow(' ~') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.red(original.value) +\n chalk.dim(' → ') +\n chalk.green(current?.value ?? 'removed')\n );\n break;\n case 'deleted':\n console.log(\n chalk.red(' -') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.red(original.value) +\n chalk.dim(' [deleted]')\n );\n break;\n case 'added':\n console.log(\n chalk.green(' +') +\n chalk.dim(` ${original.file} `) +\n chalk.white(`${original.property}: `) +\n chalk.green(original.value)\n );\n break;\n }\n}\n","import chalk from 'chalk';\nimport { loadSnapshot } from '../core/snapshot.js';\nimport { generateRules, saveRules } from '../core/rules-generator.js';\nimport type { RuleFormat } from '../types/index.js';\n\ninterface RulesOptions {\n format?: string;\n append?: boolean;\n}\n\nconst ALL_FORMATS: RuleFormat[] = ['cursorrules', 'claude-md', 'agents-md', 'copilot', 'clinerules'];\n\nexport async function rulesCommand(options: RulesOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n const snapshot = loadSnapshot(projectRoot);\n if (!snapshot) {\n console.log(chalk.red('\\n❌ No snapshot found.'));\n console.log(chalk.dim(' Run ') + chalk.cyan('npx drift-guard init') + chalk.dim(' first.\\n'));\n process.exit(1);\n }\n\n const formats: RuleFormat[] = options.format === 'all' || !options.format\n ? ALL_FORMATS\n : [options.format as RuleFormat];\n\n console.log(chalk.bold('\\n🛡️ drift-guard rules\\n'));\n console.log(chalk.dim(`Generating AI protection rules from ${snapshot.tokens.length} locked tokens...\\n`));\n\n for (const format of formats) {\n try {\n const content = generateRules(snapshot, format);\n const filePath = saveRules(projectRoot, format, content, options.append ?? false);\n console.log(chalk.green(' ✅ ') + chalk.white(filePath));\n } catch (error) {\n console.log(chalk.red(' ❌ ') + chalk.white(format) + chalk.dim(`: ${(error as Error).message}`));\n }\n }\n\n console.log();\n console.log(chalk.dim('Your AI coding agents will now protect these design tokens.'));\n console.log(chalk.dim('Supported tools: Cursor, Claude Code, Codex, GitHub Copilot, Cline\\n'));\n}\n","import chalk from 'chalk';\nimport { createSnapshot, saveSnapshot } from '../core/snapshot.js';\n\ninterface SnapshotOptions {\n from?: string;\n}\n\nexport async function snapshotCommand(options: SnapshotOptions): Promise<void> {\n const projectRoot = process.cwd();\n\n console.log(chalk.bold('\\n🛡️ drift-guard snapshot update\\n'));\n console.log(chalk.dim('Re-scanning project and updating snapshot...\\n'));\n\n const snapshot = await createSnapshot(projectRoot, options.from);\n\n if (snapshot.tokens.length === 0) {\n console.log(chalk.yellow('⚠️ No design tokens found.\\n'));\n return;\n }\n\n const snapshotPath = saveSnapshot(projectRoot, snapshot);\n\n console.log(chalk.green('✅ Snapshot updated!\\n'));\n console.log(chalk.dim(' File: ') + chalk.white(snapshotPath));\n console.log(chalk.dim(' Tokens: ') + chalk.white(snapshot.tokens.length.toString()));\n console.log(chalk.dim(' Updated: ') + chalk.white(snapshot.createdAt));\n console.log();\n console.log(chalk.dim('💡 Remember to regenerate rules: ') + chalk.cyan('npx drift-guard rules\\n'));\n}\n"],"mappings":";;;;;;;;;;;;;;AACA,SAAS,eAAe;AACxB,OAAOA,YAAW;;;ACFlB,OAAO,WAAW;AASlB,eAAsB,YAAY,SAAqC;AACrE,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,YAAY,SAAS,QAAQ,aAAa,MAAM,EAAE;AAExD,UAAQ,IAAI,MAAM,KAAK,uCAA2B,CAAC;AACnD,UAAQ,IAAI,MAAM,IAAI,yCAAyC,CAAC;AAGhE,QAAM,SAAS,EAAE,GAAG,gBAAgB,UAAU;AAC9C,aAAW,aAAa,MAAM;AAG9B,QAAM,WAAW,MAAM,eAAe,aAAa,QAAQ,IAAI;AAE/D,MAAI,SAAS,OAAO,WAAW,GAAG;AAChC,YAAQ,IAAI,MAAM,OAAO,uCAA6B,CAAC;AACvD,YAAQ,IAAI,MAAM,IAAI,4DAA4D,CAAC;AACnF,YAAQ,IAAI,MAAM,IAAI,qEAAqE,CAAC;AAC5F;AAAA,EACF;AAGA,QAAM,eAAe,aAAa,aAAa,QAAQ;AAGvD,UAAQ,IAAI,MAAM,MAAM,mCAA8B,CAAC;AACvD,UAAQ,IAAI,MAAM,IAAI,cAAc,IAAI,MAAM,MAAM,YAAY,CAAC;AACjE,UAAQ,IAAI,MAAM,IAAI,mBAAmB,IAAI,MAAM,MAAM,SAAS,YAAY,OAAO,SAAS,CAAC,CAAC;AAChG,UAAQ,IAAI,MAAM,IAAI,mBAAmB,IAAI,MAAM,MAAM,SAAS,OAAO,OAAO,SAAS,CAAC,CAAC;AAC3F,UAAQ,IAAI,MAAM,IAAI,eAAe,IAAI,MAAM,MAAM,GAAG,SAAS,GAAG,CAAC;AACrE,UAAQ,IAAI;AAGZ,UAAQ,IAAI,MAAM,KAAK,kBAAkB,CAAC;AAC1C,QAAM,aAAa,CAAC,SAAS,QAAQ,WAAW,UAAU,UAAU,QAAQ;AAC5E,aAAW,OAAO,YAAY;AAC5B,UAAM,QAAQ,SAAS,QAAQ,GAAG;AAClC,QAAI,QAAQ,GAAG;AACb,YAAM,OAAO,EAAE,OAAO,aAAM,MAAM,aAAM,SAAS,aAAM,QAAQ,mBAAO,QAAQ,UAAK,QAAQ,YAAK,EAAE,GAAG;AACrG,cAAQ,IAAI,MAAM,IAAI,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,MAAM,MAAM,MAAM,SAAS,CAAC,CAAC;AAAA,IAC7E;AAAA,EACF;AAEA,UAAQ,IAAI;AACZ,UAAQ,IAAI,MAAM,IAAI,aAAa,CAAC;AACpC,UAAQ,IAAI,MAAM,KAAK,OAAO,IAAI,6CAA6C;AAC/E,UAAQ,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,uBAAuB,IAAI,4CAAuC;AAC/G,UAAQ,IAAI,MAAM,KAAK,OAAO,IAAI,MAAM,KAAK,uBAAuB,IAAI,wCAAmC;AAC3G,UAAQ,IAAI;AACd;;;AC1DA,OAAOC,YAAW;AAWlB,eAAsB,aAAa,SAAsC;AACvE,QAAM,cAAc,QAAQ,IAAI;AAGhC,QAAM,WAAW,aAAa,WAAW;AACzC,MAAI,CAAC,UAAU;AACb,YAAQ,IAAIC,OAAM,IAAI,6BAAwB,CAAC;AAC/C,YAAQ,IAAIA,OAAM,IAAI,QAAQ,IAAIA,OAAM,KAAK,sBAAsB,IAAIA,OAAM,IAAI,WAAW,CAAC;AAC7F,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,SAAS,WAAW,WAAW;AACrC,QAAM,YAAY,QAAQ,YACtB,SAAS,QAAQ,WAAW,EAAE,IAC9B,OAAO;AAEX,UAAQ,IAAIA,OAAM,KAAK,wCAA4B,CAAC;AACpD,UAAQ,IAAIA,OAAM,IAAI,mCAAmC,SAAS,SAAS;AAAA,CAAO,CAAC;AAGnF,QAAM,SAAS,MAAM,YAAY,aAAa,UAAU,SAAS;AAEjE,MAAI,QAAQ,WAAW,QAAQ;AAC7B,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC7C,OAAO;AACL,oBAAgB,MAAM;AAAA,EACxB;AAGA,MAAI,CAAC,OAAO,WAAW,QAAQ,MAAM,QAAQ,IAAI,IAAI,IAAI;AACvD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,WAAW;AAAA,EACrB;AACF;AAEA,SAAS,gBAAgB,QAA2B;AAElD,QAAM,aAAa,OAAO,SAASA,OAAM,QAAQA,OAAM;AACvD,QAAM,OAAO,OAAO,SAAS,WAAM;AAEnC,UAAQ,IAAI,GAAG,IAAI,IAAIA,OAAM,KAAK,cAAc,CAAC,IAAI,WAAW,GAAG,OAAO,UAAU,GAAG,CAAC,gBAAgB,OAAO,SAAS,IAAI;AAC5H,UAAQ,IAAIA,OAAM,IAAI,MAAM,OAAO,aAAa,OAAO,OAAO,WAAW;AAAA,CAAmB,CAAC;AAE7F,MAAI,OAAO,MAAM,WAAW,GAAG;AAC7B,YAAQ,IAAIA,OAAM,MAAM,iEAA0D,CAAC;AACnF;AAAA,EACF;AAGA,UAAQ,IAAIA,OAAM,KAAK,wBAAwB,CAAC;AAChD,QAAM,aAAa,CAAC,SAAS,QAAQ,WAAW,UAAU,UAAU,QAAQ;AAC5E,aAAW,OAAO,YAAY;AAC5B,UAAM,UAAU,OAAO,gBAAgB,GAAG;AAC1C,QAAI,QAAQ,UAAU,EAAG;AAEzB,UAAM,UAAU,EAAE,OAAO,aAAM,MAAM,aAAM,SAAS,aAAM,QAAQ,mBAAO,QAAQ,UAAK,QAAQ,YAAK,EAAE,GAAG;AACxG,UAAM,WAAW,QAAQ,UAAU,IAAIA,OAAM,MAAMA,OAAM;AACzD,YAAQ,IAAI,MAAM,OAAO,IAAI,GAAG,KAAK,SAAS,GAAG,QAAQ,OAAO,IAAI,QAAQ,KAAK,EAAE,CAAC,KAAK,QAAQ,YAAY,IAAI;AAAA,EACnH;AACA,UAAQ,IAAI;AAGZ,QAAM,cAAc,OAAO,MAAM,MAAM,GAAG,EAAE;AAC5C,MAAI,YAAY,SAAS,GAAG;AAC1B,YAAQ,IAAIA,OAAM,KAAK,aAAa,CAAC;AACrC,eAAW,QAAQ,aAAa;AAC9B,qBAAe,IAAI;AAAA,IACrB;AAEA,QAAI,OAAO,MAAM,SAAS,IAAI;AAC5B,cAAQ,IAAIA,OAAM,IAAI,cAAc,OAAO,MAAM,SAAS,EAAE;AAAA,CAAiB,CAAC;AAAA,IAChF;AAAA,EACF;AAEA,UAAQ,IAAI;AAEZ,MAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,IAAIA,OAAM,OAAO,4CAAqC,CAAC;AAC/D,YAAQ,IAAIA,OAAM,KAAK,yCAAyC,CAAC;AAAA,EACnE;AACF;AAEA,SAAS,eAAe,MAAuB;AAC7C,QAAM,EAAE,UAAU,SAAS,WAAW,IAAI;AAE1C,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,cAAQ;AAAA,QACNA,OAAM,OAAO,MAAM,IACnBA,OAAM,IAAI,IAAI,SAAS,IAAI,GAAG,IAC9BA,OAAM,MAAM,GAAG,SAAS,QAAQ,IAAI,IACpCA,OAAM,IAAI,SAAS,KAAK,IACxBA,OAAM,IAAI,UAAK,IACfA,OAAM,MAAM,SAAS,SAAS,SAAS;AAAA,MACzC;AACA;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACNA,OAAM,IAAI,MAAM,IAChBA,OAAM,IAAI,IAAI,SAAS,IAAI,GAAG,IAC9BA,OAAM,MAAM,GAAG,SAAS,QAAQ,IAAI,IACpCA,OAAM,IAAI,SAAS,KAAK,IACxBA,OAAM,IAAI,YAAY;AAAA,MACxB;AACA;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACNA,OAAM,MAAM,MAAM,IAClBA,OAAM,IAAI,IAAI,SAAS,IAAI,GAAG,IAC9BA,OAAM,MAAM,GAAG,SAAS,QAAQ,IAAI,IACpCA,OAAM,MAAM,SAAS,KAAK;AAAA,MAC5B;AACA;AAAA,EACJ;AACF;;;AChIA,OAAOC,YAAW;AAUlB,IAAM,cAA4B,CAAC,eAAe,aAAa,aAAa,WAAW,YAAY;AAEnG,eAAsB,aAAa,SAAsC;AACvE,QAAM,cAAc,QAAQ,IAAI;AAEhC,QAAM,WAAW,aAAa,WAAW;AACzC,MAAI,CAAC,UAAU;AACb,YAAQ,IAAIC,OAAM,IAAI,6BAAwB,CAAC;AAC/C,YAAQ,IAAIA,OAAM,IAAI,QAAQ,IAAIA,OAAM,KAAK,sBAAsB,IAAIA,OAAM,IAAI,WAAW,CAAC;AAC7F,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,UAAwB,QAAQ,WAAW,SAAS,CAAC,QAAQ,SAC/D,cACA,CAAC,QAAQ,MAAoB;AAEjC,UAAQ,IAAIA,OAAM,KAAK,wCAA4B,CAAC;AACpD,UAAQ,IAAIA,OAAM,IAAI,uCAAuC,SAAS,OAAO,MAAM;AAAA,CAAqB,CAAC;AAEzG,aAAW,UAAU,SAAS;AAC5B,QAAI;AACF,YAAM,UAAU,cAAc,UAAU,MAAM;AAC9C,YAAM,WAAW,UAAU,aAAa,QAAQ,SAAS,QAAQ,UAAU,KAAK;AAChF,cAAQ,IAAIA,OAAM,MAAM,WAAM,IAAIA,OAAM,MAAM,QAAQ,CAAC;AAAA,IACzD,SAAS,OAAO;AACd,cAAQ,IAAIA,OAAM,IAAI,WAAM,IAAIA,OAAM,MAAM,MAAM,IAAIA,OAAM,IAAI,KAAM,MAAgB,OAAO,EAAE,CAAC;AAAA,IAClG;AAAA,EACF;AAEA,UAAQ,IAAI;AACZ,UAAQ,IAAIA,OAAM,IAAI,6DAA6D,CAAC;AACpF,UAAQ,IAAIA,OAAM,IAAI,sEAAsE,CAAC;AAC/F;;;AC1CA,OAAOC,YAAW;AAOlB,eAAsB,gBAAgB,SAAyC;AAC7E,QAAM,cAAc,QAAQ,IAAI;AAEhC,UAAQ,IAAIC,OAAM,KAAK,kDAAsC,CAAC;AAC9D,UAAQ,IAAIA,OAAM,IAAI,gDAAgD,CAAC;AAEvE,QAAM,WAAW,MAAM,eAAe,aAAa,QAAQ,IAAI;AAE/D,MAAI,SAAS,OAAO,WAAW,GAAG;AAChC,YAAQ,IAAIA,OAAM,OAAO,yCAA+B,CAAC;AACzD;AAAA,EACF;AAEA,QAAM,eAAe,aAAa,aAAa,QAAQ;AAEvD,UAAQ,IAAIA,OAAM,MAAM,4BAAuB,CAAC;AAChD,UAAQ,IAAIA,OAAM,IAAI,UAAU,IAAIA,OAAM,MAAM,YAAY,CAAC;AAC7D,UAAQ,IAAIA,OAAM,IAAI,YAAY,IAAIA,OAAM,MAAM,SAAS,OAAO,OAAO,SAAS,CAAC,CAAC;AACpF,UAAQ,IAAIA,OAAM,IAAI,aAAa,IAAIA,OAAM,MAAM,SAAS,SAAS,CAAC;AACtE,UAAQ,IAAI;AACZ,UAAQ,IAAIA,OAAM,IAAI,0CAAmC,IAAIA,OAAM,KAAK,yBAAyB,CAAC;AACpG;;;AJpBA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,aAAa,EAClB;AAAA,EACCC,OAAM,KAAK,8BAAkB,IAC7B;AAGF,EACC,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,qDAAqD,EACjE,OAAO,iBAAiB,yCAAyC,EACjE,OAAO,wBAAwB,0CAA0C,IAAI,EAC7E,OAAO,WAAW;AAErB,QACG,QAAQ,OAAO,EACf,YAAY,mDAAmD,EAC/D,OAAO,wBAAwB,qCAAqC,EACpE,OAAO,qBAAqB,+BAA+B,MAAM,EACjE,OAAO,QAAQ,wDAAwD,EACvE,OAAO,YAAY;AAEtB,QACG,QAAQ,OAAO,EACf,YAAY,uDAAuD,EACnE,OAAO,mBAAmB,4EAA4E,KAAK,EAC3G,OAAO,YAAY,sDAAsD,EACzE,OAAO,YAAY;AAEtB,QACG,QAAQ,UAAU,EAClB,YAAY,yBAAyB,EACrC,QAAQ,QAAQ,EAChB,YAAY,2EAA2E,EACvF,OAAO,iBAAiB,yCAAyC,EACjE,OAAO,eAAe;AAEzB,QAAQ,MAAM;","names":["chalk","chalk","chalk","chalk","chalk","chalk","chalk","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 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"]}