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