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