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