@mikulgohil/ai-kit 1.10.0 → 1.11.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.js CHANGED
@@ -15,8 +15,9 @@ var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
15
15
  var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
16
16
  var AGENTS_DIR = path.join(PACKAGE_ROOT, "agents");
17
17
  var CONTEXTS_DIR = path.join(PACKAGE_ROOT, "contexts");
18
- var VERSION = "1.10.0";
18
+ var VERSION = "1.11.0";
19
19
  var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
20
+ var BACKUP_DIR = ".ai-kit/backups";
20
21
  var GENERATED_FILES = {
21
22
  claudeMd: "CLAUDE.md",
22
23
  cursorRules: ".cursorrules",
@@ -43,16 +44,17 @@ var TEMPLATE_FRAGMENTS = [
43
44
  ];
44
45
 
45
46
  // src/commands/init.ts
46
- import path20 from "path";
47
+ import path21 from "path";
47
48
  import fs9 from "fs-extra";
48
49
  import { select } from "@inquirer/prompts";
49
50
  import ora from "ora";
50
51
 
51
52
  // src/scanner/index.ts
52
- import path13 from "path";
53
+ import path14 from "path";
53
54
 
54
55
  // src/utils.ts
55
56
  import fs from "fs-extra";
57
+ import path2 from "path";
56
58
  import chalk from "chalk";
57
59
  function readJsonSafe(filePath) {
58
60
  try {
@@ -105,9 +107,91 @@ function mergeWithMarkers(existingContent, newGenerated) {
105
107
  const after = existingContent.substring(endIdx + AI_KIT_END.length);
106
108
  return `${before}${newGenerated}${after}`;
107
109
  }
110
+ async function backupFiles(projectDir, files) {
111
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
112
+ const backupDir = path2.join(projectDir, BACKUP_DIR, timestamp);
113
+ await fs.ensureDir(backupDir);
114
+ let backedUp = 0;
115
+ for (const file of files) {
116
+ const fullPath = path2.join(projectDir, file);
117
+ if (await fs.pathExists(fullPath)) {
118
+ const dest = path2.join(backupDir, file);
119
+ await fs.ensureDir(path2.dirname(dest));
120
+ await fs.copy(fullPath, dest);
121
+ backedUp++;
122
+ }
123
+ }
124
+ if (backedUp === 0) {
125
+ await fs.remove(backupDir);
126
+ return "";
127
+ }
128
+ return backupDir;
129
+ }
130
+ async function listBackups(projectDir) {
131
+ const backupsRoot = path2.join(projectDir, BACKUP_DIR);
132
+ if (!await fs.pathExists(backupsRoot)) return [];
133
+ const entries = await fs.readdir(backupsRoot);
134
+ return entries.filter((e) => /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/.test(e)).sort().reverse();
135
+ }
136
+ async function restoreBackup(projectDir, backupName) {
137
+ const backupDir = path2.join(projectDir, BACKUP_DIR, backupName);
138
+ if (!await fs.pathExists(backupDir)) {
139
+ throw new Error(`Backup not found: ${backupName}`);
140
+ }
141
+ const restored = [];
142
+ async function walk(dir, rel) {
143
+ const entries = await fs.readdir(dir, { withFileTypes: true });
144
+ for (const entry of entries) {
145
+ const entryRel = rel ? `${rel}/${entry.name}` : entry.name;
146
+ const srcPath = path2.join(dir, entry.name);
147
+ const destPath = path2.join(projectDir, entryRel);
148
+ if (entry.isDirectory()) {
149
+ await walk(srcPath, entryRel);
150
+ } else {
151
+ await fs.ensureDir(path2.dirname(destPath));
152
+ await fs.copy(srcPath, destPath, { overwrite: true });
153
+ restored.push(entryRel);
154
+ }
155
+ }
156
+ }
157
+ await walk(backupDir, "");
158
+ return restored;
159
+ }
160
+ function parseSections(markdown) {
161
+ const lines = markdown.split("\n");
162
+ const sections = [];
163
+ let current = null;
164
+ const contentLines = [];
165
+ function flush() {
166
+ if (current) {
167
+ current.content = contentLines.join("\n").trim();
168
+ current.raw = `${"#".repeat(current.level)} ${current.heading}
169
+
170
+ ${current.content}`.trim();
171
+ sections.push(current);
172
+ contentLines.length = 0;
173
+ }
174
+ }
175
+ for (const line of lines) {
176
+ const match = line.match(/^(#{1,6})\s+(.+)$/);
177
+ if (match) {
178
+ flush();
179
+ current = {
180
+ heading: match[2].trim(),
181
+ level: match[1].length,
182
+ content: "",
183
+ raw: ""
184
+ };
185
+ } else if (current) {
186
+ contentLines.push(line);
187
+ }
188
+ }
189
+ flush();
190
+ return sections;
191
+ }
108
192
 
109
193
  // src/scanner/nextjs.ts
110
- import path2 from "path";
194
+ import path3 from "path";
111
195
  function detectNextjs(projectPath, pkg) {
112
196
  const deps = {
113
197
  ...pkg.dependencies,
@@ -119,8 +203,8 @@ function detectNextjs(projectPath, pkg) {
119
203
  }
120
204
  const nextjsVersion = deps.next.replace(/[\^~>=<]/g, "");
121
205
  const nextjsMajorVersion = parseInt(nextjsVersion.split(".")[0], 10) || void 0;
122
- const hasAppDir = dirExists(path2.join(projectPath, "app")) || dirExists(path2.join(projectPath, "src", "app"));
123
- const hasPagesDir = dirExists(path2.join(projectPath, "pages")) || dirExists(path2.join(projectPath, "src", "pages"));
206
+ const hasAppDir = dirExists(path3.join(projectPath, "app")) || dirExists(path3.join(projectPath, "src", "app"));
207
+ const hasPagesDir = dirExists(path3.join(projectPath, "pages")) || dirExists(path3.join(projectPath, "src", "pages"));
124
208
  let routerType;
125
209
  if (hasAppDir && hasPagesDir) routerType = "hybrid";
126
210
  else if (hasAppDir) routerType = "app";
@@ -206,7 +290,7 @@ function detectOptimizely(pkg) {
206
290
  }
207
291
 
208
292
  // src/scanner/styling.ts
209
- import path3 from "path";
293
+ import path4 from "path";
210
294
  function detectStyling(projectPath, pkg) {
211
295
  const deps = {
212
296
  ...pkg.dependencies,
@@ -218,7 +302,7 @@ function detectStyling(projectPath, pkg) {
218
302
  styling.push("tailwind");
219
303
  tailwindVersion = (deps.tailwindcss || deps["@tailwindcss/postcss"] || "").replace(/[\^~>=<]/g, "");
220
304
  }
221
- const hasTailwindConfig = fileExists(path3.join(projectPath, "tailwind.config.js")) || fileExists(path3.join(projectPath, "tailwind.config.ts")) || fileExists(path3.join(projectPath, "tailwind.config.mjs"));
305
+ const hasTailwindConfig = fileExists(path4.join(projectPath, "tailwind.config.js")) || fileExists(path4.join(projectPath, "tailwind.config.ts")) || fileExists(path4.join(projectPath, "tailwind.config.mjs"));
222
306
  if (hasTailwindConfig && !styling.includes("tailwind")) {
223
307
  styling.push("tailwind");
224
308
  }
@@ -231,9 +315,9 @@ function detectStyling(projectPath, pkg) {
231
315
  }
232
316
 
233
317
  // src/scanner/typescript.ts
234
- import path4 from "path";
318
+ import path5 from "path";
235
319
  function detectTypescript(projectPath) {
236
- const tsconfigPath = path4.join(projectPath, "tsconfig.json");
320
+ const tsconfigPath = path5.join(projectPath, "tsconfig.json");
237
321
  if (!fileExists(tsconfigPath)) {
238
322
  return { typescript: false };
239
323
  }
@@ -245,18 +329,18 @@ function detectTypescript(projectPath) {
245
329
  }
246
330
 
247
331
  // src/scanner/monorepo.ts
248
- import path5 from "path";
332
+ import path6 from "path";
249
333
  function detectMonorepo(projectPath, pkg) {
250
- if (fileExists(path5.join(projectPath, "turbo.json"))) {
334
+ if (fileExists(path6.join(projectPath, "turbo.json"))) {
251
335
  return { monorepo: true, monorepoTool: "turborepo" };
252
336
  }
253
- if (fileExists(path5.join(projectPath, "nx.json"))) {
337
+ if (fileExists(path6.join(projectPath, "nx.json"))) {
254
338
  return { monorepo: true, monorepoTool: "nx" };
255
339
  }
256
- if (fileExists(path5.join(projectPath, "lerna.json"))) {
340
+ if (fileExists(path6.join(projectPath, "lerna.json"))) {
257
341
  return { monorepo: true, monorepoTool: "lerna" };
258
342
  }
259
- if (fileExists(path5.join(projectPath, "pnpm-workspace.yaml"))) {
343
+ if (fileExists(path6.join(projectPath, "pnpm-workspace.yaml"))) {
260
344
  return { monorepo: true, monorepoTool: "pnpm-workspaces" };
261
345
  }
262
346
  if (pkg.workspaces) {
@@ -266,10 +350,10 @@ function detectMonorepo(projectPath, pkg) {
266
350
  }
267
351
 
268
352
  // src/scanner/package-manager.ts
269
- import path6 from "path";
353
+ import path7 from "path";
270
354
  function detectPackageManager(projectPath) {
271
355
  const pkg = readJsonSafe(
272
- path6.join(projectPath, "package.json")
356
+ path7.join(projectPath, "package.json")
273
357
  );
274
358
  if (pkg?.packageManager) {
275
359
  if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
@@ -277,15 +361,15 @@ function detectPackageManager(projectPath) {
277
361
  if (pkg.packageManager.startsWith("bun")) return "bun";
278
362
  return "npm";
279
363
  }
280
- if (fileExists(path6.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
281
- if (fileExists(path6.join(projectPath, "yarn.lock"))) return "yarn";
282
- if (fileExists(path6.join(projectPath, "bun.lockb"))) return "bun";
283
- if (fileExists(path6.join(projectPath, "bun.lock"))) return "bun";
364
+ if (fileExists(path7.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
365
+ if (fileExists(path7.join(projectPath, "yarn.lock"))) return "yarn";
366
+ if (fileExists(path7.join(projectPath, "bun.lockb"))) return "bun";
367
+ if (fileExists(path7.join(projectPath, "bun.lock"))) return "bun";
284
368
  return "npm";
285
369
  }
286
370
 
287
371
  // src/scanner/figma.ts
288
- import path7 from "path";
372
+ import path8 from "path";
289
373
  function detectFigma(projectPath, pkg) {
290
374
  return {
291
375
  figmaMcp: detectFigmaMcp(projectPath),
@@ -297,9 +381,9 @@ function detectFigma(projectPath, pkg) {
297
381
  }
298
382
  function detectFigmaMcp(projectPath) {
299
383
  const settingsPaths = [
300
- path7.join(projectPath, ".claude", "settings.json"),
301
- path7.join(projectPath, ".claude", "settings.local.json"),
302
- path7.join(projectPath, ".mcp.json")
384
+ path8.join(projectPath, ".claude", "settings.json"),
385
+ path8.join(projectPath, ".claude", "settings.local.json"),
386
+ path8.join(projectPath, ".mcp.json")
303
387
  ];
304
388
  for (const settingsPath of settingsPaths) {
305
389
  const content = readFileSafe(settingsPath);
@@ -320,28 +404,28 @@ function detectFigmaCodeCli(pkg) {
320
404
  }
321
405
  function detectDesignTokens(projectPath) {
322
406
  const tokenPaths = [
323
- path7.join(projectPath, "tokens.json"),
324
- path7.join(projectPath, "tokens"),
325
- path7.join(projectPath, "design-tokens.json"),
326
- path7.join(projectPath, "src", "tokens")
407
+ path8.join(projectPath, "tokens.json"),
408
+ path8.join(projectPath, "tokens"),
409
+ path8.join(projectPath, "design-tokens.json"),
410
+ path8.join(projectPath, "src", "tokens")
327
411
  ];
328
412
  for (const tokenPath of tokenPaths) {
329
413
  if (fileExists(tokenPath)) return true;
330
414
  }
331
415
  const globalsCss = readFileSafe(
332
- path7.join(projectPath, "src", "app", "globals.css")
416
+ path8.join(projectPath, "src", "app", "globals.css")
333
417
  );
334
418
  if (globalsCss && globalsCss.includes("@theme")) return true;
335
419
  return false;
336
420
  }
337
421
  function detectTokenFormat(projectPath) {
338
422
  const globalsCss = readFileSafe(
339
- path7.join(projectPath, "src", "app", "globals.css")
423
+ path8.join(projectPath, "src", "app", "globals.css")
340
424
  );
341
425
  if (globalsCss && globalsCss.includes("@theme")) return "tailwind-v4";
342
426
  const twConfigPaths = [
343
- path7.join(projectPath, "tailwind.config.ts"),
344
- path7.join(projectPath, "tailwind.config.js")
427
+ path8.join(projectPath, "tailwind.config.ts"),
428
+ path8.join(projectPath, "tailwind.config.js")
345
429
  ];
346
430
  for (const twPath of twConfigPaths) {
347
431
  const content = readFileSafe(twPath);
@@ -356,12 +440,12 @@ function detectVisualTests(projectPath, pkg) {
356
440
  ...pkg.devDependencies
357
441
  };
358
442
  const hasPlaywright = "@playwright/test" in deps || "playwright" in deps;
359
- const hasPlaywrightConfig = fileExists(path7.join(projectPath, "playwright.config.ts")) || fileExists(path7.join(projectPath, "playwright.config.js"));
443
+ const hasPlaywrightConfig = fileExists(path8.join(projectPath, "playwright.config.ts")) || fileExists(path8.join(projectPath, "playwright.config.js"));
360
444
  return hasPlaywright || hasPlaywrightConfig;
361
445
  }
362
446
 
363
447
  // src/scanner/tools.ts
364
- import path8 from "path";
448
+ import path9 from "path";
365
449
  function detectTools(projectPath, pkg) {
366
450
  const deps = {
367
451
  ...pkg.dependencies,
@@ -381,13 +465,13 @@ function detectTools(projectPath, pkg) {
381
465
  }
382
466
  function detectPlaywright(projectPath, deps) {
383
467
  if ("@playwright/test" in deps) return true;
384
- if (fileExists(path8.join(projectPath, "playwright.config.ts"))) return true;
385
- if (fileExists(path8.join(projectPath, "playwright.config.js"))) return true;
468
+ if (fileExists(path9.join(projectPath, "playwright.config.ts"))) return true;
469
+ if (fileExists(path9.join(projectPath, "playwright.config.js"))) return true;
386
470
  return false;
387
471
  }
388
472
  function detectStorybook(projectPath, deps) {
389
473
  if ("@storybook/react" in deps) return true;
390
- if (dirExists(path8.join(projectPath, ".storybook"))) return true;
474
+ if (dirExists(path9.join(projectPath, ".storybook"))) return true;
391
475
  return false;
392
476
  }
393
477
  function detectEslint(projectPath, deps) {
@@ -405,7 +489,7 @@ function detectEslint(projectPath, deps) {
405
489
  "eslint.config.ts"
406
490
  ];
407
491
  for (const config of eslintConfigs) {
408
- if (fileExists(path8.join(projectPath, config))) return true;
492
+ if (fileExists(path9.join(projectPath, config))) return true;
409
493
  }
410
494
  return false;
411
495
  }
@@ -424,14 +508,14 @@ function detectPrettier(projectPath, deps) {
424
508
  "prettier.config.ts"
425
509
  ];
426
510
  for (const config of prettierConfigs) {
427
- if (fileExists(path8.join(projectPath, config))) return true;
511
+ if (fileExists(path9.join(projectPath, config))) return true;
428
512
  }
429
513
  return false;
430
514
  }
431
515
  function detectBiome(projectPath, deps) {
432
516
  if ("@biomejs/biome" in deps) return true;
433
- if (fileExists(path8.join(projectPath, "biome.json"))) return true;
434
- if (fileExists(path8.join(projectPath, "biome.jsonc"))) return true;
517
+ if (fileExists(path9.join(projectPath, "biome.json"))) return true;
518
+ if (fileExists(path9.join(projectPath, "biome.jsonc"))) return true;
435
519
  return false;
436
520
  }
437
521
  function detectAxeCore(deps) {
@@ -439,13 +523,13 @@ function detectAxeCore(deps) {
439
523
  }
440
524
  function detectSnyk(projectPath, deps) {
441
525
  if ("snyk" in deps) return true;
442
- if (fileExists(path8.join(projectPath, ".snyk"))) return true;
526
+ if (fileExists(path9.join(projectPath, ".snyk"))) return true;
443
527
  return false;
444
528
  }
445
529
  function detectKnip(projectPath, deps) {
446
530
  if ("knip" in deps) return true;
447
- if (fileExists(path8.join(projectPath, "knip.json"))) return true;
448
- if (fileExists(path8.join(projectPath, "knip.config.ts"))) return true;
531
+ if (fileExists(path9.join(projectPath, "knip.json"))) return true;
532
+ if (fileExists(path9.join(projectPath, "knip.config.ts"))) return true;
449
533
  return false;
450
534
  }
451
535
  function detectBundleAnalyzer(deps) {
@@ -453,12 +537,12 @@ function detectBundleAnalyzer(deps) {
453
537
  }
454
538
 
455
539
  // src/scanner/mcp.ts
456
- import path9 from "path";
540
+ import path10 from "path";
457
541
  function detectMcpServers(projectPath) {
458
542
  const settingsPaths = [
459
- path9.join(projectPath, ".claude", "settings.json"),
460
- path9.join(projectPath, ".claude", "settings.local.json"),
461
- path9.join(projectPath, ".mcp.json")
543
+ path10.join(projectPath, ".claude", "settings.json"),
544
+ path10.join(projectPath, ".claude", "settings.local.json"),
545
+ path10.join(projectPath, ".mcp.json")
462
546
  ];
463
547
  let combined = "";
464
548
  for (const settingsPath of settingsPaths) {
@@ -477,18 +561,18 @@ function detectMcpServers(projectPath) {
477
561
  }
478
562
 
479
563
  // src/scanner/design-tokens.ts
480
- import path10 from "path";
564
+ import path11 from "path";
481
565
  function detectDesignTokens2(projectPath) {
482
566
  const globalsCss = readFileSafe(
483
- path10.join(projectPath, "src", "app", "globals.css")
567
+ path11.join(projectPath, "src", "app", "globals.css")
484
568
  );
485
569
  if (globalsCss && globalsCss.includes("@theme")) {
486
570
  return parseThemeInline(globalsCss);
487
571
  }
488
572
  const twConfigPaths = [
489
- path10.join(projectPath, "tailwind.config.ts"),
490
- path10.join(projectPath, "tailwind.config.js"),
491
- path10.join(projectPath, "tailwind.config.mjs")
573
+ path11.join(projectPath, "tailwind.config.ts"),
574
+ path11.join(projectPath, "tailwind.config.js"),
575
+ path11.join(projectPath, "tailwind.config.mjs")
492
576
  ];
493
577
  for (const twPath of twConfigPaths) {
494
578
  const content = readFileSafe(twPath);
@@ -622,7 +706,7 @@ function parseCssVariables(css) {
622
706
  }
623
707
 
624
708
  // src/scanner/static-site.ts
625
- import path11 from "path";
709
+ import path12 from "path";
626
710
  import fs2 from "fs-extra";
627
711
  function detectStaticSite(projectPath, pkg) {
628
712
  const hasStaticExport = checkStaticExport(projectPath);
@@ -651,9 +735,9 @@ function detectStaticSite(projectPath, pkg) {
651
735
  }
652
736
  function checkStaticExport(projectPath) {
653
737
  const configPaths = [
654
- path11.join(projectPath, "next.config.js"),
655
- path11.join(projectPath, "next.config.ts"),
656
- path11.join(projectPath, "next.config.mjs")
738
+ path12.join(projectPath, "next.config.js"),
739
+ path12.join(projectPath, "next.config.ts"),
740
+ path12.join(projectPath, "next.config.mjs")
657
741
  ];
658
742
  for (const configPath of configPaths) {
659
743
  const content = readFileSafe(configPath);
@@ -665,8 +749,8 @@ function checkStaticExport(projectPath) {
665
749
  }
666
750
  function checkGenerateStaticParams(projectPath) {
667
751
  const appDirs = [
668
- path11.join(projectPath, "app"),
669
- path11.join(projectPath, "src", "app")
752
+ path12.join(projectPath, "app"),
753
+ path12.join(projectPath, "src", "app")
670
754
  ];
671
755
  for (const appDir of appDirs) {
672
756
  if (dirExists(appDir) && hasPatternInDir(appDir, /generateStaticParams/)) {
@@ -674,8 +758,8 @@ function checkGenerateStaticParams(projectPath) {
674
758
  }
675
759
  }
676
760
  const pagesDirs = [
677
- path11.join(projectPath, "pages"),
678
- path11.join(projectPath, "src", "pages")
761
+ path12.join(projectPath, "pages"),
762
+ path12.join(projectPath, "src", "pages")
679
763
  ];
680
764
  for (const pagesDir of pagesDirs) {
681
765
  if (dirExists(pagesDir) && hasPatternInDir(pagesDir, /getStaticPaths/)) {
@@ -686,8 +770,8 @@ function checkGenerateStaticParams(projectPath) {
686
770
  }
687
771
  function checkRevalidatePatterns(projectPath) {
688
772
  const appDirs = [
689
- path11.join(projectPath, "app"),
690
- path11.join(projectPath, "src", "app")
773
+ path12.join(projectPath, "app"),
774
+ path12.join(projectPath, "src", "app")
691
775
  ];
692
776
  for (const appDir of appDirs) {
693
777
  if (dirExists(appDir) && hasPatternInDir(appDir, /revalidate\s*[:=]/)) {
@@ -698,8 +782,8 @@ function checkRevalidatePatterns(projectPath) {
698
782
  }
699
783
  function checkServerActions(projectPath) {
700
784
  const srcDirs = [
701
- path11.join(projectPath, "app"),
702
- path11.join(projectPath, "src")
785
+ path12.join(projectPath, "app"),
786
+ path12.join(projectPath, "src")
703
787
  ];
704
788
  for (const srcDir of srcDirs) {
705
789
  if (dirExists(srcDir) && hasPatternInDir(srcDir, /['"]use server['"]/)) {
@@ -710,15 +794,15 @@ function checkServerActions(projectPath) {
710
794
  }
711
795
  function checkApiRoutes(projectPath) {
712
796
  const appApiDirs = [
713
- path11.join(projectPath, "app", "api"),
714
- path11.join(projectPath, "src", "app", "api")
797
+ path12.join(projectPath, "app", "api"),
798
+ path12.join(projectPath, "src", "app", "api")
715
799
  ];
716
800
  for (const apiDir of appApiDirs) {
717
801
  if (dirExists(apiDir)) return true;
718
802
  }
719
803
  const pagesApiDirs = [
720
- path11.join(projectPath, "pages", "api"),
721
- path11.join(projectPath, "src", "pages", "api")
804
+ path12.join(projectPath, "pages", "api"),
805
+ path12.join(projectPath, "src", "pages", "api")
722
806
  ];
723
807
  for (const apiDir of pagesApiDirs) {
724
808
  if (dirExists(apiDir)) return true;
@@ -732,7 +816,7 @@ function hasPatternInDir(dir, pattern, depth = 0) {
732
816
  const entries = fs2.readdirSync(dir, { withFileTypes: true });
733
817
  for (const entry of entries) {
734
818
  if (SCAN_IGNORE.includes(entry.name)) continue;
735
- const full = path11.join(dir, entry.name);
819
+ const full = path12.join(dir, entry.name);
736
820
  if (entry.isDirectory()) {
737
821
  if (hasPatternInDir(full, pattern, depth + 1)) return true;
738
822
  } else if (/\.(tsx?|jsx?|mjs)$/.test(entry.name)) {
@@ -746,19 +830,19 @@ function hasPatternInDir(dir, pattern, depth = 0) {
746
830
  }
747
831
 
748
832
  // src/scanner/aiignore.ts
749
- import path12 from "path";
833
+ import path13 from "path";
750
834
  function loadAiIgnorePatterns(projectPath) {
751
- const content = readFileSafe(path12.join(projectPath, ".aiignore"));
835
+ const content = readFileSafe(path13.join(projectPath, ".aiignore"));
752
836
  if (!content) return [];
753
837
  return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
754
838
  }
755
839
 
756
840
  // src/scanner/index.ts
757
841
  async function scanProject(projectPath) {
758
- const pkgPath = path13.join(projectPath, "package.json");
842
+ const pkgPath = path14.join(projectPath, "package.json");
759
843
  const pkg = readJsonSafe(pkgPath) || {};
760
844
  const scripts = pkg.scripts || {};
761
- const projectName = pkg.name || path13.basename(projectPath);
845
+ const projectName = pkg.name || path14.basename(projectPath);
762
846
  const nextjsResult = detectNextjs(projectPath, pkg);
763
847
  const sitecoreResult = detectSitecore(pkg);
764
848
  const optimizelyResult = detectOptimizely(pkg);
@@ -801,10 +885,10 @@ async function scanProject(projectPath) {
801
885
  }
802
886
 
803
887
  // src/generator/assembler.ts
804
- import path14 from "path";
888
+ import path15 from "path";
805
889
  import fs3 from "fs-extra";
806
890
  function readTemplate(relativePath) {
807
- const fullPath = path14.join(TEMPLATES_DIR, relativePath);
891
+ const fullPath = path15.join(TEMPLATES_DIR, relativePath);
808
892
  const content = readFileSafe(fullPath);
809
893
  if (!content) {
810
894
  throw new Error(`Template not found: ${relativePath}`);
@@ -812,12 +896,12 @@ function readTemplate(relativePath) {
812
896
  return content.trim();
813
897
  }
814
898
  function loadCustomFragments(projectPath) {
815
- const customDir = path14.join(projectPath, ".ai-kit", "fragments");
899
+ const customDir = path15.join(projectPath, ".ai-kit", "fragments");
816
900
  if (!fs3.existsSync(customDir)) return [];
817
901
  try {
818
902
  const files = fs3.readdirSync(customDir).filter((f) => f.endsWith(".md"));
819
903
  return files.map((f) => {
820
- const content = fs3.readFileSync(path14.join(customDir, f), "utf-8");
904
+ const content = fs3.readFileSync(path15.join(customDir, f), "utf-8");
821
905
  return content.trim();
822
906
  }).filter(Boolean);
823
907
  } catch {
@@ -1138,10 +1222,33 @@ function generateConfig(scan, templates, commands, guides, options) {
1138
1222
  // src/generator/hooks.ts
1139
1223
  function generateHooks(scan, profile = "standard") {
1140
1224
  const hooks = {};
1225
+ const sessionStart = [];
1141
1226
  const preToolUse = [];
1142
1227
  const postToolUse = [];
1143
1228
  const postCompact = [];
1144
1229
  const stop = [];
1230
+ const stackParts = [
1231
+ scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""}`.trim() : scan.framework,
1232
+ scan.routerType ? `(${scan.routerType} router)` : "",
1233
+ scan.cms !== "none" ? scan.cms : "",
1234
+ scan.styling.length > 0 ? scan.styling.join(", ") : ""
1235
+ ].filter(Boolean);
1236
+ const scriptNames = Object.keys(scan.scripts).slice(0, 8).join(", ");
1237
+ const stackStr = stackParts.join(" + ");
1238
+ sessionStart.push({
1239
+ matcher: "",
1240
+ hooks: [
1241
+ {
1242
+ type: "command",
1243
+ command: [
1244
+ `echo "\u{1F4CB} ai-kit v${VERSION} | Stack: ${stackStr}"`,
1245
+ `echo " PM: ${scan.packageManager} | Scripts: ${scriptNames}"`,
1246
+ scan.monorepo ? `echo " Monorepo: ${scan.monorepoTool || "yes"}"` : "",
1247
+ `if [ -f "ai-kit.config.json" ]; then SCAN_DATE=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('ai-kit.config.json','utf8'));console.log(c.generatedAt?.split('T')[0]||'unknown')}catch{console.log('unknown')}" 2>/dev/null); echo " Last scan: $SCAN_DATE"; fi`
1248
+ ].filter(Boolean).join("\n")
1249
+ }
1250
+ ]
1251
+ });
1145
1252
  preToolUse.push({
1146
1253
  matcher: "Bash(git push*)",
1147
1254
  hooks: [
@@ -1345,6 +1452,7 @@ function generateHooks(scan, profile = "standard") {
1345
1452
  ]
1346
1453
  });
1347
1454
  }
1455
+ if (sessionStart.length > 0) hooks.SessionStart = sessionStart;
1348
1456
  if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
1349
1457
  if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
1350
1458
  if (postCompact.length > 0) hooks.PostCompact = postCompact;
@@ -1359,7 +1467,7 @@ function generateSettingsLocal(scan, profile = "standard") {
1359
1467
  }
1360
1468
 
1361
1469
  // src/copier/skills.ts
1362
- import path15 from "path";
1470
+ import path16 from "path";
1363
1471
  import fs4 from "fs-extra";
1364
1472
  var AVAILABLE_SKILLS = [
1365
1473
  "prompt-help",
@@ -1468,34 +1576,34 @@ var SKILL_DESCRIPTIONS = {
1468
1576
  async function copySkills(targetDir) {
1469
1577
  const copied = [];
1470
1578
  for (const skill of AVAILABLE_SKILLS) {
1471
- const src = path15.join(COMMANDS_DIR, `${skill}.md`);
1579
+ const src = path16.join(COMMANDS_DIR, `${skill}.md`);
1472
1580
  if (!await fs4.pathExists(src)) continue;
1473
1581
  const content = await fs4.readFile(src, "utf-8");
1474
1582
  const description = SKILL_DESCRIPTIONS[skill] || skill;
1475
- const claudeSkillDir = path15.join(targetDir, ".claude", "skills", skill);
1583
+ const claudeSkillDir = path16.join(targetDir, ".claude", "skills", skill);
1476
1584
  await fs4.ensureDir(claudeSkillDir);
1477
1585
  await fs4.writeFile(
1478
- path15.join(claudeSkillDir, "SKILL.md"),
1586
+ path16.join(claudeSkillDir, "SKILL.md"),
1479
1587
  content,
1480
1588
  "utf-8"
1481
1589
  );
1482
- const cursorSkillDir = path15.join(targetDir, ".cursor", "skills", skill);
1590
+ const cursorSkillDir = path16.join(targetDir, ".cursor", "skills", skill);
1483
1591
  await fs4.ensureDir(cursorSkillDir);
1484
1592
  await fs4.writeFile(
1485
- path15.join(cursorSkillDir, "SKILL.md"),
1593
+ path16.join(cursorSkillDir, "SKILL.md"),
1486
1594
  content,
1487
1595
  "utf-8"
1488
1596
  );
1489
- const legacyDir = path15.join(targetDir, ".claude", "commands");
1597
+ const legacyDir = path16.join(targetDir, ".claude", "commands");
1490
1598
  await fs4.ensureDir(legacyDir);
1491
- await fs4.copy(src, path15.join(legacyDir, `${skill}.md`), { overwrite: true });
1599
+ await fs4.copy(src, path16.join(legacyDir, `${skill}.md`), { overwrite: true });
1492
1600
  copied.push(skill);
1493
1601
  }
1494
1602
  return copied;
1495
1603
  }
1496
1604
 
1497
1605
  // src/copier/guides.ts
1498
- import path16 from "path";
1606
+ import path17 from "path";
1499
1607
  import fs5 from "fs-extra";
1500
1608
  var AVAILABLE_GUIDES = [
1501
1609
  "getting-started",
@@ -1506,12 +1614,12 @@ var AVAILABLE_GUIDES = [
1506
1614
  "hooks-and-agents"
1507
1615
  ];
1508
1616
  async function copyGuides(targetDir) {
1509
- const guidesTarget = path16.join(targetDir, "ai-kit", "guides");
1617
+ const guidesTarget = path17.join(targetDir, "ai-kit", "guides");
1510
1618
  await fs5.ensureDir(guidesTarget);
1511
1619
  const copied = [];
1512
1620
  for (const guide of AVAILABLE_GUIDES) {
1513
- const src = path16.join(GUIDES_DIR, `${guide}.md`);
1514
- const dest = path16.join(guidesTarget, `${guide}.md`);
1621
+ const src = path17.join(GUIDES_DIR, `${guide}.md`);
1622
+ const dest = path17.join(guidesTarget, `${guide}.md`);
1515
1623
  if (await fs5.pathExists(src)) {
1516
1624
  await fs5.copy(src, dest, { overwrite: true });
1517
1625
  copied.push(guide);
@@ -1521,16 +1629,16 @@ async function copyGuides(targetDir) {
1521
1629
  }
1522
1630
 
1523
1631
  // src/copier/docs.ts
1524
- import path17 from "path";
1632
+ import path18 from "path";
1525
1633
  import fs6 from "fs-extra";
1526
1634
  var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
1527
1635
  async function scaffoldDocs(targetDir) {
1528
- const docsTarget = path17.join(targetDir, "docs");
1636
+ const docsTarget = path18.join(targetDir, "docs");
1529
1637
  await fs6.ensureDir(docsTarget);
1530
1638
  const created = [];
1531
1639
  for (const doc of DOC_SCAFFOLDS) {
1532
- const src = path17.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
1533
- const dest = path17.join(docsTarget, `${doc}.md`);
1640
+ const src = path18.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
1641
+ const dest = path18.join(docsTarget, `${doc}.md`);
1534
1642
  if (await fs6.pathExists(dest)) {
1535
1643
  continue;
1536
1644
  }
@@ -1543,7 +1651,7 @@ async function scaffoldDocs(targetDir) {
1543
1651
  }
1544
1652
 
1545
1653
  // src/copier/agents.ts
1546
- import path18 from "path";
1654
+ import path19 from "path";
1547
1655
  import fs7 from "fs-extra";
1548
1656
  var UNIVERSAL_AGENTS = [
1549
1657
  "planner",
@@ -1575,22 +1683,22 @@ var CONDITIONAL_AGENTS = [
1575
1683
  }
1576
1684
  ];
1577
1685
  async function copyAgents(targetDir, scan) {
1578
- const agentsTarget = path18.join(targetDir, ".claude", "agents");
1686
+ const agentsTarget = path19.join(targetDir, ".claude", "agents");
1579
1687
  await fs7.ensureDir(agentsTarget);
1580
1688
  const copied = [];
1581
1689
  for (const agent of UNIVERSAL_AGENTS) {
1582
- const src = path18.join(AGENTS_DIR, `${agent}.md`);
1690
+ const src = path19.join(AGENTS_DIR, `${agent}.md`);
1583
1691
  if (!await fs7.pathExists(src)) continue;
1584
- await fs7.copy(src, path18.join(agentsTarget, `${agent}.md`), {
1692
+ await fs7.copy(src, path19.join(agentsTarget, `${agent}.md`), {
1585
1693
  overwrite: true
1586
1694
  });
1587
1695
  copied.push(agent);
1588
1696
  }
1589
1697
  for (const { name, condition } of CONDITIONAL_AGENTS) {
1590
1698
  if (!condition(scan)) continue;
1591
- const src = path18.join(AGENTS_DIR, `${name}.md`);
1699
+ const src = path19.join(AGENTS_DIR, `${name}.md`);
1592
1700
  if (!await fs7.pathExists(src)) continue;
1593
- await fs7.copy(src, path18.join(agentsTarget, `${name}.md`), {
1701
+ await fs7.copy(src, path19.join(agentsTarget, `${name}.md`), {
1594
1702
  overwrite: true
1595
1703
  });
1596
1704
  copied.push(name);
@@ -1599,17 +1707,17 @@ async function copyAgents(targetDir, scan) {
1599
1707
  }
1600
1708
 
1601
1709
  // src/copier/contexts.ts
1602
- import path19 from "path";
1710
+ import path20 from "path";
1603
1711
  import fs8 from "fs-extra";
1604
1712
  var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
1605
1713
  async function copyContexts(targetDir) {
1606
- const contextsTarget = path19.join(targetDir, ".claude", "contexts");
1714
+ const contextsTarget = path20.join(targetDir, ".claude", "contexts");
1607
1715
  await fs8.ensureDir(contextsTarget);
1608
1716
  const copied = [];
1609
1717
  for (const context of AVAILABLE_CONTEXTS) {
1610
- const src = path19.join(CONTEXTS_DIR, `${context}.md`);
1718
+ const src = path20.join(CONTEXTS_DIR, `${context}.md`);
1611
1719
  if (!await fs8.pathExists(src)) continue;
1612
- await fs8.copy(src, path19.join(contextsTarget, `${context}.md`), {
1720
+ await fs8.copy(src, path20.join(contextsTarget, `${context}.md`), {
1613
1721
  overwrite: true
1614
1722
  });
1615
1723
  copied.push(context);
@@ -1619,10 +1727,10 @@ async function copyContexts(targetDir) {
1619
1727
 
1620
1728
  // src/commands/init.ts
1621
1729
  async function initCommand(targetPath) {
1622
- const projectDir = path20.resolve(targetPath || process.cwd());
1730
+ const projectDir = path21.resolve(targetPath || process.cwd());
1623
1731
  logSection("AI Kit \u2014 Project Setup");
1624
1732
  logInfo(`Scanning: ${projectDir}`);
1625
- const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
1733
+ const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
1626
1734
  let savedConfig = null;
1627
1735
  let reuseMode = false;
1628
1736
  if (fileExists(configPath)) {
@@ -1799,7 +1907,7 @@ async function selectHookProfile() {
1799
1907
  });
1800
1908
  }
1801
1909
  async function selectConflictStrategy(projectDir) {
1802
- const hasExisting = fileExists(path20.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path20.join(projectDir, GENERATED_FILES.cursorRules));
1910
+ const hasExisting = fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules));
1803
1911
  if (!hasExisting) return "overwrite";
1804
1912
  return select({
1805
1913
  message: "Existing AI config files detected. How should we handle conflicts?",
@@ -1829,7 +1937,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1829
1937
  docs: []
1830
1938
  };
1831
1939
  if (tools.claude) {
1832
- const claudeMdPath = path20.join(projectDir, GENERATED_FILES.claudeMd);
1940
+ const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
1833
1941
  if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
1834
1942
  const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1835
1943
  await fs9.writeFile(claudeMdPath, content, "utf-8");
@@ -1841,14 +1949,14 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1841
1949
  result.agents = await copyAgents(projectDir, scan);
1842
1950
  result.contexts = await copyContexts(projectDir);
1843
1951
  const hookProfile = opts?.hookProfile || "standard";
1844
- const settingsLocalPath = path20.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1952
+ const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1845
1953
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
1846
- await fs9.ensureDir(path20.dirname(settingsLocalPath));
1954
+ await fs9.ensureDir(path21.dirname(settingsLocalPath));
1847
1955
  await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1848
1956
  result.hooks = true;
1849
1957
  }
1850
1958
  if (tools.cursor) {
1851
- const cursorPath = path20.join(projectDir, GENERATED_FILES.cursorRules);
1959
+ const cursorPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
1852
1960
  if (conflict === "overwrite" || !fileExists(cursorPath)) {
1853
1961
  const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1854
1962
  await fs9.writeFile(cursorPath, content, "utf-8");
@@ -1856,11 +1964,11 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1856
1964
  } else {
1857
1965
  logWarning(".cursorrules exists, skipping");
1858
1966
  }
1859
- const mdcDir = path20.join(projectDir, GENERATED_FILES.cursorMdcDir);
1967
+ const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
1860
1968
  await fs9.ensureDir(mdcDir);
1861
1969
  const mdcFiles = generateMdcFiles(scan);
1862
1970
  for (const mdc of mdcFiles) {
1863
- await fs9.writeFile(path20.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1971
+ await fs9.writeFile(path21.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1864
1972
  }
1865
1973
  result.cursorMdcFiles = mdcFiles.length;
1866
1974
  }
@@ -1879,7 +1987,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1879
1987
  tools
1880
1988
  });
1881
1989
  await fs9.writeJson(
1882
- path20.join(projectDir, AI_KIT_CONFIG_FILE),
1990
+ path21.join(projectDir, AI_KIT_CONFIG_FILE),
1883
1991
  config,
1884
1992
  { spaces: 2 }
1885
1993
  );
@@ -1955,13 +2063,13 @@ function showRecommendations(scan) {
1955
2063
  }
1956
2064
 
1957
2065
  // src/commands/update.ts
1958
- import path21 from "path";
2066
+ import path22 from "path";
1959
2067
  import fs10 from "fs-extra";
1960
2068
  import ora2 from "ora";
1961
2069
  import { confirm as confirm2 } from "@inquirer/prompts";
1962
2070
  async function updateCommand(targetPath) {
1963
- const projectDir = path21.resolve(targetPath || process.cwd());
1964
- const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
2071
+ const projectDir = path22.resolve(targetPath || process.cwd());
2072
+ const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
1965
2073
  if (!fileExists(configPath)) {
1966
2074
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
1967
2075
  return;
@@ -1986,6 +2094,16 @@ async function updateCommand(targetPath) {
1986
2094
  const spinner = ora2("Re-scanning project...").start();
1987
2095
  const scan = await scanProject(projectDir);
1988
2096
  spinner.succeed("Project re-scanned");
2097
+ const filesToBackup = [
2098
+ GENERATED_FILES.claudeMd,
2099
+ GENERATED_FILES.cursorRules,
2100
+ GENERATED_FILES.claudeSettingsLocal,
2101
+ AI_KIT_CONFIG_FILE
2102
+ ];
2103
+ const backupPath = await backupFiles(projectDir, filesToBackup);
2104
+ if (backupPath) {
2105
+ logSuccess(`Backed up current configs to ${path22.relative(projectDir, backupPath)}`);
2106
+ }
1989
2107
  logSection("Updating Files");
1990
2108
  const strictness = existingConfig.strictness || "standard";
1991
2109
  const hookProfile = existingConfig.hookProfile || "standard";
@@ -1994,8 +2112,8 @@ async function updateCommand(targetPath) {
1994
2112
  const genOpts = { strictness, customFragments };
1995
2113
  logInfo(`Using saved profile \u2014 Tools: ${tools.claude && tools.cursor ? "Claude Code + Cursor" : tools.claude ? "Claude Code" : "Cursor"} \xB7 Strictness: ${strictness} \xB7 Hooks: ${hookProfile}`);
1996
2114
  const templates = [];
1997
- if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)))) {
1998
- const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
2115
+ if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path22.join(projectDir, GENERATED_FILES.claudeMd)))) {
2116
+ const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
1999
2117
  const newContent = generateClaudeMd(scan, genOpts);
2000
2118
  const existing = readFileSafe(claudeMdPath);
2001
2119
  if (existing) {
@@ -2006,8 +2124,8 @@ async function updateCommand(targetPath) {
2006
2124
  templates.push("CLAUDE.md");
2007
2125
  logSuccess("CLAUDE.md updated");
2008
2126
  }
2009
- if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules)))) {
2010
- const cursorRulesPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
2127
+ if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path22.join(projectDir, GENERATED_FILES.cursorRules)))) {
2128
+ const cursorRulesPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
2011
2129
  const newContent = generateCursorRules(scan, genOpts);
2012
2130
  const existing = readFileSafe(cursorRulesPath);
2013
2131
  if (existing) {
@@ -2017,11 +2135,11 @@ async function updateCommand(targetPath) {
2017
2135
  }
2018
2136
  templates.push(".cursorrules");
2019
2137
  logSuccess(".cursorrules updated");
2020
- const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
2138
+ const mdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
2021
2139
  await fs10.ensureDir(mdcDir);
2022
2140
  const mdcFiles = generateMdcFiles(scan);
2023
2141
  for (const mdc of mdcFiles) {
2024
- await fs10.writeFile(path21.join(mdcDir, mdc.filename), mdc.content, "utf-8");
2142
+ await fs10.writeFile(path22.join(mdcDir, mdc.filename), mdc.content, "utf-8");
2025
2143
  }
2026
2144
  logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
2027
2145
  }
@@ -2032,9 +2150,9 @@ async function updateCommand(targetPath) {
2032
2150
  const contexts = await copyContexts(projectDir);
2033
2151
  logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
2034
2152
  if (existingConfig.hooks !== false) {
2035
- const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2153
+ const settingsLocalPath = path22.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2036
2154
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
2037
- await fs10.ensureDir(path21.dirname(settingsLocalPath));
2155
+ await fs10.ensureDir(path22.dirname(settingsLocalPath));
2038
2156
  await fs10.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
2039
2157
  logSuccess(`Hooks updated (profile: ${hookProfile})`);
2040
2158
  }
@@ -2052,14 +2170,17 @@ async function updateCommand(targetPath) {
2052
2170
  logSuccess("ai-kit.config.json updated");
2053
2171
  console.log("");
2054
2172
  logInfo("All AI configs refreshed with latest project scan.");
2173
+ if (backupPath) {
2174
+ logInfo("Rollback available: `ai-kit rollback --latest`");
2175
+ }
2055
2176
  }
2056
2177
 
2057
2178
  // src/commands/reset.ts
2058
- import path22 from "path";
2179
+ import path23 from "path";
2059
2180
  import fs11 from "fs-extra";
2060
2181
  import { confirm as confirm3 } from "@inquirer/prompts";
2061
2182
  async function resetCommand(targetPath) {
2062
- const projectDir = path22.resolve(targetPath || process.cwd());
2183
+ const projectDir = path23.resolve(targetPath || process.cwd());
2063
2184
  logSection("AI Kit \u2014 Reset");
2064
2185
  logWarning("This will remove all AI Kit generated files:");
2065
2186
  logInfo(` - ${GENERATED_FILES.claudeMd}`);
@@ -2080,42 +2201,42 @@ async function resetCommand(targetPath) {
2080
2201
  return;
2081
2202
  }
2082
2203
  const removed = [];
2083
- const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
2204
+ const claudeMdPath = path23.join(projectDir, GENERATED_FILES.claudeMd);
2084
2205
  if (fileExists(claudeMdPath)) {
2085
2206
  await fs11.remove(claudeMdPath);
2086
2207
  removed.push(GENERATED_FILES.claudeMd);
2087
2208
  }
2088
- const cursorPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
2209
+ const cursorPath = path23.join(projectDir, GENERATED_FILES.cursorRules);
2089
2210
  if (fileExists(cursorPath)) {
2090
2211
  await fs11.remove(cursorPath);
2091
2212
  removed.push(GENERATED_FILES.cursorRules);
2092
2213
  }
2093
- const cursorMdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
2214
+ const cursorMdcDir = path23.join(projectDir, GENERATED_FILES.cursorMdcDir);
2094
2215
  if (fileExists(cursorMdcDir)) {
2095
2216
  await fs11.remove(cursorMdcDir);
2096
2217
  removed.push(GENERATED_FILES.cursorMdcDir);
2097
2218
  }
2098
- const commandsDir = path22.join(projectDir, GENERATED_FILES.claudeCommands);
2219
+ const commandsDir = path23.join(projectDir, GENERATED_FILES.claudeCommands);
2099
2220
  if (fileExists(commandsDir)) {
2100
2221
  await fs11.remove(commandsDir);
2101
2222
  removed.push(GENERATED_FILES.claudeCommands);
2102
2223
  }
2103
- const claudeSkillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
2224
+ const claudeSkillsDir = path23.join(projectDir, GENERATED_FILES.claudeSkills);
2104
2225
  if (fileExists(claudeSkillsDir)) {
2105
2226
  await fs11.remove(claudeSkillsDir);
2106
2227
  removed.push(GENERATED_FILES.claudeSkills);
2107
2228
  }
2108
- const cursorSkillsDir = path22.join(projectDir, GENERATED_FILES.cursorSkills);
2229
+ const cursorSkillsDir = path23.join(projectDir, GENERATED_FILES.cursorSkills);
2109
2230
  if (fileExists(cursorSkillsDir)) {
2110
2231
  await fs11.remove(cursorSkillsDir);
2111
2232
  removed.push(GENERATED_FILES.cursorSkills);
2112
2233
  }
2113
- const aiKitDir = path22.join(projectDir, "ai-kit");
2234
+ const aiKitDir = path23.join(projectDir, "ai-kit");
2114
2235
  if (fileExists(aiKitDir)) {
2115
2236
  await fs11.remove(aiKitDir);
2116
2237
  removed.push("ai-kit/");
2117
2238
  }
2118
- const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
2239
+ const configPath = path23.join(projectDir, AI_KIT_CONFIG_FILE);
2119
2240
  if (fileExists(configPath)) {
2120
2241
  await fs11.remove(configPath);
2121
2242
  removed.push(AI_KIT_CONFIG_FILE);
@@ -2129,7 +2250,7 @@ async function resetCommand(targetPath) {
2129
2250
  }
2130
2251
 
2131
2252
  // src/commands/tokens.ts
2132
- import path23 from "path";
2253
+ import path24 from "path";
2133
2254
  import fs12 from "fs-extra";
2134
2255
  import chalk2 from "chalk";
2135
2256
  import ora3 from "ora";
@@ -2140,14 +2261,14 @@ var PRICING = {
2140
2261
  };
2141
2262
  var PLAN_BUDGET = 20;
2142
2263
  function findSessionFiles() {
2143
- const claudeDir = path23.join(os.homedir(), ".claude", "projects");
2264
+ const claudeDir = path24.join(os.homedir(), ".claude", "projects");
2144
2265
  if (!fs12.existsSync(claudeDir)) return [];
2145
2266
  const files = [];
2146
2267
  function walkDir(dir) {
2147
2268
  try {
2148
2269
  const entries = fs12.readdirSync(dir, { withFileTypes: true });
2149
2270
  for (const entry of entries) {
2150
- const full = path23.join(dir, entry.name);
2271
+ const full = path24.join(dir, entry.name);
2151
2272
  if (entry.isDirectory()) {
2152
2273
  walkDir(full);
2153
2274
  } else if (entry.name.endsWith(".jsonl")) {
@@ -2202,8 +2323,8 @@ function parseSessionFile(filePath) {
2202
2323
  const stat = fs12.statSync(filePath);
2203
2324
  sessionDate = stat.mtime.toISOString().slice(0, 10);
2204
2325
  }
2205
- const sessionId = path23.basename(filePath, ".jsonl");
2206
- const projectName = path23.basename(path23.dirname(filePath));
2326
+ const sessionId = path24.basename(filePath, ".jsonl");
2327
+ const projectName = path24.basename(path24.dirname(filePath));
2207
2328
  return {
2208
2329
  sessionId,
2209
2330
  filePath,
@@ -2432,7 +2553,7 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
2432
2553
  );
2433
2554
  console.log("");
2434
2555
  if (options.csv) {
2435
- const csvPath = path23.join(process.cwd(), "token-usage.csv");
2556
+ const csvPath = path24.join(process.cwd(), "token-usage.csv");
2436
2557
  const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
2437
2558
  const daily = aggregateByDate(sessions);
2438
2559
  const csvRows = daily.map(
@@ -2475,9 +2596,9 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2475
2596
  }
2476
2597
  };
2477
2598
  const outputDir = process.cwd();
2478
- const dataPath = path23.join(outputDir, "token-data.json");
2479
- const dashboardSrc = path23.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
2480
- const dashboardDest = path23.join(outputDir, "token-dashboard.html");
2599
+ const dataPath = path24.join(outputDir, "token-data.json");
2600
+ const dashboardSrc = path24.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
2601
+ const dashboardDest = path24.join(outputDir, "token-dashboard.html");
2481
2602
  await fs12.writeJson(dataPath, exportData, { spaces: 2 });
2482
2603
  logInfo(`Token data written to ${dataPath}`);
2483
2604
  if (await fs12.pathExists(dashboardSrc)) {
@@ -2498,12 +2619,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2498
2619
  }
2499
2620
 
2500
2621
  // src/commands/doctor.ts
2501
- import path24 from "path";
2622
+ import path25 from "path";
2502
2623
  import chalk3 from "chalk";
2503
2624
  import ora4 from "ora";
2504
2625
  async function doctorCommand(targetPath) {
2505
- const projectDir = path24.resolve(targetPath || process.cwd());
2506
- const configPath = path24.join(projectDir, AI_KIT_CONFIG_FILE);
2626
+ const projectDir = path25.resolve(targetPath || process.cwd());
2627
+ const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
2507
2628
  let passed = 0;
2508
2629
  let warnings = 0;
2509
2630
  let issues = 0;
@@ -2535,7 +2656,7 @@ async function doctorCommand(targetPath) {
2535
2656
  }
2536
2657
  for (const template of config.templates) {
2537
2658
  const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
2538
- const templatePath = path24.join(projectDir, templateFile);
2659
+ const templatePath = path25.join(projectDir, templateFile);
2539
2660
  if (fileExists(templatePath)) {
2540
2661
  logSuccess(`${template} exists and in sync`);
2541
2662
  passed++;
@@ -2546,8 +2667,8 @@ async function doctorCommand(targetPath) {
2546
2667
  }
2547
2668
  const missingSkills = [];
2548
2669
  for (const skill of config.commands) {
2549
- const claudeSkillPath = path24.join(projectDir, GENERATED_FILES.claudeSkills, skill);
2550
- const cursorSkillPath = path24.join(projectDir, GENERATED_FILES.cursorSkills, skill);
2670
+ const claudeSkillPath = path25.join(projectDir, GENERATED_FILES.claudeSkills, skill);
2671
+ const cursorSkillPath = path25.join(projectDir, GENERATED_FILES.cursorSkills, skill);
2551
2672
  if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
2552
2673
  missingSkills.push(skill);
2553
2674
  }
@@ -2561,10 +2682,10 @@ async function doctorCommand(targetPath) {
2561
2682
  );
2562
2683
  issues++;
2563
2684
  }
2564
- const guidesDir = path24.join(projectDir, "ai-kit", "guides");
2685
+ const guidesDir = path25.join(projectDir, "ai-kit", "guides");
2565
2686
  const missingGuides = [];
2566
2687
  for (const guide of config.guides) {
2567
- const guidePath = path24.join(guidesDir, guide);
2688
+ const guidePath = path25.join(guidesDir, guide);
2568
2689
  if (!fileExists(guidePath)) {
2569
2690
  missingGuides.push(guide);
2570
2691
  }
@@ -2702,13 +2823,13 @@ function compareScanResults(previous, current) {
2702
2823
  }
2703
2824
 
2704
2825
  // src/commands/diff.ts
2705
- import path25 from "path";
2826
+ import path26 from "path";
2706
2827
  import fs13 from "fs-extra";
2707
2828
  import chalk4 from "chalk";
2708
2829
  import ora5 from "ora";
2709
2830
  async function diffCommand(targetPath) {
2710
- const projectDir = path25.resolve(targetPath || process.cwd());
2711
- const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
2831
+ const projectDir = path26.resolve(targetPath || process.cwd());
2832
+ const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
2712
2833
  console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
2713
2834
  if (!fileExists(configPath)) {
2714
2835
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
@@ -2775,11 +2896,11 @@ async function diffCommand(targetPath) {
2775
2896
  if (cursorRulesStatus.status === "modified") modified++;
2776
2897
  else if (cursorRulesStatus.status === "added") added++;
2777
2898
  else unchanged++;
2778
- const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
2899
+ const skillsDir = path26.join(projectDir, GENERATED_FILES.claudeSkills);
2779
2900
  const skillCount = countFilesInDir(skillsDir);
2780
2901
  console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
2781
2902
  unchanged++;
2782
- const guidesDir = path25.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2903
+ const guidesDir = path26.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2783
2904
  const guideCount = existingConfig.guides?.length || 0;
2784
2905
  console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
2785
2906
  unchanged++;
@@ -2888,7 +3009,7 @@ function diffStack(oldScan, newScan) {
2888
3009
  return changes;
2889
3010
  }
2890
3011
  function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
2891
- const filePath = path25.join(projectDir, filename);
3012
+ const filePath = path26.join(projectDir, filename);
2892
3013
  const currentContent = readFileSafe(filePath);
2893
3014
  const newContent = generate2();
2894
3015
  if (!currentContent) {
@@ -2934,7 +3055,7 @@ function countFilesInDir(dirPath) {
2934
3055
  }
2935
3056
 
2936
3057
  // src/commands/export.ts
2937
- import path26 from "path";
3058
+ import path27 from "path";
2938
3059
  import fs14 from "fs-extra";
2939
3060
  import ora6 from "ora";
2940
3061
  import { select as select2 } from "@inquirer/prompts";
@@ -2971,9 +3092,9 @@ function toCline(content) {
2971
3092
  ${stripped}`;
2972
3093
  }
2973
3094
  async function exportCommand(targetPath, options) {
2974
- const projectDir = path26.resolve(targetPath || process.cwd());
2975
- const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
2976
- const claudeMdPath = path26.join(projectDir, GENERATED_FILES.claudeMd);
3095
+ const projectDir = path27.resolve(targetPath || process.cwd());
3096
+ const configPath = path27.join(projectDir, AI_KIT_CONFIG_FILE);
3097
+ const claudeMdPath = path27.join(projectDir, GENERATED_FILES.claudeMd);
2977
3098
  logSection("AI Kit \u2014 Export");
2978
3099
  if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
2979
3100
  logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
@@ -3015,7 +3136,7 @@ async function exportCommand(targetPath, options) {
3015
3136
  for (const fmt2 of formats) {
3016
3137
  const target = EXPORT_TARGETS[fmt2];
3017
3138
  const transformer = transformers[fmt2];
3018
- const outputPath = path26.join(projectDir, target.file);
3139
+ const outputPath = path27.join(projectDir, target.file);
3019
3140
  const transformed = transformer(claudeContent);
3020
3141
  await fs14.writeFile(outputPath, transformed, "utf-8");
3021
3142
  exported++;
@@ -3033,7 +3154,7 @@ async function exportCommand(targetPath, options) {
3033
3154
  }
3034
3155
 
3035
3156
  // src/commands/stats.ts
3036
- import path27 from "path";
3157
+ import path28 from "path";
3037
3158
  import chalk5 from "chalk";
3038
3159
  var SKILL_CATEGORIES = {
3039
3160
  "Getting Started": ["prompt-help", "understand"],
@@ -3136,8 +3257,8 @@ var MCP_DISPLAY_NAMES = {
3136
3257
  perplexity: "Perplexity"
3137
3258
  };
3138
3259
  async function statsCommand(targetPath) {
3139
- const projectDir = path27.resolve(targetPath || process.cwd());
3140
- const configPath = path27.join(projectDir, AI_KIT_CONFIG_FILE);
3260
+ const projectDir = path28.resolve(targetPath || process.cwd());
3261
+ const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
3141
3262
  logSection("AI Kit \u2014 Project Stats");
3142
3263
  console.log("");
3143
3264
  if (!fileExists(configPath)) {
@@ -3231,21 +3352,21 @@ async function statsCommand(targetPath) {
3231
3352
  }
3232
3353
 
3233
3354
  // src/commands/audit.ts
3234
- import path28 from "path";
3355
+ import path29 from "path";
3235
3356
  import fs15 from "fs-extra";
3236
3357
  import chalk6 from "chalk";
3237
3358
  async function auditCommand(targetPath) {
3238
- const projectDir = path28.resolve(targetPath || process.cwd());
3359
+ const projectDir = path29.resolve(targetPath || process.cwd());
3239
3360
  logSection("AI Kit \u2014 Security & Configuration Audit");
3240
3361
  logInfo(`Auditing: ${projectDir}`);
3241
3362
  const checks = [];
3242
- const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
3363
+ const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
3243
3364
  if (fileExists(configPath)) {
3244
3365
  checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
3245
3366
  } else {
3246
3367
  checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
3247
3368
  }
3248
- const claudeMdPath = path28.join(projectDir, GENERATED_FILES.claudeMd);
3369
+ const claudeMdPath = path29.join(projectDir, GENERATED_FILES.claudeMd);
3249
3370
  const claudeMd = readFileSafe(claudeMdPath);
3250
3371
  if (claudeMd) {
3251
3372
  if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
@@ -3270,7 +3391,7 @@ async function auditCommand(targetPath) {
3270
3391
  checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
3271
3392
  }
3272
3393
  }
3273
- const settingsLocalPath = path28.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3394
+ const settingsLocalPath = path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3274
3395
  if (fileExists(settingsLocalPath)) {
3275
3396
  const settings2 = readJsonSafe(settingsLocalPath);
3276
3397
  if (settings2?.hooks) {
@@ -3281,14 +3402,14 @@ async function auditCommand(targetPath) {
3281
3402
  } else {
3282
3403
  checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
3283
3404
  }
3284
- const agentsDir = path28.join(projectDir, GENERATED_FILES.claudeAgents);
3405
+ const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3285
3406
  if (await fs15.pathExists(agentsDir)) {
3286
3407
  const agentFiles = (await fs15.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
3287
3408
  if (agentFiles.length > 0) {
3288
3409
  checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
3289
3410
  let invalidAgents = 0;
3290
3411
  for (const file of agentFiles) {
3291
- const content = readFileSafe(path28.join(agentsDir, file));
3412
+ const content = readFileSafe(path29.join(agentsDir, file));
3292
3413
  if (content && !content.startsWith("---")) {
3293
3414
  invalidAgents++;
3294
3415
  }
@@ -3302,18 +3423,18 @@ async function auditCommand(targetPath) {
3302
3423
  } else {
3303
3424
  checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
3304
3425
  }
3305
- const contextsDir = path28.join(projectDir, GENERATED_FILES.claudeContexts);
3426
+ const contextsDir = path29.join(projectDir, GENERATED_FILES.claudeContexts);
3306
3427
  if (await fs15.pathExists(contextsDir)) {
3307
3428
  const contextFiles = (await fs15.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
3308
3429
  checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
3309
3430
  } else {
3310
3431
  checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
3311
3432
  }
3312
- const skillsDir = path28.join(projectDir, GENERATED_FILES.claudeSkills);
3433
+ const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3313
3434
  if (await fs15.pathExists(skillsDir)) {
3314
3435
  const skillDirs = (await fs15.readdir(skillsDir)).filter(async (f) => {
3315
3436
  try {
3316
- return (await fs15.stat(path28.join(skillsDir, f))).isDirectory();
3437
+ return (await fs15.stat(path29.join(skillsDir, f))).isDirectory();
3317
3438
  } catch {
3318
3439
  return false;
3319
3440
  }
@@ -3322,7 +3443,7 @@ async function auditCommand(targetPath) {
3322
3443
  } else {
3323
3444
  checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
3324
3445
  }
3325
- const gitignorePath = path28.join(projectDir, ".gitignore");
3446
+ const gitignorePath = path29.join(projectDir, ".gitignore");
3326
3447
  const gitignore = readFileSafe(gitignorePath);
3327
3448
  if (gitignore) {
3328
3449
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
@@ -3338,7 +3459,7 @@ async function auditCommand(targetPath) {
3338
3459
  checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
3339
3460
  }
3340
3461
  }
3341
- const settingsPath = path28.join(projectDir, ".claude", "settings.json");
3462
+ const settingsPath = path29.join(projectDir, ".claude", "settings.json");
3342
3463
  const settings = readFileSafe(settingsPath);
3343
3464
  if (settings) {
3344
3465
  const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
@@ -3371,7 +3492,7 @@ async function auditCommand(targetPath) {
3371
3492
  }
3372
3493
 
3373
3494
  // src/commands/health.ts
3374
- import path29 from "path";
3495
+ import path30 from "path";
3375
3496
  import fs16 from "fs-extra";
3376
3497
  import chalk7 from "chalk";
3377
3498
  import ora7 from "ora";
@@ -3413,7 +3534,7 @@ function checkSetup(projectDir, config) {
3413
3534
  detail: `config v${config.version} \u2260 CLI v${VERSION} \u2014 run \`ai-kit update\``
3414
3535
  });
3415
3536
  }
3416
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3537
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3417
3538
  if (claudeMd && claudeMd.includes("AI-KIT:START")) {
3418
3539
  checks.push({ name: "CLAUDE.md", status: "pass", detail: "Present with markers" });
3419
3540
  } else if (claudeMd) {
@@ -3421,19 +3542,19 @@ function checkSetup(projectDir, config) {
3421
3542
  } else {
3422
3543
  checks.push({ name: "CLAUDE.md", status: "fail", detail: "Not found" });
3423
3544
  }
3424
- if (fileExists(path29.join(projectDir, GENERATED_FILES.cursorRules))) {
3545
+ if (fileExists(path30.join(projectDir, GENERATED_FILES.cursorRules))) {
3425
3546
  checks.push({ name: ".cursorrules", status: "pass", detail: "Present" });
3426
3547
  } else {
3427
3548
  checks.push({ name: ".cursorrules", status: "warn", detail: "Not generated" });
3428
3549
  }
3429
- const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3550
+ const skillsDir = path30.join(projectDir, GENERATED_FILES.claudeSkills);
3430
3551
  if (dirExists(skillsDir)) {
3431
3552
  const count = config.commands.length;
3432
3553
  checks.push({ name: "Skills", status: "pass", detail: `${count} installed` });
3433
3554
  } else {
3434
3555
  checks.push({ name: "Skills", status: "warn", detail: "No skills directory" });
3435
3556
  }
3436
- const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3557
+ const agentsDir = path30.join(projectDir, GENERATED_FILES.claudeAgents);
3437
3558
  if (dirExists(agentsDir)) {
3438
3559
  try {
3439
3560
  const agentFiles = fs16.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
@@ -3444,7 +3565,7 @@ function checkSetup(projectDir, config) {
3444
3565
  } else {
3445
3566
  checks.push({ name: "Agents", status: "warn", detail: "Not configured" });
3446
3567
  }
3447
- const settingsLocal = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3568
+ const settingsLocal = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3448
3569
  if (settingsLocal && settingsLocal.includes('"hooks"')) {
3449
3570
  checks.push({ name: "Hooks", status: "pass", detail: "Configured" });
3450
3571
  } else {
@@ -3454,7 +3575,7 @@ function checkSetup(projectDir, config) {
3454
3575
  }
3455
3576
  function checkSecurity(projectDir) {
3456
3577
  const checks = [];
3457
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3578
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3458
3579
  if (claudeMd) {
3459
3580
  const secretPatterns = [
3460
3581
  /(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
@@ -3469,7 +3590,7 @@ function checkSecurity(projectDir) {
3469
3590
  detail: hasSecrets ? "Potential secrets detected \u2014 remove immediately" : "Clean"
3470
3591
  });
3471
3592
  }
3472
- const gitignore = readFileSafe(path29.join(projectDir, ".gitignore"));
3593
+ const gitignore = readFileSafe(path30.join(projectDir, ".gitignore"));
3473
3594
  if (gitignore) {
3474
3595
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
3475
3596
  checks.push({
@@ -3478,7 +3599,7 @@ function checkSecurity(projectDir) {
3478
3599
  detail: envIgnored ? "Protected" : "NOT gitignored \u2014 add .env to .gitignore"
3479
3600
  });
3480
3601
  }
3481
- const settingsJson = readFileSafe(path29.join(projectDir, ".claude", "settings.json"));
3602
+ const settingsJson = readFileSafe(path30.join(projectDir, ".claude", "settings.json"));
3482
3603
  if (settingsJson) {
3483
3604
  const hasHardcoded = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settingsJson);
3484
3605
  checks.push({
@@ -3587,7 +3708,7 @@ function checkDocs(projectDir) {
3587
3708
  { name: "Time Log", path: "docs/time-log.md" }
3588
3709
  ];
3589
3710
  for (const doc of docsToCheck) {
3590
- const content = readFileSafe(path29.join(projectDir, doc.path));
3711
+ const content = readFileSafe(path30.join(projectDir, doc.path));
3591
3712
  if (content) {
3592
3713
  const hasEntries = content.includes("## 20") || content.split("---").length > 2;
3593
3714
  checks.push({
@@ -3602,8 +3723,8 @@ function checkDocs(projectDir) {
3602
3723
  return { title: "Documentation", checks };
3603
3724
  }
3604
3725
  async function healthCommand(targetPath) {
3605
- const projectDir = path29.resolve(targetPath || process.cwd());
3606
- const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
3726
+ const projectDir = path30.resolve(targetPath || process.cwd());
3727
+ const configPath = path30.join(projectDir, AI_KIT_CONFIG_FILE);
3607
3728
  console.log("");
3608
3729
  logSection("AI Kit \u2014 Project Health");
3609
3730
  console.log(chalk7.dim(` ${projectDir}`));
@@ -3687,7 +3808,7 @@ async function healthCommand(targetPath) {
3687
3808
  }
3688
3809
 
3689
3810
  // src/commands/patterns.ts
3690
- import path30 from "path";
3811
+ import path31 from "path";
3691
3812
  import fs17 from "fs-extra";
3692
3813
  import chalk8 from "chalk";
3693
3814
  import ora8 from "ora";
@@ -3774,7 +3895,7 @@ function walkTsFiles(dir, files) {
3774
3895
  try {
3775
3896
  const entries = fs17.readdirSync(dir, { withFileTypes: true });
3776
3897
  for (const entry of entries) {
3777
- const full = path30.join(dir, entry.name);
3898
+ const full = path31.join(dir, entry.name);
3778
3899
  if (entry.isDirectory()) {
3779
3900
  if (IGNORE_DIRS.includes(entry.name)) continue;
3780
3901
  walkTsFiles(full, files);
@@ -3786,8 +3907,8 @@ function walkTsFiles(dir, files) {
3786
3907
  }
3787
3908
  }
3788
3909
  async function patternsCommand(targetPath) {
3789
- const projectDir = path30.resolve(targetPath || process.cwd());
3790
- const configPath = path30.join(projectDir, AI_KIT_CONFIG_FILE);
3910
+ const projectDir = path31.resolve(targetPath || process.cwd());
3911
+ const configPath = path31.join(projectDir, AI_KIT_CONFIG_FILE);
3791
3912
  console.log("");
3792
3913
  logSection("AI Kit \u2014 Pattern Library");
3793
3914
  console.log(chalk8.dim(` ${projectDir}`));
@@ -3798,7 +3919,7 @@ async function patternsCommand(targetPath) {
3798
3919
  }
3799
3920
  const spinner = ora8("Scanning for code patterns...").start();
3800
3921
  const files = [];
3801
- const srcDir = path30.join(projectDir, "src");
3922
+ const srcDir = path31.join(projectDir, "src");
3802
3923
  if (dirExists(srcDir)) {
3803
3924
  walkTsFiles(srcDir, files);
3804
3925
  } else {
@@ -3820,7 +3941,7 @@ async function patternsCommand(targetPath) {
3820
3941
  if (pattern.regex.test(lines2[i])) {
3821
3942
  pattern.matches.push({
3822
3943
  pattern: pattern.label,
3823
- file: path30.relative(projectDir, file),
3944
+ file: path31.relative(projectDir, file),
3824
3945
  line: i + 1
3825
3946
  });
3826
3947
  }
@@ -3848,9 +3969,9 @@ async function patternsCommand(targetPath) {
3848
3969
  logInfo("No recognizable patterns found.");
3849
3970
  return;
3850
3971
  }
3851
- const outputDir = path30.join(projectDir, "ai-kit");
3972
+ const outputDir = path31.join(projectDir, "ai-kit");
3852
3973
  fs17.ensureDirSync(outputDir);
3853
- const outputPath = path30.join(outputDir, "patterns.md");
3974
+ const outputPath = path31.join(outputDir, "patterns.md");
3854
3975
  const lines = [
3855
3976
  "# Code Patterns",
3856
3977
  "",
@@ -3891,13 +4012,13 @@ async function patternsCommand(targetPath) {
3891
4012
  }
3892
4013
 
3893
4014
  // src/commands/dead-code.ts
3894
- import path32 from "path";
4015
+ import path33 from "path";
3895
4016
  import fs19 from "fs-extra";
3896
4017
  import chalk9 from "chalk";
3897
4018
  import ora9 from "ora";
3898
4019
 
3899
4020
  // src/scanner/components.ts
3900
- import path31 from "path";
4021
+ import path32 from "path";
3901
4022
  import fs18 from "fs-extra";
3902
4023
  var COMPONENT_DIRS = [
3903
4024
  "src/components",
@@ -3925,7 +4046,7 @@ function findComponentFiles(projectPath) {
3925
4046
  const files = [];
3926
4047
  const directories = /* @__PURE__ */ new Set();
3927
4048
  for (const dir of COMPONENT_DIRS) {
3928
- const fullDir = path31.join(projectPath, dir);
4049
+ const fullDir = path32.join(projectPath, dir);
3929
4050
  if (dirExists(fullDir)) {
3930
4051
  walkForComponents(fullDir, files, directories);
3931
4052
  }
@@ -3936,7 +4057,7 @@ function walkForComponents(dir, files, directories) {
3936
4057
  try {
3937
4058
  const entries = fs18.readdirSync(dir, { withFileTypes: true });
3938
4059
  for (const entry of entries) {
3939
- const full = path31.join(dir, entry.name);
4060
+ const full = path32.join(dir, entry.name);
3940
4061
  if (entry.isDirectory()) {
3941
4062
  if (IGNORE_PATTERNS.includes(entry.name)) continue;
3942
4063
  walkForComponents(full, files, directories);
@@ -3976,7 +4097,7 @@ function extractComponentName(filePath, content) {
3976
4097
  /export\s+(?:const|function)\s+(\w+)/
3977
4098
  );
3978
4099
  if (namedExport) return namedExport[1];
3979
- return path31.basename(filePath, ".tsx");
4100
+ return path32.basename(filePath, ".tsx");
3980
4101
  }
3981
4102
  function extractExportType(content) {
3982
4103
  const hasDefault = /export\s+default\s/.test(content);
@@ -4065,7 +4186,7 @@ function extractDependencies(content) {
4065
4186
  for (const match of imports) {
4066
4187
  const importPath = match[1];
4067
4188
  if (importPath.startsWith(".") || importPath.startsWith("..")) {
4068
- const basename = path31.basename(importPath).replace(/\.\w+$/, "");
4189
+ const basename = path32.basename(importPath).replace(/\.\w+$/, "");
4069
4190
  if (/^[A-Z]/.test(basename)) {
4070
4191
  deps.push(basename);
4071
4192
  }
@@ -4074,33 +4195,33 @@ function extractDependencies(content) {
4074
4195
  return deps;
4075
4196
  }
4076
4197
  function checkForTests(componentPath, componentName) {
4077
- const dir = path31.dirname(componentPath);
4078
- const base = path31.basename(componentPath, ".tsx");
4198
+ const dir = path32.dirname(componentPath);
4199
+ const base = path32.basename(componentPath, ".tsx");
4079
4200
  for (const suffix of TEST_SUFFIXES) {
4080
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4201
+ if (fs18.existsSync(path32.join(dir, base + suffix))) return true;
4081
4202
  }
4082
- const testsDir = path31.join(dir, "__tests__");
4203
+ const testsDir = path32.join(dir, "__tests__");
4083
4204
  if (dirExists(testsDir)) {
4084
4205
  for (const suffix of TEST_SUFFIXES) {
4085
- if (fs18.existsSync(path31.join(testsDir, base + suffix))) return true;
4086
- if (fs18.existsSync(path31.join(testsDir, componentName + suffix))) return true;
4206
+ if (fs18.existsSync(path32.join(testsDir, base + suffix))) return true;
4207
+ if (fs18.existsSync(path32.join(testsDir, componentName + suffix))) return true;
4087
4208
  }
4088
4209
  }
4089
4210
  return false;
4090
4211
  }
4091
4212
  function checkForStory(componentPath, componentName) {
4092
- const dir = path31.dirname(componentPath);
4093
- const base = path31.basename(componentPath, ".tsx");
4213
+ const dir = path32.dirname(componentPath);
4214
+ const base = path32.basename(componentPath, ".tsx");
4094
4215
  for (const suffix of STORY_SUFFIXES) {
4095
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4096
- if (fs18.existsSync(path31.join(dir, componentName + suffix))) return true;
4216
+ if (fs18.existsSync(path32.join(dir, base + suffix))) return true;
4217
+ if (fs18.existsSync(path32.join(dir, componentName + suffix))) return true;
4097
4218
  }
4098
4219
  return false;
4099
4220
  }
4100
4221
  function checkForAiDoc(componentPath) {
4101
- const dir = path31.dirname(componentPath);
4102
- const base = path31.basename(componentPath, ".tsx");
4103
- return fs18.existsSync(path31.join(dir, `${base}.ai.md`)) || fs18.existsSync(path31.join(dir, "component.ai.md"));
4222
+ const dir = path32.dirname(componentPath);
4223
+ const base = path32.basename(componentPath, ".tsx");
4224
+ return fs18.existsSync(path32.join(dir, `${base}.ai.md`)) || fs18.existsSync(path32.join(dir, "component.ai.md"));
4104
4225
  }
4105
4226
  function categorizeComponent(relativePath) {
4106
4227
  const lower = relativePath.toLowerCase();
@@ -4118,7 +4239,7 @@ function parseComponent(filePath, projectPath) {
4118
4239
  const content = readFileSafe(filePath);
4119
4240
  if (!content) return null;
4120
4241
  const name = extractComponentName(filePath, content);
4121
- const relativePath = path31.relative(projectPath, filePath);
4242
+ const relativePath = path32.relative(projectPath, filePath);
4122
4243
  return {
4123
4244
  name,
4124
4245
  filePath,
@@ -4166,7 +4287,7 @@ function collectAllFiles(dir, files) {
4166
4287
  try {
4167
4288
  const entries = fs19.readdirSync(dir, { withFileTypes: true });
4168
4289
  for (const entry of entries) {
4169
- const full = path32.join(dir, entry.name);
4290
+ const full = path33.join(dir, entry.name);
4170
4291
  if (entry.isDirectory()) {
4171
4292
  if (IGNORE_DIRS2.includes(entry.name)) continue;
4172
4293
  collectAllFiles(full, files);
@@ -4178,12 +4299,12 @@ function collectAllFiles(dir, files) {
4178
4299
  }
4179
4300
  }
4180
4301
  function isTestOrStoryFile(filePath) {
4181
- const name = path32.basename(filePath).toLowerCase();
4302
+ const name = path33.basename(filePath).toLowerCase();
4182
4303
  return name.includes(".test.") || name.includes(".spec.") || name.includes(".stories.") || name.includes("__tests__") || name.includes("__mocks__");
4183
4304
  }
4184
4305
  async function deadCodeCommand(targetPath) {
4185
- const projectDir = path32.resolve(targetPath || process.cwd());
4186
- const configPath = path32.join(projectDir, AI_KIT_CONFIG_FILE);
4306
+ const projectDir = path33.resolve(targetPath || process.cwd());
4307
+ const configPath = path33.join(projectDir, AI_KIT_CONFIG_FILE);
4187
4308
  console.log("");
4188
4309
  logSection("AI Kit \u2014 Dead Code Report");
4189
4310
  console.log(chalk9.dim(` ${projectDir}`));
@@ -4200,7 +4321,7 @@ async function deadCodeCommand(targetPath) {
4200
4321
  }
4201
4322
  spinner.text = `Found ${scanResult.components.length} components. Checking imports...`;
4202
4323
  const allFiles = [];
4203
- const srcDir = path32.join(projectDir, "src");
4324
+ const srcDir = path33.join(projectDir, "src");
4204
4325
  if (fs19.existsSync(srcDir)) {
4205
4326
  collectAllFiles(srcDir, allFiles);
4206
4327
  } else {
@@ -4224,7 +4345,7 @@ async function deadCodeCommand(targetPath) {
4224
4345
  `(?:import|from)\\s+.*['"][^'"]*(?:/|\\b)${escapeRegex(component.name)}(?:[/'"]|\\b)`
4225
4346
  );
4226
4347
  for (const [file, content] of fileContents) {
4227
- if (path32.resolve(file) === path32.resolve(componentFile)) continue;
4348
+ if (path33.resolve(file) === path33.resolve(componentFile)) continue;
4228
4349
  if (namePattern.test(content)) {
4229
4350
  importCount++;
4230
4351
  if (isTestOrStoryFile(file)) {
@@ -4303,7 +4424,7 @@ function escapeRegex(str) {
4303
4424
  }
4304
4425
 
4305
4426
  // src/commands/drift.ts
4306
- import path33 from "path";
4427
+ import path34 from "path";
4307
4428
  import fs20 from "fs-extra";
4308
4429
  import chalk10 from "chalk";
4309
4430
  import ora10 from "ora";
@@ -4340,11 +4461,11 @@ function parseAiDocFrontmatter(content) {
4340
4461
  return { props, fields };
4341
4462
  }
4342
4463
  function findAiDocPath(componentPath) {
4343
- const dir = path33.dirname(componentPath);
4344
- const base = path33.basename(componentPath, ".tsx");
4464
+ const dir = path34.dirname(componentPath);
4465
+ const base = path34.basename(componentPath, ".tsx");
4345
4466
  const candidates = [
4346
- path33.join(dir, `${base}.ai.md`),
4347
- path33.join(dir, "component.ai.md")
4467
+ path34.join(dir, `${base}.ai.md`),
4468
+ path34.join(dir, "component.ai.md")
4348
4469
  ];
4349
4470
  for (const candidate of candidates) {
4350
4471
  if (fs20.existsSync(candidate)) return candidate;
@@ -4388,8 +4509,8 @@ function analyzeDrift(component, projectDir) {
4388
4509
  };
4389
4510
  }
4390
4511
  async function driftCommand(targetPath) {
4391
- const projectDir = path33.resolve(targetPath || process.cwd());
4392
- const configPath = path33.join(projectDir, AI_KIT_CONFIG_FILE);
4512
+ const projectDir = path34.resolve(targetPath || process.cwd());
4513
+ const configPath = path34.join(projectDir, AI_KIT_CONFIG_FILE);
4393
4514
  console.log("");
4394
4515
  logSection("AI Kit \u2014 Component Drift Detector");
4395
4516
  console.log(chalk10.dim(` ${projectDir}`));
@@ -4491,7 +4612,7 @@ async function driftCommand(targetPath) {
4491
4612
  }
4492
4613
 
4493
4614
  // src/commands/component-registry.ts
4494
- import path34 from "path";
4615
+ import path35 from "path";
4495
4616
  import fs22 from "fs-extra";
4496
4617
  import chalk11 from "chalk";
4497
4618
  import ora11 from "ora";
@@ -4516,12 +4637,12 @@ function calculateHealthScore(component) {
4516
4637
 
4517
4638
  // src/commands/component-registry.ts
4518
4639
  async function componentRegistryCommand(targetPath) {
4519
- const projectDir = path34.resolve(targetPath || process.cwd());
4640
+ const projectDir = path35.resolve(targetPath || process.cwd());
4520
4641
  console.log("");
4521
4642
  logSection("AI Kit \u2014 Component Registry");
4522
4643
  console.log(chalk11.dim(` ${projectDir}`));
4523
4644
  console.log("");
4524
- if (!fileExists(path34.join(projectDir, AI_KIT_CONFIG_FILE))) {
4645
+ if (!fileExists(path35.join(projectDir, AI_KIT_CONFIG_FILE))) {
4525
4646
  logWarning("ai-kit.config.json not found. Run `ai-kit init` first.");
4526
4647
  return;
4527
4648
  }
@@ -4555,20 +4676,20 @@ async function componentRegistryCommand(targetPath) {
4555
4676
  for (const entry of entries) {
4556
4677
  categories[entry.category] = (categories[entry.category] || 0) + 1;
4557
4678
  }
4558
- const pkgPath = path34.join(projectDir, "package.json");
4679
+ const pkgPath = path35.join(projectDir, "package.json");
4559
4680
  const pkg = fs22.readJsonSync(pkgPath, { throws: false }) || {};
4560
4681
  const registry = {
4561
4682
  version: "1.0.0",
4562
4683
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4563
- projectName: pkg.name || path34.basename(projectDir),
4684
+ projectName: pkg.name || path35.basename(projectDir),
4564
4685
  totalComponents: entries.length,
4565
4686
  categories,
4566
4687
  components: entries
4567
4688
  };
4568
- const outputPath = path34.join(projectDir, "ai-kit", "component-registry.json");
4569
- await fs22.ensureDir(path34.dirname(outputPath));
4689
+ const outputPath = path35.join(projectDir, "ai-kit", "component-registry.json");
4690
+ await fs22.ensureDir(path35.dirname(outputPath));
4570
4691
  await fs22.writeJson(outputPath, registry, { spaces: 2 });
4571
- const mdPath = path34.join(projectDir, "ai-kit", "component-registry.md");
4692
+ const mdPath = path35.join(projectDir, "ai-kit", "component-registry.md");
4572
4693
  const md = generateRegistryMarkdown(registry);
4573
4694
  await fs22.writeFile(mdPath, md, "utf-8");
4574
4695
  console.log("");
@@ -4628,6 +4749,220 @@ function generateRegistryMarkdown(registry) {
4628
4749
  return lines.join("\n");
4629
4750
  }
4630
4751
 
4752
+ // src/commands/rollback.ts
4753
+ import path36 from "path";
4754
+ import { select as select3, confirm as confirm4 } from "@inquirer/prompts";
4755
+ async function rollbackCommand(targetPath, latest) {
4756
+ const projectDir = path36.resolve(targetPath || process.cwd());
4757
+ const backups = await listBackups(projectDir);
4758
+ if (backups.length === 0) {
4759
+ logInfo("No backups found. Backups are created when you run `ai-kit update`.");
4760
+ return;
4761
+ }
4762
+ logSection("Available Backups");
4763
+ let selected;
4764
+ if (latest) {
4765
+ selected = backups[0];
4766
+ logInfo(`Restoring latest backup: ${selected}`);
4767
+ } else {
4768
+ for (const b of backups) {
4769
+ const label = b === backups[0] ? `${b} (latest)` : b;
4770
+ logInfo(label);
4771
+ }
4772
+ console.log("");
4773
+ selected = await select3({
4774
+ message: "Which backup do you want to restore?",
4775
+ choices: backups.map((b, i) => ({
4776
+ name: i === 0 ? `${b} (latest)` : b,
4777
+ value: b
4778
+ }))
4779
+ });
4780
+ }
4781
+ const proceed = await confirm4({
4782
+ message: `Restore configs from backup ${selected}? This will overwrite current files.`,
4783
+ default: true
4784
+ });
4785
+ if (!proceed) {
4786
+ logInfo("Cancelled.");
4787
+ return;
4788
+ }
4789
+ try {
4790
+ const restored = await restoreBackup(projectDir, selected);
4791
+ logSection("Restored Files");
4792
+ for (const file of restored) {
4793
+ logSuccess(`Restored: ${file}`);
4794
+ }
4795
+ console.log("");
4796
+ logInfo(`Configs restored from backup ${selected}`);
4797
+ } catch (err) {
4798
+ logError(`Failed to restore backup: ${String(err)}`);
4799
+ }
4800
+ }
4801
+
4802
+ // src/commands/migrate.ts
4803
+ import path37 from "path";
4804
+ import fs23 from "fs-extra";
4805
+ import { confirm as confirm5 } from "@inquirer/prompts";
4806
+ import ora12 from "ora";
4807
+ var AI_KIT_START2 = "<!-- AI-KIT:START -->";
4808
+ var AI_KIT_END2 = "<!-- AI-KIT:END -->";
4809
+ var CUSTOM_START = "<!-- CUSTOM RULES (preserved by ai-kit) -->";
4810
+ var CUSTOM_END = "<!-- /CUSTOM RULES -->";
4811
+ async function migrateCommand(targetPath, opts) {
4812
+ const projectDir = path37.resolve(targetPath || process.cwd());
4813
+ const configPath = path37.join(projectDir, AI_KIT_CONFIG_FILE);
4814
+ const dryRun = opts?.dryRun ?? false;
4815
+ logSection("AI Kit \u2014 Migrate Existing Project");
4816
+ const existingConfig = readJsonSafe(configPath);
4817
+ if (existingConfig) {
4818
+ logInfo("This project is already managed by ai-kit.");
4819
+ logInfo("Use `ai-kit update` to refresh configs.");
4820
+ return;
4821
+ }
4822
+ const claudeMdPath = path37.join(projectDir, GENERATED_FILES.claudeMd);
4823
+ const cursorRulesPath = path37.join(projectDir, GENERATED_FILES.cursorRules);
4824
+ const existingClaudeMd = readFileSafe(claudeMdPath);
4825
+ const existingCursorRules = readFileSafe(cursorRulesPath);
4826
+ if (!existingClaudeMd && !existingCursorRules) {
4827
+ logInfo("No existing AI config files found (CLAUDE.md or .cursorrules).");
4828
+ logInfo("Use `ai-kit init` for a fresh setup.");
4829
+ return;
4830
+ }
4831
+ const spinner = ora12("Scanning project...").start();
4832
+ const scan = await scanProject(projectDir);
4833
+ spinner.succeed("Project scanned");
4834
+ logSection("Detected Stack");
4835
+ const frameworkLabel = scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""} (${scan.routerType || "unknown"} router)`.trim() : scan.framework;
4836
+ logInfo(`Framework: ${frameworkLabel}`);
4837
+ logInfo(`CMS: ${scan.cms === "none" ? "None" : scan.cms}`);
4838
+ logInfo(`Styling: ${scan.styling.join(", ") || "None"}`);
4839
+ logInfo(`TypeScript: ${scan.typescript ? "Yes" : "No"}`);
4840
+ logInfo(`Package Manager: ${scan.packageManager}`);
4841
+ const customSections = existingClaudeMd ? parseSections(existingClaudeMd) : [];
4842
+ const customFragments = loadCustomFragments(projectDir);
4843
+ const strictness = "standard";
4844
+ const hookProfile = "standard";
4845
+ const tools = {
4846
+ claude: true,
4847
+ cursor: !!existingCursorRules || fileExists(path37.join(projectDir, ".cursor"))
4848
+ };
4849
+ const aiKitClaudeMd = generateClaudeMd(scan, { strictness, customFragments });
4850
+ const aiKitSections = parseSections(
4851
+ aiKitClaudeMd.replace(AI_KIT_START2, "").replace(AI_KIT_END2, "")
4852
+ );
4853
+ logSection("Migration Plan");
4854
+ if (customSections.length > 0) {
4855
+ logInfo(`Your existing CLAUDE.md has ${customSections.length} section(s):`);
4856
+ for (const s of customSections) {
4857
+ logSuccess(` KEEP: "${s.heading}"`);
4858
+ }
4859
+ }
4860
+ logInfo(`ai-kit will generate ${aiKitSections.length} section(s) for your stack:`);
4861
+ for (const s of aiKitSections) {
4862
+ logInfo(` + ADD: "${s.heading}"`);
4863
+ }
4864
+ console.log("");
4865
+ logInfo("Your custom sections will be placed at the TOP of the file.");
4866
+ logInfo("ai-kit sections will be wrapped in AI-KIT markers below.");
4867
+ logInfo("Future `ai-kit update` will only touch the marked section.");
4868
+ if (dryRun) {
4869
+ console.log("");
4870
+ logInfo("Dry run \u2014 no files were modified.");
4871
+ return;
4872
+ }
4873
+ console.log("");
4874
+ const proceed = await confirm5({
4875
+ message: "Apply this migration?",
4876
+ default: true
4877
+ });
4878
+ if (!proceed) {
4879
+ logInfo("Cancelled.");
4880
+ return;
4881
+ }
4882
+ const filesToBackup = [
4883
+ GENERATED_FILES.claudeMd,
4884
+ GENERATED_FILES.cursorRules
4885
+ ].filter((f) => fileExists(path37.join(projectDir, f)));
4886
+ if (filesToBackup.length > 0) {
4887
+ const backupPath = await backupFiles(projectDir, filesToBackup);
4888
+ if (backupPath) {
4889
+ logSuccess(
4890
+ `Backed up existing files to ${path37.relative(projectDir, backupPath)}`
4891
+ );
4892
+ }
4893
+ }
4894
+ logSection("Migrating");
4895
+ if (tools.claude) {
4896
+ const customBlock = customSections.length > 0 ? `${CUSTOM_START}
4897
+
4898
+ ${customSections.map((s) => s.raw).join("\n\n")}
4899
+
4900
+ ${CUSTOM_END}
4901
+
4902
+ ` : "";
4903
+ const merged = `${customBlock}${aiKitClaudeMd}`;
4904
+ await fs23.writeFile(claudeMdPath, merged, "utf-8");
4905
+ logSuccess("CLAUDE.md migrated (custom rules preserved at top)");
4906
+ }
4907
+ if (tools.cursor) {
4908
+ const cursorContent = generateCursorRules(scan, {
4909
+ strictness,
4910
+ customFragments
4911
+ });
4912
+ await fs23.writeFile(cursorRulesPath, cursorContent, "utf-8");
4913
+ logSuccess(".cursorrules generated");
4914
+ const mdcDir = path37.join(projectDir, GENERATED_FILES.cursorMdcDir);
4915
+ await fs23.ensureDir(mdcDir);
4916
+ const mdcFiles = generateMdcFiles(scan);
4917
+ for (const mdc of mdcFiles) {
4918
+ await fs23.writeFile(path37.join(mdcDir, mdc.filename), mdc.content, "utf-8");
4919
+ }
4920
+ logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files generated`);
4921
+ }
4922
+ const commands = await copySkills(projectDir);
4923
+ logSuccess(`${commands.length} skills copied`);
4924
+ const agents = await copyAgents(projectDir, scan);
4925
+ logSuccess(`${agents.length} agents copied`);
4926
+ const contexts = await copyContexts(projectDir);
4927
+ logSuccess(`${contexts.length} context modes copied`);
4928
+ const settingsLocalPath = path37.join(
4929
+ projectDir,
4930
+ GENERATED_FILES.claudeSettingsLocal
4931
+ );
4932
+ const settingsLocal = generateSettingsLocal(scan, hookProfile);
4933
+ await fs23.ensureDir(path37.dirname(settingsLocalPath));
4934
+ await fs23.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
4935
+ logSuccess(`Hooks configured (profile: ${hookProfile})`);
4936
+ const guides = await copyGuides(projectDir);
4937
+ logSuccess(`${guides.length} guides copied`);
4938
+ const templates = [];
4939
+ if (tools.claude) templates.push("CLAUDE.md");
4940
+ if (tools.cursor) templates.push(".cursorrules");
4941
+ const config = generateConfig(scan, templates, commands, guides, {
4942
+ strictness,
4943
+ customFragments,
4944
+ agents,
4945
+ contexts,
4946
+ hooks: true,
4947
+ hookProfile,
4948
+ tools
4949
+ });
4950
+ await fs23.writeJson(configPath, config, { spaces: 2 });
4951
+ logSuccess("ai-kit.config.json created");
4952
+ logSection("Migration Complete");
4953
+ if (customSections.length > 0) {
4954
+ logInfo(
4955
+ `${customSections.length} custom section(s) preserved in CLAUDE.md`
4956
+ );
4957
+ }
4958
+ logInfo("This project is now managed by ai-kit.");
4959
+ logInfo("Run `ai-kit update` anytime to refresh generated sections.");
4960
+ logInfo("Your custom rules above the AI-KIT markers will never be touched.");
4961
+ if (filesToBackup.length > 0) {
4962
+ logInfo("Rollback available: `ai-kit rollback --latest`");
4963
+ }
4964
+ }
4965
+
4631
4966
  // src/cli/error-handler.ts
4632
4967
  function withErrorHandler(fn) {
4633
4968
  const wrapped = async (...args) => {
@@ -4688,6 +5023,12 @@ function registerCommands(program2) {
4688
5023
  program2.command("component-registry").description("Generate a component registry for AI agent discovery").argument("[path]", "Project directory (defaults to current directory)").action(withErrorHandler(async (targetPath) => {
4689
5024
  await componentRegistryCommand(targetPath);
4690
5025
  }));
5026
+ program2.command("rollback").description("Restore AI configs from a previous backup").argument("[path]", "Project directory (defaults to current directory)").option("--latest", "Restore most recent backup without selection prompt").action(withErrorHandler(async (targetPath, opts) => {
5027
+ await rollbackCommand(targetPath, opts?.latest);
5028
+ }));
5029
+ program2.command("migrate").description("Adopt ai-kit in a project with existing CLAUDE.md/.cursorrules").argument("[path]", "Project directory (defaults to current directory)").option("--dry-run", "Preview migration without writing files").action(withErrorHandler(async (targetPath, opts) => {
5030
+ await migrateCommand(targetPath, opts);
5031
+ }));
4691
5032
  }
4692
5033
 
4693
5034
  // src/index.ts