@mikulgohil/ai-kit 1.10.0 → 2.0.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.
Files changed (79) hide show
  1. package/README.md +78 -39
  2. package/agents/{api-designer.md → kit-api-designer.md} +1 -1
  3. package/agents/{architect.md → kit-architect.md} +1 -1
  4. package/agents/{build-resolver.md → kit-build-resolver.md} +1 -1
  5. package/agents/{ci-debugger.md → kit-ci-debugger.md} +1 -1
  6. package/agents/{code-reviewer.md → kit-code-reviewer.md} +1 -1
  7. package/agents/{data-scientist.md → kit-data-scientist.md} +1 -1
  8. package/agents/{dependency-auditor.md → kit-dependency-auditor.md} +1 -1
  9. package/agents/{doc-updater.md → kit-doc-updater.md} +1 -1
  10. package/agents/{e2e-runner.md → kit-e2e-runner.md} +1 -1
  11. package/agents/{migration-specialist.md → kit-migration-specialist.md} +1 -1
  12. package/agents/{performance-profiler.md → kit-performance-profiler.md} +1 -1
  13. package/agents/{planner.md → kit-planner.md} +1 -1
  14. package/agents/{refactor-cleaner.md → kit-refactor-cleaner.md} +1 -1
  15. package/agents/{security-reviewer.md → kit-security-reviewer.md} +1 -1
  16. package/agents/{sitecore-specialist.md → kit-sitecore-specialist.md} +1 -1
  17. package/agents/{tdd-guide.md → kit-tdd-guide.md} +1 -1
  18. package/commands/{clarify-requirements.md → kit-clarify-requirements.md} +1 -1
  19. package/commands/{harness-audit.md → kit-harness-audit.md} +1 -1
  20. package/commands/{orchestrate.md → kit-orchestrate.md} +11 -11
  21. package/commands/{resume-session.md → kit-resume-session.md} +1 -1
  22. package/commands/{token-tips.md → kit-token-tips.md} +4 -4
  23. package/dist/index.js +624 -253
  24. package/dist/index.js.map +1 -1
  25. package/guides/figma-workflow.md +5 -5
  26. package/guides/getting-started.md +58 -32
  27. package/guides/hooks-and-agents.md +19 -19
  28. package/guides/prompt-playbook.md +1 -1
  29. package/guides/token-saving-tips.md +3 -3
  30. package/package.json +7 -3
  31. package/templates/claude-md/base.md +27 -27
  32. /package/commands/{accessibility-audit.md → kit-accessibility-audit.md} +0 -0
  33. /package/commands/{api-route.md → kit-api-route.md} +0 -0
  34. /package/commands/{bundle-check.md → kit-bundle-check.md} +0 -0
  35. /package/commands/{changelog.md → kit-changelog.md} +0 -0
  36. /package/commands/{checkpoint.md → kit-checkpoint.md} +0 -0
  37. /package/commands/{commit-msg.md → kit-commit-msg.md} +0 -0
  38. /package/commands/{deep-interview.md → kit-deep-interview.md} +0 -0
  39. /package/commands/{dep-check.md → kit-dep-check.md} +0 -0
  40. /package/commands/{design-tokens.md → kit-design-tokens.md} +0 -0
  41. /package/commands/{document.md → kit-document.md} +0 -0
  42. /package/commands/{env-setup.md → kit-env-setup.md} +0 -0
  43. /package/commands/{error-boundary.md → kit-error-boundary.md} +0 -0
  44. /package/commands/{extract-hook.md → kit-extract-hook.md} +0 -0
  45. /package/commands/{fetch-docs.md → kit-fetch-docs.md} +0 -0
  46. /package/commands/{figma-to-code.md → kit-figma-to-code.md} +0 -0
  47. /package/commands/{fix-bug.md → kit-fix-bug.md} +0 -0
  48. /package/commands/{i18n-check.md → kit-i18n-check.md} +0 -0
  49. /package/commands/{learn-from-pr.md → kit-learn-from-pr.md} +0 -0
  50. /package/commands/{middleware.md → kit-middleware.md} +0 -0
  51. /package/commands/{migrate.md → kit-migrate.md} +0 -0
  52. /package/commands/{new-component.md → kit-new-component.md} +0 -0
  53. /package/commands/{new-page.md → kit-new-page.md} +0 -0
  54. /package/commands/{optimize.md → kit-optimize.md} +0 -0
  55. /package/commands/{perf-audit.md → kit-perf-audit.md} +0 -0
  56. /package/commands/{pr-description.md → kit-pr-description.md} +0 -0
  57. /package/commands/{pre-pr.md → kit-pre-pr.md} +0 -0
  58. /package/commands/{prompt-help.md → kit-prompt-help.md} +0 -0
  59. /package/commands/{quality-gate-check.md → kit-quality-gate-check.md} +0 -0
  60. /package/commands/{quality-gate.md → kit-quality-gate.md} +0 -0
  61. /package/commands/{refactor.md → kit-refactor.md} +0 -0
  62. /package/commands/{release-notes.md → kit-release-notes.md} +0 -0
  63. /package/commands/{release.md → kit-release.md} +0 -0
  64. /package/commands/{responsive-check.md → kit-responsive-check.md} +0 -0
  65. /package/commands/{review.md → kit-review.md} +0 -0
  66. /package/commands/{save-session.md → kit-save-session.md} +0 -0
  67. /package/commands/{scaffold-spec.md → kit-scaffold-spec.md} +0 -0
  68. /package/commands/{schema-gen.md → kit-schema-gen.md} +0 -0
  69. /package/commands/{search-first.md → kit-search-first.md} +0 -0
  70. /package/commands/{security-check.md → kit-security-check.md} +0 -0
  71. /package/commands/{server-action.md → kit-server-action.md} +0 -0
  72. /package/commands/{sitecore-debug.md → kit-sitecore-debug.md} +0 -0
  73. /package/commands/{standup.md → kit-standup.md} +0 -0
  74. /package/commands/{storybook-gen.md → kit-storybook-gen.md} +0 -0
  75. /package/commands/{test-gaps.md → kit-test-gaps.md} +0 -0
  76. /package/commands/{test.md → kit-test.md} +0 -0
  77. /package/commands/{type-fix.md → kit-type-fix.md} +0 -0
  78. /package/commands/{understand.md → kit-understand.md} +0 -0
  79. /package/commands/{upgrade.md → kit-upgrade.md} +0 -0
package/dist/index.js CHANGED
@@ -15,8 +15,10 @@ var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
15
15
  var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
16
16
  var AGENTS_DIR = path.join(PACKAGE_ROOT, "agents");
17
17
  var CONTEXTS_DIR = path.join(PACKAGE_ROOT, "contexts");
18
- var VERSION = "1.10.0";
18
+ var VERSION = "2.0.0";
19
+ var SKILL_PREFIX = "kit-";
19
20
  var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
21
+ var BACKUP_DIR = ".ai-kit/backups";
20
22
  var GENERATED_FILES = {
21
23
  claudeMd: "CLAUDE.md",
22
24
  cursorRules: ".cursorrules",
@@ -43,16 +45,17 @@ var TEMPLATE_FRAGMENTS = [
43
45
  ];
44
46
 
45
47
  // src/commands/init.ts
46
- import path20 from "path";
48
+ import path21 from "path";
47
49
  import fs9 from "fs-extra";
48
50
  import { select } from "@inquirer/prompts";
49
51
  import ora from "ora";
50
52
 
51
53
  // src/scanner/index.ts
52
- import path13 from "path";
54
+ import path14 from "path";
53
55
 
54
56
  // src/utils.ts
55
57
  import fs from "fs-extra";
58
+ import path2 from "path";
56
59
  import chalk from "chalk";
57
60
  function readJsonSafe(filePath) {
58
61
  try {
@@ -105,9 +108,91 @@ function mergeWithMarkers(existingContent, newGenerated) {
105
108
  const after = existingContent.substring(endIdx + AI_KIT_END.length);
106
109
  return `${before}${newGenerated}${after}`;
107
110
  }
111
+ async function backupFiles(projectDir, files) {
112
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
113
+ const backupDir = path2.join(projectDir, BACKUP_DIR, timestamp);
114
+ await fs.ensureDir(backupDir);
115
+ let backedUp = 0;
116
+ for (const file of files) {
117
+ const fullPath = path2.join(projectDir, file);
118
+ if (await fs.pathExists(fullPath)) {
119
+ const dest = path2.join(backupDir, file);
120
+ await fs.ensureDir(path2.dirname(dest));
121
+ await fs.copy(fullPath, dest);
122
+ backedUp++;
123
+ }
124
+ }
125
+ if (backedUp === 0) {
126
+ await fs.remove(backupDir);
127
+ return "";
128
+ }
129
+ return backupDir;
130
+ }
131
+ async function listBackups(projectDir) {
132
+ const backupsRoot = path2.join(projectDir, BACKUP_DIR);
133
+ if (!await fs.pathExists(backupsRoot)) return [];
134
+ const entries = await fs.readdir(backupsRoot);
135
+ return entries.filter((e) => /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}$/.test(e)).sort().reverse();
136
+ }
137
+ async function restoreBackup(projectDir, backupName) {
138
+ const backupDir = path2.join(projectDir, BACKUP_DIR, backupName);
139
+ if (!await fs.pathExists(backupDir)) {
140
+ throw new Error(`Backup not found: ${backupName}`);
141
+ }
142
+ const restored = [];
143
+ async function walk(dir, rel) {
144
+ const entries = await fs.readdir(dir, { withFileTypes: true });
145
+ for (const entry of entries) {
146
+ const entryRel = rel ? `${rel}/${entry.name}` : entry.name;
147
+ const srcPath = path2.join(dir, entry.name);
148
+ const destPath = path2.join(projectDir, entryRel);
149
+ if (entry.isDirectory()) {
150
+ await walk(srcPath, entryRel);
151
+ } else {
152
+ await fs.ensureDir(path2.dirname(destPath));
153
+ await fs.copy(srcPath, destPath, { overwrite: true });
154
+ restored.push(entryRel);
155
+ }
156
+ }
157
+ }
158
+ await walk(backupDir, "");
159
+ return restored;
160
+ }
161
+ function parseSections(markdown) {
162
+ const lines = markdown.split("\n");
163
+ const sections = [];
164
+ let current = null;
165
+ const contentLines = [];
166
+ function flush() {
167
+ if (current) {
168
+ current.content = contentLines.join("\n").trim();
169
+ current.raw = `${"#".repeat(current.level)} ${current.heading}
170
+
171
+ ${current.content}`.trim();
172
+ sections.push(current);
173
+ contentLines.length = 0;
174
+ }
175
+ }
176
+ for (const line of lines) {
177
+ const match = line.match(/^(#{1,6})\s+(.+)$/);
178
+ if (match) {
179
+ flush();
180
+ current = {
181
+ heading: match[2].trim(),
182
+ level: match[1].length,
183
+ content: "",
184
+ raw: ""
185
+ };
186
+ } else if (current) {
187
+ contentLines.push(line);
188
+ }
189
+ }
190
+ flush();
191
+ return sections;
192
+ }
108
193
 
109
194
  // src/scanner/nextjs.ts
110
- import path2 from "path";
195
+ import path3 from "path";
111
196
  function detectNextjs(projectPath, pkg) {
112
197
  const deps = {
113
198
  ...pkg.dependencies,
@@ -119,8 +204,8 @@ function detectNextjs(projectPath, pkg) {
119
204
  }
120
205
  const nextjsVersion = deps.next.replace(/[\^~>=<]/g, "");
121
206
  const nextjsMajorVersion = parseInt(nextjsVersion.split(".")[0], 10) || void 0;
122
- const hasAppDir = dirExists(path2.join(projectPath, "app")) || dirExists(path2.join(projectPath, "src", "app"));
123
- const hasPagesDir = dirExists(path2.join(projectPath, "pages")) || dirExists(path2.join(projectPath, "src", "pages"));
207
+ const hasAppDir = dirExists(path3.join(projectPath, "app")) || dirExists(path3.join(projectPath, "src", "app"));
208
+ const hasPagesDir = dirExists(path3.join(projectPath, "pages")) || dirExists(path3.join(projectPath, "src", "pages"));
124
209
  let routerType;
125
210
  if (hasAppDir && hasPagesDir) routerType = "hybrid";
126
211
  else if (hasAppDir) routerType = "app";
@@ -206,7 +291,7 @@ function detectOptimizely(pkg) {
206
291
  }
207
292
 
208
293
  // src/scanner/styling.ts
209
- import path3 from "path";
294
+ import path4 from "path";
210
295
  function detectStyling(projectPath, pkg) {
211
296
  const deps = {
212
297
  ...pkg.dependencies,
@@ -218,7 +303,7 @@ function detectStyling(projectPath, pkg) {
218
303
  styling.push("tailwind");
219
304
  tailwindVersion = (deps.tailwindcss || deps["@tailwindcss/postcss"] || "").replace(/[\^~>=<]/g, "");
220
305
  }
221
- const hasTailwindConfig = fileExists(path3.join(projectPath, "tailwind.config.js")) || fileExists(path3.join(projectPath, "tailwind.config.ts")) || fileExists(path3.join(projectPath, "tailwind.config.mjs"));
306
+ const hasTailwindConfig = fileExists(path4.join(projectPath, "tailwind.config.js")) || fileExists(path4.join(projectPath, "tailwind.config.ts")) || fileExists(path4.join(projectPath, "tailwind.config.mjs"));
222
307
  if (hasTailwindConfig && !styling.includes("tailwind")) {
223
308
  styling.push("tailwind");
224
309
  }
@@ -231,9 +316,9 @@ function detectStyling(projectPath, pkg) {
231
316
  }
232
317
 
233
318
  // src/scanner/typescript.ts
234
- import path4 from "path";
319
+ import path5 from "path";
235
320
  function detectTypescript(projectPath) {
236
- const tsconfigPath = path4.join(projectPath, "tsconfig.json");
321
+ const tsconfigPath = path5.join(projectPath, "tsconfig.json");
237
322
  if (!fileExists(tsconfigPath)) {
238
323
  return { typescript: false };
239
324
  }
@@ -245,18 +330,18 @@ function detectTypescript(projectPath) {
245
330
  }
246
331
 
247
332
  // src/scanner/monorepo.ts
248
- import path5 from "path";
333
+ import path6 from "path";
249
334
  function detectMonorepo(projectPath, pkg) {
250
- if (fileExists(path5.join(projectPath, "turbo.json"))) {
335
+ if (fileExists(path6.join(projectPath, "turbo.json"))) {
251
336
  return { monorepo: true, monorepoTool: "turborepo" };
252
337
  }
253
- if (fileExists(path5.join(projectPath, "nx.json"))) {
338
+ if (fileExists(path6.join(projectPath, "nx.json"))) {
254
339
  return { monorepo: true, monorepoTool: "nx" };
255
340
  }
256
- if (fileExists(path5.join(projectPath, "lerna.json"))) {
341
+ if (fileExists(path6.join(projectPath, "lerna.json"))) {
257
342
  return { monorepo: true, monorepoTool: "lerna" };
258
343
  }
259
- if (fileExists(path5.join(projectPath, "pnpm-workspace.yaml"))) {
344
+ if (fileExists(path6.join(projectPath, "pnpm-workspace.yaml"))) {
260
345
  return { monorepo: true, monorepoTool: "pnpm-workspaces" };
261
346
  }
262
347
  if (pkg.workspaces) {
@@ -266,10 +351,10 @@ function detectMonorepo(projectPath, pkg) {
266
351
  }
267
352
 
268
353
  // src/scanner/package-manager.ts
269
- import path6 from "path";
354
+ import path7 from "path";
270
355
  function detectPackageManager(projectPath) {
271
356
  const pkg = readJsonSafe(
272
- path6.join(projectPath, "package.json")
357
+ path7.join(projectPath, "package.json")
273
358
  );
274
359
  if (pkg?.packageManager) {
275
360
  if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
@@ -277,15 +362,15 @@ function detectPackageManager(projectPath) {
277
362
  if (pkg.packageManager.startsWith("bun")) return "bun";
278
363
  return "npm";
279
364
  }
280
- if (fileExists(path6.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
281
- if (fileExists(path6.join(projectPath, "yarn.lock"))) return "yarn";
282
- if (fileExists(path6.join(projectPath, "bun.lockb"))) return "bun";
283
- if (fileExists(path6.join(projectPath, "bun.lock"))) return "bun";
365
+ if (fileExists(path7.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
366
+ if (fileExists(path7.join(projectPath, "yarn.lock"))) return "yarn";
367
+ if (fileExists(path7.join(projectPath, "bun.lockb"))) return "bun";
368
+ if (fileExists(path7.join(projectPath, "bun.lock"))) return "bun";
284
369
  return "npm";
285
370
  }
286
371
 
287
372
  // src/scanner/figma.ts
288
- import path7 from "path";
373
+ import path8 from "path";
289
374
  function detectFigma(projectPath, pkg) {
290
375
  return {
291
376
  figmaMcp: detectFigmaMcp(projectPath),
@@ -297,9 +382,9 @@ function detectFigma(projectPath, pkg) {
297
382
  }
298
383
  function detectFigmaMcp(projectPath) {
299
384
  const settingsPaths = [
300
- path7.join(projectPath, ".claude", "settings.json"),
301
- path7.join(projectPath, ".claude", "settings.local.json"),
302
- path7.join(projectPath, ".mcp.json")
385
+ path8.join(projectPath, ".claude", "settings.json"),
386
+ path8.join(projectPath, ".claude", "settings.local.json"),
387
+ path8.join(projectPath, ".mcp.json")
303
388
  ];
304
389
  for (const settingsPath of settingsPaths) {
305
390
  const content = readFileSafe(settingsPath);
@@ -320,28 +405,28 @@ function detectFigmaCodeCli(pkg) {
320
405
  }
321
406
  function detectDesignTokens(projectPath) {
322
407
  const tokenPaths = [
323
- path7.join(projectPath, "tokens.json"),
324
- path7.join(projectPath, "tokens"),
325
- path7.join(projectPath, "design-tokens.json"),
326
- path7.join(projectPath, "src", "tokens")
408
+ path8.join(projectPath, "tokens.json"),
409
+ path8.join(projectPath, "tokens"),
410
+ path8.join(projectPath, "design-tokens.json"),
411
+ path8.join(projectPath, "src", "tokens")
327
412
  ];
328
413
  for (const tokenPath of tokenPaths) {
329
414
  if (fileExists(tokenPath)) return true;
330
415
  }
331
416
  const globalsCss = readFileSafe(
332
- path7.join(projectPath, "src", "app", "globals.css")
417
+ path8.join(projectPath, "src", "app", "globals.css")
333
418
  );
334
419
  if (globalsCss && globalsCss.includes("@theme")) return true;
335
420
  return false;
336
421
  }
337
422
  function detectTokenFormat(projectPath) {
338
423
  const globalsCss = readFileSafe(
339
- path7.join(projectPath, "src", "app", "globals.css")
424
+ path8.join(projectPath, "src", "app", "globals.css")
340
425
  );
341
426
  if (globalsCss && globalsCss.includes("@theme")) return "tailwind-v4";
342
427
  const twConfigPaths = [
343
- path7.join(projectPath, "tailwind.config.ts"),
344
- path7.join(projectPath, "tailwind.config.js")
428
+ path8.join(projectPath, "tailwind.config.ts"),
429
+ path8.join(projectPath, "tailwind.config.js")
345
430
  ];
346
431
  for (const twPath of twConfigPaths) {
347
432
  const content = readFileSafe(twPath);
@@ -356,12 +441,12 @@ function detectVisualTests(projectPath, pkg) {
356
441
  ...pkg.devDependencies
357
442
  };
358
443
  const hasPlaywright = "@playwright/test" in deps || "playwright" in deps;
359
- const hasPlaywrightConfig = fileExists(path7.join(projectPath, "playwright.config.ts")) || fileExists(path7.join(projectPath, "playwright.config.js"));
444
+ const hasPlaywrightConfig = fileExists(path8.join(projectPath, "playwright.config.ts")) || fileExists(path8.join(projectPath, "playwright.config.js"));
360
445
  return hasPlaywright || hasPlaywrightConfig;
361
446
  }
362
447
 
363
448
  // src/scanner/tools.ts
364
- import path8 from "path";
449
+ import path9 from "path";
365
450
  function detectTools(projectPath, pkg) {
366
451
  const deps = {
367
452
  ...pkg.dependencies,
@@ -381,13 +466,13 @@ function detectTools(projectPath, pkg) {
381
466
  }
382
467
  function detectPlaywright(projectPath, deps) {
383
468
  if ("@playwright/test" in deps) return true;
384
- if (fileExists(path8.join(projectPath, "playwright.config.ts"))) return true;
385
- if (fileExists(path8.join(projectPath, "playwright.config.js"))) return true;
469
+ if (fileExists(path9.join(projectPath, "playwright.config.ts"))) return true;
470
+ if (fileExists(path9.join(projectPath, "playwright.config.js"))) return true;
386
471
  return false;
387
472
  }
388
473
  function detectStorybook(projectPath, deps) {
389
474
  if ("@storybook/react" in deps) return true;
390
- if (dirExists(path8.join(projectPath, ".storybook"))) return true;
475
+ if (dirExists(path9.join(projectPath, ".storybook"))) return true;
391
476
  return false;
392
477
  }
393
478
  function detectEslint(projectPath, deps) {
@@ -405,7 +490,7 @@ function detectEslint(projectPath, deps) {
405
490
  "eslint.config.ts"
406
491
  ];
407
492
  for (const config of eslintConfigs) {
408
- if (fileExists(path8.join(projectPath, config))) return true;
493
+ if (fileExists(path9.join(projectPath, config))) return true;
409
494
  }
410
495
  return false;
411
496
  }
@@ -424,14 +509,14 @@ function detectPrettier(projectPath, deps) {
424
509
  "prettier.config.ts"
425
510
  ];
426
511
  for (const config of prettierConfigs) {
427
- if (fileExists(path8.join(projectPath, config))) return true;
512
+ if (fileExists(path9.join(projectPath, config))) return true;
428
513
  }
429
514
  return false;
430
515
  }
431
516
  function detectBiome(projectPath, deps) {
432
517
  if ("@biomejs/biome" in deps) return true;
433
- if (fileExists(path8.join(projectPath, "biome.json"))) return true;
434
- if (fileExists(path8.join(projectPath, "biome.jsonc"))) return true;
518
+ if (fileExists(path9.join(projectPath, "biome.json"))) return true;
519
+ if (fileExists(path9.join(projectPath, "biome.jsonc"))) return true;
435
520
  return false;
436
521
  }
437
522
  function detectAxeCore(deps) {
@@ -439,13 +524,13 @@ function detectAxeCore(deps) {
439
524
  }
440
525
  function detectSnyk(projectPath, deps) {
441
526
  if ("snyk" in deps) return true;
442
- if (fileExists(path8.join(projectPath, ".snyk"))) return true;
527
+ if (fileExists(path9.join(projectPath, ".snyk"))) return true;
443
528
  return false;
444
529
  }
445
530
  function detectKnip(projectPath, deps) {
446
531
  if ("knip" in deps) return true;
447
- if (fileExists(path8.join(projectPath, "knip.json"))) return true;
448
- if (fileExists(path8.join(projectPath, "knip.config.ts"))) return true;
532
+ if (fileExists(path9.join(projectPath, "knip.json"))) return true;
533
+ if (fileExists(path9.join(projectPath, "knip.config.ts"))) return true;
449
534
  return false;
450
535
  }
451
536
  function detectBundleAnalyzer(deps) {
@@ -453,12 +538,12 @@ function detectBundleAnalyzer(deps) {
453
538
  }
454
539
 
455
540
  // src/scanner/mcp.ts
456
- import path9 from "path";
541
+ import path10 from "path";
457
542
  function detectMcpServers(projectPath) {
458
543
  const settingsPaths = [
459
- path9.join(projectPath, ".claude", "settings.json"),
460
- path9.join(projectPath, ".claude", "settings.local.json"),
461
- path9.join(projectPath, ".mcp.json")
544
+ path10.join(projectPath, ".claude", "settings.json"),
545
+ path10.join(projectPath, ".claude", "settings.local.json"),
546
+ path10.join(projectPath, ".mcp.json")
462
547
  ];
463
548
  let combined = "";
464
549
  for (const settingsPath of settingsPaths) {
@@ -477,18 +562,18 @@ function detectMcpServers(projectPath) {
477
562
  }
478
563
 
479
564
  // src/scanner/design-tokens.ts
480
- import path10 from "path";
565
+ import path11 from "path";
481
566
  function detectDesignTokens2(projectPath) {
482
567
  const globalsCss = readFileSafe(
483
- path10.join(projectPath, "src", "app", "globals.css")
568
+ path11.join(projectPath, "src", "app", "globals.css")
484
569
  );
485
570
  if (globalsCss && globalsCss.includes("@theme")) {
486
571
  return parseThemeInline(globalsCss);
487
572
  }
488
573
  const twConfigPaths = [
489
- path10.join(projectPath, "tailwind.config.ts"),
490
- path10.join(projectPath, "tailwind.config.js"),
491
- path10.join(projectPath, "tailwind.config.mjs")
574
+ path11.join(projectPath, "tailwind.config.ts"),
575
+ path11.join(projectPath, "tailwind.config.js"),
576
+ path11.join(projectPath, "tailwind.config.mjs")
492
577
  ];
493
578
  for (const twPath of twConfigPaths) {
494
579
  const content = readFileSafe(twPath);
@@ -622,7 +707,7 @@ function parseCssVariables(css) {
622
707
  }
623
708
 
624
709
  // src/scanner/static-site.ts
625
- import path11 from "path";
710
+ import path12 from "path";
626
711
  import fs2 from "fs-extra";
627
712
  function detectStaticSite(projectPath, pkg) {
628
713
  const hasStaticExport = checkStaticExport(projectPath);
@@ -651,9 +736,9 @@ function detectStaticSite(projectPath, pkg) {
651
736
  }
652
737
  function checkStaticExport(projectPath) {
653
738
  const configPaths = [
654
- path11.join(projectPath, "next.config.js"),
655
- path11.join(projectPath, "next.config.ts"),
656
- path11.join(projectPath, "next.config.mjs")
739
+ path12.join(projectPath, "next.config.js"),
740
+ path12.join(projectPath, "next.config.ts"),
741
+ path12.join(projectPath, "next.config.mjs")
657
742
  ];
658
743
  for (const configPath of configPaths) {
659
744
  const content = readFileSafe(configPath);
@@ -665,8 +750,8 @@ function checkStaticExport(projectPath) {
665
750
  }
666
751
  function checkGenerateStaticParams(projectPath) {
667
752
  const appDirs = [
668
- path11.join(projectPath, "app"),
669
- path11.join(projectPath, "src", "app")
753
+ path12.join(projectPath, "app"),
754
+ path12.join(projectPath, "src", "app")
670
755
  ];
671
756
  for (const appDir of appDirs) {
672
757
  if (dirExists(appDir) && hasPatternInDir(appDir, /generateStaticParams/)) {
@@ -674,8 +759,8 @@ function checkGenerateStaticParams(projectPath) {
674
759
  }
675
760
  }
676
761
  const pagesDirs = [
677
- path11.join(projectPath, "pages"),
678
- path11.join(projectPath, "src", "pages")
762
+ path12.join(projectPath, "pages"),
763
+ path12.join(projectPath, "src", "pages")
679
764
  ];
680
765
  for (const pagesDir of pagesDirs) {
681
766
  if (dirExists(pagesDir) && hasPatternInDir(pagesDir, /getStaticPaths/)) {
@@ -686,8 +771,8 @@ function checkGenerateStaticParams(projectPath) {
686
771
  }
687
772
  function checkRevalidatePatterns(projectPath) {
688
773
  const appDirs = [
689
- path11.join(projectPath, "app"),
690
- path11.join(projectPath, "src", "app")
774
+ path12.join(projectPath, "app"),
775
+ path12.join(projectPath, "src", "app")
691
776
  ];
692
777
  for (const appDir of appDirs) {
693
778
  if (dirExists(appDir) && hasPatternInDir(appDir, /revalidate\s*[:=]/)) {
@@ -698,8 +783,8 @@ function checkRevalidatePatterns(projectPath) {
698
783
  }
699
784
  function checkServerActions(projectPath) {
700
785
  const srcDirs = [
701
- path11.join(projectPath, "app"),
702
- path11.join(projectPath, "src")
786
+ path12.join(projectPath, "app"),
787
+ path12.join(projectPath, "src")
703
788
  ];
704
789
  for (const srcDir of srcDirs) {
705
790
  if (dirExists(srcDir) && hasPatternInDir(srcDir, /['"]use server['"]/)) {
@@ -710,15 +795,15 @@ function checkServerActions(projectPath) {
710
795
  }
711
796
  function checkApiRoutes(projectPath) {
712
797
  const appApiDirs = [
713
- path11.join(projectPath, "app", "api"),
714
- path11.join(projectPath, "src", "app", "api")
798
+ path12.join(projectPath, "app", "api"),
799
+ path12.join(projectPath, "src", "app", "api")
715
800
  ];
716
801
  for (const apiDir of appApiDirs) {
717
802
  if (dirExists(apiDir)) return true;
718
803
  }
719
804
  const pagesApiDirs = [
720
- path11.join(projectPath, "pages", "api"),
721
- path11.join(projectPath, "src", "pages", "api")
805
+ path12.join(projectPath, "pages", "api"),
806
+ path12.join(projectPath, "src", "pages", "api")
722
807
  ];
723
808
  for (const apiDir of pagesApiDirs) {
724
809
  if (dirExists(apiDir)) return true;
@@ -732,7 +817,7 @@ function hasPatternInDir(dir, pattern, depth = 0) {
732
817
  const entries = fs2.readdirSync(dir, { withFileTypes: true });
733
818
  for (const entry of entries) {
734
819
  if (SCAN_IGNORE.includes(entry.name)) continue;
735
- const full = path11.join(dir, entry.name);
820
+ const full = path12.join(dir, entry.name);
736
821
  if (entry.isDirectory()) {
737
822
  if (hasPatternInDir(full, pattern, depth + 1)) return true;
738
823
  } else if (/\.(tsx?|jsx?|mjs)$/.test(entry.name)) {
@@ -746,19 +831,19 @@ function hasPatternInDir(dir, pattern, depth = 0) {
746
831
  }
747
832
 
748
833
  // src/scanner/aiignore.ts
749
- import path12 from "path";
834
+ import path13 from "path";
750
835
  function loadAiIgnorePatterns(projectPath) {
751
- const content = readFileSafe(path12.join(projectPath, ".aiignore"));
836
+ const content = readFileSafe(path13.join(projectPath, ".aiignore"));
752
837
  if (!content) return [];
753
838
  return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
754
839
  }
755
840
 
756
841
  // src/scanner/index.ts
757
842
  async function scanProject(projectPath) {
758
- const pkgPath = path13.join(projectPath, "package.json");
843
+ const pkgPath = path14.join(projectPath, "package.json");
759
844
  const pkg = readJsonSafe(pkgPath) || {};
760
845
  const scripts = pkg.scripts || {};
761
- const projectName = pkg.name || path13.basename(projectPath);
846
+ const projectName = pkg.name || path14.basename(projectPath);
762
847
  const nextjsResult = detectNextjs(projectPath, pkg);
763
848
  const sitecoreResult = detectSitecore(pkg);
764
849
  const optimizelyResult = detectOptimizely(pkg);
@@ -801,10 +886,10 @@ async function scanProject(projectPath) {
801
886
  }
802
887
 
803
888
  // src/generator/assembler.ts
804
- import path14 from "path";
889
+ import path15 from "path";
805
890
  import fs3 from "fs-extra";
806
891
  function readTemplate(relativePath) {
807
- const fullPath = path14.join(TEMPLATES_DIR, relativePath);
892
+ const fullPath = path15.join(TEMPLATES_DIR, relativePath);
808
893
  const content = readFileSafe(fullPath);
809
894
  if (!content) {
810
895
  throw new Error(`Template not found: ${relativePath}`);
@@ -812,12 +897,12 @@ function readTemplate(relativePath) {
812
897
  return content.trim();
813
898
  }
814
899
  function loadCustomFragments(projectPath) {
815
- const customDir = path14.join(projectPath, ".ai-kit", "fragments");
900
+ const customDir = path15.join(projectPath, ".ai-kit", "fragments");
816
901
  if (!fs3.existsSync(customDir)) return [];
817
902
  try {
818
903
  const files = fs3.readdirSync(customDir).filter((f) => f.endsWith(".md"));
819
904
  return files.map((f) => {
820
- const content = fs3.readFileSync(path14.join(customDir, f), "utf-8");
905
+ const content = fs3.readFileSync(path15.join(customDir, f), "utf-8");
821
906
  return content.trim();
822
907
  }).filter(Boolean);
823
908
  } catch {
@@ -1138,10 +1223,33 @@ function generateConfig(scan, templates, commands, guides, options) {
1138
1223
  // src/generator/hooks.ts
1139
1224
  function generateHooks(scan, profile = "standard") {
1140
1225
  const hooks = {};
1226
+ const sessionStart = [];
1141
1227
  const preToolUse = [];
1142
1228
  const postToolUse = [];
1143
1229
  const postCompact = [];
1144
1230
  const stop = [];
1231
+ const stackParts = [
1232
+ scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""}`.trim() : scan.framework,
1233
+ scan.routerType ? `(${scan.routerType} router)` : "",
1234
+ scan.cms !== "none" ? scan.cms : "",
1235
+ scan.styling.length > 0 ? scan.styling.join(", ") : ""
1236
+ ].filter(Boolean);
1237
+ const scriptNames = Object.keys(scan.scripts).slice(0, 8).join(", ");
1238
+ const stackStr = stackParts.join(" + ");
1239
+ sessionStart.push({
1240
+ matcher: "",
1241
+ hooks: [
1242
+ {
1243
+ type: "command",
1244
+ command: [
1245
+ `echo "\u{1F4CB} ai-kit v${VERSION} | Stack: ${stackStr}"`,
1246
+ `echo " PM: ${scan.packageManager} | Scripts: ${scriptNames}"`,
1247
+ scan.monorepo ? `echo " Monorepo: ${scan.monorepoTool || "yes"}"` : "",
1248
+ `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`
1249
+ ].filter(Boolean).join("\n")
1250
+ }
1251
+ ]
1252
+ });
1145
1253
  preToolUse.push({
1146
1254
  matcher: "Bash(git push*)",
1147
1255
  hooks: [
@@ -1345,6 +1453,7 @@ function generateHooks(scan, profile = "standard") {
1345
1453
  ]
1346
1454
  });
1347
1455
  }
1456
+ if (sessionStart.length > 0) hooks.SessionStart = sessionStart;
1348
1457
  if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
1349
1458
  if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
1350
1459
  if (postCompact.length > 0) hooks.PostCompact = postCompact;
@@ -1359,9 +1468,9 @@ function generateSettingsLocal(scan, profile = "standard") {
1359
1468
  }
1360
1469
 
1361
1470
  // src/copier/skills.ts
1362
- import path15 from "path";
1471
+ import path16 from "path";
1363
1472
  import fs4 from "fs-extra";
1364
- var AVAILABLE_SKILLS = [
1473
+ var BASE_SKILLS = [
1365
1474
  "prompt-help",
1366
1475
  "review",
1367
1476
  "fix-bug",
@@ -1413,7 +1522,8 @@ var AVAILABLE_SKILLS = [
1413
1522
  // New skills (v1.10.0) — documentation freshness
1414
1523
  "fetch-docs"
1415
1524
  ];
1416
- var SKILL_DESCRIPTIONS = {
1525
+ var AVAILABLE_SKILLS = BASE_SKILLS.map((s) => `${SKILL_PREFIX}${s}`);
1526
+ var BASE_SKILL_DESCRIPTIONS = {
1417
1527
  "prompt-help": "Help developers write effective AI prompts with structured context",
1418
1528
  "review": "Deep code review following project coding standards",
1419
1529
  "fix-bug": "Systematic debugging workflow with root cause analysis and regression testing",
@@ -1440,7 +1550,6 @@ var SKILL_DESCRIPTIONS = {
1440
1550
  "responsive-check": "Audit responsive design \u2014 breakpoints, touch targets, overflow",
1441
1551
  "document": "Generate documentation for existing components and utilities",
1442
1552
  "token-tips": "Token usage optimization strategies for AI coding assistants",
1443
- // New skills (v1.1.0)
1444
1553
  "perf-audit": "Lighthouse-style performance audit covering Core Web Vitals, resource loading, and caching",
1445
1554
  "bundle-check": "Analyze bundle size, find heavy imports, suggest tree-shaking and code splitting",
1446
1555
  "i18n-check": "Find hardcoded strings, missing translation keys, and internationalization gaps",
@@ -1448,7 +1557,6 @@ var SKILL_DESCRIPTIONS = {
1448
1557
  "changelog": "Generate formatted changelogs from git history following Keep a Changelog format",
1449
1558
  "release": "Guided release workflow with versioning, changelog, tagging, and release notes",
1450
1559
  "storybook-gen": "Generate Storybook stories with controls, play functions, and visual tests",
1451
- // New skills (v1.2.0) — hooks, agents, sessions, orchestration
1452
1560
  "search-first": "Research-before-coding \u2014 search docs, existing patterns, and APIs before writing code",
1453
1561
  "quality-gate-check": "Post-implementation quality checklist \u2014 type safety, a11y, security, performance, Sitecore",
1454
1562
  "server-action": "Scaffold Next.js Server Actions with Zod validation, error handling, and revalidation",
@@ -1459,43 +1567,44 @@ var SKILL_DESCRIPTIONS = {
1459
1567
  "orchestrate": "Multi-agent orchestration \u2014 break complex tasks into subtasks and delegate to agents",
1460
1568
  "quality-gate": "Run comprehensive quality checks: types, lint, format, tests, bundle, a11y, security",
1461
1569
  "harness-audit": "Audit AI agent configuration \u2014 check CLAUDE.md, hooks, agents, skills, MCP servers",
1462
- // New skills (v1.7.0) — requirements clarification
1463
1570
  "deep-interview": "Socratic requirements gathering \u2014 structured interview to transform vague ideas into detailed specifications",
1464
1571
  "clarify-requirements": "Quick task clarification \u2014 identify gaps and ambiguities in under 5 minutes before coding",
1465
- // New skills (v1.10.0) — documentation freshness
1466
1572
  "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"
1467
1573
  };
1574
+ var SKILL_DESCRIPTIONS = Object.fromEntries(
1575
+ Object.entries(BASE_SKILL_DESCRIPTIONS).map(([k, v]) => [`${SKILL_PREFIX}${k}`, v])
1576
+ );
1468
1577
  async function copySkills(targetDir) {
1469
1578
  const copied = [];
1470
1579
  for (const skill of AVAILABLE_SKILLS) {
1471
- const src = path15.join(COMMANDS_DIR, `${skill}.md`);
1580
+ const src = path16.join(COMMANDS_DIR, `${skill}.md`);
1472
1581
  if (!await fs4.pathExists(src)) continue;
1473
1582
  const content = await fs4.readFile(src, "utf-8");
1474
1583
  const description = SKILL_DESCRIPTIONS[skill] || skill;
1475
- const claudeSkillDir = path15.join(targetDir, ".claude", "skills", skill);
1584
+ const claudeSkillDir = path16.join(targetDir, ".claude", "skills", skill);
1476
1585
  await fs4.ensureDir(claudeSkillDir);
1477
1586
  await fs4.writeFile(
1478
- path15.join(claudeSkillDir, "SKILL.md"),
1587
+ path16.join(claudeSkillDir, "SKILL.md"),
1479
1588
  content,
1480
1589
  "utf-8"
1481
1590
  );
1482
- const cursorSkillDir = path15.join(targetDir, ".cursor", "skills", skill);
1591
+ const cursorSkillDir = path16.join(targetDir, ".cursor", "skills", skill);
1483
1592
  await fs4.ensureDir(cursorSkillDir);
1484
1593
  await fs4.writeFile(
1485
- path15.join(cursorSkillDir, "SKILL.md"),
1594
+ path16.join(cursorSkillDir, "SKILL.md"),
1486
1595
  content,
1487
1596
  "utf-8"
1488
1597
  );
1489
- const legacyDir = path15.join(targetDir, ".claude", "commands");
1598
+ const legacyDir = path16.join(targetDir, ".claude", "commands");
1490
1599
  await fs4.ensureDir(legacyDir);
1491
- await fs4.copy(src, path15.join(legacyDir, `${skill}.md`), { overwrite: true });
1600
+ await fs4.copy(src, path16.join(legacyDir, `${skill}.md`), { overwrite: true });
1492
1601
  copied.push(skill);
1493
1602
  }
1494
1603
  return copied;
1495
1604
  }
1496
1605
 
1497
1606
  // src/copier/guides.ts
1498
- import path16 from "path";
1607
+ import path17 from "path";
1499
1608
  import fs5 from "fs-extra";
1500
1609
  var AVAILABLE_GUIDES = [
1501
1610
  "getting-started",
@@ -1506,12 +1615,12 @@ var AVAILABLE_GUIDES = [
1506
1615
  "hooks-and-agents"
1507
1616
  ];
1508
1617
  async function copyGuides(targetDir) {
1509
- const guidesTarget = path16.join(targetDir, "ai-kit", "guides");
1618
+ const guidesTarget = path17.join(targetDir, "ai-kit", "guides");
1510
1619
  await fs5.ensureDir(guidesTarget);
1511
1620
  const copied = [];
1512
1621
  for (const guide of AVAILABLE_GUIDES) {
1513
- const src = path16.join(GUIDES_DIR, `${guide}.md`);
1514
- const dest = path16.join(guidesTarget, `${guide}.md`);
1622
+ const src = path17.join(GUIDES_DIR, `${guide}.md`);
1623
+ const dest = path17.join(guidesTarget, `${guide}.md`);
1515
1624
  if (await fs5.pathExists(src)) {
1516
1625
  await fs5.copy(src, dest, { overwrite: true });
1517
1626
  copied.push(guide);
@@ -1521,16 +1630,16 @@ async function copyGuides(targetDir) {
1521
1630
  }
1522
1631
 
1523
1632
  // src/copier/docs.ts
1524
- import path17 from "path";
1633
+ import path18 from "path";
1525
1634
  import fs6 from "fs-extra";
1526
1635
  var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
1527
1636
  async function scaffoldDocs(targetDir) {
1528
- const docsTarget = path17.join(targetDir, "docs");
1637
+ const docsTarget = path18.join(targetDir, "docs");
1529
1638
  await fs6.ensureDir(docsTarget);
1530
1639
  const created = [];
1531
1640
  for (const doc of DOC_SCAFFOLDS) {
1532
- const src = path17.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
1533
- const dest = path17.join(docsTarget, `${doc}.md`);
1641
+ const src = path18.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
1642
+ const dest = path18.join(docsTarget, `${doc}.md`);
1534
1643
  if (await fs6.pathExists(dest)) {
1535
1644
  continue;
1536
1645
  }
@@ -1543,9 +1652,9 @@ async function scaffoldDocs(targetDir) {
1543
1652
  }
1544
1653
 
1545
1654
  // src/copier/agents.ts
1546
- import path18 from "path";
1655
+ import path19 from "path";
1547
1656
  import fs7 from "fs-extra";
1548
- var UNIVERSAL_AGENTS = [
1657
+ var BASE_UNIVERSAL_AGENTS = [
1549
1658
  "planner",
1550
1659
  "code-reviewer",
1551
1660
  "security-reviewer",
@@ -1560,37 +1669,38 @@ var UNIVERSAL_AGENTS = [
1560
1669
  "dependency-auditor",
1561
1670
  "api-designer"
1562
1671
  ];
1672
+ var UNIVERSAL_AGENTS = BASE_UNIVERSAL_AGENTS.map((a) => `${SKILL_PREFIX}${a}`);
1563
1673
  var CONDITIONAL_AGENTS = [
1564
1674
  {
1565
- name: "e2e-runner",
1675
+ name: `${SKILL_PREFIX}e2e-runner`,
1566
1676
  condition: (scan) => scan.tools.playwright
1567
1677
  },
1568
1678
  {
1569
- name: "sitecore-specialist",
1679
+ name: `${SKILL_PREFIX}sitecore-specialist`,
1570
1680
  condition: (scan) => scan.cms !== "none"
1571
1681
  },
1572
1682
  {
1573
- name: "tdd-guide",
1683
+ name: `${SKILL_PREFIX}tdd-guide`,
1574
1684
  condition: (scan) => scan.tools.playwright || !!scan.scripts["test"]
1575
1685
  }
1576
1686
  ];
1577
1687
  async function copyAgents(targetDir, scan) {
1578
- const agentsTarget = path18.join(targetDir, ".claude", "agents");
1688
+ const agentsTarget = path19.join(targetDir, ".claude", "agents");
1579
1689
  await fs7.ensureDir(agentsTarget);
1580
1690
  const copied = [];
1581
1691
  for (const agent of UNIVERSAL_AGENTS) {
1582
- const src = path18.join(AGENTS_DIR, `${agent}.md`);
1692
+ const src = path19.join(AGENTS_DIR, `${agent}.md`);
1583
1693
  if (!await fs7.pathExists(src)) continue;
1584
- await fs7.copy(src, path18.join(agentsTarget, `${agent}.md`), {
1694
+ await fs7.copy(src, path19.join(agentsTarget, `${agent}.md`), {
1585
1695
  overwrite: true
1586
1696
  });
1587
1697
  copied.push(agent);
1588
1698
  }
1589
1699
  for (const { name, condition } of CONDITIONAL_AGENTS) {
1590
1700
  if (!condition(scan)) continue;
1591
- const src = path18.join(AGENTS_DIR, `${name}.md`);
1701
+ const src = path19.join(AGENTS_DIR, `${name}.md`);
1592
1702
  if (!await fs7.pathExists(src)) continue;
1593
- await fs7.copy(src, path18.join(agentsTarget, `${name}.md`), {
1703
+ await fs7.copy(src, path19.join(agentsTarget, `${name}.md`), {
1594
1704
  overwrite: true
1595
1705
  });
1596
1706
  copied.push(name);
@@ -1599,17 +1709,17 @@ async function copyAgents(targetDir, scan) {
1599
1709
  }
1600
1710
 
1601
1711
  // src/copier/contexts.ts
1602
- import path19 from "path";
1712
+ import path20 from "path";
1603
1713
  import fs8 from "fs-extra";
1604
1714
  var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
1605
1715
  async function copyContexts(targetDir) {
1606
- const contextsTarget = path19.join(targetDir, ".claude", "contexts");
1716
+ const contextsTarget = path20.join(targetDir, ".claude", "contexts");
1607
1717
  await fs8.ensureDir(contextsTarget);
1608
1718
  const copied = [];
1609
1719
  for (const context of AVAILABLE_CONTEXTS) {
1610
- const src = path19.join(CONTEXTS_DIR, `${context}.md`);
1720
+ const src = path20.join(CONTEXTS_DIR, `${context}.md`);
1611
1721
  if (!await fs8.pathExists(src)) continue;
1612
- await fs8.copy(src, path19.join(contextsTarget, `${context}.md`), {
1722
+ await fs8.copy(src, path20.join(contextsTarget, `${context}.md`), {
1613
1723
  overwrite: true
1614
1724
  });
1615
1725
  copied.push(context);
@@ -1619,10 +1729,10 @@ async function copyContexts(targetDir) {
1619
1729
 
1620
1730
  // src/commands/init.ts
1621
1731
  async function initCommand(targetPath) {
1622
- const projectDir = path20.resolve(targetPath || process.cwd());
1732
+ const projectDir = path21.resolve(targetPath || process.cwd());
1623
1733
  logSection("AI Kit \u2014 Project Setup");
1624
1734
  logInfo(`Scanning: ${projectDir}`);
1625
- const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
1735
+ const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
1626
1736
  let savedConfig = null;
1627
1737
  let reuseMode = false;
1628
1738
  if (fileExists(configPath)) {
@@ -1799,7 +1909,7 @@ async function selectHookProfile() {
1799
1909
  });
1800
1910
  }
1801
1911
  async function selectConflictStrategy(projectDir) {
1802
- const hasExisting = fileExists(path20.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path20.join(projectDir, GENERATED_FILES.cursorRules));
1912
+ const hasExisting = fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules));
1803
1913
  if (!hasExisting) return "overwrite";
1804
1914
  return select({
1805
1915
  message: "Existing AI config files detected. How should we handle conflicts?",
@@ -1829,7 +1939,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1829
1939
  docs: []
1830
1940
  };
1831
1941
  if (tools.claude) {
1832
- const claudeMdPath = path20.join(projectDir, GENERATED_FILES.claudeMd);
1942
+ const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
1833
1943
  if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
1834
1944
  const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1835
1945
  await fs9.writeFile(claudeMdPath, content, "utf-8");
@@ -1841,14 +1951,14 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1841
1951
  result.agents = await copyAgents(projectDir, scan);
1842
1952
  result.contexts = await copyContexts(projectDir);
1843
1953
  const hookProfile = opts?.hookProfile || "standard";
1844
- const settingsLocalPath = path20.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1954
+ const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1845
1955
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
1846
- await fs9.ensureDir(path20.dirname(settingsLocalPath));
1956
+ await fs9.ensureDir(path21.dirname(settingsLocalPath));
1847
1957
  await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1848
1958
  result.hooks = true;
1849
1959
  }
1850
1960
  if (tools.cursor) {
1851
- const cursorPath = path20.join(projectDir, GENERATED_FILES.cursorRules);
1961
+ const cursorPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
1852
1962
  if (conflict === "overwrite" || !fileExists(cursorPath)) {
1853
1963
  const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1854
1964
  await fs9.writeFile(cursorPath, content, "utf-8");
@@ -1856,11 +1966,11 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1856
1966
  } else {
1857
1967
  logWarning(".cursorrules exists, skipping");
1858
1968
  }
1859
- const mdcDir = path20.join(projectDir, GENERATED_FILES.cursorMdcDir);
1969
+ const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
1860
1970
  await fs9.ensureDir(mdcDir);
1861
1971
  const mdcFiles = generateMdcFiles(scan);
1862
1972
  for (const mdc of mdcFiles) {
1863
- await fs9.writeFile(path20.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1973
+ await fs9.writeFile(path21.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1864
1974
  }
1865
1975
  result.cursorMdcFiles = mdcFiles.length;
1866
1976
  }
@@ -1879,7 +1989,7 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1879
1989
  tools
1880
1990
  });
1881
1991
  await fs9.writeJson(
1882
- path20.join(projectDir, AI_KIT_CONFIG_FILE),
1992
+ path21.join(projectDir, AI_KIT_CONFIG_FILE),
1883
1993
  config,
1884
1994
  { spaces: 2 }
1885
1995
  );
@@ -1955,13 +2065,13 @@ function showRecommendations(scan) {
1955
2065
  }
1956
2066
 
1957
2067
  // src/commands/update.ts
1958
- import path21 from "path";
2068
+ import path22 from "path";
1959
2069
  import fs10 from "fs-extra";
1960
2070
  import ora2 from "ora";
1961
2071
  import { confirm as confirm2 } from "@inquirer/prompts";
1962
2072
  async function updateCommand(targetPath) {
1963
- const projectDir = path21.resolve(targetPath || process.cwd());
1964
- const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
2073
+ const projectDir = path22.resolve(targetPath || process.cwd());
2074
+ const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
1965
2075
  if (!fileExists(configPath)) {
1966
2076
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
1967
2077
  return;
@@ -1986,6 +2096,17 @@ async function updateCommand(targetPath) {
1986
2096
  const spinner = ora2("Re-scanning project...").start();
1987
2097
  const scan = await scanProject(projectDir);
1988
2098
  spinner.succeed("Project re-scanned");
2099
+ const filesToBackup = [
2100
+ GENERATED_FILES.claudeMd,
2101
+ GENERATED_FILES.cursorRules,
2102
+ GENERATED_FILES.claudeSettingsLocal,
2103
+ AI_KIT_CONFIG_FILE
2104
+ ];
2105
+ const backupPath = await backupFiles(projectDir, filesToBackup);
2106
+ if (backupPath) {
2107
+ logSuccess(`Backed up current configs to ${path22.relative(projectDir, backupPath)}`);
2108
+ }
2109
+ await cleanupUnprefixedFiles(projectDir);
1989
2110
  logSection("Updating Files");
1990
2111
  const strictness = existingConfig.strictness || "standard";
1991
2112
  const hookProfile = existingConfig.hookProfile || "standard";
@@ -1994,8 +2115,8 @@ async function updateCommand(targetPath) {
1994
2115
  const genOpts = { strictness, customFragments };
1995
2116
  logInfo(`Using saved profile \u2014 Tools: ${tools.claude && tools.cursor ? "Claude Code + Cursor" : tools.claude ? "Claude Code" : "Cursor"} \xB7 Strictness: ${strictness} \xB7 Hooks: ${hookProfile}`);
1996
2117
  const templates = [];
1997
- if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)))) {
1998
- const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
2118
+ if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path22.join(projectDir, GENERATED_FILES.claudeMd)))) {
2119
+ const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
1999
2120
  const newContent = generateClaudeMd(scan, genOpts);
2000
2121
  const existing = readFileSafe(claudeMdPath);
2001
2122
  if (existing) {
@@ -2006,8 +2127,8 @@ async function updateCommand(targetPath) {
2006
2127
  templates.push("CLAUDE.md");
2007
2128
  logSuccess("CLAUDE.md updated");
2008
2129
  }
2009
- if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules)))) {
2010
- const cursorRulesPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
2130
+ if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path22.join(projectDir, GENERATED_FILES.cursorRules)))) {
2131
+ const cursorRulesPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
2011
2132
  const newContent = generateCursorRules(scan, genOpts);
2012
2133
  const existing = readFileSafe(cursorRulesPath);
2013
2134
  if (existing) {
@@ -2017,11 +2138,11 @@ async function updateCommand(targetPath) {
2017
2138
  }
2018
2139
  templates.push(".cursorrules");
2019
2140
  logSuccess(".cursorrules updated");
2020
- const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
2141
+ const mdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
2021
2142
  await fs10.ensureDir(mdcDir);
2022
2143
  const mdcFiles = generateMdcFiles(scan);
2023
2144
  for (const mdc of mdcFiles) {
2024
- await fs10.writeFile(path21.join(mdcDir, mdc.filename), mdc.content, "utf-8");
2145
+ await fs10.writeFile(path22.join(mdcDir, mdc.filename), mdc.content, "utf-8");
2025
2146
  }
2026
2147
  logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
2027
2148
  }
@@ -2032,9 +2153,9 @@ async function updateCommand(targetPath) {
2032
2153
  const contexts = await copyContexts(projectDir);
2033
2154
  logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
2034
2155
  if (existingConfig.hooks !== false) {
2035
- const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2156
+ const settingsLocalPath = path22.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2036
2157
  const settingsLocal = generateSettingsLocal(scan, hookProfile);
2037
- await fs10.ensureDir(path21.dirname(settingsLocalPath));
2158
+ await fs10.ensureDir(path22.dirname(settingsLocalPath));
2038
2159
  await fs10.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
2039
2160
  logSuccess(`Hooks updated (profile: ${hookProfile})`);
2040
2161
  }
@@ -2052,14 +2173,44 @@ async function updateCommand(targetPath) {
2052
2173
  logSuccess("ai-kit.config.json updated");
2053
2174
  console.log("");
2054
2175
  logInfo("All AI configs refreshed with latest project scan.");
2176
+ if (backupPath) {
2177
+ logInfo("Rollback available: `ai-kit rollback --latest`");
2178
+ }
2179
+ }
2180
+ async function cleanupUnprefixedFiles(projectDir) {
2181
+ let cleaned = 0;
2182
+ for (const name of BASE_SKILLS) {
2183
+ for (const root of [".claude/skills", ".cursor/skills"]) {
2184
+ const oldDir = path22.join(projectDir, root, name);
2185
+ if (await fs10.pathExists(oldDir)) {
2186
+ await fs10.remove(oldDir);
2187
+ cleaned++;
2188
+ }
2189
+ }
2190
+ const oldCmd = path22.join(projectDir, ".claude", "commands", `${name}.md`);
2191
+ if (await fs10.pathExists(oldCmd)) {
2192
+ await fs10.remove(oldCmd);
2193
+ cleaned++;
2194
+ }
2195
+ }
2196
+ for (const name of BASE_UNIVERSAL_AGENTS) {
2197
+ const oldAgent = path22.join(projectDir, ".claude", "agents", `${name}.md`);
2198
+ if (await fs10.pathExists(oldAgent)) {
2199
+ await fs10.remove(oldAgent);
2200
+ cleaned++;
2201
+ }
2202
+ }
2203
+ if (cleaned > 0) {
2204
+ logInfo(`Cleaned up ${cleaned} old unprefixed skill/agent files (migrated to kit- prefix)`);
2205
+ }
2055
2206
  }
2056
2207
 
2057
2208
  // src/commands/reset.ts
2058
- import path22 from "path";
2209
+ import path23 from "path";
2059
2210
  import fs11 from "fs-extra";
2060
2211
  import { confirm as confirm3 } from "@inquirer/prompts";
2061
2212
  async function resetCommand(targetPath) {
2062
- const projectDir = path22.resolve(targetPath || process.cwd());
2213
+ const projectDir = path23.resolve(targetPath || process.cwd());
2063
2214
  logSection("AI Kit \u2014 Reset");
2064
2215
  logWarning("This will remove all AI Kit generated files:");
2065
2216
  logInfo(` - ${GENERATED_FILES.claudeMd}`);
@@ -2080,42 +2231,42 @@ async function resetCommand(targetPath) {
2080
2231
  return;
2081
2232
  }
2082
2233
  const removed = [];
2083
- const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
2234
+ const claudeMdPath = path23.join(projectDir, GENERATED_FILES.claudeMd);
2084
2235
  if (fileExists(claudeMdPath)) {
2085
2236
  await fs11.remove(claudeMdPath);
2086
2237
  removed.push(GENERATED_FILES.claudeMd);
2087
2238
  }
2088
- const cursorPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
2239
+ const cursorPath = path23.join(projectDir, GENERATED_FILES.cursorRules);
2089
2240
  if (fileExists(cursorPath)) {
2090
2241
  await fs11.remove(cursorPath);
2091
2242
  removed.push(GENERATED_FILES.cursorRules);
2092
2243
  }
2093
- const cursorMdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
2244
+ const cursorMdcDir = path23.join(projectDir, GENERATED_FILES.cursorMdcDir);
2094
2245
  if (fileExists(cursorMdcDir)) {
2095
2246
  await fs11.remove(cursorMdcDir);
2096
2247
  removed.push(GENERATED_FILES.cursorMdcDir);
2097
2248
  }
2098
- const commandsDir = path22.join(projectDir, GENERATED_FILES.claudeCommands);
2249
+ const commandsDir = path23.join(projectDir, GENERATED_FILES.claudeCommands);
2099
2250
  if (fileExists(commandsDir)) {
2100
2251
  await fs11.remove(commandsDir);
2101
2252
  removed.push(GENERATED_FILES.claudeCommands);
2102
2253
  }
2103
- const claudeSkillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
2254
+ const claudeSkillsDir = path23.join(projectDir, GENERATED_FILES.claudeSkills);
2104
2255
  if (fileExists(claudeSkillsDir)) {
2105
2256
  await fs11.remove(claudeSkillsDir);
2106
2257
  removed.push(GENERATED_FILES.claudeSkills);
2107
2258
  }
2108
- const cursorSkillsDir = path22.join(projectDir, GENERATED_FILES.cursorSkills);
2259
+ const cursorSkillsDir = path23.join(projectDir, GENERATED_FILES.cursorSkills);
2109
2260
  if (fileExists(cursorSkillsDir)) {
2110
2261
  await fs11.remove(cursorSkillsDir);
2111
2262
  removed.push(GENERATED_FILES.cursorSkills);
2112
2263
  }
2113
- const aiKitDir = path22.join(projectDir, "ai-kit");
2264
+ const aiKitDir = path23.join(projectDir, "ai-kit");
2114
2265
  if (fileExists(aiKitDir)) {
2115
2266
  await fs11.remove(aiKitDir);
2116
2267
  removed.push("ai-kit/");
2117
2268
  }
2118
- const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
2269
+ const configPath = path23.join(projectDir, AI_KIT_CONFIG_FILE);
2119
2270
  if (fileExists(configPath)) {
2120
2271
  await fs11.remove(configPath);
2121
2272
  removed.push(AI_KIT_CONFIG_FILE);
@@ -2129,7 +2280,7 @@ async function resetCommand(targetPath) {
2129
2280
  }
2130
2281
 
2131
2282
  // src/commands/tokens.ts
2132
- import path23 from "path";
2283
+ import path24 from "path";
2133
2284
  import fs12 from "fs-extra";
2134
2285
  import chalk2 from "chalk";
2135
2286
  import ora3 from "ora";
@@ -2140,14 +2291,14 @@ var PRICING = {
2140
2291
  };
2141
2292
  var PLAN_BUDGET = 20;
2142
2293
  function findSessionFiles() {
2143
- const claudeDir = path23.join(os.homedir(), ".claude", "projects");
2294
+ const claudeDir = path24.join(os.homedir(), ".claude", "projects");
2144
2295
  if (!fs12.existsSync(claudeDir)) return [];
2145
2296
  const files = [];
2146
2297
  function walkDir(dir) {
2147
2298
  try {
2148
2299
  const entries = fs12.readdirSync(dir, { withFileTypes: true });
2149
2300
  for (const entry of entries) {
2150
- const full = path23.join(dir, entry.name);
2301
+ const full = path24.join(dir, entry.name);
2151
2302
  if (entry.isDirectory()) {
2152
2303
  walkDir(full);
2153
2304
  } else if (entry.name.endsWith(".jsonl")) {
@@ -2202,8 +2353,8 @@ function parseSessionFile(filePath) {
2202
2353
  const stat = fs12.statSync(filePath);
2203
2354
  sessionDate = stat.mtime.toISOString().slice(0, 10);
2204
2355
  }
2205
- const sessionId = path23.basename(filePath, ".jsonl");
2206
- const projectName = path23.basename(path23.dirname(filePath));
2356
+ const sessionId = path24.basename(filePath, ".jsonl");
2357
+ const projectName = path24.basename(path24.dirname(filePath));
2207
2358
  return {
2208
2359
  sessionId,
2209
2360
  filePath,
@@ -2425,14 +2576,14 @@ ${chalk2.bold("Model Recommendation")}`);
2425
2576
  printRoiEstimate(monthSessions, monthCost);
2426
2577
  console.log(
2427
2578
  `
2428
- ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
2579
+ ${chalk2.dim("Tip: Use /kit-understand before modifying unfamiliar code \u2014")}`
2429
2580
  );
2430
2581
  console.log(
2431
2582
  chalk2.dim(" it's cheaper than a failed implementation attempt.")
2432
2583
  );
2433
2584
  console.log("");
2434
2585
  if (options.csv) {
2435
- const csvPath = path23.join(process.cwd(), "token-usage.csv");
2586
+ const csvPath = path24.join(process.cwd(), "token-usage.csv");
2436
2587
  const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
2437
2588
  const daily = aggregateByDate(sessions);
2438
2589
  const csvRows = daily.map(
@@ -2475,9 +2626,9 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2475
2626
  }
2476
2627
  };
2477
2628
  const outputDir = process.cwd();
2478
- const dataPath = path23.join(outputDir, "token-data.json");
2479
- const dashboardSrc = path23.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
2480
- const dashboardDest = path23.join(outputDir, "token-dashboard.html");
2629
+ const dataPath = path24.join(outputDir, "token-data.json");
2630
+ const dashboardSrc = path24.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
2631
+ const dashboardDest = path24.join(outputDir, "token-dashboard.html");
2481
2632
  await fs12.writeJson(dataPath, exportData, { spaces: 2 });
2482
2633
  logInfo(`Token data written to ${dataPath}`);
2483
2634
  if (await fs12.pathExists(dashboardSrc)) {
@@ -2488,9 +2639,9 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2488
2639
  }
2489
2640
  spinner.succeed("Dashboard exported");
2490
2641
  try {
2491
- const { exec } = await import("child_process");
2642
+ const { execFile } = await import("child_process");
2492
2643
  const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
2493
- exec(`${openCmd} "${dashboardDest}"`);
2644
+ execFile(openCmd, [dashboardDest]);
2494
2645
  logInfo("Opening dashboard in browser...");
2495
2646
  } catch {
2496
2647
  logInfo(`Open ${dashboardDest} in your browser to view the dashboard.`);
@@ -2498,12 +2649,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
2498
2649
  }
2499
2650
 
2500
2651
  // src/commands/doctor.ts
2501
- import path24 from "path";
2652
+ import path25 from "path";
2502
2653
  import chalk3 from "chalk";
2503
2654
  import ora4 from "ora";
2504
2655
  async function doctorCommand(targetPath) {
2505
- const projectDir = path24.resolve(targetPath || process.cwd());
2506
- const configPath = path24.join(projectDir, AI_KIT_CONFIG_FILE);
2656
+ const projectDir = path25.resolve(targetPath || process.cwd());
2657
+ const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
2507
2658
  let passed = 0;
2508
2659
  let warnings = 0;
2509
2660
  let issues = 0;
@@ -2535,7 +2686,7 @@ async function doctorCommand(targetPath) {
2535
2686
  }
2536
2687
  for (const template of config.templates) {
2537
2688
  const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
2538
- const templatePath = path24.join(projectDir, templateFile);
2689
+ const templatePath = path25.join(projectDir, templateFile);
2539
2690
  if (fileExists(templatePath)) {
2540
2691
  logSuccess(`${template} exists and in sync`);
2541
2692
  passed++;
@@ -2546,8 +2697,8 @@ async function doctorCommand(targetPath) {
2546
2697
  }
2547
2698
  const missingSkills = [];
2548
2699
  for (const skill of config.commands) {
2549
- const claudeSkillPath = path24.join(projectDir, GENERATED_FILES.claudeSkills, skill);
2550
- const cursorSkillPath = path24.join(projectDir, GENERATED_FILES.cursorSkills, skill);
2700
+ const claudeSkillPath = path25.join(projectDir, GENERATED_FILES.claudeSkills, skill);
2701
+ const cursorSkillPath = path25.join(projectDir, GENERATED_FILES.cursorSkills, skill);
2551
2702
  if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
2552
2703
  missingSkills.push(skill);
2553
2704
  }
@@ -2561,10 +2712,10 @@ async function doctorCommand(targetPath) {
2561
2712
  );
2562
2713
  issues++;
2563
2714
  }
2564
- const guidesDir = path24.join(projectDir, "ai-kit", "guides");
2715
+ const guidesDir = path25.join(projectDir, "ai-kit", "guides");
2565
2716
  const missingGuides = [];
2566
2717
  for (const guide of config.guides) {
2567
- const guidePath = path24.join(guidesDir, guide);
2718
+ const guidePath = path25.join(guidesDir, guide);
2568
2719
  if (!fileExists(guidePath)) {
2569
2720
  missingGuides.push(guide);
2570
2721
  }
@@ -2702,13 +2853,13 @@ function compareScanResults(previous, current) {
2702
2853
  }
2703
2854
 
2704
2855
  // src/commands/diff.ts
2705
- import path25 from "path";
2856
+ import path26 from "path";
2706
2857
  import fs13 from "fs-extra";
2707
2858
  import chalk4 from "chalk";
2708
2859
  import ora5 from "ora";
2709
2860
  async function diffCommand(targetPath) {
2710
- const projectDir = path25.resolve(targetPath || process.cwd());
2711
- const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
2861
+ const projectDir = path26.resolve(targetPath || process.cwd());
2862
+ const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
2712
2863
  console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
2713
2864
  if (!fileExists(configPath)) {
2714
2865
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
@@ -2775,11 +2926,11 @@ async function diffCommand(targetPath) {
2775
2926
  if (cursorRulesStatus.status === "modified") modified++;
2776
2927
  else if (cursorRulesStatus.status === "added") added++;
2777
2928
  else unchanged++;
2778
- const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
2929
+ const skillsDir = path26.join(projectDir, GENERATED_FILES.claudeSkills);
2779
2930
  const skillCount = countFilesInDir(skillsDir);
2780
2931
  console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
2781
2932
  unchanged++;
2782
- const guidesDir = path25.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2933
+ const guidesDir = path26.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2783
2934
  const guideCount = existingConfig.guides?.length || 0;
2784
2935
  console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
2785
2936
  unchanged++;
@@ -2888,7 +3039,7 @@ function diffStack(oldScan, newScan) {
2888
3039
  return changes;
2889
3040
  }
2890
3041
  function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
2891
- const filePath = path25.join(projectDir, filename);
3042
+ const filePath = path26.join(projectDir, filename);
2892
3043
  const currentContent = readFileSafe(filePath);
2893
3044
  const newContent = generate2();
2894
3045
  if (!currentContent) {
@@ -2934,7 +3085,7 @@ function countFilesInDir(dirPath) {
2934
3085
  }
2935
3086
 
2936
3087
  // src/commands/export.ts
2937
- import path26 from "path";
3088
+ import path27 from "path";
2938
3089
  import fs14 from "fs-extra";
2939
3090
  import ora6 from "ora";
2940
3091
  import { select as select2 } from "@inquirer/prompts";
@@ -2971,9 +3122,9 @@ function toCline(content) {
2971
3122
  ${stripped}`;
2972
3123
  }
2973
3124
  async function exportCommand(targetPath, options) {
2974
- const projectDir = path26.resolve(targetPath || process.cwd());
2975
- const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
2976
- const claudeMdPath = path26.join(projectDir, GENERATED_FILES.claudeMd);
3125
+ const projectDir = path27.resolve(targetPath || process.cwd());
3126
+ const configPath = path27.join(projectDir, AI_KIT_CONFIG_FILE);
3127
+ const claudeMdPath = path27.join(projectDir, GENERATED_FILES.claudeMd);
2977
3128
  logSection("AI Kit \u2014 Export");
2978
3129
  if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
2979
3130
  logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
@@ -3015,7 +3166,7 @@ async function exportCommand(targetPath, options) {
3015
3166
  for (const fmt2 of formats) {
3016
3167
  const target = EXPORT_TARGETS[fmt2];
3017
3168
  const transformer = transformers[fmt2];
3018
- const outputPath = path26.join(projectDir, target.file);
3169
+ const outputPath = path27.join(projectDir, target.file);
3019
3170
  const transformed = transformer(claudeContent);
3020
3171
  await fs14.writeFile(outputPath, transformed, "utf-8");
3021
3172
  exported++;
@@ -3033,7 +3184,7 @@ async function exportCommand(targetPath, options) {
3033
3184
  }
3034
3185
 
3035
3186
  // src/commands/stats.ts
3036
- import path27 from "path";
3187
+ import path28 from "path";
3037
3188
  import chalk5 from "chalk";
3038
3189
  var SKILL_CATEGORIES = {
3039
3190
  "Getting Started": ["prompt-help", "understand"],
@@ -3136,8 +3287,8 @@ var MCP_DISPLAY_NAMES = {
3136
3287
  perplexity: "Perplexity"
3137
3288
  };
3138
3289
  async function statsCommand(targetPath) {
3139
- const projectDir = path27.resolve(targetPath || process.cwd());
3140
- const configPath = path27.join(projectDir, AI_KIT_CONFIG_FILE);
3290
+ const projectDir = path28.resolve(targetPath || process.cwd());
3291
+ const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
3141
3292
  logSection("AI Kit \u2014 Project Stats");
3142
3293
  console.log("");
3143
3294
  if (!fileExists(configPath)) {
@@ -3231,21 +3382,21 @@ async function statsCommand(targetPath) {
3231
3382
  }
3232
3383
 
3233
3384
  // src/commands/audit.ts
3234
- import path28 from "path";
3385
+ import path29 from "path";
3235
3386
  import fs15 from "fs-extra";
3236
3387
  import chalk6 from "chalk";
3237
3388
  async function auditCommand(targetPath) {
3238
- const projectDir = path28.resolve(targetPath || process.cwd());
3389
+ const projectDir = path29.resolve(targetPath || process.cwd());
3239
3390
  logSection("AI Kit \u2014 Security & Configuration Audit");
3240
3391
  logInfo(`Auditing: ${projectDir}`);
3241
3392
  const checks = [];
3242
- const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
3393
+ const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
3243
3394
  if (fileExists(configPath)) {
3244
3395
  checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
3245
3396
  } else {
3246
3397
  checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
3247
3398
  }
3248
- const claudeMdPath = path28.join(projectDir, GENERATED_FILES.claudeMd);
3399
+ const claudeMdPath = path29.join(projectDir, GENERATED_FILES.claudeMd);
3249
3400
  const claudeMd = readFileSafe(claudeMdPath);
3250
3401
  if (claudeMd) {
3251
3402
  if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
@@ -3270,7 +3421,7 @@ async function auditCommand(targetPath) {
3270
3421
  checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
3271
3422
  }
3272
3423
  }
3273
- const settingsLocalPath = path28.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3424
+ const settingsLocalPath = path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
3274
3425
  if (fileExists(settingsLocalPath)) {
3275
3426
  const settings2 = readJsonSafe(settingsLocalPath);
3276
3427
  if (settings2?.hooks) {
@@ -3281,14 +3432,14 @@ async function auditCommand(targetPath) {
3281
3432
  } else {
3282
3433
  checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
3283
3434
  }
3284
- const agentsDir = path28.join(projectDir, GENERATED_FILES.claudeAgents);
3435
+ const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3285
3436
  if (await fs15.pathExists(agentsDir)) {
3286
3437
  const agentFiles = (await fs15.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
3287
3438
  if (agentFiles.length > 0) {
3288
3439
  checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
3289
3440
  let invalidAgents = 0;
3290
3441
  for (const file of agentFiles) {
3291
- const content = readFileSafe(path28.join(agentsDir, file));
3442
+ const content = readFileSafe(path29.join(agentsDir, file));
3292
3443
  if (content && !content.startsWith("---")) {
3293
3444
  invalidAgents++;
3294
3445
  }
@@ -3302,18 +3453,18 @@ async function auditCommand(targetPath) {
3302
3453
  } else {
3303
3454
  checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
3304
3455
  }
3305
- const contextsDir = path28.join(projectDir, GENERATED_FILES.claudeContexts);
3456
+ const contextsDir = path29.join(projectDir, GENERATED_FILES.claudeContexts);
3306
3457
  if (await fs15.pathExists(contextsDir)) {
3307
3458
  const contextFiles = (await fs15.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
3308
3459
  checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
3309
3460
  } else {
3310
3461
  checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
3311
3462
  }
3312
- const skillsDir = path28.join(projectDir, GENERATED_FILES.claudeSkills);
3463
+ const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3313
3464
  if (await fs15.pathExists(skillsDir)) {
3314
3465
  const skillDirs = (await fs15.readdir(skillsDir)).filter(async (f) => {
3315
3466
  try {
3316
- return (await fs15.stat(path28.join(skillsDir, f))).isDirectory();
3467
+ return (await fs15.stat(path29.join(skillsDir, f))).isDirectory();
3317
3468
  } catch {
3318
3469
  return false;
3319
3470
  }
@@ -3322,7 +3473,7 @@ async function auditCommand(targetPath) {
3322
3473
  } else {
3323
3474
  checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
3324
3475
  }
3325
- const gitignorePath = path28.join(projectDir, ".gitignore");
3476
+ const gitignorePath = path29.join(projectDir, ".gitignore");
3326
3477
  const gitignore = readFileSafe(gitignorePath);
3327
3478
  if (gitignore) {
3328
3479
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
@@ -3338,7 +3489,7 @@ async function auditCommand(targetPath) {
3338
3489
  checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
3339
3490
  }
3340
3491
  }
3341
- const settingsPath = path28.join(projectDir, ".claude", "settings.json");
3492
+ const settingsPath = path29.join(projectDir, ".claude", "settings.json");
3342
3493
  const settings = readFileSafe(settingsPath);
3343
3494
  if (settings) {
3344
3495
  const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
@@ -3371,7 +3522,7 @@ async function auditCommand(targetPath) {
3371
3522
  }
3372
3523
 
3373
3524
  // src/commands/health.ts
3374
- import path29 from "path";
3525
+ import path30 from "path";
3375
3526
  import fs16 from "fs-extra";
3376
3527
  import chalk7 from "chalk";
3377
3528
  import ora7 from "ora";
@@ -3413,7 +3564,7 @@ function checkSetup(projectDir, config) {
3413
3564
  detail: `config v${config.version} \u2260 CLI v${VERSION} \u2014 run \`ai-kit update\``
3414
3565
  });
3415
3566
  }
3416
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3567
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3417
3568
  if (claudeMd && claudeMd.includes("AI-KIT:START")) {
3418
3569
  checks.push({ name: "CLAUDE.md", status: "pass", detail: "Present with markers" });
3419
3570
  } else if (claudeMd) {
@@ -3421,19 +3572,19 @@ function checkSetup(projectDir, config) {
3421
3572
  } else {
3422
3573
  checks.push({ name: "CLAUDE.md", status: "fail", detail: "Not found" });
3423
3574
  }
3424
- if (fileExists(path29.join(projectDir, GENERATED_FILES.cursorRules))) {
3575
+ if (fileExists(path30.join(projectDir, GENERATED_FILES.cursorRules))) {
3425
3576
  checks.push({ name: ".cursorrules", status: "pass", detail: "Present" });
3426
3577
  } else {
3427
3578
  checks.push({ name: ".cursorrules", status: "warn", detail: "Not generated" });
3428
3579
  }
3429
- const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
3580
+ const skillsDir = path30.join(projectDir, GENERATED_FILES.claudeSkills);
3430
3581
  if (dirExists(skillsDir)) {
3431
3582
  const count = config.commands.length;
3432
3583
  checks.push({ name: "Skills", status: "pass", detail: `${count} installed` });
3433
3584
  } else {
3434
3585
  checks.push({ name: "Skills", status: "warn", detail: "No skills directory" });
3435
3586
  }
3436
- const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
3587
+ const agentsDir = path30.join(projectDir, GENERATED_FILES.claudeAgents);
3437
3588
  if (dirExists(agentsDir)) {
3438
3589
  try {
3439
3590
  const agentFiles = fs16.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
@@ -3444,7 +3595,7 @@ function checkSetup(projectDir, config) {
3444
3595
  } else {
3445
3596
  checks.push({ name: "Agents", status: "warn", detail: "Not configured" });
3446
3597
  }
3447
- const settingsLocal = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3598
+ const settingsLocal = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
3448
3599
  if (settingsLocal && settingsLocal.includes('"hooks"')) {
3449
3600
  checks.push({ name: "Hooks", status: "pass", detail: "Configured" });
3450
3601
  } else {
@@ -3454,7 +3605,7 @@ function checkSetup(projectDir, config) {
3454
3605
  }
3455
3606
  function checkSecurity(projectDir) {
3456
3607
  const checks = [];
3457
- const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
3608
+ const claudeMd = readFileSafe(path30.join(projectDir, GENERATED_FILES.claudeMd));
3458
3609
  if (claudeMd) {
3459
3610
  const secretPatterns = [
3460
3611
  /(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
@@ -3469,7 +3620,7 @@ function checkSecurity(projectDir) {
3469
3620
  detail: hasSecrets ? "Potential secrets detected \u2014 remove immediately" : "Clean"
3470
3621
  });
3471
3622
  }
3472
- const gitignore = readFileSafe(path29.join(projectDir, ".gitignore"));
3623
+ const gitignore = readFileSafe(path30.join(projectDir, ".gitignore"));
3473
3624
  if (gitignore) {
3474
3625
  const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
3475
3626
  checks.push({
@@ -3478,7 +3629,7 @@ function checkSecurity(projectDir) {
3478
3629
  detail: envIgnored ? "Protected" : "NOT gitignored \u2014 add .env to .gitignore"
3479
3630
  });
3480
3631
  }
3481
- const settingsJson = readFileSafe(path29.join(projectDir, ".claude", "settings.json"));
3632
+ const settingsJson = readFileSafe(path30.join(projectDir, ".claude", "settings.json"));
3482
3633
  if (settingsJson) {
3483
3634
  const hasHardcoded = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settingsJson);
3484
3635
  checks.push({
@@ -3587,7 +3738,7 @@ function checkDocs(projectDir) {
3587
3738
  { name: "Time Log", path: "docs/time-log.md" }
3588
3739
  ];
3589
3740
  for (const doc of docsToCheck) {
3590
- const content = readFileSafe(path29.join(projectDir, doc.path));
3741
+ const content = readFileSafe(path30.join(projectDir, doc.path));
3591
3742
  if (content) {
3592
3743
  const hasEntries = content.includes("## 20") || content.split("---").length > 2;
3593
3744
  checks.push({
@@ -3602,8 +3753,8 @@ function checkDocs(projectDir) {
3602
3753
  return { title: "Documentation", checks };
3603
3754
  }
3604
3755
  async function healthCommand(targetPath) {
3605
- const projectDir = path29.resolve(targetPath || process.cwd());
3606
- const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
3756
+ const projectDir = path30.resolve(targetPath || process.cwd());
3757
+ const configPath = path30.join(projectDir, AI_KIT_CONFIG_FILE);
3607
3758
  console.log("");
3608
3759
  logSection("AI Kit \u2014 Project Health");
3609
3760
  console.log(chalk7.dim(` ${projectDir}`));
@@ -3687,7 +3838,7 @@ async function healthCommand(targetPath) {
3687
3838
  }
3688
3839
 
3689
3840
  // src/commands/patterns.ts
3690
- import path30 from "path";
3841
+ import path31 from "path";
3691
3842
  import fs17 from "fs-extra";
3692
3843
  import chalk8 from "chalk";
3693
3844
  import ora8 from "ora";
@@ -3774,7 +3925,7 @@ function walkTsFiles(dir, files) {
3774
3925
  try {
3775
3926
  const entries = fs17.readdirSync(dir, { withFileTypes: true });
3776
3927
  for (const entry of entries) {
3777
- const full = path30.join(dir, entry.name);
3928
+ const full = path31.join(dir, entry.name);
3778
3929
  if (entry.isDirectory()) {
3779
3930
  if (IGNORE_DIRS.includes(entry.name)) continue;
3780
3931
  walkTsFiles(full, files);
@@ -3786,8 +3937,8 @@ function walkTsFiles(dir, files) {
3786
3937
  }
3787
3938
  }
3788
3939
  async function patternsCommand(targetPath) {
3789
- const projectDir = path30.resolve(targetPath || process.cwd());
3790
- const configPath = path30.join(projectDir, AI_KIT_CONFIG_FILE);
3940
+ const projectDir = path31.resolve(targetPath || process.cwd());
3941
+ const configPath = path31.join(projectDir, AI_KIT_CONFIG_FILE);
3791
3942
  console.log("");
3792
3943
  logSection("AI Kit \u2014 Pattern Library");
3793
3944
  console.log(chalk8.dim(` ${projectDir}`));
@@ -3798,7 +3949,7 @@ async function patternsCommand(targetPath) {
3798
3949
  }
3799
3950
  const spinner = ora8("Scanning for code patterns...").start();
3800
3951
  const files = [];
3801
- const srcDir = path30.join(projectDir, "src");
3952
+ const srcDir = path31.join(projectDir, "src");
3802
3953
  if (dirExists(srcDir)) {
3803
3954
  walkTsFiles(srcDir, files);
3804
3955
  } else {
@@ -3820,7 +3971,7 @@ async function patternsCommand(targetPath) {
3820
3971
  if (pattern.regex.test(lines2[i])) {
3821
3972
  pattern.matches.push({
3822
3973
  pattern: pattern.label,
3823
- file: path30.relative(projectDir, file),
3974
+ file: path31.relative(projectDir, file),
3824
3975
  line: i + 1
3825
3976
  });
3826
3977
  }
@@ -3848,9 +3999,9 @@ async function patternsCommand(targetPath) {
3848
3999
  logInfo("No recognizable patterns found.");
3849
4000
  return;
3850
4001
  }
3851
- const outputDir = path30.join(projectDir, "ai-kit");
4002
+ const outputDir = path31.join(projectDir, "ai-kit");
3852
4003
  fs17.ensureDirSync(outputDir);
3853
- const outputPath = path30.join(outputDir, "patterns.md");
4004
+ const outputPath = path31.join(outputDir, "patterns.md");
3854
4005
  const lines = [
3855
4006
  "# Code Patterns",
3856
4007
  "",
@@ -3891,13 +4042,13 @@ async function patternsCommand(targetPath) {
3891
4042
  }
3892
4043
 
3893
4044
  // src/commands/dead-code.ts
3894
- import path32 from "path";
4045
+ import path33 from "path";
3895
4046
  import fs19 from "fs-extra";
3896
4047
  import chalk9 from "chalk";
3897
4048
  import ora9 from "ora";
3898
4049
 
3899
4050
  // src/scanner/components.ts
3900
- import path31 from "path";
4051
+ import path32 from "path";
3901
4052
  import fs18 from "fs-extra";
3902
4053
  var COMPONENT_DIRS = [
3903
4054
  "src/components",
@@ -3925,7 +4076,7 @@ function findComponentFiles(projectPath) {
3925
4076
  const files = [];
3926
4077
  const directories = /* @__PURE__ */ new Set();
3927
4078
  for (const dir of COMPONENT_DIRS) {
3928
- const fullDir = path31.join(projectPath, dir);
4079
+ const fullDir = path32.join(projectPath, dir);
3929
4080
  if (dirExists(fullDir)) {
3930
4081
  walkForComponents(fullDir, files, directories);
3931
4082
  }
@@ -3936,7 +4087,7 @@ function walkForComponents(dir, files, directories) {
3936
4087
  try {
3937
4088
  const entries = fs18.readdirSync(dir, { withFileTypes: true });
3938
4089
  for (const entry of entries) {
3939
- const full = path31.join(dir, entry.name);
4090
+ const full = path32.join(dir, entry.name);
3940
4091
  if (entry.isDirectory()) {
3941
4092
  if (IGNORE_PATTERNS.includes(entry.name)) continue;
3942
4093
  walkForComponents(full, files, directories);
@@ -3976,7 +4127,7 @@ function extractComponentName(filePath, content) {
3976
4127
  /export\s+(?:const|function)\s+(\w+)/
3977
4128
  );
3978
4129
  if (namedExport) return namedExport[1];
3979
- return path31.basename(filePath, ".tsx");
4130
+ return path32.basename(filePath, ".tsx");
3980
4131
  }
3981
4132
  function extractExportType(content) {
3982
4133
  const hasDefault = /export\s+default\s/.test(content);
@@ -4065,7 +4216,7 @@ function extractDependencies(content) {
4065
4216
  for (const match of imports) {
4066
4217
  const importPath = match[1];
4067
4218
  if (importPath.startsWith(".") || importPath.startsWith("..")) {
4068
- const basename = path31.basename(importPath).replace(/\.\w+$/, "");
4219
+ const basename = path32.basename(importPath).replace(/\.\w+$/, "");
4069
4220
  if (/^[A-Z]/.test(basename)) {
4070
4221
  deps.push(basename);
4071
4222
  }
@@ -4074,33 +4225,33 @@ function extractDependencies(content) {
4074
4225
  return deps;
4075
4226
  }
4076
4227
  function checkForTests(componentPath, componentName) {
4077
- const dir = path31.dirname(componentPath);
4078
- const base = path31.basename(componentPath, ".tsx");
4228
+ const dir = path32.dirname(componentPath);
4229
+ const base = path32.basename(componentPath, ".tsx");
4079
4230
  for (const suffix of TEST_SUFFIXES) {
4080
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4231
+ if (fs18.existsSync(path32.join(dir, base + suffix))) return true;
4081
4232
  }
4082
- const testsDir = path31.join(dir, "__tests__");
4233
+ const testsDir = path32.join(dir, "__tests__");
4083
4234
  if (dirExists(testsDir)) {
4084
4235
  for (const suffix of TEST_SUFFIXES) {
4085
- if (fs18.existsSync(path31.join(testsDir, base + suffix))) return true;
4086
- if (fs18.existsSync(path31.join(testsDir, componentName + suffix))) return true;
4236
+ if (fs18.existsSync(path32.join(testsDir, base + suffix))) return true;
4237
+ if (fs18.existsSync(path32.join(testsDir, componentName + suffix))) return true;
4087
4238
  }
4088
4239
  }
4089
4240
  return false;
4090
4241
  }
4091
4242
  function checkForStory(componentPath, componentName) {
4092
- const dir = path31.dirname(componentPath);
4093
- const base = path31.basename(componentPath, ".tsx");
4243
+ const dir = path32.dirname(componentPath);
4244
+ const base = path32.basename(componentPath, ".tsx");
4094
4245
  for (const suffix of STORY_SUFFIXES) {
4095
- if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
4096
- if (fs18.existsSync(path31.join(dir, componentName + suffix))) return true;
4246
+ if (fs18.existsSync(path32.join(dir, base + suffix))) return true;
4247
+ if (fs18.existsSync(path32.join(dir, componentName + suffix))) return true;
4097
4248
  }
4098
4249
  return false;
4099
4250
  }
4100
4251
  function checkForAiDoc(componentPath) {
4101
- const dir = path31.dirname(componentPath);
4102
- const base = path31.basename(componentPath, ".tsx");
4103
- return fs18.existsSync(path31.join(dir, `${base}.ai.md`)) || fs18.existsSync(path31.join(dir, "component.ai.md"));
4252
+ const dir = path32.dirname(componentPath);
4253
+ const base = path32.basename(componentPath, ".tsx");
4254
+ return fs18.existsSync(path32.join(dir, `${base}.ai.md`)) || fs18.existsSync(path32.join(dir, "component.ai.md"));
4104
4255
  }
4105
4256
  function categorizeComponent(relativePath) {
4106
4257
  const lower = relativePath.toLowerCase();
@@ -4118,7 +4269,7 @@ function parseComponent(filePath, projectPath) {
4118
4269
  const content = readFileSafe(filePath);
4119
4270
  if (!content) return null;
4120
4271
  const name = extractComponentName(filePath, content);
4121
- const relativePath = path31.relative(projectPath, filePath);
4272
+ const relativePath = path32.relative(projectPath, filePath);
4122
4273
  return {
4123
4274
  name,
4124
4275
  filePath,
@@ -4166,7 +4317,7 @@ function collectAllFiles(dir, files) {
4166
4317
  try {
4167
4318
  const entries = fs19.readdirSync(dir, { withFileTypes: true });
4168
4319
  for (const entry of entries) {
4169
- const full = path32.join(dir, entry.name);
4320
+ const full = path33.join(dir, entry.name);
4170
4321
  if (entry.isDirectory()) {
4171
4322
  if (IGNORE_DIRS2.includes(entry.name)) continue;
4172
4323
  collectAllFiles(full, files);
@@ -4178,12 +4329,12 @@ function collectAllFiles(dir, files) {
4178
4329
  }
4179
4330
  }
4180
4331
  function isTestOrStoryFile(filePath) {
4181
- const name = path32.basename(filePath).toLowerCase();
4332
+ const name = path33.basename(filePath).toLowerCase();
4182
4333
  return name.includes(".test.") || name.includes(".spec.") || name.includes(".stories.") || name.includes("__tests__") || name.includes("__mocks__");
4183
4334
  }
4184
4335
  async function deadCodeCommand(targetPath) {
4185
- const projectDir = path32.resolve(targetPath || process.cwd());
4186
- const configPath = path32.join(projectDir, AI_KIT_CONFIG_FILE);
4336
+ const projectDir = path33.resolve(targetPath || process.cwd());
4337
+ const configPath = path33.join(projectDir, AI_KIT_CONFIG_FILE);
4187
4338
  console.log("");
4188
4339
  logSection("AI Kit \u2014 Dead Code Report");
4189
4340
  console.log(chalk9.dim(` ${projectDir}`));
@@ -4200,7 +4351,7 @@ async function deadCodeCommand(targetPath) {
4200
4351
  }
4201
4352
  spinner.text = `Found ${scanResult.components.length} components. Checking imports...`;
4202
4353
  const allFiles = [];
4203
- const srcDir = path32.join(projectDir, "src");
4354
+ const srcDir = path33.join(projectDir, "src");
4204
4355
  if (fs19.existsSync(srcDir)) {
4205
4356
  collectAllFiles(srcDir, allFiles);
4206
4357
  } else {
@@ -4224,7 +4375,7 @@ async function deadCodeCommand(targetPath) {
4224
4375
  `(?:import|from)\\s+.*['"][^'"]*(?:/|\\b)${escapeRegex(component.name)}(?:[/'"]|\\b)`
4225
4376
  );
4226
4377
  for (const [file, content] of fileContents) {
4227
- if (path32.resolve(file) === path32.resolve(componentFile)) continue;
4378
+ if (path33.resolve(file) === path33.resolve(componentFile)) continue;
4228
4379
  if (namePattern.test(content)) {
4229
4380
  importCount++;
4230
4381
  if (isTestOrStoryFile(file)) {
@@ -4303,7 +4454,7 @@ function escapeRegex(str) {
4303
4454
  }
4304
4455
 
4305
4456
  // src/commands/drift.ts
4306
- import path33 from "path";
4457
+ import path34 from "path";
4307
4458
  import fs20 from "fs-extra";
4308
4459
  import chalk10 from "chalk";
4309
4460
  import ora10 from "ora";
@@ -4340,11 +4491,11 @@ function parseAiDocFrontmatter(content) {
4340
4491
  return { props, fields };
4341
4492
  }
4342
4493
  function findAiDocPath(componentPath) {
4343
- const dir = path33.dirname(componentPath);
4344
- const base = path33.basename(componentPath, ".tsx");
4494
+ const dir = path34.dirname(componentPath);
4495
+ const base = path34.basename(componentPath, ".tsx");
4345
4496
  const candidates = [
4346
- path33.join(dir, `${base}.ai.md`),
4347
- path33.join(dir, "component.ai.md")
4497
+ path34.join(dir, `${base}.ai.md`),
4498
+ path34.join(dir, "component.ai.md")
4348
4499
  ];
4349
4500
  for (const candidate of candidates) {
4350
4501
  if (fs20.existsSync(candidate)) return candidate;
@@ -4388,8 +4539,8 @@ function analyzeDrift(component, projectDir) {
4388
4539
  };
4389
4540
  }
4390
4541
  async function driftCommand(targetPath) {
4391
- const projectDir = path33.resolve(targetPath || process.cwd());
4392
- const configPath = path33.join(projectDir, AI_KIT_CONFIG_FILE);
4542
+ const projectDir = path34.resolve(targetPath || process.cwd());
4543
+ const configPath = path34.join(projectDir, AI_KIT_CONFIG_FILE);
4393
4544
  console.log("");
4394
4545
  logSection("AI Kit \u2014 Component Drift Detector");
4395
4546
  console.log(chalk10.dim(` ${projectDir}`));
@@ -4491,7 +4642,7 @@ async function driftCommand(targetPath) {
4491
4642
  }
4492
4643
 
4493
4644
  // src/commands/component-registry.ts
4494
- import path34 from "path";
4645
+ import path35 from "path";
4495
4646
  import fs22 from "fs-extra";
4496
4647
  import chalk11 from "chalk";
4497
4648
  import ora11 from "ora";
@@ -4516,12 +4667,12 @@ function calculateHealthScore(component) {
4516
4667
 
4517
4668
  // src/commands/component-registry.ts
4518
4669
  async function componentRegistryCommand(targetPath) {
4519
- const projectDir = path34.resolve(targetPath || process.cwd());
4670
+ const projectDir = path35.resolve(targetPath || process.cwd());
4520
4671
  console.log("");
4521
4672
  logSection("AI Kit \u2014 Component Registry");
4522
4673
  console.log(chalk11.dim(` ${projectDir}`));
4523
4674
  console.log("");
4524
- if (!fileExists(path34.join(projectDir, AI_KIT_CONFIG_FILE))) {
4675
+ if (!fileExists(path35.join(projectDir, AI_KIT_CONFIG_FILE))) {
4525
4676
  logWarning("ai-kit.config.json not found. Run `ai-kit init` first.");
4526
4677
  return;
4527
4678
  }
@@ -4555,20 +4706,20 @@ async function componentRegistryCommand(targetPath) {
4555
4706
  for (const entry of entries) {
4556
4707
  categories[entry.category] = (categories[entry.category] || 0) + 1;
4557
4708
  }
4558
- const pkgPath = path34.join(projectDir, "package.json");
4709
+ const pkgPath = path35.join(projectDir, "package.json");
4559
4710
  const pkg = fs22.readJsonSync(pkgPath, { throws: false }) || {};
4560
4711
  const registry = {
4561
4712
  version: "1.0.0",
4562
4713
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4563
- projectName: pkg.name || path34.basename(projectDir),
4714
+ projectName: pkg.name || path35.basename(projectDir),
4564
4715
  totalComponents: entries.length,
4565
4716
  categories,
4566
4717
  components: entries
4567
4718
  };
4568
- const outputPath = path34.join(projectDir, "ai-kit", "component-registry.json");
4569
- await fs22.ensureDir(path34.dirname(outputPath));
4719
+ const outputPath = path35.join(projectDir, "ai-kit", "component-registry.json");
4720
+ await fs22.ensureDir(path35.dirname(outputPath));
4570
4721
  await fs22.writeJson(outputPath, registry, { spaces: 2 });
4571
- const mdPath = path34.join(projectDir, "ai-kit", "component-registry.md");
4722
+ const mdPath = path35.join(projectDir, "ai-kit", "component-registry.md");
4572
4723
  const md = generateRegistryMarkdown(registry);
4573
4724
  await fs22.writeFile(mdPath, md, "utf-8");
4574
4725
  console.log("");
@@ -4628,6 +4779,220 @@ function generateRegistryMarkdown(registry) {
4628
4779
  return lines.join("\n");
4629
4780
  }
4630
4781
 
4782
+ // src/commands/rollback.ts
4783
+ import path36 from "path";
4784
+ import { select as select3, confirm as confirm4 } from "@inquirer/prompts";
4785
+ async function rollbackCommand(targetPath, latest) {
4786
+ const projectDir = path36.resolve(targetPath || process.cwd());
4787
+ const backups = await listBackups(projectDir);
4788
+ if (backups.length === 0) {
4789
+ logInfo("No backups found. Backups are created when you run `ai-kit update`.");
4790
+ return;
4791
+ }
4792
+ logSection("Available Backups");
4793
+ let selected;
4794
+ if (latest) {
4795
+ selected = backups[0];
4796
+ logInfo(`Restoring latest backup: ${selected}`);
4797
+ } else {
4798
+ for (const b of backups) {
4799
+ const label = b === backups[0] ? `${b} (latest)` : b;
4800
+ logInfo(label);
4801
+ }
4802
+ console.log("");
4803
+ selected = await select3({
4804
+ message: "Which backup do you want to restore?",
4805
+ choices: backups.map((b, i) => ({
4806
+ name: i === 0 ? `${b} (latest)` : b,
4807
+ value: b
4808
+ }))
4809
+ });
4810
+ }
4811
+ const proceed = await confirm4({
4812
+ message: `Restore configs from backup ${selected}? This will overwrite current files.`,
4813
+ default: true
4814
+ });
4815
+ if (!proceed) {
4816
+ logInfo("Cancelled.");
4817
+ return;
4818
+ }
4819
+ try {
4820
+ const restored = await restoreBackup(projectDir, selected);
4821
+ logSection("Restored Files");
4822
+ for (const file of restored) {
4823
+ logSuccess(`Restored: ${file}`);
4824
+ }
4825
+ console.log("");
4826
+ logInfo(`Configs restored from backup ${selected}`);
4827
+ } catch (err) {
4828
+ logError(`Failed to restore backup: ${String(err)}`);
4829
+ }
4830
+ }
4831
+
4832
+ // src/commands/migrate.ts
4833
+ import path37 from "path";
4834
+ import fs23 from "fs-extra";
4835
+ import { confirm as confirm5 } from "@inquirer/prompts";
4836
+ import ora12 from "ora";
4837
+ var AI_KIT_START2 = "<!-- AI-KIT:START -->";
4838
+ var AI_KIT_END2 = "<!-- AI-KIT:END -->";
4839
+ var CUSTOM_START = "<!-- CUSTOM RULES (preserved by ai-kit) -->";
4840
+ var CUSTOM_END = "<!-- /CUSTOM RULES -->";
4841
+ async function migrateCommand(targetPath, opts) {
4842
+ const projectDir = path37.resolve(targetPath || process.cwd());
4843
+ const configPath = path37.join(projectDir, AI_KIT_CONFIG_FILE);
4844
+ const dryRun = opts?.dryRun ?? false;
4845
+ logSection("AI Kit \u2014 Migrate Existing Project");
4846
+ const existingConfig = readJsonSafe(configPath);
4847
+ if (existingConfig) {
4848
+ logInfo("This project is already managed by ai-kit.");
4849
+ logInfo("Use `ai-kit update` to refresh configs.");
4850
+ return;
4851
+ }
4852
+ const claudeMdPath = path37.join(projectDir, GENERATED_FILES.claudeMd);
4853
+ const cursorRulesPath = path37.join(projectDir, GENERATED_FILES.cursorRules);
4854
+ const existingClaudeMd = readFileSafe(claudeMdPath);
4855
+ const existingCursorRules = readFileSafe(cursorRulesPath);
4856
+ if (!existingClaudeMd && !existingCursorRules) {
4857
+ logInfo("No existing AI config files found (CLAUDE.md or .cursorrules).");
4858
+ logInfo("Use `ai-kit init` for a fresh setup.");
4859
+ return;
4860
+ }
4861
+ const spinner = ora12("Scanning project...").start();
4862
+ const scan = await scanProject(projectDir);
4863
+ spinner.succeed("Project scanned");
4864
+ logSection("Detected Stack");
4865
+ const frameworkLabel = scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""} (${scan.routerType || "unknown"} router)`.trim() : scan.framework;
4866
+ logInfo(`Framework: ${frameworkLabel}`);
4867
+ logInfo(`CMS: ${scan.cms === "none" ? "None" : scan.cms}`);
4868
+ logInfo(`Styling: ${scan.styling.join(", ") || "None"}`);
4869
+ logInfo(`TypeScript: ${scan.typescript ? "Yes" : "No"}`);
4870
+ logInfo(`Package Manager: ${scan.packageManager}`);
4871
+ const customSections = existingClaudeMd ? parseSections(existingClaudeMd) : [];
4872
+ const customFragments = loadCustomFragments(projectDir);
4873
+ const strictness = "standard";
4874
+ const hookProfile = "standard";
4875
+ const tools = {
4876
+ claude: true,
4877
+ cursor: !!existingCursorRules || fileExists(path37.join(projectDir, ".cursor"))
4878
+ };
4879
+ const aiKitClaudeMd = generateClaudeMd(scan, { strictness, customFragments });
4880
+ const aiKitSections = parseSections(
4881
+ aiKitClaudeMd.replace(AI_KIT_START2, "").replace(AI_KIT_END2, "")
4882
+ );
4883
+ logSection("Migration Plan");
4884
+ if (customSections.length > 0) {
4885
+ logInfo(`Your existing CLAUDE.md has ${customSections.length} section(s):`);
4886
+ for (const s of customSections) {
4887
+ logSuccess(` KEEP: "${s.heading}"`);
4888
+ }
4889
+ }
4890
+ logInfo(`ai-kit will generate ${aiKitSections.length} section(s) for your stack:`);
4891
+ for (const s of aiKitSections) {
4892
+ logInfo(` + ADD: "${s.heading}"`);
4893
+ }
4894
+ console.log("");
4895
+ logInfo("Your custom sections will be placed at the TOP of the file.");
4896
+ logInfo("ai-kit sections will be wrapped in AI-KIT markers below.");
4897
+ logInfo("Future `ai-kit update` will only touch the marked section.");
4898
+ if (dryRun) {
4899
+ console.log("");
4900
+ logInfo("Dry run \u2014 no files were modified.");
4901
+ return;
4902
+ }
4903
+ console.log("");
4904
+ const proceed = await confirm5({
4905
+ message: "Apply this migration?",
4906
+ default: true
4907
+ });
4908
+ if (!proceed) {
4909
+ logInfo("Cancelled.");
4910
+ return;
4911
+ }
4912
+ const filesToBackup = [
4913
+ GENERATED_FILES.claudeMd,
4914
+ GENERATED_FILES.cursorRules
4915
+ ].filter((f) => fileExists(path37.join(projectDir, f)));
4916
+ if (filesToBackup.length > 0) {
4917
+ const backupPath = await backupFiles(projectDir, filesToBackup);
4918
+ if (backupPath) {
4919
+ logSuccess(
4920
+ `Backed up existing files to ${path37.relative(projectDir, backupPath)}`
4921
+ );
4922
+ }
4923
+ }
4924
+ logSection("Migrating");
4925
+ if (tools.claude) {
4926
+ const customBlock = customSections.length > 0 ? `${CUSTOM_START}
4927
+
4928
+ ${customSections.map((s) => s.raw).join("\n\n")}
4929
+
4930
+ ${CUSTOM_END}
4931
+
4932
+ ` : "";
4933
+ const merged = `${customBlock}${aiKitClaudeMd}`;
4934
+ await fs23.writeFile(claudeMdPath, merged, "utf-8");
4935
+ logSuccess("CLAUDE.md migrated (custom rules preserved at top)");
4936
+ }
4937
+ if (tools.cursor) {
4938
+ const cursorContent = generateCursorRules(scan, {
4939
+ strictness,
4940
+ customFragments
4941
+ });
4942
+ await fs23.writeFile(cursorRulesPath, cursorContent, "utf-8");
4943
+ logSuccess(".cursorrules generated");
4944
+ const mdcDir = path37.join(projectDir, GENERATED_FILES.cursorMdcDir);
4945
+ await fs23.ensureDir(mdcDir);
4946
+ const mdcFiles = generateMdcFiles(scan);
4947
+ for (const mdc of mdcFiles) {
4948
+ await fs23.writeFile(path37.join(mdcDir, mdc.filename), mdc.content, "utf-8");
4949
+ }
4950
+ logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files generated`);
4951
+ }
4952
+ const commands = await copySkills(projectDir);
4953
+ logSuccess(`${commands.length} skills copied`);
4954
+ const agents = await copyAgents(projectDir, scan);
4955
+ logSuccess(`${agents.length} agents copied`);
4956
+ const contexts = await copyContexts(projectDir);
4957
+ logSuccess(`${contexts.length} context modes copied`);
4958
+ const settingsLocalPath = path37.join(
4959
+ projectDir,
4960
+ GENERATED_FILES.claudeSettingsLocal
4961
+ );
4962
+ const settingsLocal = generateSettingsLocal(scan, hookProfile);
4963
+ await fs23.ensureDir(path37.dirname(settingsLocalPath));
4964
+ await fs23.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
4965
+ logSuccess(`Hooks configured (profile: ${hookProfile})`);
4966
+ const guides = await copyGuides(projectDir);
4967
+ logSuccess(`${guides.length} guides copied`);
4968
+ const templates = [];
4969
+ if (tools.claude) templates.push("CLAUDE.md");
4970
+ if (tools.cursor) templates.push(".cursorrules");
4971
+ const config = generateConfig(scan, templates, commands, guides, {
4972
+ strictness,
4973
+ customFragments,
4974
+ agents,
4975
+ contexts,
4976
+ hooks: true,
4977
+ hookProfile,
4978
+ tools
4979
+ });
4980
+ await fs23.writeJson(configPath, config, { spaces: 2 });
4981
+ logSuccess("ai-kit.config.json created");
4982
+ logSection("Migration Complete");
4983
+ if (customSections.length > 0) {
4984
+ logInfo(
4985
+ `${customSections.length} custom section(s) preserved in CLAUDE.md`
4986
+ );
4987
+ }
4988
+ logInfo("This project is now managed by ai-kit.");
4989
+ logInfo("Run `ai-kit update` anytime to refresh generated sections.");
4990
+ logInfo("Your custom rules above the AI-KIT markers will never be touched.");
4991
+ if (filesToBackup.length > 0) {
4992
+ logInfo("Rollback available: `ai-kit rollback --latest`");
4993
+ }
4994
+ }
4995
+
4631
4996
  // src/cli/error-handler.ts
4632
4997
  function withErrorHandler(fn) {
4633
4998
  const wrapped = async (...args) => {
@@ -4688,6 +5053,12 @@ function registerCommands(program2) {
4688
5053
  program2.command("component-registry").description("Generate a component registry for AI agent discovery").argument("[path]", "Project directory (defaults to current directory)").action(withErrorHandler(async (targetPath) => {
4689
5054
  await componentRegistryCommand(targetPath);
4690
5055
  }));
5056
+ 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) => {
5057
+ await rollbackCommand(targetPath, opts?.latest);
5058
+ }));
5059
+ 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) => {
5060
+ await migrateCommand(targetPath, opts);
5061
+ }));
4691
5062
  }
4692
5063
 
4693
5064
  // src/index.ts