@mikulgohil/ai-kit 1.9.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.9.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,
@@ -118,13 +202,14 @@ function detectNextjs(projectPath, pkg) {
118
202
  return { framework: "unknown" };
119
203
  }
120
204
  const nextjsVersion = deps.next.replace(/[\^~>=<]/g, "");
121
- const hasAppDir = dirExists(path2.join(projectPath, "app")) || dirExists(path2.join(projectPath, "src", "app"));
122
- const hasPagesDir = dirExists(path2.join(projectPath, "pages")) || dirExists(path2.join(projectPath, "src", "pages"));
205
+ const nextjsMajorVersion = parseInt(nextjsVersion.split(".")[0], 10) || void 0;
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"));
123
208
  let routerType;
124
209
  if (hasAppDir && hasPagesDir) routerType = "hybrid";
125
210
  else if (hasAppDir) routerType = "app";
126
211
  else if (hasPagesDir) routerType = "pages";
127
- return { framework: "nextjs", nextjsVersion, routerType };
212
+ return { framework: "nextjs", nextjsVersion, nextjsMajorVersion, routerType };
128
213
  }
129
214
 
130
215
  // src/scanner/sitecore.ts
@@ -205,7 +290,7 @@ function detectOptimizely(pkg) {
205
290
  }
206
291
 
207
292
  // src/scanner/styling.ts
208
- import path3 from "path";
293
+ import path4 from "path";
209
294
  function detectStyling(projectPath, pkg) {
210
295
  const deps = {
211
296
  ...pkg.dependencies,
@@ -217,7 +302,7 @@ function detectStyling(projectPath, pkg) {
217
302
  styling.push("tailwind");
218
303
  tailwindVersion = (deps.tailwindcss || deps["@tailwindcss/postcss"] || "").replace(/[\^~>=<]/g, "");
219
304
  }
220
- 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"));
221
306
  if (hasTailwindConfig && !styling.includes("tailwind")) {
222
307
  styling.push("tailwind");
223
308
  }
@@ -230,9 +315,9 @@ function detectStyling(projectPath, pkg) {
230
315
  }
231
316
 
232
317
  // src/scanner/typescript.ts
233
- import path4 from "path";
318
+ import path5 from "path";
234
319
  function detectTypescript(projectPath) {
235
- const tsconfigPath = path4.join(projectPath, "tsconfig.json");
320
+ const tsconfigPath = path5.join(projectPath, "tsconfig.json");
236
321
  if (!fileExists(tsconfigPath)) {
237
322
  return { typescript: false };
238
323
  }
@@ -244,18 +329,18 @@ function detectTypescript(projectPath) {
244
329
  }
245
330
 
246
331
  // src/scanner/monorepo.ts
247
- import path5 from "path";
332
+ import path6 from "path";
248
333
  function detectMonorepo(projectPath, pkg) {
249
- if (fileExists(path5.join(projectPath, "turbo.json"))) {
334
+ if (fileExists(path6.join(projectPath, "turbo.json"))) {
250
335
  return { monorepo: true, monorepoTool: "turborepo" };
251
336
  }
252
- if (fileExists(path5.join(projectPath, "nx.json"))) {
337
+ if (fileExists(path6.join(projectPath, "nx.json"))) {
253
338
  return { monorepo: true, monorepoTool: "nx" };
254
339
  }
255
- if (fileExists(path5.join(projectPath, "lerna.json"))) {
340
+ if (fileExists(path6.join(projectPath, "lerna.json"))) {
256
341
  return { monorepo: true, monorepoTool: "lerna" };
257
342
  }
258
- if (fileExists(path5.join(projectPath, "pnpm-workspace.yaml"))) {
343
+ if (fileExists(path6.join(projectPath, "pnpm-workspace.yaml"))) {
259
344
  return { monorepo: true, monorepoTool: "pnpm-workspaces" };
260
345
  }
261
346
  if (pkg.workspaces) {
@@ -265,10 +350,10 @@ function detectMonorepo(projectPath, pkg) {
265
350
  }
266
351
 
267
352
  // src/scanner/package-manager.ts
268
- import path6 from "path";
353
+ import path7 from "path";
269
354
  function detectPackageManager(projectPath) {
270
355
  const pkg = readJsonSafe(
271
- path6.join(projectPath, "package.json")
356
+ path7.join(projectPath, "package.json")
272
357
  );
273
358
  if (pkg?.packageManager) {
274
359
  if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
@@ -276,15 +361,15 @@ function detectPackageManager(projectPath) {
276
361
  if (pkg.packageManager.startsWith("bun")) return "bun";
277
362
  return "npm";
278
363
  }
279
- if (fileExists(path6.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
280
- if (fileExists(path6.join(projectPath, "yarn.lock"))) return "yarn";
281
- if (fileExists(path6.join(projectPath, "bun.lockb"))) return "bun";
282
- 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";
283
368
  return "npm";
284
369
  }
285
370
 
286
371
  // src/scanner/figma.ts
287
- import path7 from "path";
372
+ import path8 from "path";
288
373
  function detectFigma(projectPath, pkg) {
289
374
  return {
290
375
  figmaMcp: detectFigmaMcp(projectPath),
@@ -296,9 +381,9 @@ function detectFigma(projectPath, pkg) {
296
381
  }
297
382
  function detectFigmaMcp(projectPath) {
298
383
  const settingsPaths = [
299
- path7.join(projectPath, ".claude", "settings.json"),
300
- path7.join(projectPath, ".claude", "settings.local.json"),
301
- 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")
302
387
  ];
303
388
  for (const settingsPath of settingsPaths) {
304
389
  const content = readFileSafe(settingsPath);
@@ -319,28 +404,28 @@ function detectFigmaCodeCli(pkg) {
319
404
  }
320
405
  function detectDesignTokens(projectPath) {
321
406
  const tokenPaths = [
322
- path7.join(projectPath, "tokens.json"),
323
- path7.join(projectPath, "tokens"),
324
- path7.join(projectPath, "design-tokens.json"),
325
- 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")
326
411
  ];
327
412
  for (const tokenPath of tokenPaths) {
328
413
  if (fileExists(tokenPath)) return true;
329
414
  }
330
415
  const globalsCss = readFileSafe(
331
- path7.join(projectPath, "src", "app", "globals.css")
416
+ path8.join(projectPath, "src", "app", "globals.css")
332
417
  );
333
418
  if (globalsCss && globalsCss.includes("@theme")) return true;
334
419
  return false;
335
420
  }
336
421
  function detectTokenFormat(projectPath) {
337
422
  const globalsCss = readFileSafe(
338
- path7.join(projectPath, "src", "app", "globals.css")
423
+ path8.join(projectPath, "src", "app", "globals.css")
339
424
  );
340
425
  if (globalsCss && globalsCss.includes("@theme")) return "tailwind-v4";
341
426
  const twConfigPaths = [
342
- path7.join(projectPath, "tailwind.config.ts"),
343
- path7.join(projectPath, "tailwind.config.js")
427
+ path8.join(projectPath, "tailwind.config.ts"),
428
+ path8.join(projectPath, "tailwind.config.js")
344
429
  ];
345
430
  for (const twPath of twConfigPaths) {
346
431
  const content = readFileSafe(twPath);
@@ -355,12 +440,12 @@ function detectVisualTests(projectPath, pkg) {
355
440
  ...pkg.devDependencies
356
441
  };
357
442
  const hasPlaywright = "@playwright/test" in deps || "playwright" in deps;
358
- 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"));
359
444
  return hasPlaywright || hasPlaywrightConfig;
360
445
  }
361
446
 
362
447
  // src/scanner/tools.ts
363
- import path8 from "path";
448
+ import path9 from "path";
364
449
  function detectTools(projectPath, pkg) {
365
450
  const deps = {
366
451
  ...pkg.dependencies,
@@ -380,13 +465,13 @@ function detectTools(projectPath, pkg) {
380
465
  }
381
466
  function detectPlaywright(projectPath, deps) {
382
467
  if ("@playwright/test" in deps) return true;
383
- if (fileExists(path8.join(projectPath, "playwright.config.ts"))) return true;
384
- 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;
385
470
  return false;
386
471
  }
387
472
  function detectStorybook(projectPath, deps) {
388
473
  if ("@storybook/react" in deps) return true;
389
- if (dirExists(path8.join(projectPath, ".storybook"))) return true;
474
+ if (dirExists(path9.join(projectPath, ".storybook"))) return true;
390
475
  return false;
391
476
  }
392
477
  function detectEslint(projectPath, deps) {
@@ -404,7 +489,7 @@ function detectEslint(projectPath, deps) {
404
489
  "eslint.config.ts"
405
490
  ];
406
491
  for (const config of eslintConfigs) {
407
- if (fileExists(path8.join(projectPath, config))) return true;
492
+ if (fileExists(path9.join(projectPath, config))) return true;
408
493
  }
409
494
  return false;
410
495
  }
@@ -423,14 +508,14 @@ function detectPrettier(projectPath, deps) {
423
508
  "prettier.config.ts"
424
509
  ];
425
510
  for (const config of prettierConfigs) {
426
- if (fileExists(path8.join(projectPath, config))) return true;
511
+ if (fileExists(path9.join(projectPath, config))) return true;
427
512
  }
428
513
  return false;
429
514
  }
430
515
  function detectBiome(projectPath, deps) {
431
516
  if ("@biomejs/biome" in deps) return true;
432
- if (fileExists(path8.join(projectPath, "biome.json"))) return true;
433
- 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;
434
519
  return false;
435
520
  }
436
521
  function detectAxeCore(deps) {
@@ -438,13 +523,13 @@ function detectAxeCore(deps) {
438
523
  }
439
524
  function detectSnyk(projectPath, deps) {
440
525
  if ("snyk" in deps) return true;
441
- if (fileExists(path8.join(projectPath, ".snyk"))) return true;
526
+ if (fileExists(path9.join(projectPath, ".snyk"))) return true;
442
527
  return false;
443
528
  }
444
529
  function detectKnip(projectPath, deps) {
445
530
  if ("knip" in deps) return true;
446
- if (fileExists(path8.join(projectPath, "knip.json"))) return true;
447
- 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;
448
533
  return false;
449
534
  }
450
535
  function detectBundleAnalyzer(deps) {
@@ -452,12 +537,12 @@ function detectBundleAnalyzer(deps) {
452
537
  }
453
538
 
454
539
  // src/scanner/mcp.ts
455
- import path9 from "path";
540
+ import path10 from "path";
456
541
  function detectMcpServers(projectPath) {
457
542
  const settingsPaths = [
458
- path9.join(projectPath, ".claude", "settings.json"),
459
- path9.join(projectPath, ".claude", "settings.local.json"),
460
- 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")
461
546
  ];
462
547
  let combined = "";
463
548
  for (const settingsPath of settingsPaths) {
@@ -476,18 +561,18 @@ function detectMcpServers(projectPath) {
476
561
  }
477
562
 
478
563
  // src/scanner/design-tokens.ts
479
- import path10 from "path";
564
+ import path11 from "path";
480
565
  function detectDesignTokens2(projectPath) {
481
566
  const globalsCss = readFileSafe(
482
- path10.join(projectPath, "src", "app", "globals.css")
567
+ path11.join(projectPath, "src", "app", "globals.css")
483
568
  );
484
569
  if (globalsCss && globalsCss.includes("@theme")) {
485
570
  return parseThemeInline(globalsCss);
486
571
  }
487
572
  const twConfigPaths = [
488
- path10.join(projectPath, "tailwind.config.ts"),
489
- path10.join(projectPath, "tailwind.config.js"),
490
- 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")
491
576
  ];
492
577
  for (const twPath of twConfigPaths) {
493
578
  const content = readFileSafe(twPath);
@@ -621,7 +706,7 @@ function parseCssVariables(css) {
621
706
  }
622
707
 
623
708
  // src/scanner/static-site.ts
624
- import path11 from "path";
709
+ import path12 from "path";
625
710
  import fs2 from "fs-extra";
626
711
  function detectStaticSite(projectPath, pkg) {
627
712
  const hasStaticExport = checkStaticExport(projectPath);
@@ -650,9 +735,9 @@ function detectStaticSite(projectPath, pkg) {
650
735
  }
651
736
  function checkStaticExport(projectPath) {
652
737
  const configPaths = [
653
- path11.join(projectPath, "next.config.js"),
654
- path11.join(projectPath, "next.config.ts"),
655
- 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")
656
741
  ];
657
742
  for (const configPath of configPaths) {
658
743
  const content = readFileSafe(configPath);
@@ -664,8 +749,8 @@ function checkStaticExport(projectPath) {
664
749
  }
665
750
  function checkGenerateStaticParams(projectPath) {
666
751
  const appDirs = [
667
- path11.join(projectPath, "app"),
668
- path11.join(projectPath, "src", "app")
752
+ path12.join(projectPath, "app"),
753
+ path12.join(projectPath, "src", "app")
669
754
  ];
670
755
  for (const appDir of appDirs) {
671
756
  if (dirExists(appDir) && hasPatternInDir(appDir, /generateStaticParams/)) {
@@ -673,8 +758,8 @@ function checkGenerateStaticParams(projectPath) {
673
758
  }
674
759
  }
675
760
  const pagesDirs = [
676
- path11.join(projectPath, "pages"),
677
- path11.join(projectPath, "src", "pages")
761
+ path12.join(projectPath, "pages"),
762
+ path12.join(projectPath, "src", "pages")
678
763
  ];
679
764
  for (const pagesDir of pagesDirs) {
680
765
  if (dirExists(pagesDir) && hasPatternInDir(pagesDir, /getStaticPaths/)) {
@@ -685,8 +770,8 @@ function checkGenerateStaticParams(projectPath) {
685
770
  }
686
771
  function checkRevalidatePatterns(projectPath) {
687
772
  const appDirs = [
688
- path11.join(projectPath, "app"),
689
- path11.join(projectPath, "src", "app")
773
+ path12.join(projectPath, "app"),
774
+ path12.join(projectPath, "src", "app")
690
775
  ];
691
776
  for (const appDir of appDirs) {
692
777
  if (dirExists(appDir) && hasPatternInDir(appDir, /revalidate\s*[:=]/)) {
@@ -697,8 +782,8 @@ function checkRevalidatePatterns(projectPath) {
697
782
  }
698
783
  function checkServerActions(projectPath) {
699
784
  const srcDirs = [
700
- path11.join(projectPath, "app"),
701
- path11.join(projectPath, "src")
785
+ path12.join(projectPath, "app"),
786
+ path12.join(projectPath, "src")
702
787
  ];
703
788
  for (const srcDir of srcDirs) {
704
789
  if (dirExists(srcDir) && hasPatternInDir(srcDir, /['"]use server['"]/)) {
@@ -709,15 +794,15 @@ function checkServerActions(projectPath) {
709
794
  }
710
795
  function checkApiRoutes(projectPath) {
711
796
  const appApiDirs = [
712
- path11.join(projectPath, "app", "api"),
713
- path11.join(projectPath, "src", "app", "api")
797
+ path12.join(projectPath, "app", "api"),
798
+ path12.join(projectPath, "src", "app", "api")
714
799
  ];
715
800
  for (const apiDir of appApiDirs) {
716
801
  if (dirExists(apiDir)) return true;
717
802
  }
718
803
  const pagesApiDirs = [
719
- path11.join(projectPath, "pages", "api"),
720
- path11.join(projectPath, "src", "pages", "api")
804
+ path12.join(projectPath, "pages", "api"),
805
+ path12.join(projectPath, "src", "pages", "api")
721
806
  ];
722
807
  for (const apiDir of pagesApiDirs) {
723
808
  if (dirExists(apiDir)) return true;
@@ -731,7 +816,7 @@ function hasPatternInDir(dir, pattern, depth = 0) {
731
816
  const entries = fs2.readdirSync(dir, { withFileTypes: true });
732
817
  for (const entry of entries) {
733
818
  if (SCAN_IGNORE.includes(entry.name)) continue;
734
- const full = path11.join(dir, entry.name);
819
+ const full = path12.join(dir, entry.name);
735
820
  if (entry.isDirectory()) {
736
821
  if (hasPatternInDir(full, pattern, depth + 1)) return true;
737
822
  } else if (/\.(tsx?|jsx?|mjs)$/.test(entry.name)) {
@@ -745,19 +830,19 @@ function hasPatternInDir(dir, pattern, depth = 0) {
745
830
  }
746
831
 
747
832
  // src/scanner/aiignore.ts
748
- import path12 from "path";
833
+ import path13 from "path";
749
834
  function loadAiIgnorePatterns(projectPath) {
750
- const content = readFileSafe(path12.join(projectPath, ".aiignore"));
835
+ const content = readFileSafe(path13.join(projectPath, ".aiignore"));
751
836
  if (!content) return [];
752
837
  return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
753
838
  }
754
839
 
755
840
  // src/scanner/index.ts
756
841
  async function scanProject(projectPath) {
757
- const pkgPath = path13.join(projectPath, "package.json");
842
+ const pkgPath = path14.join(projectPath, "package.json");
758
843
  const pkg = readJsonSafe(pkgPath) || {};
759
844
  const scripts = pkg.scripts || {};
760
- const projectName = pkg.name || path13.basename(projectPath);
845
+ const projectName = pkg.name || path14.basename(projectPath);
761
846
  const nextjsResult = detectNextjs(projectPath, pkg);
762
847
  const sitecoreResult = detectSitecore(pkg);
763
848
  const optimizelyResult = detectOptimizely(pkg);
@@ -800,10 +885,10 @@ async function scanProject(projectPath) {
800
885
  }
801
886
 
802
887
  // src/generator/assembler.ts
803
- import path14 from "path";
888
+ import path15 from "path";
804
889
  import fs3 from "fs-extra";
805
890
  function readTemplate(relativePath) {
806
- const fullPath = path14.join(TEMPLATES_DIR, relativePath);
891
+ const fullPath = path15.join(TEMPLATES_DIR, relativePath);
807
892
  const content = readFileSafe(fullPath);
808
893
  if (!content) {
809
894
  throw new Error(`Template not found: ${relativePath}`);
@@ -811,12 +896,12 @@ function readTemplate(relativePath) {
811
896
  return content.trim();
812
897
  }
813
898
  function loadCustomFragments(projectPath) {
814
- const customDir = path14.join(projectPath, ".ai-kit", "fragments");
899
+ const customDir = path15.join(projectPath, ".ai-kit", "fragments");
815
900
  if (!fs3.existsSync(customDir)) return [];
816
901
  try {
817
902
  const files = fs3.readdirSync(customDir).filter((f) => f.endsWith(".md"));
818
903
  return files.map((f) => {
819
- const content = fs3.readFileSync(path14.join(customDir, f), "utf-8");
904
+ const content = fs3.readFileSync(path15.join(customDir, f), "utf-8");
820
905
  return content.trim();
821
906
  }).filter(Boolean);
822
907
  } catch {
@@ -1053,7 +1138,7 @@ var MDC_CONFIG = {
1053
1138
  function generateMdcFiles(scan) {
1054
1139
  const fragments = selectFragments(scan);
1055
1140
  const variables = buildCursorVariables(scan);
1056
- return fragments.map((fragment) => {
1141
+ const mdcFiles = fragments.map((fragment) => {
1057
1142
  const config = MDC_CONFIG[fragment] || {
1058
1143
  description: fragment,
1059
1144
  globs: "**/*"
@@ -1076,6 +1161,42 @@ function generateMdcFiles(scan) {
1076
1161
  ${replaced}`
1077
1162
  };
1078
1163
  });
1164
+ const techStack = [
1165
+ scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""}`.trim() : scan.framework,
1166
+ scan.cms !== "none" ? scan.cms : null,
1167
+ scan.styling.length > 0 ? scan.styling.join(", ") : null,
1168
+ scan.typescript ? "TypeScript" : null,
1169
+ scan.monorepo ? scan.monorepoTool : null
1170
+ ].filter(Boolean).join(" + ");
1171
+ const indexContent = [
1172
+ "---",
1173
+ `description: ${scan.projectName} \u2014 repo-wide project conventions and AI rules`,
1174
+ "globs: **/*",
1175
+ "alwaysApply: true",
1176
+ "---",
1177
+ "",
1178
+ `<!-- Generated by ai-kit v${VERSION} -->`,
1179
+ "",
1180
+ `# ${scan.projectName}`,
1181
+ "",
1182
+ `**Tech Stack**: ${techStack}`,
1183
+ "",
1184
+ "## Active Rules",
1185
+ ...fragments.map((f) => `- [\`${f}\`](./${f}.mdc) \u2014 ${MDC_CONFIG[f]?.description || f}`),
1186
+ "",
1187
+ "## Conventions",
1188
+ "- Follow existing patterns in the codebase",
1189
+ "- Match naming conventions already in use",
1190
+ `- Package manager: \`${scan.packageManager}\``,
1191
+ scan.routerType ? `- Router: ${scan.routerType === "app" ? "App Router" : scan.routerType === "pages" ? "Pages Router" : "Hybrid (App + Pages)"}` : "",
1192
+ "",
1193
+ "> Rules are auto-generated by [ai-kit](https://github.com/mikulgohil/ai-kit). Run `ai-kit update` to refresh."
1194
+ ].filter((line) => line !== void 0).join("\n");
1195
+ mdcFiles.unshift({
1196
+ filename: "index.mdc",
1197
+ content: indexContent
1198
+ });
1199
+ return mdcFiles;
1079
1200
  }
1080
1201
 
1081
1202
  // src/generator/config.ts
@@ -1101,9 +1222,33 @@ function generateConfig(scan, templates, commands, guides, options) {
1101
1222
  // src/generator/hooks.ts
1102
1223
  function generateHooks(scan, profile = "standard") {
1103
1224
  const hooks = {};
1225
+ const sessionStart = [];
1104
1226
  const preToolUse = [];
1105
1227
  const postToolUse = [];
1228
+ const postCompact = [];
1106
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
+ });
1107
1252
  preToolUse.push({
1108
1253
  matcher: "Bash(git push*)",
1109
1254
  hooks: [
@@ -1277,6 +1422,25 @@ function generateHooks(scan, profile = "standard") {
1277
1422
  ]
1278
1423
  });
1279
1424
  }
1425
+ if (profile !== "minimal") {
1426
+ postCompact.push({
1427
+ matcher: "",
1428
+ hooks: [
1429
+ {
1430
+ type: "command",
1431
+ command: [
1432
+ 'echo "\u{1F504} Context was compacted. Key reminders:"',
1433
+ 'if [ -f "CLAUDE.md" ]; then echo " \u2192 CLAUDE.md is loaded \u2014 project rules are preserved"; fi',
1434
+ 'if [ -f "ai-kit.config.json" ]; then',
1435
+ ` STACK=$(node -e "try{const c=JSON.parse(require('fs').readFileSync('ai-kit.config.json','utf8'));console.log([c.scanResult.framework,c.scanResult.cms,c.scanResult.styling?.join(',')].filter(Boolean).join(' + '))}catch{}" 2>/dev/null)`,
1436
+ ' if [ -n "$STACK" ]; then echo " \u2192 Tech stack: $STACK"; fi',
1437
+ "fi",
1438
+ 'echo " \u2192 Run /effort to adjust reasoning depth if needed"'
1439
+ ].join("\n")
1440
+ }
1441
+ ]
1442
+ });
1443
+ }
1280
1444
  if (profile === "strict") {
1281
1445
  stop.push({
1282
1446
  matcher: "",
@@ -1288,8 +1452,10 @@ function generateHooks(scan, profile = "standard") {
1288
1452
  ]
1289
1453
  });
1290
1454
  }
1455
+ if (sessionStart.length > 0) hooks.SessionStart = sessionStart;
1291
1456
  if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
1292
1457
  if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
1458
+ if (postCompact.length > 0) hooks.PostCompact = postCompact;
1293
1459
  if (stop.length > 0) hooks.Stop = stop;
1294
1460
  return hooks;
1295
1461
  }
@@ -1301,7 +1467,7 @@ function generateSettingsLocal(scan, profile = "standard") {
1301
1467
  }
1302
1468
 
1303
1469
  // src/copier/skills.ts
1304
- import path15 from "path";
1470
+ import path16 from "path";
1305
1471
  import fs4 from "fs-extra";
1306
1472
  var AVAILABLE_SKILLS = [
1307
1473
  "prompt-help",
@@ -1351,7 +1517,9 @@ var AVAILABLE_SKILLS = [
1351
1517
  "harness-audit",
1352
1518
  // New skills (v1.7.0) — requirements clarification (inspired by OMC Deep Interview)
1353
1519
  "deep-interview",
1354
- "clarify-requirements"
1520
+ "clarify-requirements",
1521
+ // New skills (v1.10.0) — documentation freshness
1522
+ "fetch-docs"
1355
1523
  ];
1356
1524
  var SKILL_DESCRIPTIONS = {
1357
1525
  "prompt-help": "Help developers write effective AI prompts with structured context",
@@ -1401,39 +1569,41 @@ var SKILL_DESCRIPTIONS = {
1401
1569
  "harness-audit": "Audit AI agent configuration \u2014 check CLAUDE.md, hooks, agents, skills, MCP servers",
1402
1570
  // New skills (v1.7.0) — requirements clarification
1403
1571
  "deep-interview": "Socratic requirements gathering \u2014 structured interview to transform vague ideas into detailed specifications",
1404
- "clarify-requirements": "Quick task clarification \u2014 identify gaps and ambiguities in under 5 minutes before coding"
1572
+ "clarify-requirements": "Quick task clarification \u2014 identify gaps and ambiguities in under 5 minutes before coding",
1573
+ // New skills (v1.10.0) — documentation freshness
1574
+ "fetch-docs": "Pre-load current, version-specific docs for your tech stack using Context7 MCP \u2014 run at session start to prevent outdated API usage"
1405
1575
  };
1406
1576
  async function copySkills(targetDir) {
1407
1577
  const copied = [];
1408
1578
  for (const skill of AVAILABLE_SKILLS) {
1409
- const src = path15.join(COMMANDS_DIR, `${skill}.md`);
1579
+ const src = path16.join(COMMANDS_DIR, `${skill}.md`);
1410
1580
  if (!await fs4.pathExists(src)) continue;
1411
1581
  const content = await fs4.readFile(src, "utf-8");
1412
1582
  const description = SKILL_DESCRIPTIONS[skill] || skill;
1413
- const claudeSkillDir = path15.join(targetDir, ".claude", "skills", skill);
1583
+ const claudeSkillDir = path16.join(targetDir, ".claude", "skills", skill);
1414
1584
  await fs4.ensureDir(claudeSkillDir);
1415
1585
  await fs4.writeFile(
1416
- path15.join(claudeSkillDir, "SKILL.md"),
1586
+ path16.join(claudeSkillDir, "SKILL.md"),
1417
1587
  content,
1418
1588
  "utf-8"
1419
1589
  );
1420
- const cursorSkillDir = path15.join(targetDir, ".cursor", "skills", skill);
1590
+ const cursorSkillDir = path16.join(targetDir, ".cursor", "skills", skill);
1421
1591
  await fs4.ensureDir(cursorSkillDir);
1422
1592
  await fs4.writeFile(
1423
- path15.join(cursorSkillDir, "SKILL.md"),
1593
+ path16.join(cursorSkillDir, "SKILL.md"),
1424
1594
  content,
1425
1595
  "utf-8"
1426
1596
  );
1427
- const legacyDir = path15.join(targetDir, ".claude", "commands");
1597
+ const legacyDir = path16.join(targetDir, ".claude", "commands");
1428
1598
  await fs4.ensureDir(legacyDir);
1429
- await fs4.copy(src, path15.join(legacyDir, `${skill}.md`), { overwrite: true });
1599
+ await fs4.copy(src, path16.join(legacyDir, `${skill}.md`), { overwrite: true });
1430
1600
  copied.push(skill);
1431
1601
  }
1432
1602
  return copied;
1433
1603
  }
1434
1604
 
1435
1605
  // src/copier/guides.ts
1436
- import path16 from "path";
1606
+ import path17 from "path";
1437
1607
  import fs5 from "fs-extra";
1438
1608
  var AVAILABLE_GUIDES = [
1439
1609
  "getting-started",
@@ -1444,12 +1614,12 @@ var AVAILABLE_GUIDES = [
1444
1614
  "hooks-and-agents"
1445
1615
  ];
1446
1616
  async function copyGuides(targetDir) {
1447
- const guidesTarget = path16.join(targetDir, "ai-kit", "guides");
1617
+ const guidesTarget = path17.join(targetDir, "ai-kit", "guides");
1448
1618
  await fs5.ensureDir(guidesTarget);
1449
1619
  const copied = [];
1450
1620
  for (const guide of AVAILABLE_GUIDES) {
1451
- const src = path16.join(GUIDES_DIR, `${guide}.md`);
1452
- const dest = path16.join(guidesTarget, `${guide}.md`);
1621
+ const src = path17.join(GUIDES_DIR, `${guide}.md`);
1622
+ const dest = path17.join(guidesTarget, `${guide}.md`);
1453
1623
  if (await fs5.pathExists(src)) {
1454
1624
  await fs5.copy(src, dest, { overwrite: true });
1455
1625
  copied.push(guide);
@@ -1459,16 +1629,16 @@ async function copyGuides(targetDir) {
1459
1629
  }
1460
1630
 
1461
1631
  // src/copier/docs.ts
1462
- import path17 from "path";
1632
+ import path18 from "path";
1463
1633
  import fs6 from "fs-extra";
1464
1634
  var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
1465
1635
  async function scaffoldDocs(targetDir) {
1466
- const docsTarget = path17.join(targetDir, "docs");
1636
+ const docsTarget = path18.join(targetDir, "docs");
1467
1637
  await fs6.ensureDir(docsTarget);
1468
1638
  const created = [];
1469
1639
  for (const doc of DOC_SCAFFOLDS) {
1470
- const src = path17.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
1471
- 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`);
1472
1642
  if (await fs6.pathExists(dest)) {
1473
1643
  continue;
1474
1644
  }
@@ -1481,7 +1651,7 @@ async function scaffoldDocs(targetDir) {
1481
1651
  }
1482
1652
 
1483
1653
  // src/copier/agents.ts
1484
- import path18 from "path";
1654
+ import path19 from "path";
1485
1655
  import fs7 from "fs-extra";
1486
1656
  var UNIVERSAL_AGENTS = [
1487
1657
  "planner",
@@ -1513,22 +1683,22 @@ var CONDITIONAL_AGENTS = [
1513
1683
  }
1514
1684
  ];
1515
1685
  async function copyAgents(targetDir, scan) {
1516
- const agentsTarget = path18.join(targetDir, ".claude", "agents");
1686
+ const agentsTarget = path19.join(targetDir, ".claude", "agents");
1517
1687
  await fs7.ensureDir(agentsTarget);
1518
1688
  const copied = [];
1519
1689
  for (const agent of UNIVERSAL_AGENTS) {
1520
- const src = path18.join(AGENTS_DIR, `${agent}.md`);
1690
+ const src = path19.join(AGENTS_DIR, `${agent}.md`);
1521
1691
  if (!await fs7.pathExists(src)) continue;
1522
- await fs7.copy(src, path18.join(agentsTarget, `${agent}.md`), {
1692
+ await fs7.copy(src, path19.join(agentsTarget, `${agent}.md`), {
1523
1693
  overwrite: true
1524
1694
  });
1525
1695
  copied.push(agent);
1526
1696
  }
1527
1697
  for (const { name, condition } of CONDITIONAL_AGENTS) {
1528
1698
  if (!condition(scan)) continue;
1529
- const src = path18.join(AGENTS_DIR, `${name}.md`);
1699
+ const src = path19.join(AGENTS_DIR, `${name}.md`);
1530
1700
  if (!await fs7.pathExists(src)) continue;
1531
- await fs7.copy(src, path18.join(agentsTarget, `${name}.md`), {
1701
+ await fs7.copy(src, path19.join(agentsTarget, `${name}.md`), {
1532
1702
  overwrite: true
1533
1703
  });
1534
1704
  copied.push(name);
@@ -1537,17 +1707,17 @@ async function copyAgents(targetDir, scan) {
1537
1707
  }
1538
1708
 
1539
1709
  // src/copier/contexts.ts
1540
- import path19 from "path";
1710
+ import path20 from "path";
1541
1711
  import fs8 from "fs-extra";
1542
1712
  var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
1543
1713
  async function copyContexts(targetDir) {
1544
- const contextsTarget = path19.join(targetDir, ".claude", "contexts");
1714
+ const contextsTarget = path20.join(targetDir, ".claude", "contexts");
1545
1715
  await fs8.ensureDir(contextsTarget);
1546
1716
  const copied = [];
1547
1717
  for (const context of AVAILABLE_CONTEXTS) {
1548
- const src = path19.join(CONTEXTS_DIR, `${context}.md`);
1718
+ const src = path20.join(CONTEXTS_DIR, `${context}.md`);
1549
1719
  if (!await fs8.pathExists(src)) continue;
1550
- await fs8.copy(src, path19.join(contextsTarget, `${context}.md`), {
1720
+ await fs8.copy(src, path20.join(contextsTarget, `${context}.md`), {
1551
1721
  overwrite: true
1552
1722
  });
1553
1723
  copied.push(context);
@@ -1557,10 +1727,10 @@ async function copyContexts(targetDir) {
1557
1727
 
1558
1728
  // src/commands/init.ts
1559
1729
  async function initCommand(targetPath) {
1560
- const projectDir = path20.resolve(targetPath || process.cwd());
1730
+ const projectDir = path21.resolve(targetPath || process.cwd());
1561
1731
  logSection("AI Kit \u2014 Project Setup");
1562
1732
  logInfo(`Scanning: ${projectDir}`);
1563
- const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
1733
+ const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
1564
1734
  let savedConfig = null;
1565
1735
  let reuseMode = false;
1566
1736
  if (fileExists(configPath)) {
@@ -1737,7 +1907,7 @@ async function selectHookProfile() {
1737
1907
  });
1738
1908
  }
1739
1909
  async function selectConflictStrategy(projectDir) {
1740
- 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));
1741
1911
  if (!hasExisting) return "overwrite";
1742
1912
  return select({
1743
1913
  message: "Existing AI config files detected. How should we handle conflicts?",
@@ -1767,7 +1937,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1767
1937
  docs: []
1768
1938
  };
1769
1939
  if (tools.claude) {
1770
- const claudeMdPath = path20.join(projectDir, GENERATED_FILES.claudeMd);
1940
+ const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
1771
1941
  if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
1772
1942
  const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1773
1943
  await fs9.writeFile(claudeMdPath, content, "utf-8");
@@ -1779,14 +1949,14 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1779
1949
  result.agents = await copyAgents(projectDir, scan);
1780
1950
  result.contexts = await copyContexts(projectDir);
1781
1951
  const hookProfile = opts?.hookProfile || "standard";
1782
- const settingsLocalPath = path20.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1952
+ const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1783
1953
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
1784
- await fs9.ensureDir(path20.dirname(settingsLocalPath));
1954
+ await fs9.ensureDir(path21.dirname(settingsLocalPath));
1785
1955
  await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1786
1956
  result.hooks = true;
1787
1957
  }
1788
1958
  if (tools.cursor) {
1789
- const cursorPath = path20.join(projectDir, GENERATED_FILES.cursorRules);
1959
+ const cursorPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
1790
1960
  if (conflict === "overwrite" || !fileExists(cursorPath)) {
1791
1961
  const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1792
1962
  await fs9.writeFile(cursorPath, content, "utf-8");
@@ -1794,11 +1964,11 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1794
1964
  } else {
1795
1965
  logWarning(".cursorrules exists, skipping");
1796
1966
  }
1797
- const mdcDir = path20.join(projectDir, GENERATED_FILES.cursorMdcDir);
1967
+ const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
1798
1968
  await fs9.ensureDir(mdcDir);
1799
1969
  const mdcFiles = generateMdcFiles(scan);
1800
1970
  for (const mdc of mdcFiles) {
1801
- 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");
1802
1972
  }
1803
1973
  result.cursorMdcFiles = mdcFiles.length;
1804
1974
  }
@@ -1817,7 +1987,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1817
1987
  tools
1818
1988
  });
1819
1989
  await fs9.writeJson(
1820
- path20.join(projectDir, AI_KIT_CONFIG_FILE),
1990
+ path21.join(projectDir, AI_KIT_CONFIG_FILE),
1821
1991
  config,
1822
1992
  { spaces: 2 }
1823
1993
  );
@@ -1893,13 +2063,13 @@ function showRecommendations(scan) {
1893
2063
  }
1894
2064
 
1895
2065
  // src/commands/update.ts
1896
- import path21 from "path";
2066
+ import path22 from "path";
1897
2067
  import fs10 from "fs-extra";
1898
2068
  import ora2 from "ora";
1899
2069
  import { confirm as confirm2 } from "@inquirer/prompts";
1900
2070
  async function updateCommand(targetPath) {
1901
- const projectDir = path21.resolve(targetPath || process.cwd());
1902
- 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);
1903
2073
  if (!fileExists(configPath)) {
1904
2074
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
1905
2075
  return;
@@ -1924,6 +2094,16 @@ async function updateCommand(targetPath) {
1924
2094
  const spinner = ora2("Re-scanning project...").start();
1925
2095
  const scan = await scanProject(projectDir);
1926
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
+ }
1927
2107
  logSection("Updating Files");
1928
2108
  const strictness = existingConfig.strictness || "standard";
1929
2109
  const hookProfile = existingConfig.hookProfile || "standard";
@@ -1932,8 +2112,8 @@ async function updateCommand(targetPath) {
1932
2112
  const genOpts = { strictness, customFragments };
1933
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}`);
1934
2114
  const templates = [];
1935
- if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)))) {
1936
- 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);
1937
2117
  const newContent = generateClaudeMd(scan, genOpts);
1938
2118
  const existing = readFileSafe(claudeMdPath);
1939
2119
  if (existing) {
@@ -1944,8 +2124,8 @@ async function updateCommand(targetPath) {
1944
2124
  templates.push("CLAUDE.md");
1945
2125
  logSuccess("CLAUDE.md updated");
1946
2126
  }
1947
- if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules)))) {
1948
- 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);
1949
2129
  const newContent = generateCursorRules(scan, genOpts);
1950
2130
  const existing = readFileSafe(cursorRulesPath);
1951
2131
  if (existing) {
@@ -1955,11 +2135,11 @@ async function updateCommand(targetPath) {
1955
2135
  }
1956
2136
  templates.push(".cursorrules");
1957
2137
  logSuccess(".cursorrules updated");
1958
- const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
2138
+ const mdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
1959
2139
  await fs10.ensureDir(mdcDir);
1960
2140
  const mdcFiles = generateMdcFiles(scan);
1961
2141
  for (const mdc of mdcFiles) {
1962
- 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");
1963
2143
  }
1964
2144
  logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
1965
2145
  }
@@ -1970,9 +2150,9 @@ async function updateCommand(targetPath) {
1970
2150
  const contexts = await copyContexts(projectDir);
1971
2151
  logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
1972
2152
  if (existingConfig.hooks !== false) {
1973
- const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2153
+ const settingsLocalPath = path22.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1974
2154
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
1975
- await fs10.ensureDir(path21.dirname(settingsLocalPath));
2155
+ await fs10.ensureDir(path22.dirname(settingsLocalPath));
1976
2156
  await fs10.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1977
2157
  logSuccess(`Hooks updated (profile: ${hookProfile})`);
1978
2158
  }
@@ -1990,14 +2170,17 @@ async function updateCommand(targetPath) {
1990
2170
  logSuccess("ai-kit.config.json updated");
1991
2171
  console.log("");
1992
2172
  logInfo("All AI configs refreshed with latest project scan.");
2173
+ if (backupPath) {
2174
+ logInfo("Rollback available: `ai-kit rollback --latest`");
2175
+ }
1993
2176
  }
1994
2177
 
1995
2178
  // src/commands/reset.ts
1996
- import path22 from "path";
2179
+ import path23 from "path";
1997
2180
  import fs11 from "fs-extra";
1998
2181
  import { confirm as confirm3 } from "@inquirer/prompts";
1999
2182
  async function resetCommand(targetPath) {
2000
- const projectDir = path22.resolve(targetPath || process.cwd());
2183
+ const projectDir = path23.resolve(targetPath || process.cwd());
2001
2184
  logSection("AI Kit \u2014 Reset");
2002
2185
  logWarning("This will remove all AI Kit generated files:");
2003
2186
  logInfo(` - ${GENERATED_FILES.claudeMd}`);
@@ -2018,42 +2201,42 @@ async function resetCommand(targetPath) {
2018
2201
  return;
2019
2202
  }
2020
2203
  const removed = [];
2021
- const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
2204
+ const claudeMdPath = path23.join(projectDir, GENERATED_FILES.claudeMd);
2022
2205
  if (fileExists(claudeMdPath)) {
2023
2206
  await fs11.remove(claudeMdPath);
2024
2207
  removed.push(GENERATED_FILES.claudeMd);
2025
2208
  }
2026
- const cursorPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
2209
+ const cursorPath = path23.join(projectDir, GENERATED_FILES.cursorRules);
2027
2210
  if (fileExists(cursorPath)) {
2028
2211
  await fs11.remove(cursorPath);
2029
2212
  removed.push(GENERATED_FILES.cursorRules);
2030
2213
  }
2031
- const cursorMdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
2214
+ const cursorMdcDir = path23.join(projectDir, GENERATED_FILES.cursorMdcDir);
2032
2215
  if (fileExists(cursorMdcDir)) {
2033
2216
  await fs11.remove(cursorMdcDir);
2034
2217
  removed.push(GENERATED_FILES.cursorMdcDir);
2035
2218
  }
2036
- const commandsDir = path22.join(projectDir, GENERATED_FILES.claudeCommands);
2219
+ const commandsDir = path23.join(projectDir, GENERATED_FILES.claudeCommands);
2037
2220
  if (fileExists(commandsDir)) {
2038
2221
  await fs11.remove(commandsDir);
2039
2222
  removed.push(GENERATED_FILES.claudeCommands);
2040
2223
  }
2041
- const claudeSkillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
2224
+ const claudeSkillsDir = path23.join(projectDir, GENERATED_FILES.claudeSkills);
2042
2225
  if (fileExists(claudeSkillsDir)) {
2043
2226
  await fs11.remove(claudeSkillsDir);
2044
2227
  removed.push(GENERATED_FILES.claudeSkills);
2045
2228
  }
2046
- const cursorSkillsDir = path22.join(projectDir, GENERATED_FILES.cursorSkills);
2229
+ const cursorSkillsDir = path23.join(projectDir, GENERATED_FILES.cursorSkills);
2047
2230
  if (fileExists(cursorSkillsDir)) {
2048
2231
  await fs11.remove(cursorSkillsDir);
2049
2232
  removed.push(GENERATED_FILES.cursorSkills);
2050
2233
  }
2051
- const aiKitDir = path22.join(projectDir, "ai-kit");
2234
+ const aiKitDir = path23.join(projectDir, "ai-kit");
2052
2235
  if (fileExists(aiKitDir)) {
2053
2236
  await fs11.remove(aiKitDir);
2054
2237
  removed.push("ai-kit/");
2055
2238
  }
2056
- const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
2239
+ const configPath = path23.join(projectDir, AI_KIT_CONFIG_FILE);
2057
2240
  if (fileExists(configPath)) {
2058
2241
  await fs11.remove(configPath);
2059
2242
  removed.push(AI_KIT_CONFIG_FILE);
@@ -2067,7 +2250,7 @@ async function resetCommand(targetPath) {
2067
2250
  }
2068
2251
 
2069
2252
  // src/commands/tokens.ts
2070
- import path23 from "path";
2253
+ import path24 from "path";
2071
2254
  import fs12 from "fs-extra";
2072
2255
  import chalk2 from "chalk";
2073
2256
  import ora3 from "ora";
@@ -2078,14 +2261,14 @@ var PRICING = {
2078
2261
  };
2079
2262
  var PLAN_BUDGET = 20;
2080
2263
  function findSessionFiles() {
2081
- const claudeDir = path23.join(os.homedir(), ".claude", "projects");
2264
+ const claudeDir = path24.join(os.homedir(), ".claude", "projects");
2082
2265
  if (!fs12.existsSync(claudeDir)) return [];
2083
2266
  const files = [];
2084
2267
  function walkDir(dir) {
2085
2268
  try {
2086
2269
  const entries = fs12.readdirSync(dir, { withFileTypes: true });
2087
2270
  for (const entry of entries) {
2088
- const full = path23.join(dir, entry.name);
2271
+ const full = path24.join(dir, entry.name);
2089
2272
  if (entry.isDirectory()) {
2090
2273
  walkDir(full);
2091
2274
  } else if (entry.name.endsWith(".jsonl")) {
@@ -2140,8 +2323,8 @@ function parseSessionFile(filePath) {
2140
2323
  const stat = fs12.statSync(filePath);
2141
2324
  sessionDate = stat.mtime.toISOString().slice(0, 10);
2142
2325
  }
2143
- const sessionId = path23.basename(filePath, ".jsonl");
2144
- const projectName = path23.basename(path23.dirname(filePath));
2326
+ const sessionId = path24.basename(filePath, ".jsonl");
2327
+ const projectName = path24.basename(path24.dirname(filePath));
2145
2328
  return {
2146
2329
  sessionId,
2147
2330
  filePath,
@@ -2370,7 +2553,7 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
2370
2553
  );
2371
2554
  console.log("");
2372
2555
  if (options.csv) {
2373
- const csvPath = path23.join(process.cwd(), "token-usage.csv");
2556
+ const csvPath = path24.join(process.cwd(), "token-usage.csv");
2374
2557
  const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
2375
2558
  const daily = aggregateByDate(sessions);
2376
2559
  const csvRows = daily.map(
@@ -2413,9 +2596,9 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2413
2596
  }
2414
2597
  };
2415
2598
  const outputDir = process.cwd();
2416
- const dataPath = path23.join(outputDir, "token-data.json");
2417
- const dashboardSrc = path23.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
2418
- 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");
2419
2602
  await fs12.writeJson(dataPath, exportData, { spaces: 2 });
2420
2603
  logInfo(`Token data written to ${dataPath}`);
2421
2604
  if (await fs12.pathExists(dashboardSrc)) {
@@ -2436,12 +2619,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2436
2619
  }
2437
2620
 
2438
2621
  // src/commands/doctor.ts
2439
- import path24 from "path";
2622
+ import path25 from "path";
2440
2623
  import chalk3 from "chalk";
2441
2624
  import ora4 from "ora";
2442
2625
  async function doctorCommand(targetPath) {
2443
- const projectDir = path24.resolve(targetPath || process.cwd());
2444
- 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);
2445
2628
  let passed = 0;
2446
2629
  let warnings = 0;
2447
2630
  let issues = 0;
@@ -2473,7 +2656,7 @@ async function doctorCommand(targetPath) {
2473
2656
  }
2474
2657
  for (const template of config.templates) {
2475
2658
  const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
2476
- const templatePath = path24.join(projectDir, templateFile);
2659
+ const templatePath = path25.join(projectDir, templateFile);
2477
2660
  if (fileExists(templatePath)) {
2478
2661
  logSuccess(`${template} exists and in sync`);
2479
2662
  passed++;
@@ -2484,8 +2667,8 @@ async function doctorCommand(targetPath) {
2484
2667
  }
2485
2668
  const missingSkills = [];
2486
2669
  for (const skill of config.commands) {
2487
- const claudeSkillPath = path24.join(projectDir, GENERATED_FILES.claudeSkills, skill);
2488
- 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);
2489
2672
  if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
2490
2673
  missingSkills.push(skill);
2491
2674
  }
@@ -2499,10 +2682,10 @@ async function doctorCommand(targetPath) {
2499
2682
  );
2500
2683
  issues++;
2501
2684
  }
2502
- const guidesDir = path24.join(projectDir, "ai-kit", "guides");
2685
+ const guidesDir = path25.join(projectDir, "ai-kit", "guides");
2503
2686
  const missingGuides = [];
2504
2687
  for (const guide of config.guides) {
2505
- const guidePath = path24.join(guidesDir, guide);
2688
+ const guidePath = path25.join(guidesDir, guide);
2506
2689
  if (!fileExists(guidePath)) {
2507
2690
  missingGuides.push(guide);
2508
2691
  }
@@ -2640,13 +2823,13 @@ function compareScanResults(previous, current) {
2640
2823
  }
2641
2824
 
2642
2825
  // src/commands/diff.ts
2643
- import path25 from "path";
2826
+ import path26 from "path";
2644
2827
  import fs13 from "fs-extra";
2645
2828
  import chalk4 from "chalk";
2646
2829
  import ora5 from "ora";
2647
2830
  async function diffCommand(targetPath) {
2648
- const projectDir = path25.resolve(targetPath || process.cwd());
2649
- 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);
2650
2833
  console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
2651
2834
  if (!fileExists(configPath)) {
2652
2835
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
@@ -2713,11 +2896,11 @@ async function diffCommand(targetPath) {
2713
2896
  if (cursorRulesStatus.status === "modified") modified++;
2714
2897
  else if (cursorRulesStatus.status === "added") added++;
2715
2898
  else unchanged++;
2716
- const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
2899
+ const skillsDir = path26.join(projectDir, GENERATED_FILES.claudeSkills);
2717
2900
  const skillCount = countFilesInDir(skillsDir);
2718
2901
  console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
2719
2902
  unchanged++;
2720
- const guidesDir = path25.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2903
+ const guidesDir = path26.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2721
2904
  const guideCount = existingConfig.guides?.length || 0;
2722
2905
  console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
2723
2906
  unchanged++;
@@ -2826,7 +3009,7 @@ function diffStack(oldScan, newScan) {
2826
3009
  return changes;
2827
3010
  }
2828
3011
  function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
2829
- const filePath = path25.join(projectDir, filename);
3012
+ const filePath = path26.join(projectDir, filename);
2830
3013
  const currentContent = readFileSafe(filePath);
2831
3014
  const newContent = generate2();
2832
3015
  if (!currentContent) {
@@ -2872,7 +3055,7 @@ function countFilesInDir(dirPath) {
2872
3055
  }
2873
3056
 
2874
3057
  // src/commands/export.ts
2875
- import path26 from "path";
3058
+ import path27 from "path";
2876
3059
  import fs14 from "fs-extra";
2877
3060
  import ora6 from "ora";
2878
3061
  import { select as select2 } from "@inquirer/prompts";
@@ -2909,9 +3092,9 @@ function toCline(content) {
2909
3092
  ${stripped}`;
2910
3093
  }
2911
3094
  async function exportCommand(targetPath, options) {
2912
- const projectDir = path26.resolve(targetPath || process.cwd());
2913
- const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
2914
- 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);
2915
3098
  logSection("AI Kit \u2014 Export");
2916
3099
  if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
2917
3100
  logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
@@ -2953,7 +3136,7 @@ async function exportCommand(targetPath, options) {
2953
3136
  for (const fmt2 of formats) {
2954
3137
  const target = EXPORT_TARGETS[fmt2];
2955
3138
  const transformer = transformers[fmt2];
2956
- const outputPath = path26.join(projectDir, target.file);
3139
+ const outputPath = path27.join(projectDir, target.file);
2957
3140
  const transformed = transformer(claudeContent);
2958
3141
  await fs14.writeFile(outputPath, transformed, "utf-8");
2959
3142
  exported++;
@@ -2971,7 +3154,7 @@ async function exportCommand(targetPath, options) {
2971
3154
  }
2972
3155
 
2973
3156
  // src/commands/stats.ts
2974
- import path27 from "path";
3157
+ import path28 from "path";
2975
3158
  import chalk5 from "chalk";
2976
3159
  var SKILL_CATEGORIES = {
2977
3160
  "Getting Started": ["prompt-help", "understand"],
@@ -3074,8 +3257,8 @@ var MCP_DISPLAY_NAMES = {
3074
3257
  perplexity: "Perplexity"
3075
3258
  };
3076
3259
  async function statsCommand(targetPath) {
3077
- const projectDir = path27.resolve(targetPath || process.cwd());
3078
- 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);
3079
3262
  logSection("AI Kit \u2014 Project Stats");
3080
3263
  console.log("");
3081
3264
  if (!fileExists(configPath)) {
@@ -3169,21 +3352,21 @@ async function statsCommand(targetPath) {
3169
3352
  }
3170
3353
 
3171
3354
  // src/commands/audit.ts
3172
- import path28 from "path";
3355
+ import path29 from "path";
3173
3356
  import fs15 from "fs-extra";
3174
3357
  import chalk6 from "chalk";
3175
3358
  async function auditCommand(targetPath) {
3176
- const projectDir = path28.resolve(targetPath || process.cwd());
3359
+ const projectDir = path29.resolve(targetPath || process.cwd());
3177
3360
  logSection("AI Kit \u2014 Security & Configuration Audit");
3178
3361
  logInfo(`Auditing: ${projectDir}`);
3179
3362
  const checks = [];
3180
- const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
3363
+ const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
3181
3364
  if (fileExists(configPath)) {
3182
3365
  checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
3183
3366
  } else {
3184
3367
  checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
3185
3368
  }
3186
- const claudeMdPath = path28.join(projectDir, GENERATED_FILES.claudeMd);
3369
+ const claudeMdPath = path29.join(projectDir, GENERATED_FILES.claudeMd);
3187
3370
  const claudeMd = readFileSafe(claudeMdPath);
3188
3371
  if (claudeMd) {
3189
3372
  if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
@@ -3208,7 +3391,7 @@ async function auditCommand(targetPath) {
3208
3391
  checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
3209
3392
  }
3210
3393
  }
3211
- const settingsLocalPath = path28.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3394
+ const settingsLocalPath = path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3212
3395
  if (fileExists(settingsLocalPath)) {
3213
3396
  const settings2 = readJsonSafe(settingsLocalPath);
3214
3397
  if (settings2?.hooks) {
@@ -3219,14 +3402,14 @@ async function auditCommand(targetPath) {
3219
3402
  } else {
3220
3403
  checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
3221
3404
  }
3222
- const agentsDir = path28.join(projectDir, GENERATED_FILES.claudeAgents);
3405
+ const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3223
3406
  if (await fs15.pathExists(agentsDir)) {
3224
3407
  const agentFiles = (await fs15.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
3225
3408
  if (agentFiles.length > 0) {
3226
3409
  checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
3227
3410
  let invalidAgents = 0;
3228
3411
  for (const file of agentFiles) {
3229
- const content = readFileSafe(path28.join(agentsDir, file));
3412
+ const content = readFileSafe(path29.join(agentsDir, file));
3230
3413
  if (content && !content.startsWith("---")) {
3231
3414
  invalidAgents++;
3232
3415
  }
@@ -3240,18 +3423,18 @@ async function auditCommand(targetPath) {
3240
3423
  } else {
3241
3424
  checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
3242
3425
  }
3243
- const contextsDir = path28.join(projectDir, GENERATED_FILES.claudeContexts);
3426
+ const contextsDir = path29.join(projectDir, GENERATED_FILES.claudeContexts);
3244
3427
  if (await fs15.pathExists(contextsDir)) {
3245
3428
  const contextFiles = (await fs15.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
3246
3429
  checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
3247
3430
  } else {
3248
3431
  checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
3249
3432
  }
3250
- const skillsDir = path28.join(projectDir, GENERATED_FILES.claudeSkills);
3433
+ const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3251
3434
  if (await fs15.pathExists(skillsDir)) {
3252
3435
  const skillDirs = (await fs15.readdir(skillsDir)).filter(async (f) => {
3253
3436
  try {
3254
- return (await fs15.stat(path28.join(skillsDir, f))).isDirectory();
3437
+ return (await fs15.stat(path29.join(skillsDir, f))).isDirectory();
3255
3438
  } catch {
3256
3439
  return false;
3257
3440
  }
@@ -3260,7 +3443,7 @@ async function auditCommand(targetPath) {
3260
3443
  } else {
3261
3444
  checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
3262
3445
  }
3263
- const gitignorePath = path28.join(projectDir, ".gitignore");
3446
+ const gitignorePath = path29.join(projectDir, ".gitignore");
3264
3447
  const gitignore = readFileSafe(gitignorePath);
3265
3448
  if (gitignore) {
3266
3449
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
@@ -3276,7 +3459,7 @@ async function auditCommand(targetPath) {
3276
3459
  checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
3277
3460
  }
3278
3461
  }
3279
- const settingsPath = path28.join(projectDir, ".claude", "settings.json");
3462
+ const settingsPath = path29.join(projectDir, ".claude", "settings.json");
3280
3463
  const settings = readFileSafe(settingsPath);
3281
3464
  if (settings) {
3282
3465
  const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
@@ -3309,7 +3492,7 @@ async function auditCommand(targetPath) {
3309
3492
  }
3310
3493
 
3311
3494
  // src/commands/health.ts
3312
- import path29 from "path";
3495
+ import path30 from "path";
3313
3496
  import fs16 from "fs-extra";
3314
3497
  import chalk7 from "chalk";
3315
3498
  import ora7 from "ora";
@@ -3351,7 +3534,7 @@ function checkSetup(projectDir, config) {
3351
3534
  detail: `config v${config.version} \u2260 CLI v${VERSION} \u2014 run \`ai-kit update\``
3352
3535
  });
3353
3536
  }
3354
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3537
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3355
3538
  if (claudeMd && claudeMd.includes("AI-KIT:START")) {
3356
3539
  checks.push({ name: "CLAUDE.md", status: "pass", detail: "Present with markers" });
3357
3540
  } else if (claudeMd) {
@@ -3359,19 +3542,19 @@ function checkSetup(projectDir, config) {
3359
3542
  } else {
3360
3543
  checks.push({ name: "CLAUDE.md", status: "fail", detail: "Not found" });
3361
3544
  }
3362
- if (fileExists(path29.join(projectDir, GENERATED_FILES.cursorRules))) {
3545
+ if (fileExists(path30.join(projectDir, GENERATED_FILES.cursorRules))) {
3363
3546
  checks.push({ name: ".cursorrules", status: "pass", detail: "Present" });
3364
3547
  } else {
3365
3548
  checks.push({ name: ".cursorrules", status: "warn", detail: "Not generated" });
3366
3549
  }
3367
- const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3550
+ const skillsDir = path30.join(projectDir, GENERATED_FILES.claudeSkills);
3368
3551
  if (dirExists(skillsDir)) {
3369
3552
  const count = config.commands.length;
3370
3553
  checks.push({ name: "Skills", status: "pass", detail: `${count} installed` });
3371
3554
  } else {
3372
3555
  checks.push({ name: "Skills", status: "warn", detail: "No skills directory" });
3373
3556
  }
3374
- const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3557
+ const agentsDir = path30.join(projectDir, GENERATED_FILES.claudeAgents);
3375
3558
  if (dirExists(agentsDir)) {
3376
3559
  try {
3377
3560
  const agentFiles = fs16.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
@@ -3382,7 +3565,7 @@ function checkSetup(projectDir, config) {
3382
3565
  } else {
3383
3566
  checks.push({ name: "Agents", status: "warn", detail: "Not configured" });
3384
3567
  }
3385
- const settingsLocal = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3568
+ const settingsLocal = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3386
3569
  if (settingsLocal && settingsLocal.includes('"hooks"')) {
3387
3570
  checks.push({ name: "Hooks", status: "pass", detail: "Configured" });
3388
3571
  } else {
@@ -3392,7 +3575,7 @@ function checkSetup(projectDir, config) {
3392
3575
  }
3393
3576
  function checkSecurity(projectDir) {
3394
3577
  const checks = [];
3395
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3578
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3396
3579
  if (claudeMd) {
3397
3580
  const secretPatterns = [
3398
3581
  /(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
@@ -3407,7 +3590,7 @@ function checkSecurity(projectDir) {
3407
3590
  detail: hasSecrets ? "Potential secrets detected \u2014 remove immediately" : "Clean"
3408
3591
  });
3409
3592
  }
3410
- const gitignore = readFileSafe(path29.join(projectDir, ".gitignore"));
3593
+ const gitignore = readFileSafe(path30.join(projectDir, ".gitignore"));
3411
3594
  if (gitignore) {
3412
3595
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
3413
3596
  checks.push({
@@ -3416,7 +3599,7 @@ function checkSecurity(projectDir) {
3416
3599
  detail: envIgnored ? "Protected" : "NOT gitignored \u2014 add .env to .gitignore"
3417
3600
  });
3418
3601
  }
3419
- const settingsJson = readFileSafe(path29.join(projectDir, ".claude", "settings.json"));
3602
+ const settingsJson = readFileSafe(path30.join(projectDir, ".claude", "settings.json"));
3420
3603
  if (settingsJson) {
3421
3604
  const hasHardcoded = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settingsJson);
3422
3605
  checks.push({
@@ -3525,7 +3708,7 @@ function checkDocs(projectDir) {
3525
3708
  { name: "Time Log", path: "docs/time-log.md" }
3526
3709
  ];
3527
3710
  for (const doc of docsToCheck) {
3528
- const content = readFileSafe(path29.join(projectDir, doc.path));
3711
+ const content = readFileSafe(path30.join(projectDir, doc.path));
3529
3712
  if (content) {
3530
3713
  const hasEntries = content.includes("## 20") || content.split("---").length > 2;
3531
3714
  checks.push({
@@ -3540,8 +3723,8 @@ function checkDocs(projectDir) {
3540
3723
  return { title: "Documentation", checks };
3541
3724
  }
3542
3725
  async function healthCommand(targetPath) {
3543
- const projectDir = path29.resolve(targetPath || process.cwd());
3544
- 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);
3545
3728
  console.log("");
3546
3729
  logSection("AI Kit \u2014 Project Health");
3547
3730
  console.log(chalk7.dim(` ${projectDir}`));
@@ -3625,7 +3808,7 @@ async function healthCommand(targetPath) {
3625
3808
  }
3626
3809
 
3627
3810
  // src/commands/patterns.ts
3628
- import path30 from "path";
3811
+ import path31 from "path";
3629
3812
  import fs17 from "fs-extra";
3630
3813
  import chalk8 from "chalk";
3631
3814
  import ora8 from "ora";
@@ -3712,7 +3895,7 @@ function walkTsFiles(dir, files) {
3712
3895
  try {
3713
3896
  const entries = fs17.readdirSync(dir, { withFileTypes: true });
3714
3897
  for (const entry of entries) {
3715
- const full = path30.join(dir, entry.name);
3898
+ const full = path31.join(dir, entry.name);
3716
3899
  if (entry.isDirectory()) {
3717
3900
  if (IGNORE_DIRS.includes(entry.name)) continue;
3718
3901
  walkTsFiles(full, files);
@@ -3724,8 +3907,8 @@ function walkTsFiles(dir, files) {
3724
3907
  }
3725
3908
  }
3726
3909
  async function patternsCommand(targetPath) {
3727
- const projectDir = path30.resolve(targetPath || process.cwd());
3728
- 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);
3729
3912
  console.log("");
3730
3913
  logSection("AI Kit \u2014 Pattern Library");
3731
3914
  console.log(chalk8.dim(` ${projectDir}`));
@@ -3736,7 +3919,7 @@ async function patternsCommand(targetPath) {
3736
3919
  }
3737
3920
  const spinner = ora8("Scanning for code patterns...").start();
3738
3921
  const files = [];
3739
- const srcDir = path30.join(projectDir, "src");
3922
+ const srcDir = path31.join(projectDir, "src");
3740
3923
  if (dirExists(srcDir)) {
3741
3924
  walkTsFiles(srcDir, files);
3742
3925
  } else {
@@ -3758,7 +3941,7 @@ async function patternsCommand(targetPath) {
3758
3941
  if (pattern.regex.test(lines2[i])) {
3759
3942
  pattern.matches.push({
3760
3943
  pattern: pattern.label,
3761
- file: path30.relative(projectDir, file),
3944
+ file: path31.relative(projectDir, file),
3762
3945
  line: i + 1
3763
3946
  });
3764
3947
  }
@@ -3786,9 +3969,9 @@ async function patternsCommand(targetPath) {
3786
3969
  logInfo("No recognizable patterns found.");
3787
3970
  return;
3788
3971
  }
3789
- const outputDir = path30.join(projectDir, "ai-kit");
3972
+ const outputDir = path31.join(projectDir, "ai-kit");
3790
3973
  fs17.ensureDirSync(outputDir);
3791
- const outputPath = path30.join(outputDir, "patterns.md");
3974
+ const outputPath = path31.join(outputDir, "patterns.md");
3792
3975
  const lines = [
3793
3976
  "# Code Patterns",
3794
3977
  "",
@@ -3829,13 +4012,13 @@ async function patternsCommand(targetPath) {
3829
4012
  }
3830
4013
 
3831
4014
  // src/commands/dead-code.ts
3832
- import path32 from "path";
4015
+ import path33 from "path";
3833
4016
  import fs19 from "fs-extra";
3834
4017
  import chalk9 from "chalk";
3835
4018
  import ora9 from "ora";
3836
4019
 
3837
4020
  // src/scanner/components.ts
3838
- import path31 from "path";
4021
+ import path32 from "path";
3839
4022
  import fs18 from "fs-extra";
3840
4023
  var COMPONENT_DIRS = [
3841
4024
  "src/components",
@@ -3863,7 +4046,7 @@ function findComponentFiles(projectPath) {
3863
4046
  const files = [];
3864
4047
  const directories = /* @__PURE__ */ new Set();
3865
4048
  for (const dir of COMPONENT_DIRS) {
3866
- const fullDir = path31.join(projectPath, dir);
4049
+ const fullDir = path32.join(projectPath, dir);
3867
4050
  if (dirExists(fullDir)) {
3868
4051
  walkForComponents(fullDir, files, directories);
3869
4052
  }
@@ -3874,7 +4057,7 @@ function walkForComponents(dir, files, directories) {
3874
4057
  try {
3875
4058
  const entries = fs18.readdirSync(dir, { withFileTypes: true });
3876
4059
  for (const entry of entries) {
3877
- const full = path31.join(dir, entry.name);
4060
+ const full = path32.join(dir, entry.name);
3878
4061
  if (entry.isDirectory()) {
3879
4062
  if (IGNORE_PATTERNS.includes(entry.name)) continue;
3880
4063
  walkForComponents(full, files, directories);
@@ -3914,7 +4097,7 @@ function extractComponentName(filePath, content) {
3914
4097
  /export\s+(?:const|function)\s+(\w+)/
3915
4098
  );
3916
4099
  if (namedExport) return namedExport[1];
3917
- return path31.basename(filePath, ".tsx");
4100
+ return path32.basename(filePath, ".tsx");
3918
4101
  }
3919
4102
  function extractExportType(content) {
3920
4103
  const hasDefault = /export\s+default\s/.test(content);
@@ -4003,7 +4186,7 @@ function extractDependencies(content) {
4003
4186
  for (const match of imports) {
4004
4187
  const importPath = match[1];
4005
4188
  if (importPath.startsWith(".") || importPath.startsWith("..")) {
4006
- const basename = path31.basename(importPath).replace(/\.\w+$/, "");
4189
+ const basename = path32.basename(importPath).replace(/\.\w+$/, "");
4007
4190
  if (/^[A-Z]/.test(basename)) {
4008
4191
  deps.push(basename);
4009
4192
  }
@@ -4012,33 +4195,33 @@ function extractDependencies(content) {
4012
4195
  return deps;
4013
4196
  }
4014
4197
  function checkForTests(componentPath, componentName) {
4015
- const dir = path31.dirname(componentPath);
4016
- const base = path31.basename(componentPath, ".tsx");
4198
+ const dir = path32.dirname(componentPath);
4199
+ const base = path32.basename(componentPath, ".tsx");
4017
4200
  for (const suffix of TEST_SUFFIXES) {
4018
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4201
+ if (fs18.existsSync(path32.join(dir, base + suffix))) return true;
4019
4202
  }
4020
- const testsDir = path31.join(dir, "__tests__");
4203
+ const testsDir = path32.join(dir, "__tests__");
4021
4204
  if (dirExists(testsDir)) {
4022
4205
  for (const suffix of TEST_SUFFIXES) {
4023
- if (fs18.existsSync(path31.join(testsDir, base + suffix))) return true;
4024
- 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;
4025
4208
  }
4026
4209
  }
4027
4210
  return false;
4028
4211
  }
4029
4212
  function checkForStory(componentPath, componentName) {
4030
- const dir = path31.dirname(componentPath);
4031
- const base = path31.basename(componentPath, ".tsx");
4213
+ const dir = path32.dirname(componentPath);
4214
+ const base = path32.basename(componentPath, ".tsx");
4032
4215
  for (const suffix of STORY_SUFFIXES) {
4033
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4034
- 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;
4035
4218
  }
4036
4219
  return false;
4037
4220
  }
4038
4221
  function checkForAiDoc(componentPath) {
4039
- const dir = path31.dirname(componentPath);
4040
- const base = path31.basename(componentPath, ".tsx");
4041
- 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"));
4042
4225
  }
4043
4226
  function categorizeComponent(relativePath) {
4044
4227
  const lower = relativePath.toLowerCase();
@@ -4056,7 +4239,7 @@ function parseComponent(filePath, projectPath) {
4056
4239
  const content = readFileSafe(filePath);
4057
4240
  if (!content) return null;
4058
4241
  const name = extractComponentName(filePath, content);
4059
- const relativePath = path31.relative(projectPath, filePath);
4242
+ const relativePath = path32.relative(projectPath, filePath);
4060
4243
  return {
4061
4244
  name,
4062
4245
  filePath,
@@ -4104,7 +4287,7 @@ function collectAllFiles(dir, files) {
4104
4287
  try {
4105
4288
  const entries = fs19.readdirSync(dir, { withFileTypes: true });
4106
4289
  for (const entry of entries) {
4107
- const full = path32.join(dir, entry.name);
4290
+ const full = path33.join(dir, entry.name);
4108
4291
  if (entry.isDirectory()) {
4109
4292
  if (IGNORE_DIRS2.includes(entry.name)) continue;
4110
4293
  collectAllFiles(full, files);
@@ -4116,12 +4299,12 @@ function collectAllFiles(dir, files) {
4116
4299
  }
4117
4300
  }
4118
4301
  function isTestOrStoryFile(filePath) {
4119
- const name = path32.basename(filePath).toLowerCase();
4302
+ const name = path33.basename(filePath).toLowerCase();
4120
4303
  return name.includes(".test.") || name.includes(".spec.") || name.includes(".stories.") || name.includes("__tests__") || name.includes("__mocks__");
4121
4304
  }
4122
4305
  async function deadCodeCommand(targetPath) {
4123
- const projectDir = path32.resolve(targetPath || process.cwd());
4124
- 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);
4125
4308
  console.log("");
4126
4309
  logSection("AI Kit \u2014 Dead Code Report");
4127
4310
  console.log(chalk9.dim(` ${projectDir}`));
@@ -4138,7 +4321,7 @@ async function deadCodeCommand(targetPath) {
4138
4321
  }
4139
4322
  spinner.text = `Found ${scanResult.components.length} components. Checking imports...`;
4140
4323
  const allFiles = [];
4141
- const srcDir = path32.join(projectDir, "src");
4324
+ const srcDir = path33.join(projectDir, "src");
4142
4325
  if (fs19.existsSync(srcDir)) {
4143
4326
  collectAllFiles(srcDir, allFiles);
4144
4327
  } else {
@@ -4162,7 +4345,7 @@ async function deadCodeCommand(targetPath) {
4162
4345
  `(?:import|from)\\s+.*['"][^'"]*(?:/|\\b)${escapeRegex(component.name)}(?:[/'"]|\\b)`
4163
4346
  );
4164
4347
  for (const [file, content] of fileContents) {
4165
- if (path32.resolve(file) === path32.resolve(componentFile)) continue;
4348
+ if (path33.resolve(file) === path33.resolve(componentFile)) continue;
4166
4349
  if (namePattern.test(content)) {
4167
4350
  importCount++;
4168
4351
  if (isTestOrStoryFile(file)) {
@@ -4241,7 +4424,7 @@ function escapeRegex(str) {
4241
4424
  }
4242
4425
 
4243
4426
  // src/commands/drift.ts
4244
- import path33 from "path";
4427
+ import path34 from "path";
4245
4428
  import fs20 from "fs-extra";
4246
4429
  import chalk10 from "chalk";
4247
4430
  import ora10 from "ora";
@@ -4278,11 +4461,11 @@ function parseAiDocFrontmatter(content) {
4278
4461
  return { props, fields };
4279
4462
  }
4280
4463
  function findAiDocPath(componentPath) {
4281
- const dir = path33.dirname(componentPath);
4282
- const base = path33.basename(componentPath, ".tsx");
4464
+ const dir = path34.dirname(componentPath);
4465
+ const base = path34.basename(componentPath, ".tsx");
4283
4466
  const candidates = [
4284
- path33.join(dir, `${base}.ai.md`),
4285
- path33.join(dir, "component.ai.md")
4467
+ path34.join(dir, `${base}.ai.md`),
4468
+ path34.join(dir, "component.ai.md")
4286
4469
  ];
4287
4470
  for (const candidate of candidates) {
4288
4471
  if (fs20.existsSync(candidate)) return candidate;
@@ -4326,8 +4509,8 @@ function analyzeDrift(component, projectDir) {
4326
4509
  };
4327
4510
  }
4328
4511
  async function driftCommand(targetPath) {
4329
- const projectDir = path33.resolve(targetPath || process.cwd());
4330
- 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);
4331
4514
  console.log("");
4332
4515
  logSection("AI Kit \u2014 Component Drift Detector");
4333
4516
  console.log(chalk10.dim(` ${projectDir}`));
@@ -4429,7 +4612,7 @@ async function driftCommand(targetPath) {
4429
4612
  }
4430
4613
 
4431
4614
  // src/commands/component-registry.ts
4432
- import path34 from "path";
4615
+ import path35 from "path";
4433
4616
  import fs22 from "fs-extra";
4434
4617
  import chalk11 from "chalk";
4435
4618
  import ora11 from "ora";
@@ -4454,12 +4637,12 @@ function calculateHealthScore(component) {
4454
4637
 
4455
4638
  // src/commands/component-registry.ts
4456
4639
  async function componentRegistryCommand(targetPath) {
4457
- const projectDir = path34.resolve(targetPath || process.cwd());
4640
+ const projectDir = path35.resolve(targetPath || process.cwd());
4458
4641
  console.log("");
4459
4642
  logSection("AI Kit \u2014 Component Registry");
4460
4643
  console.log(chalk11.dim(` ${projectDir}`));
4461
4644
  console.log("");
4462
- if (!fileExists(path34.join(projectDir, AI_KIT_CONFIG_FILE))) {
4645
+ if (!fileExists(path35.join(projectDir, AI_KIT_CONFIG_FILE))) {
4463
4646
  logWarning("ai-kit.config.json not found. Run `ai-kit init` first.");
4464
4647
  return;
4465
4648
  }
@@ -4493,20 +4676,20 @@ async function componentRegistryCommand(targetPath) {
4493
4676
  for (const entry of entries) {
4494
4677
  categories[entry.category] = (categories[entry.category] || 0) + 1;
4495
4678
  }
4496
- const pkgPath = path34.join(projectDir, "package.json");
4679
+ const pkgPath = path35.join(projectDir, "package.json");
4497
4680
  const pkg = fs22.readJsonSync(pkgPath, { throws: false }) || {};
4498
4681
  const registry = {
4499
4682
  version: "1.0.0",
4500
4683
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4501
- projectName: pkg.name || path34.basename(projectDir),
4684
+ projectName: pkg.name || path35.basename(projectDir),
4502
4685
  totalComponents: entries.length,
4503
4686
  categories,
4504
4687
  components: entries
4505
4688
  };
4506
- const outputPath = path34.join(projectDir, "ai-kit", "component-registry.json");
4507
- await fs22.ensureDir(path34.dirname(outputPath));
4689
+ const outputPath = path35.join(projectDir, "ai-kit", "component-registry.json");
4690
+ await fs22.ensureDir(path35.dirname(outputPath));
4508
4691
  await fs22.writeJson(outputPath, registry, { spaces: 2 });
4509
- const mdPath = path34.join(projectDir, "ai-kit", "component-registry.md");
4692
+ const mdPath = path35.join(projectDir, "ai-kit", "component-registry.md");
4510
4693
  const md = generateRegistryMarkdown(registry);
4511
4694
  await fs22.writeFile(mdPath, md, "utf-8");
4512
4695
  console.log("");
@@ -4566,6 +4749,220 @@ function generateRegistryMarkdown(registry) {
4566
4749
  return lines.join("\n");
4567
4750
  }
4568
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
+
4569
4966
  // src/cli/error-handler.ts
4570
4967
  function withErrorHandler(fn) {
4571
4968
  const wrapped = async (...args) => {
@@ -4626,6 +5023,12 @@ function registerCommands(program2) {
4626
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) => {
4627
5024
  await componentRegistryCommand(targetPath);
4628
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
+ }));
4629
5032
  }
4630
5033
 
4631
5034
  // src/index.ts