@mikulgohil/ai-kit 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +795 -266
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
- package/templates/claude-md/static-site.md +32 -0
- package/templates/cursorrules/static-site.md +22 -0
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
|
|
|
15
15
|
var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
|
|
16
16
|
var AGENTS_DIR = path.join(PACKAGE_ROOT, "agents");
|
|
17
17
|
var CONTEXTS_DIR = path.join(PACKAGE_ROOT, "contexts");
|
|
18
|
-
var VERSION = "1.
|
|
18
|
+
var VERSION = "1.5.0";
|
|
19
19
|
var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
|
|
20
20
|
var GENERATED_FILES = {
|
|
21
21
|
claudeMd: "CLAUDE.md",
|
|
@@ -37,17 +37,18 @@ var TEMPLATE_FRAGMENTS = [
|
|
|
37
37
|
"tailwind",
|
|
38
38
|
"typescript",
|
|
39
39
|
"monorepo",
|
|
40
|
-
"figma"
|
|
40
|
+
"figma",
|
|
41
|
+
"static-site"
|
|
41
42
|
];
|
|
42
43
|
|
|
43
44
|
// src/commands/init.ts
|
|
44
|
-
import
|
|
45
|
-
import
|
|
46
|
-
import { select
|
|
45
|
+
import path20 from "path";
|
|
46
|
+
import fs9 from "fs-extra";
|
|
47
|
+
import { select } from "@inquirer/prompts";
|
|
47
48
|
import ora from "ora";
|
|
48
49
|
|
|
49
50
|
// src/scanner/index.ts
|
|
50
|
-
import
|
|
51
|
+
import path13 from "path";
|
|
51
52
|
|
|
52
53
|
// src/utils.ts
|
|
53
54
|
import fs from "fs-extra";
|
|
@@ -423,12 +424,289 @@ function detectMcpServers(projectPath) {
|
|
|
423
424
|
};
|
|
424
425
|
}
|
|
425
426
|
|
|
427
|
+
// src/scanner/design-tokens.ts
|
|
428
|
+
import path10 from "path";
|
|
429
|
+
function detectDesignTokens2(projectPath) {
|
|
430
|
+
const globalsCss = readFileSafe(
|
|
431
|
+
path10.join(projectPath, "src", "app", "globals.css")
|
|
432
|
+
);
|
|
433
|
+
if (globalsCss && globalsCss.includes("@theme")) {
|
|
434
|
+
return parseThemeInline(globalsCss);
|
|
435
|
+
}
|
|
436
|
+
const twConfigPaths = [
|
|
437
|
+
path10.join(projectPath, "tailwind.config.ts"),
|
|
438
|
+
path10.join(projectPath, "tailwind.config.js"),
|
|
439
|
+
path10.join(projectPath, "tailwind.config.mjs")
|
|
440
|
+
];
|
|
441
|
+
for (const twPath of twConfigPaths) {
|
|
442
|
+
const content = readFileSafe(twPath);
|
|
443
|
+
if (content) {
|
|
444
|
+
return parseTailwindConfig(content);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (globalsCss) {
|
|
448
|
+
return parseCssVariables(globalsCss);
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
detected: false,
|
|
452
|
+
colors: [],
|
|
453
|
+
spacing: [],
|
|
454
|
+
fonts: [],
|
|
455
|
+
breakpoints: [],
|
|
456
|
+
source: "none"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function parseThemeInline(css) {
|
|
460
|
+
const colors = [];
|
|
461
|
+
const spacing = [];
|
|
462
|
+
const fonts = [];
|
|
463
|
+
const breakpoints = [];
|
|
464
|
+
const themeMatch = css.match(/@theme\s*\{([\s\S]*?)\}/);
|
|
465
|
+
if (!themeMatch) {
|
|
466
|
+
return { detected: false, colors: [], spacing: [], fonts: [], breakpoints: [], source: "none" };
|
|
467
|
+
}
|
|
468
|
+
const themeBody = themeMatch[1];
|
|
469
|
+
const lines = themeBody.split("\n");
|
|
470
|
+
for (const line of lines) {
|
|
471
|
+
const trimmed = line.trim();
|
|
472
|
+
if (!trimmed || trimmed.startsWith("/*")) continue;
|
|
473
|
+
const colorMatch = trimmed.match(/^--color-([^:]+):/);
|
|
474
|
+
if (colorMatch) {
|
|
475
|
+
colors.push(colorMatch[1].trim());
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const spacingMatch = trimmed.match(/^--spacing-([^:]+):/);
|
|
479
|
+
if (spacingMatch) {
|
|
480
|
+
spacing.push(spacingMatch[1].trim());
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const fontMatch = trimmed.match(/^--font-([^:]+):/);
|
|
484
|
+
if (fontMatch) {
|
|
485
|
+
fonts.push(fontMatch[1].trim());
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const bpMatch = trimmed.match(/^--breakpoint-([^:]+):/);
|
|
489
|
+
if (bpMatch) {
|
|
490
|
+
breakpoints.push(bpMatch[1].trim());
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
detected: colors.length > 0 || fonts.length > 0,
|
|
496
|
+
colors,
|
|
497
|
+
spacing,
|
|
498
|
+
fonts,
|
|
499
|
+
breakpoints,
|
|
500
|
+
source: "theme-inline"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
function parseTailwindConfig(content) {
|
|
504
|
+
const colors = [];
|
|
505
|
+
const spacing = [];
|
|
506
|
+
const fonts = [];
|
|
507
|
+
const breakpoints = [];
|
|
508
|
+
const colorBlock = content.match(/colors\s*:\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/);
|
|
509
|
+
if (colorBlock) {
|
|
510
|
+
const colorKeys = colorBlock[1].matchAll(/['"]?(\w[\w-]*)['"]?\s*:/g);
|
|
511
|
+
for (const match of colorKeys) {
|
|
512
|
+
if (!["DEFAULT", "transparent", "current", "inherit"].includes(match[1])) {
|
|
513
|
+
colors.push(match[1]);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const fontBlock = content.match(/fontFamily\s*:\s*\{([^}]*)\}/);
|
|
518
|
+
if (fontBlock) {
|
|
519
|
+
const fontKeys = fontBlock[1].matchAll(/['"]?(\w[\w-]*)['"]?\s*:/g);
|
|
520
|
+
for (const match of fontKeys) {
|
|
521
|
+
fonts.push(match[1]);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const spacingBlock = content.match(/spacing\s*:\s*\{([^}]*)\}/);
|
|
525
|
+
if (spacingBlock) {
|
|
526
|
+
const spacingKeys = spacingBlock[1].matchAll(/['"]?(\w[\w-]*)['"]?\s*:/g);
|
|
527
|
+
for (const match of spacingKeys) {
|
|
528
|
+
spacing.push(match[1]);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const screensBlock = content.match(/screens\s*:\s*\{([^}]*)\}/);
|
|
532
|
+
if (screensBlock) {
|
|
533
|
+
const bpKeys = screensBlock[1].matchAll(/['"]?(\w[\w-]*)['"]?\s*:/g);
|
|
534
|
+
for (const match of bpKeys) {
|
|
535
|
+
breakpoints.push(match[1]);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
detected: colors.length > 0 || fonts.length > 0,
|
|
540
|
+
colors: [...new Set(colors)],
|
|
541
|
+
spacing: [...new Set(spacing)],
|
|
542
|
+
fonts: [...new Set(fonts)],
|
|
543
|
+
breakpoints: [...new Set(breakpoints)],
|
|
544
|
+
source: "tailwind-config"
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function parseCssVariables(css) {
|
|
548
|
+
const colors = [];
|
|
549
|
+
const spacing = [];
|
|
550
|
+
const fonts = [];
|
|
551
|
+
const varMatches = css.matchAll(/--([^:]+)\s*:/g);
|
|
552
|
+
for (const match of varMatches) {
|
|
553
|
+
const name = match[1].trim();
|
|
554
|
+
if (name.startsWith("color-") || name.includes("primary") || name.includes("secondary") || name.includes("accent") || name.includes("background") || name.includes("foreground")) {
|
|
555
|
+
colors.push(name);
|
|
556
|
+
} else if (name.startsWith("spacing-") || name.startsWith("space-") || name.includes("gap")) {
|
|
557
|
+
spacing.push(name);
|
|
558
|
+
} else if (name.startsWith("font-") || name.includes("family") || name.includes("text-")) {
|
|
559
|
+
fonts.push(name);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return {
|
|
563
|
+
detected: colors.length > 0,
|
|
564
|
+
colors: [...new Set(colors)],
|
|
565
|
+
spacing: [...new Set(spacing)],
|
|
566
|
+
fonts: [...new Set(fonts)],
|
|
567
|
+
breakpoints: [],
|
|
568
|
+
source: colors.length > 0 ? "css-variables" : "none"
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/scanner/static-site.ts
|
|
573
|
+
import path11 from "path";
|
|
574
|
+
import fs2 from "fs-extra";
|
|
575
|
+
function detectStaticSite(projectPath, pkg) {
|
|
576
|
+
const hasStaticExport = checkStaticExport(projectPath);
|
|
577
|
+
const hasGenerateStaticParams = checkGenerateStaticParams(projectPath);
|
|
578
|
+
const hasRevalidate = checkRevalidatePatterns(projectPath);
|
|
579
|
+
const hasServerActions = checkServerActions(projectPath);
|
|
580
|
+
const hasApiRoutes = checkApiRoutes(projectPath);
|
|
581
|
+
let outputMode;
|
|
582
|
+
if (hasStaticExport) {
|
|
583
|
+
outputMode = "export";
|
|
584
|
+
} else if (hasGenerateStaticParams && !hasServerActions && !hasApiRoutes) {
|
|
585
|
+
outputMode = hasRevalidate ? "isr" : "export";
|
|
586
|
+
} else if (hasServerActions || hasApiRoutes) {
|
|
587
|
+
outputMode = hasGenerateStaticParams ? "hybrid" : "ssr";
|
|
588
|
+
} else {
|
|
589
|
+
outputMode = "ssr";
|
|
590
|
+
}
|
|
591
|
+
const isStatic = outputMode === "export" || outputMode === "isr";
|
|
592
|
+
return {
|
|
593
|
+
isStatic,
|
|
594
|
+
outputMode,
|
|
595
|
+
hasGenerateStaticParams,
|
|
596
|
+
hasRevalidate,
|
|
597
|
+
hasStaticExport
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function checkStaticExport(projectPath) {
|
|
601
|
+
const configPaths = [
|
|
602
|
+
path11.join(projectPath, "next.config.js"),
|
|
603
|
+
path11.join(projectPath, "next.config.ts"),
|
|
604
|
+
path11.join(projectPath, "next.config.mjs")
|
|
605
|
+
];
|
|
606
|
+
for (const configPath of configPaths) {
|
|
607
|
+
const content = readFileSafe(configPath);
|
|
608
|
+
if (content && /output\s*:\s*['"]export['"]/.test(content)) {
|
|
609
|
+
return true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return false;
|
|
613
|
+
}
|
|
614
|
+
function checkGenerateStaticParams(projectPath) {
|
|
615
|
+
const appDirs = [
|
|
616
|
+
path11.join(projectPath, "app"),
|
|
617
|
+
path11.join(projectPath, "src", "app")
|
|
618
|
+
];
|
|
619
|
+
for (const appDir of appDirs) {
|
|
620
|
+
if (dirExists(appDir) && hasPatternInDir(appDir, /generateStaticParams/)) {
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const pagesDirs = [
|
|
625
|
+
path11.join(projectPath, "pages"),
|
|
626
|
+
path11.join(projectPath, "src", "pages")
|
|
627
|
+
];
|
|
628
|
+
for (const pagesDir of pagesDirs) {
|
|
629
|
+
if (dirExists(pagesDir) && hasPatternInDir(pagesDir, /getStaticPaths/)) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
function checkRevalidatePatterns(projectPath) {
|
|
636
|
+
const appDirs = [
|
|
637
|
+
path11.join(projectPath, "app"),
|
|
638
|
+
path11.join(projectPath, "src", "app")
|
|
639
|
+
];
|
|
640
|
+
for (const appDir of appDirs) {
|
|
641
|
+
if (dirExists(appDir) && hasPatternInDir(appDir, /revalidate\s*[:=]/)) {
|
|
642
|
+
return true;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
function checkServerActions(projectPath) {
|
|
648
|
+
const srcDirs = [
|
|
649
|
+
path11.join(projectPath, "app"),
|
|
650
|
+
path11.join(projectPath, "src")
|
|
651
|
+
];
|
|
652
|
+
for (const srcDir of srcDirs) {
|
|
653
|
+
if (dirExists(srcDir) && hasPatternInDir(srcDir, /['"]use server['"]/)) {
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
function checkApiRoutes(projectPath) {
|
|
660
|
+
const appApiDirs = [
|
|
661
|
+
path11.join(projectPath, "app", "api"),
|
|
662
|
+
path11.join(projectPath, "src", "app", "api")
|
|
663
|
+
];
|
|
664
|
+
for (const apiDir of appApiDirs) {
|
|
665
|
+
if (dirExists(apiDir)) return true;
|
|
666
|
+
}
|
|
667
|
+
const pagesApiDirs = [
|
|
668
|
+
path11.join(projectPath, "pages", "api"),
|
|
669
|
+
path11.join(projectPath, "src", "pages", "api")
|
|
670
|
+
];
|
|
671
|
+
for (const apiDir of pagesApiDirs) {
|
|
672
|
+
if (dirExists(apiDir)) return true;
|
|
673
|
+
}
|
|
674
|
+
return false;
|
|
675
|
+
}
|
|
676
|
+
var SCAN_IGNORE = ["node_modules", ".next", ".git", "dist", "build", ".turbo"];
|
|
677
|
+
function hasPatternInDir(dir, pattern, depth = 0) {
|
|
678
|
+
if (depth > 5) return false;
|
|
679
|
+
try {
|
|
680
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
681
|
+
for (const entry of entries) {
|
|
682
|
+
if (SCAN_IGNORE.includes(entry.name)) continue;
|
|
683
|
+
const full = path11.join(dir, entry.name);
|
|
684
|
+
if (entry.isDirectory()) {
|
|
685
|
+
if (hasPatternInDir(full, pattern, depth + 1)) return true;
|
|
686
|
+
} else if (/\.(tsx?|jsx?|mjs)$/.test(entry.name)) {
|
|
687
|
+
const content = readFileSafe(full);
|
|
688
|
+
if (content && pattern.test(content)) return true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/scanner/aiignore.ts
|
|
697
|
+
import path12 from "path";
|
|
698
|
+
function loadAiIgnorePatterns(projectPath) {
|
|
699
|
+
const content = readFileSafe(path12.join(projectPath, ".aiignore"));
|
|
700
|
+
if (!content) return [];
|
|
701
|
+
return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
702
|
+
}
|
|
703
|
+
|
|
426
704
|
// src/scanner/index.ts
|
|
427
705
|
async function scanProject(projectPath) {
|
|
428
|
-
const pkgPath =
|
|
706
|
+
const pkgPath = path13.join(projectPath, "package.json");
|
|
429
707
|
const pkg = readJsonSafe(pkgPath) || {};
|
|
430
708
|
const scripts = pkg.scripts || {};
|
|
431
|
-
const projectName = pkg.name ||
|
|
709
|
+
const projectName = pkg.name || path13.basename(projectPath);
|
|
432
710
|
const nextjsResult = detectNextjs(projectPath, pkg);
|
|
433
711
|
const sitecoreResult = detectSitecore(pkg);
|
|
434
712
|
const stylingResult = detectStyling(projectPath, pkg);
|
|
@@ -438,6 +716,9 @@ async function scanProject(projectPath) {
|
|
|
438
716
|
const figmaResult = detectFigma(projectPath, pkg);
|
|
439
717
|
const toolsResult = detectTools(projectPath, pkg);
|
|
440
718
|
const mcpResult = detectMcpServers(projectPath);
|
|
719
|
+
const designTokensResult = detectDesignTokens2(projectPath);
|
|
720
|
+
const staticSiteResult = detectStaticSite(projectPath, pkg);
|
|
721
|
+
const aiIgnorePatterns = loadAiIgnorePatterns(projectPath);
|
|
441
722
|
const figmaDetected = figmaResult.figmaMcp || figmaResult.figmaCodeCli || figmaResult.designTokens;
|
|
442
723
|
return {
|
|
443
724
|
...nextjsResult,
|
|
@@ -449,6 +730,9 @@ async function scanProject(projectPath) {
|
|
|
449
730
|
detected: figmaDetected,
|
|
450
731
|
...figmaResult
|
|
451
732
|
},
|
|
733
|
+
designTokens: designTokensResult,
|
|
734
|
+
staticSite: staticSiteResult,
|
|
735
|
+
aiIgnorePatterns,
|
|
452
736
|
tools: toolsResult,
|
|
453
737
|
mcpServers: mcpResult,
|
|
454
738
|
packageManager,
|
|
@@ -459,10 +743,10 @@ async function scanProject(projectPath) {
|
|
|
459
743
|
}
|
|
460
744
|
|
|
461
745
|
// src/generator/assembler.ts
|
|
462
|
-
import
|
|
463
|
-
import
|
|
746
|
+
import path14 from "path";
|
|
747
|
+
import fs3 from "fs-extra";
|
|
464
748
|
function readTemplate(relativePath) {
|
|
465
|
-
const fullPath =
|
|
749
|
+
const fullPath = path14.join(TEMPLATES_DIR, relativePath);
|
|
466
750
|
const content = readFileSafe(fullPath);
|
|
467
751
|
if (!content) {
|
|
468
752
|
throw new Error(`Template not found: ${relativePath}`);
|
|
@@ -470,12 +754,12 @@ function readTemplate(relativePath) {
|
|
|
470
754
|
return content.trim();
|
|
471
755
|
}
|
|
472
756
|
function loadCustomFragments(projectPath) {
|
|
473
|
-
const customDir =
|
|
474
|
-
if (!
|
|
757
|
+
const customDir = path14.join(projectPath, ".ai-kit", "fragments");
|
|
758
|
+
if (!fs3.existsSync(customDir)) return [];
|
|
475
759
|
try {
|
|
476
|
-
const files =
|
|
760
|
+
const files = fs3.readdirSync(customDir).filter((f) => f.endsWith(".md"));
|
|
477
761
|
return files.map((f) => {
|
|
478
|
-
const content =
|
|
762
|
+
const content = fs3.readFileSync(path14.join(customDir, f), "utf-8");
|
|
479
763
|
return content.trim();
|
|
480
764
|
}).filter(Boolean);
|
|
481
765
|
} catch {
|
|
@@ -548,6 +832,9 @@ function selectFragments(scan) {
|
|
|
548
832
|
if (scan.figma?.detected) {
|
|
549
833
|
fragments.push("figma");
|
|
550
834
|
}
|
|
835
|
+
if (scan.staticSite?.isStatic) {
|
|
836
|
+
fragments.push("static-site");
|
|
837
|
+
}
|
|
551
838
|
return fragments;
|
|
552
839
|
}
|
|
553
840
|
function generateClaudeMd(scan, options) {
|
|
@@ -587,13 +874,34 @@ function buildVariables(scan) {
|
|
|
587
874
|
const scripts = Object.entries(scan.scripts).filter(
|
|
588
875
|
([key]) => ["dev", "build", "start", "lint", "test", "type-check", "typecheck"].includes(key)
|
|
589
876
|
).map(([key, value]) => `- \`${scan.packageManager} run ${key}\` \u2192 \`${value}\``).join("\n");
|
|
877
|
+
let designTokenSummary = "";
|
|
878
|
+
if (scan.designTokens?.detected) {
|
|
879
|
+
const parts = [];
|
|
880
|
+
if (scan.designTokens.colors.length > 0) {
|
|
881
|
+
parts.push(`Colors: ${scan.designTokens.colors.slice(0, 15).join(", ")}${scan.designTokens.colors.length > 15 ? ` (+${scan.designTokens.colors.length - 15} more)` : ""}`);
|
|
882
|
+
}
|
|
883
|
+
if (scan.designTokens.fonts.length > 0) {
|
|
884
|
+
parts.push(`Fonts: ${scan.designTokens.fonts.join(", ")}`);
|
|
885
|
+
}
|
|
886
|
+
if (scan.designTokens.spacing.length > 0) {
|
|
887
|
+
parts.push(`Spacing: ${scan.designTokens.spacing.slice(0, 10).join(", ")}${scan.designTokens.spacing.length > 10 ? ` (+${scan.designTokens.spacing.length - 10} more)` : ""}`);
|
|
888
|
+
}
|
|
889
|
+
if (scan.designTokens.breakpoints.length > 0) {
|
|
890
|
+
parts.push(`Breakpoints: ${scan.designTokens.breakpoints.join(", ")}`);
|
|
891
|
+
}
|
|
892
|
+
designTokenSummary = parts.map((p) => `- ${p}`).join("\n");
|
|
893
|
+
}
|
|
894
|
+
const aiIgnoreSummary = scan.aiIgnorePatterns.length > 0 ? scan.aiIgnorePatterns.map((p) => `- \`${p}\``).join("\n") : "";
|
|
590
895
|
return {
|
|
591
896
|
projectName: scan.projectName,
|
|
592
897
|
techStack: techStack.join(" \xB7 "),
|
|
593
898
|
packageManager: scan.packageManager,
|
|
594
899
|
routerType: scan.routerType || "unknown",
|
|
595
900
|
scripts: scripts || "- No scripts detected",
|
|
596
|
-
framework: scan.framework
|
|
901
|
+
framework: scan.framework,
|
|
902
|
+
designTokens: designTokenSummary || "- No design tokens detected",
|
|
903
|
+
aiIgnorePatterns: aiIgnoreSummary || "- No .aiignore file",
|
|
904
|
+
outputMode: scan.staticSite?.outputMode || "ssr"
|
|
597
905
|
};
|
|
598
906
|
}
|
|
599
907
|
|
|
@@ -665,6 +973,10 @@ var MDC_CONFIG = {
|
|
|
665
973
|
figma: {
|
|
666
974
|
description: "Figma-to-code workflow and design token rules",
|
|
667
975
|
globs: "src/components/**/*.{ts,tsx}, tokens/**/*"
|
|
976
|
+
},
|
|
977
|
+
"static-site": {
|
|
978
|
+
description: "Static site generation (SSG/ISR) rules and patterns",
|
|
979
|
+
globs: "app/**/*.{ts,tsx}, src/app/**/*.{ts,tsx}, next.config.*"
|
|
668
980
|
}
|
|
669
981
|
};
|
|
670
982
|
function generateMdcFiles(scan) {
|
|
@@ -709,7 +1021,8 @@ function generateConfig(scan, templates, commands, guides, options) {
|
|
|
709
1021
|
hooks: options?.hooks || false,
|
|
710
1022
|
hookProfile: options?.hookProfile || "standard",
|
|
711
1023
|
strictness: options?.strictness || "standard",
|
|
712
|
-
customFragments: options?.customFragments || []
|
|
1024
|
+
customFragments: options?.customFragments || [],
|
|
1025
|
+
tools: options?.tools || { claude: true, cursor: true }
|
|
713
1026
|
};
|
|
714
1027
|
}
|
|
715
1028
|
|
|
@@ -916,8 +1229,8 @@ function generateSettingsLocal(scan, profile = "standard") {
|
|
|
916
1229
|
}
|
|
917
1230
|
|
|
918
1231
|
// src/copier/skills.ts
|
|
919
|
-
import
|
|
920
|
-
import
|
|
1232
|
+
import path15 from "path";
|
|
1233
|
+
import fs4 from "fs-extra";
|
|
921
1234
|
var AVAILABLE_SKILLS = [
|
|
922
1235
|
"prompt-help",
|
|
923
1236
|
"review",
|
|
@@ -1015,35 +1328,35 @@ var SKILL_DESCRIPTIONS = {
|
|
|
1015
1328
|
async function copySkills(targetDir) {
|
|
1016
1329
|
const copied = [];
|
|
1017
1330
|
for (const skill of AVAILABLE_SKILLS) {
|
|
1018
|
-
const src =
|
|
1019
|
-
if (!await
|
|
1020
|
-
const content = await
|
|
1331
|
+
const src = path15.join(COMMANDS_DIR, `${skill}.md`);
|
|
1332
|
+
if (!await fs4.pathExists(src)) continue;
|
|
1333
|
+
const content = await fs4.readFile(src, "utf-8");
|
|
1021
1334
|
const description = SKILL_DESCRIPTIONS[skill] || skill;
|
|
1022
|
-
const claudeSkillDir =
|
|
1023
|
-
await
|
|
1024
|
-
await
|
|
1025
|
-
|
|
1335
|
+
const claudeSkillDir = path15.join(targetDir, ".claude", "skills", skill);
|
|
1336
|
+
await fs4.ensureDir(claudeSkillDir);
|
|
1337
|
+
await fs4.writeFile(
|
|
1338
|
+
path15.join(claudeSkillDir, "SKILL.md"),
|
|
1026
1339
|
content,
|
|
1027
1340
|
"utf-8"
|
|
1028
1341
|
);
|
|
1029
|
-
const cursorSkillDir =
|
|
1030
|
-
await
|
|
1031
|
-
await
|
|
1032
|
-
|
|
1342
|
+
const cursorSkillDir = path15.join(targetDir, ".cursor", "skills", skill);
|
|
1343
|
+
await fs4.ensureDir(cursorSkillDir);
|
|
1344
|
+
await fs4.writeFile(
|
|
1345
|
+
path15.join(cursorSkillDir, "SKILL.md"),
|
|
1033
1346
|
content,
|
|
1034
1347
|
"utf-8"
|
|
1035
1348
|
);
|
|
1036
|
-
const legacyDir =
|
|
1037
|
-
await
|
|
1038
|
-
await
|
|
1349
|
+
const legacyDir = path15.join(targetDir, ".claude", "commands");
|
|
1350
|
+
await fs4.ensureDir(legacyDir);
|
|
1351
|
+
await fs4.copy(src, path15.join(legacyDir, `${skill}.md`), { overwrite: true });
|
|
1039
1352
|
copied.push(skill);
|
|
1040
1353
|
}
|
|
1041
1354
|
return copied;
|
|
1042
1355
|
}
|
|
1043
1356
|
|
|
1044
1357
|
// src/copier/guides.ts
|
|
1045
|
-
import
|
|
1046
|
-
import
|
|
1358
|
+
import path16 from "path";
|
|
1359
|
+
import fs5 from "fs-extra";
|
|
1047
1360
|
var AVAILABLE_GUIDES = [
|
|
1048
1361
|
"getting-started",
|
|
1049
1362
|
"prompt-playbook",
|
|
@@ -1053,14 +1366,14 @@ var AVAILABLE_GUIDES = [
|
|
|
1053
1366
|
"hooks-and-agents"
|
|
1054
1367
|
];
|
|
1055
1368
|
async function copyGuides(targetDir) {
|
|
1056
|
-
const guidesTarget =
|
|
1057
|
-
await
|
|
1369
|
+
const guidesTarget = path16.join(targetDir, "ai-kit", "guides");
|
|
1370
|
+
await fs5.ensureDir(guidesTarget);
|
|
1058
1371
|
const copied = [];
|
|
1059
1372
|
for (const guide of AVAILABLE_GUIDES) {
|
|
1060
|
-
const src =
|
|
1061
|
-
const dest =
|
|
1062
|
-
if (await
|
|
1063
|
-
await
|
|
1373
|
+
const src = path16.join(GUIDES_DIR, `${guide}.md`);
|
|
1374
|
+
const dest = path16.join(guidesTarget, `${guide}.md`);
|
|
1375
|
+
if (await fs5.pathExists(src)) {
|
|
1376
|
+
await fs5.copy(src, dest, { overwrite: true });
|
|
1064
1377
|
copied.push(guide);
|
|
1065
1378
|
}
|
|
1066
1379
|
}
|
|
@@ -1068,21 +1381,21 @@ async function copyGuides(targetDir) {
|
|
|
1068
1381
|
}
|
|
1069
1382
|
|
|
1070
1383
|
// src/copier/docs.ts
|
|
1071
|
-
import
|
|
1072
|
-
import
|
|
1384
|
+
import path17 from "path";
|
|
1385
|
+
import fs6 from "fs-extra";
|
|
1073
1386
|
var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
|
|
1074
1387
|
async function scaffoldDocs(targetDir) {
|
|
1075
|
-
const docsTarget =
|
|
1076
|
-
await
|
|
1388
|
+
const docsTarget = path17.join(targetDir, "docs");
|
|
1389
|
+
await fs6.ensureDir(docsTarget);
|
|
1077
1390
|
const created = [];
|
|
1078
1391
|
for (const doc of DOC_SCAFFOLDS) {
|
|
1079
|
-
const src =
|
|
1080
|
-
const dest =
|
|
1081
|
-
if (await
|
|
1392
|
+
const src = path17.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
|
|
1393
|
+
const dest = path17.join(docsTarget, `${doc}.md`);
|
|
1394
|
+
if (await fs6.pathExists(dest)) {
|
|
1082
1395
|
continue;
|
|
1083
1396
|
}
|
|
1084
|
-
if (await
|
|
1085
|
-
await
|
|
1397
|
+
if (await fs6.pathExists(src)) {
|
|
1398
|
+
await fs6.copy(src, dest);
|
|
1086
1399
|
created.push(doc);
|
|
1087
1400
|
}
|
|
1088
1401
|
}
|
|
@@ -1090,8 +1403,8 @@ async function scaffoldDocs(targetDir) {
|
|
|
1090
1403
|
}
|
|
1091
1404
|
|
|
1092
1405
|
// src/copier/agents.ts
|
|
1093
|
-
import
|
|
1094
|
-
import
|
|
1406
|
+
import path18 from "path";
|
|
1407
|
+
import fs7 from "fs-extra";
|
|
1095
1408
|
var UNIVERSAL_AGENTS = [
|
|
1096
1409
|
"planner",
|
|
1097
1410
|
"code-reviewer",
|
|
@@ -1116,22 +1429,22 @@ var CONDITIONAL_AGENTS = [
|
|
|
1116
1429
|
}
|
|
1117
1430
|
];
|
|
1118
1431
|
async function copyAgents(targetDir, scan) {
|
|
1119
|
-
const agentsTarget =
|
|
1120
|
-
await
|
|
1432
|
+
const agentsTarget = path18.join(targetDir, ".claude", "agents");
|
|
1433
|
+
await fs7.ensureDir(agentsTarget);
|
|
1121
1434
|
const copied = [];
|
|
1122
1435
|
for (const agent of UNIVERSAL_AGENTS) {
|
|
1123
|
-
const src =
|
|
1124
|
-
if (!await
|
|
1125
|
-
await
|
|
1436
|
+
const src = path18.join(AGENTS_DIR, `${agent}.md`);
|
|
1437
|
+
if (!await fs7.pathExists(src)) continue;
|
|
1438
|
+
await fs7.copy(src, path18.join(agentsTarget, `${agent}.md`), {
|
|
1126
1439
|
overwrite: true
|
|
1127
1440
|
});
|
|
1128
1441
|
copied.push(agent);
|
|
1129
1442
|
}
|
|
1130
1443
|
for (const { name, condition } of CONDITIONAL_AGENTS) {
|
|
1131
1444
|
if (!condition(scan)) continue;
|
|
1132
|
-
const src =
|
|
1133
|
-
if (!await
|
|
1134
|
-
await
|
|
1445
|
+
const src = path18.join(AGENTS_DIR, `${name}.md`);
|
|
1446
|
+
if (!await fs7.pathExists(src)) continue;
|
|
1447
|
+
await fs7.copy(src, path18.join(agentsTarget, `${name}.md`), {
|
|
1135
1448
|
overwrite: true
|
|
1136
1449
|
});
|
|
1137
1450
|
copied.push(name);
|
|
@@ -1140,17 +1453,17 @@ async function copyAgents(targetDir, scan) {
|
|
|
1140
1453
|
}
|
|
1141
1454
|
|
|
1142
1455
|
// src/copier/contexts.ts
|
|
1143
|
-
import
|
|
1144
|
-
import
|
|
1456
|
+
import path19 from "path";
|
|
1457
|
+
import fs8 from "fs-extra";
|
|
1145
1458
|
var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
|
|
1146
1459
|
async function copyContexts(targetDir) {
|
|
1147
|
-
const contextsTarget =
|
|
1148
|
-
await
|
|
1460
|
+
const contextsTarget = path19.join(targetDir, ".claude", "contexts");
|
|
1461
|
+
await fs8.ensureDir(contextsTarget);
|
|
1149
1462
|
const copied = [];
|
|
1150
1463
|
for (const context of AVAILABLE_CONTEXTS) {
|
|
1151
|
-
const src =
|
|
1152
|
-
if (!await
|
|
1153
|
-
await
|
|
1464
|
+
const src = path19.join(CONTEXTS_DIR, `${context}.md`);
|
|
1465
|
+
if (!await fs8.pathExists(src)) continue;
|
|
1466
|
+
await fs8.copy(src, path19.join(contextsTarget, `${context}.md`), {
|
|
1154
1467
|
overwrite: true
|
|
1155
1468
|
});
|
|
1156
1469
|
copied.push(context);
|
|
@@ -1160,18 +1473,36 @@ async function copyContexts(targetDir) {
|
|
|
1160
1473
|
|
|
1161
1474
|
// src/commands/init.ts
|
|
1162
1475
|
async function initCommand(targetPath) {
|
|
1163
|
-
const projectDir =
|
|
1476
|
+
const projectDir = path20.resolve(targetPath || process.cwd());
|
|
1164
1477
|
logSection("AI Kit \u2014 Project Setup");
|
|
1165
1478
|
logInfo(`Scanning: ${projectDir}`);
|
|
1166
|
-
const configPath =
|
|
1479
|
+
const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1480
|
+
let savedConfig = null;
|
|
1481
|
+
let reuseMode = false;
|
|
1167
1482
|
if (fileExists(configPath)) {
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
logInfo(
|
|
1174
|
-
|
|
1483
|
+
savedConfig = readJsonSafe(configPath);
|
|
1484
|
+
if (savedConfig) {
|
|
1485
|
+
logSection("Existing Profile Found");
|
|
1486
|
+
logInfo(`Tools: ${formatToolsLabel(savedConfig.tools)}`);
|
|
1487
|
+
logInfo(`Strictness: ${savedConfig.strictness}`);
|
|
1488
|
+
logInfo(`Hook Profile: ${savedConfig.hookProfile}`);
|
|
1489
|
+
logInfo(`Generated: ${savedConfig.generatedAt}`);
|
|
1490
|
+
logInfo(`Version: v${savedConfig.version}`);
|
|
1491
|
+
console.log("");
|
|
1492
|
+
const action = await select({
|
|
1493
|
+
message: "What would you like to do?",
|
|
1494
|
+
choices: [
|
|
1495
|
+
{ name: "Update \u2014 re-scan project, keep saved settings", value: "reuse" },
|
|
1496
|
+
{ name: "Re-configure \u2014 change settings from scratch", value: "fresh" },
|
|
1497
|
+
{ name: "Cancel", value: "cancel" }
|
|
1498
|
+
],
|
|
1499
|
+
default: "reuse"
|
|
1500
|
+
});
|
|
1501
|
+
if (action === "cancel") {
|
|
1502
|
+
logInfo("Cancelled.");
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
reuseMode = action === "reuse";
|
|
1175
1506
|
}
|
|
1176
1507
|
}
|
|
1177
1508
|
const spinner = ora("Scanning project...").start();
|
|
@@ -1192,13 +1523,24 @@ async function initCommand(targetPath) {
|
|
|
1192
1523
|
logInfo(`Monorepo: ${scan.monorepo ? `Yes (${scan.monorepoTool})` : "No"}`);
|
|
1193
1524
|
logInfo(`Package Manager: ${scan.packageManager}`);
|
|
1194
1525
|
logInfo(`Formatter: ${scan.tools.biome ? "Biome" : scan.tools.prettier ? "Prettier" : "None detected"}`);
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1526
|
+
if (scan.staticSite?.isStatic) {
|
|
1527
|
+
logInfo(`Static Site: Yes (${scan.staticSite.outputMode})`);
|
|
1528
|
+
}
|
|
1529
|
+
if (scan.designTokens?.detected) {
|
|
1530
|
+
logInfo(`Design Tokens: ${scan.designTokens.colors.length} colors, ${scan.designTokens.fonts.length} fonts (${scan.designTokens.source})`);
|
|
1531
|
+
}
|
|
1532
|
+
if (scan.aiIgnorePatterns.length > 0) {
|
|
1533
|
+
logInfo(`.aiignore: ${scan.aiIgnorePatterns.length} patterns loaded`);
|
|
1534
|
+
}
|
|
1535
|
+
if (!reuseMode) {
|
|
1536
|
+
const clarifications = await askClarifications(scan);
|
|
1537
|
+
scan = applyClarifications(scan, clarifications);
|
|
1538
|
+
}
|
|
1539
|
+
const tools = reuseMode && savedConfig?.tools ? savedConfig.tools : await selectTools();
|
|
1540
|
+
const strictness = reuseMode && savedConfig?.strictness ? savedConfig.strictness : await selectStrictness();
|
|
1541
|
+
const hookProfile = reuseMode && savedConfig?.hookProfile ? savedConfig.hookProfile : await selectHookProfile();
|
|
1200
1542
|
const customFragments = loadCustomFragments(projectDir);
|
|
1201
|
-
const conflict = await selectConflictStrategy(projectDir);
|
|
1543
|
+
const conflict = reuseMode ? "overwrite" : await selectConflictStrategy(projectDir);
|
|
1202
1544
|
logSection("Generating Files");
|
|
1203
1545
|
const results = await generate(projectDir, scan, tools, conflict, {
|
|
1204
1546
|
strictness,
|
|
@@ -1228,6 +1570,13 @@ async function initCommand(targetPath) {
|
|
|
1228
1570
|
logInfo("Run `ai-kit audit` to check your AI agent configuration health.");
|
|
1229
1571
|
logInfo("Check ai-kit/guides/getting-started.md to get started.");
|
|
1230
1572
|
}
|
|
1573
|
+
function formatToolsLabel(tools) {
|
|
1574
|
+
if (!tools) return "Unknown";
|
|
1575
|
+
if (tools.claude && tools.cursor) return "Claude Code + Cursor";
|
|
1576
|
+
if (tools.claude) return "Claude Code only";
|
|
1577
|
+
if (tools.cursor) return "Cursor only";
|
|
1578
|
+
return "None";
|
|
1579
|
+
}
|
|
1231
1580
|
function formatFramework(scan) {
|
|
1232
1581
|
if (scan.framework === "nextjs") {
|
|
1233
1582
|
const version = scan.nextjsVersion ? ` ${scan.nextjsVersion}` : "";
|
|
@@ -1304,7 +1653,7 @@ async function selectHookProfile() {
|
|
|
1304
1653
|
});
|
|
1305
1654
|
}
|
|
1306
1655
|
async function selectConflictStrategy(projectDir) {
|
|
1307
|
-
const hasExisting = fileExists(
|
|
1656
|
+
const hasExisting = fileExists(path20.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path20.join(projectDir, GENERATED_FILES.cursorRules));
|
|
1308
1657
|
if (!hasExisting) return "overwrite";
|
|
1309
1658
|
return select({
|
|
1310
1659
|
message: "Existing AI config files detected. How should we handle conflicts?",
|
|
@@ -1334,10 +1683,10 @@ async function generate(projectDir, scan, tools, conflict, opts) {
|
|
|
1334
1683
|
docs: []
|
|
1335
1684
|
};
|
|
1336
1685
|
if (tools.claude) {
|
|
1337
|
-
const claudeMdPath =
|
|
1686
|
+
const claudeMdPath = path20.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1338
1687
|
if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
|
|
1339
1688
|
const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
1340
|
-
await
|
|
1689
|
+
await fs9.writeFile(claudeMdPath, content, "utf-8");
|
|
1341
1690
|
result.claudeMd = true;
|
|
1342
1691
|
} else {
|
|
1343
1692
|
logWarning("CLAUDE.md exists, skipping");
|
|
@@ -1346,26 +1695,26 @@ async function generate(projectDir, scan, tools, conflict, opts) {
|
|
|
1346
1695
|
result.agents = await copyAgents(projectDir, scan);
|
|
1347
1696
|
result.contexts = await copyContexts(projectDir);
|
|
1348
1697
|
const hookProfile = opts?.hookProfile || "standard";
|
|
1349
|
-
const settingsLocalPath =
|
|
1698
|
+
const settingsLocalPath = path20.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
1350
1699
|
const settingsLocal = generateSettingsLocal(scan, hookProfile);
|
|
1351
|
-
await
|
|
1352
|
-
await
|
|
1700
|
+
await fs9.ensureDir(path20.dirname(settingsLocalPath));
|
|
1701
|
+
await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
|
|
1353
1702
|
result.hooks = true;
|
|
1354
1703
|
}
|
|
1355
1704
|
if (tools.cursor) {
|
|
1356
|
-
const cursorPath =
|
|
1705
|
+
const cursorPath = path20.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1357
1706
|
if (conflict === "overwrite" || !fileExists(cursorPath)) {
|
|
1358
1707
|
const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
1359
|
-
await
|
|
1708
|
+
await fs9.writeFile(cursorPath, content, "utf-8");
|
|
1360
1709
|
result.cursorRules = true;
|
|
1361
1710
|
} else {
|
|
1362
1711
|
logWarning(".cursorrules exists, skipping");
|
|
1363
1712
|
}
|
|
1364
|
-
const mdcDir =
|
|
1365
|
-
await
|
|
1713
|
+
const mdcDir = path20.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1714
|
+
await fs9.ensureDir(mdcDir);
|
|
1366
1715
|
const mdcFiles = generateMdcFiles(scan);
|
|
1367
1716
|
for (const mdc of mdcFiles) {
|
|
1368
|
-
await
|
|
1717
|
+
await fs9.writeFile(path20.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
1369
1718
|
}
|
|
1370
1719
|
result.cursorMdcFiles = mdcFiles.length;
|
|
1371
1720
|
}
|
|
@@ -1380,10 +1729,11 @@ async function generate(projectDir, scan, tools, conflict, opts) {
|
|
|
1380
1729
|
agents: result.agents,
|
|
1381
1730
|
contexts: result.contexts,
|
|
1382
1731
|
hooks: result.hooks,
|
|
1383
|
-
hookProfile: opts?.hookProfile
|
|
1732
|
+
hookProfile: opts?.hookProfile,
|
|
1733
|
+
tools
|
|
1384
1734
|
});
|
|
1385
|
-
await
|
|
1386
|
-
|
|
1735
|
+
await fs9.writeJson(
|
|
1736
|
+
path20.join(projectDir, AI_KIT_CONFIG_FILE),
|
|
1387
1737
|
config,
|
|
1388
1738
|
{ spaces: 2 }
|
|
1389
1739
|
);
|
|
@@ -1459,13 +1809,13 @@ function showRecommendations(scan) {
|
|
|
1459
1809
|
}
|
|
1460
1810
|
|
|
1461
1811
|
// src/commands/update.ts
|
|
1462
|
-
import
|
|
1463
|
-
import
|
|
1812
|
+
import path21 from "path";
|
|
1813
|
+
import fs10 from "fs-extra";
|
|
1464
1814
|
import ora2 from "ora";
|
|
1465
1815
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1466
1816
|
async function updateCommand(targetPath) {
|
|
1467
|
-
const projectDir =
|
|
1468
|
-
const configPath =
|
|
1817
|
+
const projectDir = path21.resolve(targetPath || process.cwd());
|
|
1818
|
+
const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1469
1819
|
if (!fileExists(configPath)) {
|
|
1470
1820
|
logError("No ai-kit.config.json found. Run `ai-kit init` first.");
|
|
1471
1821
|
return;
|
|
@@ -1493,37 +1843,39 @@ async function updateCommand(targetPath) {
|
|
|
1493
1843
|
logSection("Updating Files");
|
|
1494
1844
|
const strictness = existingConfig.strictness || "standard";
|
|
1495
1845
|
const hookProfile = existingConfig.hookProfile || "standard";
|
|
1846
|
+
const tools = existingConfig.tools || { claude: true, cursor: true };
|
|
1496
1847
|
const customFragments = loadCustomFragments(projectDir);
|
|
1497
1848
|
const genOpts = { strictness, customFragments };
|
|
1849
|
+
logInfo(`Using saved profile \u2014 Tools: ${tools.claude && tools.cursor ? "Claude Code + Cursor" : tools.claude ? "Claude Code" : "Cursor"} \xB7 Strictness: ${strictness} \xB7 Hooks: ${hookProfile}`);
|
|
1498
1850
|
const templates = [];
|
|
1499
|
-
if (existingConfig.templates.includes("CLAUDE.md") || fileExists(
|
|
1500
|
-
const claudeMdPath =
|
|
1851
|
+
if (tools.claude && (existingConfig.templates.includes("CLAUDE.md") || fileExists(path21.join(projectDir, GENERATED_FILES.claudeMd)))) {
|
|
1852
|
+
const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1501
1853
|
const newContent = generateClaudeMd(scan, genOpts);
|
|
1502
1854
|
const existing = readFileSafe(claudeMdPath);
|
|
1503
1855
|
if (existing) {
|
|
1504
|
-
await
|
|
1856
|
+
await fs10.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1505
1857
|
} else {
|
|
1506
|
-
await
|
|
1858
|
+
await fs10.writeFile(claudeMdPath, newContent, "utf-8");
|
|
1507
1859
|
}
|
|
1508
1860
|
templates.push("CLAUDE.md");
|
|
1509
1861
|
logSuccess("CLAUDE.md updated");
|
|
1510
1862
|
}
|
|
1511
|
-
if (existingConfig.templates.includes(".cursorrules") || fileExists(
|
|
1512
|
-
const cursorRulesPath =
|
|
1863
|
+
if (tools.cursor && (existingConfig.templates.includes(".cursorrules") || fileExists(path21.join(projectDir, GENERATED_FILES.cursorRules)))) {
|
|
1864
|
+
const cursorRulesPath = path21.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1513
1865
|
const newContent = generateCursorRules(scan, genOpts);
|
|
1514
1866
|
const existing = readFileSafe(cursorRulesPath);
|
|
1515
1867
|
if (existing) {
|
|
1516
|
-
await
|
|
1868
|
+
await fs10.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1517
1869
|
} else {
|
|
1518
|
-
await
|
|
1870
|
+
await fs10.writeFile(cursorRulesPath, newContent, "utf-8");
|
|
1519
1871
|
}
|
|
1520
1872
|
templates.push(".cursorrules");
|
|
1521
1873
|
logSuccess(".cursorrules updated");
|
|
1522
|
-
const mdcDir =
|
|
1523
|
-
await
|
|
1874
|
+
const mdcDir = path21.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1875
|
+
await fs10.ensureDir(mdcDir);
|
|
1524
1876
|
const mdcFiles = generateMdcFiles(scan);
|
|
1525
1877
|
for (const mdc of mdcFiles) {
|
|
1526
|
-
await
|
|
1878
|
+
await fs10.writeFile(path21.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
1527
1879
|
}
|
|
1528
1880
|
logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
|
|
1529
1881
|
}
|
|
@@ -1534,10 +1886,10 @@ async function updateCommand(targetPath) {
|
|
|
1534
1886
|
const contexts = await copyContexts(projectDir);
|
|
1535
1887
|
logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
|
|
1536
1888
|
if (existingConfig.hooks !== false) {
|
|
1537
|
-
const settingsLocalPath =
|
|
1889
|
+
const settingsLocalPath = path21.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
1538
1890
|
const settingsLocal = generateSettingsLocal(scan, hookProfile);
|
|
1539
|
-
await
|
|
1540
|
-
await
|
|
1891
|
+
await fs10.ensureDir(path21.dirname(settingsLocalPath));
|
|
1892
|
+
await fs10.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
|
|
1541
1893
|
logSuccess(`Hooks updated (profile: ${hookProfile})`);
|
|
1542
1894
|
}
|
|
1543
1895
|
const guides = await copyGuides(projectDir);
|
|
@@ -1547,20 +1899,21 @@ async function updateCommand(targetPath) {
|
|
|
1547
1899
|
agents,
|
|
1548
1900
|
contexts,
|
|
1549
1901
|
hooks: existingConfig.hooks !== false,
|
|
1550
|
-
hookProfile
|
|
1902
|
+
hookProfile,
|
|
1903
|
+
tools
|
|
1551
1904
|
});
|
|
1552
|
-
await
|
|
1905
|
+
await fs10.writeJson(configPath, config, { spaces: 2 });
|
|
1553
1906
|
logSuccess("ai-kit.config.json updated");
|
|
1554
1907
|
console.log("");
|
|
1555
1908
|
logInfo("All AI configs refreshed with latest project scan.");
|
|
1556
1909
|
}
|
|
1557
1910
|
|
|
1558
1911
|
// src/commands/reset.ts
|
|
1559
|
-
import
|
|
1560
|
-
import
|
|
1912
|
+
import path22 from "path";
|
|
1913
|
+
import fs11 from "fs-extra";
|
|
1561
1914
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1562
1915
|
async function resetCommand(targetPath) {
|
|
1563
|
-
const projectDir =
|
|
1916
|
+
const projectDir = path22.resolve(targetPath || process.cwd());
|
|
1564
1917
|
logSection("AI Kit \u2014 Reset");
|
|
1565
1918
|
logWarning("This will remove all AI Kit generated files:");
|
|
1566
1919
|
logInfo(` - ${GENERATED_FILES.claudeMd}`);
|
|
@@ -1569,7 +1922,7 @@ async function resetCommand(targetPath) {
|
|
|
1569
1922
|
logInfo(` - ${GENERATED_FILES.claudeCommands}/`);
|
|
1570
1923
|
logInfo(` - ${GENERATED_FILES.claudeSkills}/`);
|
|
1571
1924
|
logInfo(` - ${GENERATED_FILES.cursorSkills}/`);
|
|
1572
|
-
logInfo(` - ai-kit
|
|
1925
|
+
logInfo(` - ai-kit/ (includes component-registry, patterns, guides)`);
|
|
1573
1926
|
logInfo(` - ${AI_KIT_CONFIG_FILE}`);
|
|
1574
1927
|
console.log("");
|
|
1575
1928
|
const proceed = await confirm3({
|
|
@@ -1581,44 +1934,44 @@ async function resetCommand(targetPath) {
|
|
|
1581
1934
|
return;
|
|
1582
1935
|
}
|
|
1583
1936
|
const removed = [];
|
|
1584
|
-
const claudeMdPath =
|
|
1937
|
+
const claudeMdPath = path22.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1585
1938
|
if (fileExists(claudeMdPath)) {
|
|
1586
|
-
await
|
|
1939
|
+
await fs11.remove(claudeMdPath);
|
|
1587
1940
|
removed.push(GENERATED_FILES.claudeMd);
|
|
1588
1941
|
}
|
|
1589
|
-
const cursorPath =
|
|
1942
|
+
const cursorPath = path22.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1590
1943
|
if (fileExists(cursorPath)) {
|
|
1591
|
-
await
|
|
1944
|
+
await fs11.remove(cursorPath);
|
|
1592
1945
|
removed.push(GENERATED_FILES.cursorRules);
|
|
1593
1946
|
}
|
|
1594
|
-
const cursorMdcDir =
|
|
1947
|
+
const cursorMdcDir = path22.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1595
1948
|
if (fileExists(cursorMdcDir)) {
|
|
1596
|
-
await
|
|
1949
|
+
await fs11.remove(cursorMdcDir);
|
|
1597
1950
|
removed.push(GENERATED_FILES.cursorMdcDir);
|
|
1598
1951
|
}
|
|
1599
|
-
const commandsDir =
|
|
1952
|
+
const commandsDir = path22.join(projectDir, GENERATED_FILES.claudeCommands);
|
|
1600
1953
|
if (fileExists(commandsDir)) {
|
|
1601
|
-
await
|
|
1954
|
+
await fs11.remove(commandsDir);
|
|
1602
1955
|
removed.push(GENERATED_FILES.claudeCommands);
|
|
1603
1956
|
}
|
|
1604
|
-
const claudeSkillsDir =
|
|
1957
|
+
const claudeSkillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
1605
1958
|
if (fileExists(claudeSkillsDir)) {
|
|
1606
|
-
await
|
|
1959
|
+
await fs11.remove(claudeSkillsDir);
|
|
1607
1960
|
removed.push(GENERATED_FILES.claudeSkills);
|
|
1608
1961
|
}
|
|
1609
|
-
const cursorSkillsDir =
|
|
1962
|
+
const cursorSkillsDir = path22.join(projectDir, GENERATED_FILES.cursorSkills);
|
|
1610
1963
|
if (fileExists(cursorSkillsDir)) {
|
|
1611
|
-
await
|
|
1964
|
+
await fs11.remove(cursorSkillsDir);
|
|
1612
1965
|
removed.push(GENERATED_FILES.cursorSkills);
|
|
1613
1966
|
}
|
|
1614
|
-
const aiKitDir =
|
|
1967
|
+
const aiKitDir = path22.join(projectDir, "ai-kit");
|
|
1615
1968
|
if (fileExists(aiKitDir)) {
|
|
1616
|
-
await
|
|
1969
|
+
await fs11.remove(aiKitDir);
|
|
1617
1970
|
removed.push("ai-kit/");
|
|
1618
1971
|
}
|
|
1619
|
-
const configPath =
|
|
1972
|
+
const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1620
1973
|
if (fileExists(configPath)) {
|
|
1621
|
-
await
|
|
1974
|
+
await fs11.remove(configPath);
|
|
1622
1975
|
removed.push(AI_KIT_CONFIG_FILE);
|
|
1623
1976
|
}
|
|
1624
1977
|
logSection("Reset Complete");
|
|
@@ -1630,8 +1983,8 @@ async function resetCommand(targetPath) {
|
|
|
1630
1983
|
}
|
|
1631
1984
|
|
|
1632
1985
|
// src/commands/tokens.ts
|
|
1633
|
-
import
|
|
1634
|
-
import
|
|
1986
|
+
import path23 from "path";
|
|
1987
|
+
import fs12 from "fs-extra";
|
|
1635
1988
|
import chalk2 from "chalk";
|
|
1636
1989
|
import ora3 from "ora";
|
|
1637
1990
|
import os from "os";
|
|
@@ -1641,14 +1994,14 @@ var PRICING = {
|
|
|
1641
1994
|
};
|
|
1642
1995
|
var PLAN_BUDGET = 20;
|
|
1643
1996
|
function findSessionFiles() {
|
|
1644
|
-
const claudeDir =
|
|
1645
|
-
if (!
|
|
1997
|
+
const claudeDir = path23.join(os.homedir(), ".claude", "projects");
|
|
1998
|
+
if (!fs12.existsSync(claudeDir)) return [];
|
|
1646
1999
|
const files = [];
|
|
1647
2000
|
function walkDir(dir) {
|
|
1648
2001
|
try {
|
|
1649
|
-
const entries =
|
|
2002
|
+
const entries = fs12.readdirSync(dir, { withFileTypes: true });
|
|
1650
2003
|
for (const entry of entries) {
|
|
1651
|
-
const full =
|
|
2004
|
+
const full = path23.join(dir, entry.name);
|
|
1652
2005
|
if (entry.isDirectory()) {
|
|
1653
2006
|
walkDir(full);
|
|
1654
2007
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -1663,7 +2016,7 @@ function findSessionFiles() {
|
|
|
1663
2016
|
}
|
|
1664
2017
|
function parseSessionFile(filePath) {
|
|
1665
2018
|
try {
|
|
1666
|
-
const content =
|
|
2019
|
+
const content = fs12.readFileSync(filePath, "utf-8");
|
|
1667
2020
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
1668
2021
|
const usage = {
|
|
1669
2022
|
inputTokens: 0,
|
|
@@ -1700,11 +2053,11 @@ function parseSessionFile(filePath) {
|
|
|
1700
2053
|
}
|
|
1701
2054
|
if (usage.inputTokens === 0 && usage.outputTokens === 0) return null;
|
|
1702
2055
|
if (!sessionDate) {
|
|
1703
|
-
const stat =
|
|
2056
|
+
const stat = fs12.statSync(filePath);
|
|
1704
2057
|
sessionDate = stat.mtime.toISOString().slice(0, 10);
|
|
1705
2058
|
}
|
|
1706
|
-
const sessionId =
|
|
1707
|
-
const projectName =
|
|
2059
|
+
const sessionId = path23.basename(filePath, ".jsonl");
|
|
2060
|
+
const projectName = path23.basename(path23.dirname(filePath));
|
|
1708
2061
|
return {
|
|
1709
2062
|
sessionId,
|
|
1710
2063
|
filePath,
|
|
@@ -1933,13 +2286,13 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
|
|
|
1933
2286
|
);
|
|
1934
2287
|
console.log("");
|
|
1935
2288
|
if (options.csv) {
|
|
1936
|
-
const csvPath =
|
|
2289
|
+
const csvPath = path23.join(process.cwd(), "token-usage.csv");
|
|
1937
2290
|
const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
|
|
1938
2291
|
const daily = aggregateByDate(sessions);
|
|
1939
2292
|
const csvRows = daily.map(
|
|
1940
2293
|
(d) => `${d.date},${d.sessions},${d.usage.inputTokens},${d.usage.outputTokens},${d.usage.cacheReadTokens},${d.cost.toFixed(2)}`
|
|
1941
2294
|
).join("\n");
|
|
1942
|
-
await
|
|
2295
|
+
await fs12.writeFile(csvPath, csvHeader + csvRows, "utf-8");
|
|
1943
2296
|
logSuccess(`CSV exported to ${csvPath}`);
|
|
1944
2297
|
}
|
|
1945
2298
|
if (options.export) {
|
|
@@ -1976,13 +2329,13 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1976
2329
|
}
|
|
1977
2330
|
};
|
|
1978
2331
|
const outputDir = process.cwd();
|
|
1979
|
-
const dataPath =
|
|
1980
|
-
const dashboardSrc =
|
|
1981
|
-
const dashboardDest =
|
|
1982
|
-
await
|
|
2332
|
+
const dataPath = path23.join(outputDir, "token-data.json");
|
|
2333
|
+
const dashboardSrc = path23.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
|
|
2334
|
+
const dashboardDest = path23.join(outputDir, "token-dashboard.html");
|
|
2335
|
+
await fs12.writeJson(dataPath, exportData, { spaces: 2 });
|
|
1983
2336
|
logInfo(`Token data written to ${dataPath}`);
|
|
1984
|
-
if (await
|
|
1985
|
-
await
|
|
2337
|
+
if (await fs12.pathExists(dashboardSrc)) {
|
|
2338
|
+
await fs12.copy(dashboardSrc, dashboardDest, { overwrite: true });
|
|
1986
2339
|
logInfo(`Dashboard copied to ${dashboardDest}`);
|
|
1987
2340
|
} else {
|
|
1988
2341
|
logWarning("Dashboard template not found. Skipping HTML export.");
|
|
@@ -1999,12 +2352,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1999
2352
|
}
|
|
2000
2353
|
|
|
2001
2354
|
// src/commands/doctor.ts
|
|
2002
|
-
import
|
|
2355
|
+
import path24 from "path";
|
|
2003
2356
|
import chalk3 from "chalk";
|
|
2004
2357
|
import ora4 from "ora";
|
|
2005
2358
|
async function doctorCommand(targetPath) {
|
|
2006
|
-
const projectDir =
|
|
2007
|
-
const configPath =
|
|
2359
|
+
const projectDir = path24.resolve(targetPath || process.cwd());
|
|
2360
|
+
const configPath = path24.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2008
2361
|
let passed = 0;
|
|
2009
2362
|
let warnings = 0;
|
|
2010
2363
|
let issues = 0;
|
|
@@ -2036,7 +2389,7 @@ async function doctorCommand(targetPath) {
|
|
|
2036
2389
|
}
|
|
2037
2390
|
for (const template of config.templates) {
|
|
2038
2391
|
const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
|
|
2039
|
-
const templatePath =
|
|
2392
|
+
const templatePath = path24.join(projectDir, templateFile);
|
|
2040
2393
|
if (fileExists(templatePath)) {
|
|
2041
2394
|
logSuccess(`${template} exists and in sync`);
|
|
2042
2395
|
passed++;
|
|
@@ -2047,8 +2400,8 @@ async function doctorCommand(targetPath) {
|
|
|
2047
2400
|
}
|
|
2048
2401
|
const missingSkills = [];
|
|
2049
2402
|
for (const skill of config.commands) {
|
|
2050
|
-
const claudeSkillPath =
|
|
2051
|
-
const cursorSkillPath =
|
|
2403
|
+
const claudeSkillPath = path24.join(projectDir, GENERATED_FILES.claudeSkills, skill);
|
|
2404
|
+
const cursorSkillPath = path24.join(projectDir, GENERATED_FILES.cursorSkills, skill);
|
|
2052
2405
|
if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
|
|
2053
2406
|
missingSkills.push(skill);
|
|
2054
2407
|
}
|
|
@@ -2062,10 +2415,10 @@ async function doctorCommand(targetPath) {
|
|
|
2062
2415
|
);
|
|
2063
2416
|
issues++;
|
|
2064
2417
|
}
|
|
2065
|
-
const guidesDir =
|
|
2418
|
+
const guidesDir = path24.join(projectDir, "ai-kit", "guides");
|
|
2066
2419
|
const missingGuides = [];
|
|
2067
2420
|
for (const guide of config.guides) {
|
|
2068
|
-
const guidePath =
|
|
2421
|
+
const guidePath = path24.join(guidesDir, guide);
|
|
2069
2422
|
if (!fileExists(guidePath)) {
|
|
2070
2423
|
missingGuides.push(guide);
|
|
2071
2424
|
}
|
|
@@ -2203,13 +2556,13 @@ function compareScanResults(previous, current) {
|
|
|
2203
2556
|
}
|
|
2204
2557
|
|
|
2205
2558
|
// src/commands/diff.ts
|
|
2206
|
-
import
|
|
2207
|
-
import
|
|
2559
|
+
import path25 from "path";
|
|
2560
|
+
import fs13 from "fs-extra";
|
|
2208
2561
|
import chalk4 from "chalk";
|
|
2209
2562
|
import ora5 from "ora";
|
|
2210
2563
|
async function diffCommand(targetPath) {
|
|
2211
|
-
const projectDir =
|
|
2212
|
-
const configPath =
|
|
2564
|
+
const projectDir = path25.resolve(targetPath || process.cwd());
|
|
2565
|
+
const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2213
2566
|
console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
|
|
2214
2567
|
if (!fileExists(configPath)) {
|
|
2215
2568
|
logError("No ai-kit.config.json found. Run `ai-kit init` first.");
|
|
@@ -2276,11 +2629,11 @@ async function diffCommand(targetPath) {
|
|
|
2276
2629
|
if (cursorRulesStatus.status === "modified") modified++;
|
|
2277
2630
|
else if (cursorRulesStatus.status === "added") added++;
|
|
2278
2631
|
else unchanged++;
|
|
2279
|
-
const skillsDir =
|
|
2632
|
+
const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
2280
2633
|
const skillCount = countFilesInDir(skillsDir);
|
|
2281
2634
|
console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
|
|
2282
2635
|
unchanged++;
|
|
2283
|
-
const guidesDir =
|
|
2636
|
+
const guidesDir = path25.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
|
|
2284
2637
|
const guideCount = existingConfig.guides?.length || 0;
|
|
2285
2638
|
console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
|
|
2286
2639
|
unchanged++;
|
|
@@ -2389,7 +2742,7 @@ function diffStack(oldScan, newScan) {
|
|
|
2389
2742
|
return changes;
|
|
2390
2743
|
}
|
|
2391
2744
|
function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
|
|
2392
|
-
const filePath =
|
|
2745
|
+
const filePath = path25.join(projectDir, filename);
|
|
2393
2746
|
const currentContent = readFileSafe(filePath);
|
|
2394
2747
|
const newContent = generate2();
|
|
2395
2748
|
if (!currentContent) {
|
|
@@ -2426,8 +2779,8 @@ function logFileChange(result) {
|
|
|
2426
2779
|
}
|
|
2427
2780
|
function countFilesInDir(dirPath) {
|
|
2428
2781
|
try {
|
|
2429
|
-
if (!
|
|
2430
|
-
const entries =
|
|
2782
|
+
if (!fs13.existsSync(dirPath)) return 0;
|
|
2783
|
+
const entries = fs13.readdirSync(dirPath);
|
|
2431
2784
|
return entries.filter((e) => !e.startsWith(".")).length;
|
|
2432
2785
|
} catch {
|
|
2433
2786
|
return 0;
|
|
@@ -2435,8 +2788,8 @@ function countFilesInDir(dirPath) {
|
|
|
2435
2788
|
}
|
|
2436
2789
|
|
|
2437
2790
|
// src/commands/export.ts
|
|
2438
|
-
import
|
|
2439
|
-
import
|
|
2791
|
+
import path26 from "path";
|
|
2792
|
+
import fs14 from "fs-extra";
|
|
2440
2793
|
import ora6 from "ora";
|
|
2441
2794
|
import { select as select2 } from "@inquirer/prompts";
|
|
2442
2795
|
var EXPORT_TARGETS = {
|
|
@@ -2472,9 +2825,9 @@ function toCline(content) {
|
|
|
2472
2825
|
${stripped}`;
|
|
2473
2826
|
}
|
|
2474
2827
|
async function exportCommand(targetPath, options) {
|
|
2475
|
-
const projectDir =
|
|
2476
|
-
const configPath =
|
|
2477
|
-
const claudeMdPath =
|
|
2828
|
+
const projectDir = path26.resolve(targetPath || process.cwd());
|
|
2829
|
+
const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2830
|
+
const claudeMdPath = path26.join(projectDir, GENERATED_FILES.claudeMd);
|
|
2478
2831
|
logSection("AI Kit \u2014 Export");
|
|
2479
2832
|
if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
|
|
2480
2833
|
logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
|
|
@@ -2516,9 +2869,9 @@ async function exportCommand(targetPath, options) {
|
|
|
2516
2869
|
for (const fmt2 of formats) {
|
|
2517
2870
|
const target = EXPORT_TARGETS[fmt2];
|
|
2518
2871
|
const transformer = transformers[fmt2];
|
|
2519
|
-
const outputPath =
|
|
2872
|
+
const outputPath = path26.join(projectDir, target.file);
|
|
2520
2873
|
const transformed = transformer(claudeContent);
|
|
2521
|
-
await
|
|
2874
|
+
await fs14.writeFile(outputPath, transformed, "utf-8");
|
|
2522
2875
|
exported++;
|
|
2523
2876
|
}
|
|
2524
2877
|
spinner.succeed("Export complete");
|
|
@@ -2534,7 +2887,7 @@ async function exportCommand(targetPath, options) {
|
|
|
2534
2887
|
}
|
|
2535
2888
|
|
|
2536
2889
|
// src/commands/stats.ts
|
|
2537
|
-
import
|
|
2890
|
+
import path27 from "path";
|
|
2538
2891
|
import chalk5 from "chalk";
|
|
2539
2892
|
var SKILL_CATEGORIES = {
|
|
2540
2893
|
"Getting Started": ["prompt-help", "understand"],
|
|
@@ -2637,8 +2990,8 @@ var MCP_DISPLAY_NAMES = {
|
|
|
2637
2990
|
perplexity: "Perplexity"
|
|
2638
2991
|
};
|
|
2639
2992
|
async function statsCommand(targetPath) {
|
|
2640
|
-
const projectDir =
|
|
2641
|
-
const configPath =
|
|
2993
|
+
const projectDir = path27.resolve(targetPath || process.cwd());
|
|
2994
|
+
const configPath = path27.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2642
2995
|
logSection("AI Kit \u2014 Project Stats");
|
|
2643
2996
|
console.log("");
|
|
2644
2997
|
if (!fileExists(configPath)) {
|
|
@@ -2732,21 +3085,21 @@ async function statsCommand(targetPath) {
|
|
|
2732
3085
|
}
|
|
2733
3086
|
|
|
2734
3087
|
// src/commands/audit.ts
|
|
2735
|
-
import
|
|
2736
|
-
import
|
|
3088
|
+
import path28 from "path";
|
|
3089
|
+
import fs15 from "fs-extra";
|
|
2737
3090
|
import chalk6 from "chalk";
|
|
2738
3091
|
async function auditCommand(targetPath) {
|
|
2739
|
-
const projectDir =
|
|
3092
|
+
const projectDir = path28.resolve(targetPath || process.cwd());
|
|
2740
3093
|
logSection("AI Kit \u2014 Security & Configuration Audit");
|
|
2741
3094
|
logInfo(`Auditing: ${projectDir}`);
|
|
2742
3095
|
const checks = [];
|
|
2743
|
-
const configPath =
|
|
3096
|
+
const configPath = path28.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2744
3097
|
if (fileExists(configPath)) {
|
|
2745
3098
|
checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
|
|
2746
3099
|
} else {
|
|
2747
3100
|
checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
|
|
2748
3101
|
}
|
|
2749
|
-
const claudeMdPath =
|
|
3102
|
+
const claudeMdPath = path28.join(projectDir, GENERATED_FILES.claudeMd);
|
|
2750
3103
|
const claudeMd = readFileSafe(claudeMdPath);
|
|
2751
3104
|
if (claudeMd) {
|
|
2752
3105
|
if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
|
|
@@ -2771,7 +3124,7 @@ async function auditCommand(targetPath) {
|
|
|
2771
3124
|
checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
|
|
2772
3125
|
}
|
|
2773
3126
|
}
|
|
2774
|
-
const settingsLocalPath =
|
|
3127
|
+
const settingsLocalPath = path28.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
2775
3128
|
if (fileExists(settingsLocalPath)) {
|
|
2776
3129
|
const settings2 = readJsonSafe(settingsLocalPath);
|
|
2777
3130
|
if (settings2?.hooks) {
|
|
@@ -2782,14 +3135,14 @@ async function auditCommand(targetPath) {
|
|
|
2782
3135
|
} else {
|
|
2783
3136
|
checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
|
|
2784
3137
|
}
|
|
2785
|
-
const agentsDir =
|
|
2786
|
-
if (await
|
|
2787
|
-
const agentFiles = (await
|
|
3138
|
+
const agentsDir = path28.join(projectDir, GENERATED_FILES.claudeAgents);
|
|
3139
|
+
if (await fs15.pathExists(agentsDir)) {
|
|
3140
|
+
const agentFiles = (await fs15.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
|
|
2788
3141
|
if (agentFiles.length > 0) {
|
|
2789
3142
|
checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
|
|
2790
3143
|
let invalidAgents = 0;
|
|
2791
3144
|
for (const file of agentFiles) {
|
|
2792
|
-
const content = readFileSafe(
|
|
3145
|
+
const content = readFileSafe(path28.join(agentsDir, file));
|
|
2793
3146
|
if (content && !content.startsWith("---")) {
|
|
2794
3147
|
invalidAgents++;
|
|
2795
3148
|
}
|
|
@@ -2803,18 +3156,18 @@ async function auditCommand(targetPath) {
|
|
|
2803
3156
|
} else {
|
|
2804
3157
|
checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
|
|
2805
3158
|
}
|
|
2806
|
-
const contextsDir =
|
|
2807
|
-
if (await
|
|
2808
|
-
const contextFiles = (await
|
|
3159
|
+
const contextsDir = path28.join(projectDir, GENERATED_FILES.claudeContexts);
|
|
3160
|
+
if (await fs15.pathExists(contextsDir)) {
|
|
3161
|
+
const contextFiles = (await fs15.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
|
|
2809
3162
|
checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
|
|
2810
3163
|
} else {
|
|
2811
3164
|
checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
|
|
2812
3165
|
}
|
|
2813
|
-
const skillsDir =
|
|
2814
|
-
if (await
|
|
2815
|
-
const skillDirs = (await
|
|
3166
|
+
const skillsDir = path28.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
3167
|
+
if (await fs15.pathExists(skillsDir)) {
|
|
3168
|
+
const skillDirs = (await fs15.readdir(skillsDir)).filter(async (f) => {
|
|
2816
3169
|
try {
|
|
2817
|
-
return (await
|
|
3170
|
+
return (await fs15.stat(path28.join(skillsDir, f))).isDirectory();
|
|
2818
3171
|
} catch {
|
|
2819
3172
|
return false;
|
|
2820
3173
|
}
|
|
@@ -2823,7 +3176,7 @@ async function auditCommand(targetPath) {
|
|
|
2823
3176
|
} else {
|
|
2824
3177
|
checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
|
|
2825
3178
|
}
|
|
2826
|
-
const gitignorePath =
|
|
3179
|
+
const gitignorePath = path28.join(projectDir, ".gitignore");
|
|
2827
3180
|
const gitignore = readFileSafe(gitignorePath);
|
|
2828
3181
|
if (gitignore) {
|
|
2829
3182
|
const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
|
|
@@ -2839,7 +3192,7 @@ async function auditCommand(targetPath) {
|
|
|
2839
3192
|
checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
|
|
2840
3193
|
}
|
|
2841
3194
|
}
|
|
2842
|
-
const settingsPath =
|
|
3195
|
+
const settingsPath = path28.join(projectDir, ".claude", "settings.json");
|
|
2843
3196
|
const settings = readFileSafe(settingsPath);
|
|
2844
3197
|
if (settings) {
|
|
2845
3198
|
const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
|
|
@@ -2872,8 +3225,8 @@ async function auditCommand(targetPath) {
|
|
|
2872
3225
|
}
|
|
2873
3226
|
|
|
2874
3227
|
// src/commands/health.ts
|
|
2875
|
-
import
|
|
2876
|
-
import
|
|
3228
|
+
import path29 from "path";
|
|
3229
|
+
import fs16 from "fs-extra";
|
|
2877
3230
|
import chalk7 from "chalk";
|
|
2878
3231
|
import ora7 from "ora";
|
|
2879
3232
|
function gradeFromScore(score) {
|
|
@@ -2914,7 +3267,7 @@ function checkSetup(projectDir, config) {
|
|
|
2914
3267
|
detail: `config v${config.version} \u2260 CLI v${VERSION} \u2014 run \`ai-kit update\``
|
|
2915
3268
|
});
|
|
2916
3269
|
}
|
|
2917
|
-
const claudeMd = readFileSafe(
|
|
3270
|
+
const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
|
|
2918
3271
|
if (claudeMd && claudeMd.includes("AI-KIT:START")) {
|
|
2919
3272
|
checks.push({ name: "CLAUDE.md", status: "pass", detail: "Present with markers" });
|
|
2920
3273
|
} else if (claudeMd) {
|
|
@@ -2922,22 +3275,22 @@ function checkSetup(projectDir, config) {
|
|
|
2922
3275
|
} else {
|
|
2923
3276
|
checks.push({ name: "CLAUDE.md", status: "fail", detail: "Not found" });
|
|
2924
3277
|
}
|
|
2925
|
-
if (fileExists(
|
|
3278
|
+
if (fileExists(path29.join(projectDir, GENERATED_FILES.cursorRules))) {
|
|
2926
3279
|
checks.push({ name: ".cursorrules", status: "pass", detail: "Present" });
|
|
2927
3280
|
} else {
|
|
2928
3281
|
checks.push({ name: ".cursorrules", status: "warn", detail: "Not generated" });
|
|
2929
3282
|
}
|
|
2930
|
-
const skillsDir =
|
|
3283
|
+
const skillsDir = path29.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
2931
3284
|
if (dirExists(skillsDir)) {
|
|
2932
3285
|
const count = config.commands.length;
|
|
2933
3286
|
checks.push({ name: "Skills", status: "pass", detail: `${count} installed` });
|
|
2934
3287
|
} else {
|
|
2935
3288
|
checks.push({ name: "Skills", status: "warn", detail: "No skills directory" });
|
|
2936
3289
|
}
|
|
2937
|
-
const agentsDir =
|
|
3290
|
+
const agentsDir = path29.join(projectDir, GENERATED_FILES.claudeAgents);
|
|
2938
3291
|
if (dirExists(agentsDir)) {
|
|
2939
3292
|
try {
|
|
2940
|
-
const agentFiles =
|
|
3293
|
+
const agentFiles = fs16.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
|
|
2941
3294
|
checks.push({ name: "Agents", status: "pass", detail: `${agentFiles.length} configured` });
|
|
2942
3295
|
} catch {
|
|
2943
3296
|
checks.push({ name: "Agents", status: "warn", detail: "Could not read agents" });
|
|
@@ -2945,7 +3298,7 @@ function checkSetup(projectDir, config) {
|
|
|
2945
3298
|
} else {
|
|
2946
3299
|
checks.push({ name: "Agents", status: "warn", detail: "Not configured" });
|
|
2947
3300
|
}
|
|
2948
|
-
const settingsLocal = readFileSafe(
|
|
3301
|
+
const settingsLocal = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
|
|
2949
3302
|
if (settingsLocal && settingsLocal.includes('"hooks"')) {
|
|
2950
3303
|
checks.push({ name: "Hooks", status: "pass", detail: "Configured" });
|
|
2951
3304
|
} else {
|
|
@@ -2955,7 +3308,7 @@ function checkSetup(projectDir, config) {
|
|
|
2955
3308
|
}
|
|
2956
3309
|
function checkSecurity(projectDir) {
|
|
2957
3310
|
const checks = [];
|
|
2958
|
-
const claudeMd = readFileSafe(
|
|
3311
|
+
const claudeMd = readFileSafe(path29.join(projectDir, GENERATED_FILES.claudeMd));
|
|
2959
3312
|
if (claudeMd) {
|
|
2960
3313
|
const secretPatterns = [
|
|
2961
3314
|
/(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
|
|
@@ -2970,7 +3323,7 @@ function checkSecurity(projectDir) {
|
|
|
2970
3323
|
detail: hasSecrets ? "Potential secrets detected \u2014 remove immediately" : "Clean"
|
|
2971
3324
|
});
|
|
2972
3325
|
}
|
|
2973
|
-
const gitignore = readFileSafe(
|
|
3326
|
+
const gitignore = readFileSafe(path29.join(projectDir, ".gitignore"));
|
|
2974
3327
|
if (gitignore) {
|
|
2975
3328
|
const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
|
|
2976
3329
|
checks.push({
|
|
@@ -2979,7 +3332,7 @@ function checkSecurity(projectDir) {
|
|
|
2979
3332
|
detail: envIgnored ? "Protected" : "NOT gitignored \u2014 add .env to .gitignore"
|
|
2980
3333
|
});
|
|
2981
3334
|
}
|
|
2982
|
-
const settingsJson = readFileSafe(
|
|
3335
|
+
const settingsJson = readFileSafe(path29.join(projectDir, ".claude", "settings.json"));
|
|
2983
3336
|
if (settingsJson) {
|
|
2984
3337
|
const hasHardcoded = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settingsJson);
|
|
2985
3338
|
checks.push({
|
|
@@ -3018,6 +3371,33 @@ function checkStack(config) {
|
|
|
3018
3371
|
detail: scan.monorepoTool || "Detected"
|
|
3019
3372
|
});
|
|
3020
3373
|
}
|
|
3374
|
+
if (scan.staticSite?.isStatic) {
|
|
3375
|
+
checks.push({
|
|
3376
|
+
name: "Static Site",
|
|
3377
|
+
status: "pass",
|
|
3378
|
+
detail: `${scan.staticSite.outputMode} mode`
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
if (scan.designTokens?.detected) {
|
|
3382
|
+
checks.push({
|
|
3383
|
+
name: "Design Tokens",
|
|
3384
|
+
status: "pass",
|
|
3385
|
+
detail: `${scan.designTokens.colors.length} colors, ${scan.designTokens.fonts.length} fonts (${scan.designTokens.source})`
|
|
3386
|
+
});
|
|
3387
|
+
} else if (scan.styling.includes("tailwind")) {
|
|
3388
|
+
checks.push({
|
|
3389
|
+
name: "Design Tokens",
|
|
3390
|
+
status: "warn",
|
|
3391
|
+
detail: "Tailwind detected but no custom tokens found \u2014 AI may use arbitrary values"
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
if (scan.aiIgnorePatterns?.length > 0) {
|
|
3395
|
+
checks.push({
|
|
3396
|
+
name: ".aiignore",
|
|
3397
|
+
status: "pass",
|
|
3398
|
+
detail: `${scan.aiIgnorePatterns.length} patterns loaded`
|
|
3399
|
+
});
|
|
3400
|
+
}
|
|
3021
3401
|
return { title: "Stack Detection", checks };
|
|
3022
3402
|
}
|
|
3023
3403
|
function checkTools(scan) {
|
|
@@ -3061,7 +3441,7 @@ function checkDocs(projectDir) {
|
|
|
3061
3441
|
{ name: "Time Log", path: "docs/time-log.md" }
|
|
3062
3442
|
];
|
|
3063
3443
|
for (const doc of docsToCheck) {
|
|
3064
|
-
const content = readFileSafe(
|
|
3444
|
+
const content = readFileSafe(path29.join(projectDir, doc.path));
|
|
3065
3445
|
if (content) {
|
|
3066
3446
|
const hasEntries = content.includes("## 20") || content.split("---").length > 2;
|
|
3067
3447
|
checks.push({
|
|
@@ -3076,8 +3456,8 @@ function checkDocs(projectDir) {
|
|
|
3076
3456
|
return { title: "Documentation", checks };
|
|
3077
3457
|
}
|
|
3078
3458
|
async function healthCommand(targetPath) {
|
|
3079
|
-
const projectDir =
|
|
3080
|
-
const configPath =
|
|
3459
|
+
const projectDir = path29.resolve(targetPath || process.cwd());
|
|
3460
|
+
const configPath = path29.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
3081
3461
|
console.log("");
|
|
3082
3462
|
logSection("AI Kit \u2014 Project Health");
|
|
3083
3463
|
console.log(chalk7.dim(` ${projectDir}`));
|
|
@@ -3161,8 +3541,8 @@ async function healthCommand(targetPath) {
|
|
|
3161
3541
|
}
|
|
3162
3542
|
|
|
3163
3543
|
// src/commands/patterns.ts
|
|
3164
|
-
import
|
|
3165
|
-
import
|
|
3544
|
+
import path30 from "path";
|
|
3545
|
+
import fs17 from "fs-extra";
|
|
3166
3546
|
import chalk8 from "chalk";
|
|
3167
3547
|
import ora8 from "ora";
|
|
3168
3548
|
function buildPatternCategories() {
|
|
@@ -3246,9 +3626,9 @@ var IGNORE_DIRS = [
|
|
|
3246
3626
|
];
|
|
3247
3627
|
function walkTsFiles(dir, files) {
|
|
3248
3628
|
try {
|
|
3249
|
-
const entries =
|
|
3629
|
+
const entries = fs17.readdirSync(dir, { withFileTypes: true });
|
|
3250
3630
|
for (const entry of entries) {
|
|
3251
|
-
const full =
|
|
3631
|
+
const full = path30.join(dir, entry.name);
|
|
3252
3632
|
if (entry.isDirectory()) {
|
|
3253
3633
|
if (IGNORE_DIRS.includes(entry.name)) continue;
|
|
3254
3634
|
walkTsFiles(full, files);
|
|
@@ -3260,8 +3640,8 @@ function walkTsFiles(dir, files) {
|
|
|
3260
3640
|
}
|
|
3261
3641
|
}
|
|
3262
3642
|
async function patternsCommand(targetPath) {
|
|
3263
|
-
const projectDir =
|
|
3264
|
-
const configPath =
|
|
3643
|
+
const projectDir = path30.resolve(targetPath || process.cwd());
|
|
3644
|
+
const configPath = path30.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
3265
3645
|
console.log("");
|
|
3266
3646
|
logSection("AI Kit \u2014 Pattern Library");
|
|
3267
3647
|
console.log(chalk8.dim(` ${projectDir}`));
|
|
@@ -3272,7 +3652,7 @@ async function patternsCommand(targetPath) {
|
|
|
3272
3652
|
}
|
|
3273
3653
|
const spinner = ora8("Scanning for code patterns...").start();
|
|
3274
3654
|
const files = [];
|
|
3275
|
-
const srcDir =
|
|
3655
|
+
const srcDir = path30.join(projectDir, "src");
|
|
3276
3656
|
if (dirExists(srcDir)) {
|
|
3277
3657
|
walkTsFiles(srcDir, files);
|
|
3278
3658
|
} else {
|
|
@@ -3294,7 +3674,7 @@ async function patternsCommand(targetPath) {
|
|
|
3294
3674
|
if (pattern.regex.test(lines2[i])) {
|
|
3295
3675
|
pattern.matches.push({
|
|
3296
3676
|
pattern: pattern.label,
|
|
3297
|
-
file:
|
|
3677
|
+
file: path30.relative(projectDir, file),
|
|
3298
3678
|
line: i + 1
|
|
3299
3679
|
});
|
|
3300
3680
|
}
|
|
@@ -3322,9 +3702,9 @@ async function patternsCommand(targetPath) {
|
|
|
3322
3702
|
logInfo("No recognizable patterns found.");
|
|
3323
3703
|
return;
|
|
3324
3704
|
}
|
|
3325
|
-
const outputDir =
|
|
3326
|
-
|
|
3327
|
-
const outputPath =
|
|
3705
|
+
const outputDir = path30.join(projectDir, "ai-kit");
|
|
3706
|
+
fs17.ensureDirSync(outputDir);
|
|
3707
|
+
const outputPath = path30.join(outputDir, "patterns.md");
|
|
3328
3708
|
const lines = [
|
|
3329
3709
|
"# Code Patterns",
|
|
3330
3710
|
"",
|
|
@@ -3357,7 +3737,7 @@ async function patternsCommand(targetPath) {
|
|
|
3357
3737
|
lines.push("");
|
|
3358
3738
|
}
|
|
3359
3739
|
}
|
|
3360
|
-
|
|
3740
|
+
fs17.writeFileSync(outputPath, lines.join("\n"), "utf-8");
|
|
3361
3741
|
console.log("");
|
|
3362
3742
|
logSuccess(`Pattern library written to ${chalk8.cyan("ai-kit/patterns.md")}`);
|
|
3363
3743
|
logInfo(`Total: ${chalk8.bold(String(totalPatterns))} pattern occurrences across ${chalk8.bold(String(files.length))} files`);
|
|
@@ -3365,14 +3745,14 @@ async function patternsCommand(targetPath) {
|
|
|
3365
3745
|
}
|
|
3366
3746
|
|
|
3367
3747
|
// src/commands/dead-code.ts
|
|
3368
|
-
import
|
|
3369
|
-
import
|
|
3748
|
+
import path32 from "path";
|
|
3749
|
+
import fs19 from "fs-extra";
|
|
3370
3750
|
import chalk9 from "chalk";
|
|
3371
3751
|
import ora9 from "ora";
|
|
3372
3752
|
|
|
3373
3753
|
// src/scanner/components.ts
|
|
3374
|
-
import
|
|
3375
|
-
import
|
|
3754
|
+
import path31 from "path";
|
|
3755
|
+
import fs18 from "fs-extra";
|
|
3376
3756
|
var COMPONENT_DIRS = [
|
|
3377
3757
|
"src/components",
|
|
3378
3758
|
"src/Components",
|
|
@@ -3399,7 +3779,7 @@ function findComponentFiles(projectPath) {
|
|
|
3399
3779
|
const files = [];
|
|
3400
3780
|
const directories = /* @__PURE__ */ new Set();
|
|
3401
3781
|
for (const dir of COMPONENT_DIRS) {
|
|
3402
|
-
const fullDir =
|
|
3782
|
+
const fullDir = path31.join(projectPath, dir);
|
|
3403
3783
|
if (dirExists(fullDir)) {
|
|
3404
3784
|
walkForComponents(fullDir, files, directories);
|
|
3405
3785
|
}
|
|
@@ -3408,9 +3788,9 @@ function findComponentFiles(projectPath) {
|
|
|
3408
3788
|
}
|
|
3409
3789
|
function walkForComponents(dir, files, directories) {
|
|
3410
3790
|
try {
|
|
3411
|
-
const entries =
|
|
3791
|
+
const entries = fs18.readdirSync(dir, { withFileTypes: true });
|
|
3412
3792
|
for (const entry of entries) {
|
|
3413
|
-
const full =
|
|
3793
|
+
const full = path31.join(dir, entry.name);
|
|
3414
3794
|
if (entry.isDirectory()) {
|
|
3415
3795
|
if (IGNORE_PATTERNS.includes(entry.name)) continue;
|
|
3416
3796
|
walkForComponents(full, files, directories);
|
|
@@ -3450,7 +3830,7 @@ function extractComponentName(filePath, content) {
|
|
|
3450
3830
|
/export\s+(?:const|function)\s+(\w+)/
|
|
3451
3831
|
);
|
|
3452
3832
|
if (namedExport) return namedExport[1];
|
|
3453
|
-
return
|
|
3833
|
+
return path31.basename(filePath, ".tsx");
|
|
3454
3834
|
}
|
|
3455
3835
|
function extractExportType(content) {
|
|
3456
3836
|
const hasDefault = /export\s+default\s/.test(content);
|
|
@@ -3539,7 +3919,7 @@ function extractDependencies(content) {
|
|
|
3539
3919
|
for (const match of imports) {
|
|
3540
3920
|
const importPath = match[1];
|
|
3541
3921
|
if (importPath.startsWith(".") || importPath.startsWith("..")) {
|
|
3542
|
-
const basename =
|
|
3922
|
+
const basename = path31.basename(importPath).replace(/\.\w+$/, "");
|
|
3543
3923
|
if (/^[A-Z]/.test(basename)) {
|
|
3544
3924
|
deps.push(basename);
|
|
3545
3925
|
}
|
|
@@ -3548,33 +3928,33 @@ function extractDependencies(content) {
|
|
|
3548
3928
|
return deps;
|
|
3549
3929
|
}
|
|
3550
3930
|
function checkForTests(componentPath, componentName) {
|
|
3551
|
-
const dir =
|
|
3552
|
-
const base =
|
|
3931
|
+
const dir = path31.dirname(componentPath);
|
|
3932
|
+
const base = path31.basename(componentPath, ".tsx");
|
|
3553
3933
|
for (const suffix of TEST_SUFFIXES) {
|
|
3554
|
-
if (
|
|
3934
|
+
if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
|
|
3555
3935
|
}
|
|
3556
|
-
const testsDir =
|
|
3936
|
+
const testsDir = path31.join(dir, "__tests__");
|
|
3557
3937
|
if (dirExists(testsDir)) {
|
|
3558
3938
|
for (const suffix of TEST_SUFFIXES) {
|
|
3559
|
-
if (
|
|
3560
|
-
if (
|
|
3939
|
+
if (fs18.existsSync(path31.join(testsDir, base + suffix))) return true;
|
|
3940
|
+
if (fs18.existsSync(path31.join(testsDir, componentName + suffix))) return true;
|
|
3561
3941
|
}
|
|
3562
3942
|
}
|
|
3563
3943
|
return false;
|
|
3564
3944
|
}
|
|
3565
3945
|
function checkForStory(componentPath, componentName) {
|
|
3566
|
-
const dir =
|
|
3567
|
-
const base =
|
|
3946
|
+
const dir = path31.dirname(componentPath);
|
|
3947
|
+
const base = path31.basename(componentPath, ".tsx");
|
|
3568
3948
|
for (const suffix of STORY_SUFFIXES) {
|
|
3569
|
-
if (
|
|
3570
|
-
if (
|
|
3949
|
+
if (fs18.existsSync(path31.join(dir, base + suffix))) return true;
|
|
3950
|
+
if (fs18.existsSync(path31.join(dir, componentName + suffix))) return true;
|
|
3571
3951
|
}
|
|
3572
3952
|
return false;
|
|
3573
3953
|
}
|
|
3574
3954
|
function checkForAiDoc(componentPath) {
|
|
3575
|
-
const dir =
|
|
3576
|
-
const base =
|
|
3577
|
-
return
|
|
3955
|
+
const dir = path31.dirname(componentPath);
|
|
3956
|
+
const base = path31.basename(componentPath, ".tsx");
|
|
3957
|
+
return fs18.existsSync(path31.join(dir, `${base}.ai.md`)) || fs18.existsSync(path31.join(dir, "component.ai.md"));
|
|
3578
3958
|
}
|
|
3579
3959
|
function categorizeComponent(relativePath) {
|
|
3580
3960
|
const lower = relativePath.toLowerCase();
|
|
@@ -3592,7 +3972,7 @@ function parseComponent(filePath, projectPath) {
|
|
|
3592
3972
|
const content = readFileSafe(filePath);
|
|
3593
3973
|
if (!content) return null;
|
|
3594
3974
|
const name = extractComponentName(filePath, content);
|
|
3595
|
-
const relativePath =
|
|
3975
|
+
const relativePath = path31.relative(projectPath, filePath);
|
|
3596
3976
|
return {
|
|
3597
3977
|
name,
|
|
3598
3978
|
filePath,
|
|
@@ -3638,9 +4018,9 @@ var IGNORE_DIRS2 = [
|
|
|
3638
4018
|
];
|
|
3639
4019
|
function collectAllFiles(dir, files) {
|
|
3640
4020
|
try {
|
|
3641
|
-
const entries =
|
|
4021
|
+
const entries = fs19.readdirSync(dir, { withFileTypes: true });
|
|
3642
4022
|
for (const entry of entries) {
|
|
3643
|
-
const full =
|
|
4023
|
+
const full = path32.join(dir, entry.name);
|
|
3644
4024
|
if (entry.isDirectory()) {
|
|
3645
4025
|
if (IGNORE_DIRS2.includes(entry.name)) continue;
|
|
3646
4026
|
collectAllFiles(full, files);
|
|
@@ -3652,12 +4032,12 @@ function collectAllFiles(dir, files) {
|
|
|
3652
4032
|
}
|
|
3653
4033
|
}
|
|
3654
4034
|
function isTestOrStoryFile(filePath) {
|
|
3655
|
-
const name =
|
|
4035
|
+
const name = path32.basename(filePath).toLowerCase();
|
|
3656
4036
|
return name.includes(".test.") || name.includes(".spec.") || name.includes(".stories.") || name.includes("__tests__") || name.includes("__mocks__");
|
|
3657
4037
|
}
|
|
3658
4038
|
async function deadCodeCommand(targetPath) {
|
|
3659
|
-
const projectDir =
|
|
3660
|
-
const configPath =
|
|
4039
|
+
const projectDir = path32.resolve(targetPath || process.cwd());
|
|
4040
|
+
const configPath = path32.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
3661
4041
|
console.log("");
|
|
3662
4042
|
logSection("AI Kit \u2014 Dead Code Report");
|
|
3663
4043
|
console.log(chalk9.dim(` ${projectDir}`));
|
|
@@ -3674,8 +4054,8 @@ async function deadCodeCommand(targetPath) {
|
|
|
3674
4054
|
}
|
|
3675
4055
|
spinner.text = `Found ${scanResult.components.length} components. Checking imports...`;
|
|
3676
4056
|
const allFiles = [];
|
|
3677
|
-
const srcDir =
|
|
3678
|
-
if (
|
|
4057
|
+
const srcDir = path32.join(projectDir, "src");
|
|
4058
|
+
if (fs19.existsSync(srcDir)) {
|
|
3679
4059
|
collectAllFiles(srcDir, allFiles);
|
|
3680
4060
|
} else {
|
|
3681
4061
|
collectAllFiles(projectDir, allFiles);
|
|
@@ -3698,7 +4078,7 @@ async function deadCodeCommand(targetPath) {
|
|
|
3698
4078
|
`(?:import|from)\\s+.*['"][^'"]*(?:/|\\b)${escapeRegex(component.name)}(?:[/'"]|\\b)`
|
|
3699
4079
|
);
|
|
3700
4080
|
for (const [file, content] of fileContents) {
|
|
3701
|
-
if (
|
|
4081
|
+
if (path32.resolve(file) === path32.resolve(componentFile)) continue;
|
|
3702
4082
|
if (namePattern.test(content)) {
|
|
3703
4083
|
importCount++;
|
|
3704
4084
|
if (isTestOrStoryFile(file)) {
|
|
@@ -3777,8 +4157,8 @@ function escapeRegex(str) {
|
|
|
3777
4157
|
}
|
|
3778
4158
|
|
|
3779
4159
|
// src/commands/drift.ts
|
|
3780
|
-
import
|
|
3781
|
-
import
|
|
4160
|
+
import path33 from "path";
|
|
4161
|
+
import fs20 from "fs-extra";
|
|
3782
4162
|
import chalk10 from "chalk";
|
|
3783
4163
|
import ora10 from "ora";
|
|
3784
4164
|
function parseAiDocFrontmatter(content) {
|
|
@@ -3814,14 +4194,14 @@ function parseAiDocFrontmatter(content) {
|
|
|
3814
4194
|
return { props, fields };
|
|
3815
4195
|
}
|
|
3816
4196
|
function findAiDocPath(componentPath) {
|
|
3817
|
-
const dir =
|
|
3818
|
-
const base =
|
|
4197
|
+
const dir = path33.dirname(componentPath);
|
|
4198
|
+
const base = path33.basename(componentPath, ".tsx");
|
|
3819
4199
|
const candidates = [
|
|
3820
|
-
|
|
3821
|
-
|
|
4200
|
+
path33.join(dir, `${base}.ai.md`),
|
|
4201
|
+
path33.join(dir, "component.ai.md")
|
|
3822
4202
|
];
|
|
3823
4203
|
for (const candidate of candidates) {
|
|
3824
|
-
if (
|
|
4204
|
+
if (fs20.existsSync(candidate)) return candidate;
|
|
3825
4205
|
}
|
|
3826
4206
|
return null;
|
|
3827
4207
|
}
|
|
@@ -3862,8 +4242,8 @@ function analyzeDrift(component, projectDir) {
|
|
|
3862
4242
|
};
|
|
3863
4243
|
}
|
|
3864
4244
|
async function driftCommand(targetPath) {
|
|
3865
|
-
const projectDir =
|
|
3866
|
-
const configPath =
|
|
4245
|
+
const projectDir = path33.resolve(targetPath || process.cwd());
|
|
4246
|
+
const configPath = path33.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
3867
4247
|
console.log("");
|
|
3868
4248
|
logSection("AI Kit \u2014 Component Drift Detector");
|
|
3869
4249
|
console.log(chalk10.dim(` ${projectDir}`));
|
|
@@ -3964,6 +4344,144 @@ async function driftCommand(targetPath) {
|
|
|
3964
4344
|
console.log("");
|
|
3965
4345
|
}
|
|
3966
4346
|
|
|
4347
|
+
// src/commands/component-registry.ts
|
|
4348
|
+
import path34 from "path";
|
|
4349
|
+
import fs22 from "fs-extra";
|
|
4350
|
+
import chalk11 from "chalk";
|
|
4351
|
+
import ora11 from "ora";
|
|
4352
|
+
|
|
4353
|
+
// src/generator/component-docs.ts
|
|
4354
|
+
import fs21 from "fs-extra";
|
|
4355
|
+
function calculateHealthScore(component) {
|
|
4356
|
+
let score = 0;
|
|
4357
|
+
const max = 100;
|
|
4358
|
+
if (component.props.length > 0) score += 15;
|
|
4359
|
+
if (component.hasTests) score += 25;
|
|
4360
|
+
if (component.hasStory) score += 20;
|
|
4361
|
+
if (component.hasAiDoc) score += 15;
|
|
4362
|
+
const sc = component.sitecore;
|
|
4363
|
+
if (sc.datasourceFields.length > 0 || sc.renderingParams.length > 0 || sc.placeholders.length > 0) {
|
|
4364
|
+
score += 10;
|
|
4365
|
+
}
|
|
4366
|
+
if (component.dependencies.length > 0) score += 5;
|
|
4367
|
+
if (component.exportType === "default" || component.exportType === "both") score += 10;
|
|
4368
|
+
return Math.min(score, max);
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
// src/commands/component-registry.ts
|
|
4372
|
+
async function componentRegistryCommand(targetPath) {
|
|
4373
|
+
const projectDir = path34.resolve(targetPath || process.cwd());
|
|
4374
|
+
console.log("");
|
|
4375
|
+
logSection("AI Kit \u2014 Component Registry");
|
|
4376
|
+
console.log(chalk11.dim(` ${projectDir}`));
|
|
4377
|
+
console.log("");
|
|
4378
|
+
if (!fileExists(path34.join(projectDir, AI_KIT_CONFIG_FILE))) {
|
|
4379
|
+
logWarning("ai-kit.config.json not found. Run `ai-kit init` first.");
|
|
4380
|
+
return;
|
|
4381
|
+
}
|
|
4382
|
+
const spinner = ora11("Scanning components...").start();
|
|
4383
|
+
const scanResult = scanComponents(projectDir);
|
|
4384
|
+
if (scanResult.components.length === 0) {
|
|
4385
|
+
spinner.fail("No components found.");
|
|
4386
|
+
return;
|
|
4387
|
+
}
|
|
4388
|
+
spinner.succeed(`Found ${scanResult.components.length} components in ${scanResult.directories.length} directories`);
|
|
4389
|
+
const entries = scanResult.components.map((c) => ({
|
|
4390
|
+
name: c.name,
|
|
4391
|
+
path: c.relativePath,
|
|
4392
|
+
category: c.category,
|
|
4393
|
+
props: c.props,
|
|
4394
|
+
exportType: c.exportType,
|
|
4395
|
+
dependencies: c.dependencies,
|
|
4396
|
+
sitecore: {
|
|
4397
|
+
datasourceFields: c.sitecore.datasourceFields,
|
|
4398
|
+
renderingParams: c.sitecore.renderingParams,
|
|
4399
|
+
placeholders: c.sitecore.placeholders
|
|
4400
|
+
},
|
|
4401
|
+
health: {
|
|
4402
|
+
score: calculateHealthScore(c),
|
|
4403
|
+
hasTests: c.hasTests,
|
|
4404
|
+
hasStory: c.hasStory,
|
|
4405
|
+
hasAiDoc: c.hasAiDoc
|
|
4406
|
+
}
|
|
4407
|
+
}));
|
|
4408
|
+
const categories = {};
|
|
4409
|
+
for (const entry of entries) {
|
|
4410
|
+
categories[entry.category] = (categories[entry.category] || 0) + 1;
|
|
4411
|
+
}
|
|
4412
|
+
const pkgPath = path34.join(projectDir, "package.json");
|
|
4413
|
+
const pkg = fs22.readJsonSync(pkgPath, { throws: false }) || {};
|
|
4414
|
+
const registry = {
|
|
4415
|
+
version: "1.0.0",
|
|
4416
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4417
|
+
projectName: pkg.name || path34.basename(projectDir),
|
|
4418
|
+
totalComponents: entries.length,
|
|
4419
|
+
categories,
|
|
4420
|
+
components: entries
|
|
4421
|
+
};
|
|
4422
|
+
const outputPath = path34.join(projectDir, "ai-kit", "component-registry.json");
|
|
4423
|
+
await fs22.ensureDir(path34.dirname(outputPath));
|
|
4424
|
+
await fs22.writeJson(outputPath, registry, { spaces: 2 });
|
|
4425
|
+
const mdPath = path34.join(projectDir, "ai-kit", "component-registry.md");
|
|
4426
|
+
const md = generateRegistryMarkdown(registry);
|
|
4427
|
+
await fs22.writeFile(mdPath, md, "utf-8");
|
|
4428
|
+
console.log("");
|
|
4429
|
+
console.log(` ${chalk11.bold("Categories:")}`);
|
|
4430
|
+
for (const [cat, count] of Object.entries(categories).sort((a, b) => b[1] - a[1])) {
|
|
4431
|
+
console.log(` ${chalk11.cyan(String(count).padStart(3))} ${cat}`);
|
|
4432
|
+
}
|
|
4433
|
+
const avgHealth = Math.round(entries.reduce((sum, e) => sum + e.health.score, 0) / entries.length);
|
|
4434
|
+
const withTests = entries.filter((e) => e.health.hasTests).length;
|
|
4435
|
+
const withStories = entries.filter((e) => e.health.hasStory).length;
|
|
4436
|
+
const withDocs = entries.filter((e) => e.health.hasAiDoc).length;
|
|
4437
|
+
console.log("");
|
|
4438
|
+
console.log(` ${chalk11.bold("Health:")}`);
|
|
4439
|
+
console.log(` Average score: ${chalk11.cyan(String(avgHealth))}/100`);
|
|
4440
|
+
console.log(` With tests: ${chalk11.cyan(String(withTests))}/${entries.length}`);
|
|
4441
|
+
console.log(` With stories: ${chalk11.cyan(String(withStories))}/${entries.length}`);
|
|
4442
|
+
console.log(` With .ai.md docs: ${chalk11.cyan(String(withDocs))}/${entries.length}`);
|
|
4443
|
+
console.log("");
|
|
4444
|
+
logSuccess(`Registry written to ${chalk11.cyan("ai-kit/component-registry.json")}`);
|
|
4445
|
+
logSuccess(`Summary written to ${chalk11.cyan("ai-kit/component-registry.md")}`);
|
|
4446
|
+
logInfo("AI agents can use this registry to discover existing components before creating new ones.");
|
|
4447
|
+
console.log("");
|
|
4448
|
+
}
|
|
4449
|
+
function generateRegistryMarkdown(registry) {
|
|
4450
|
+
const lines = [
|
|
4451
|
+
"# Component Registry",
|
|
4452
|
+
"",
|
|
4453
|
+
`> Auto-generated by ai-kit on ${registry.generatedAt.split("T")[0]}`,
|
|
4454
|
+
`> ${registry.totalComponents} components in ${registry.projectName}`,
|
|
4455
|
+
"",
|
|
4456
|
+
"## How to Use",
|
|
4457
|
+
"",
|
|
4458
|
+
"Before creating a new component, check this registry to see if one already exists.",
|
|
4459
|
+
"AI agents should reference this file to avoid duplicating components.",
|
|
4460
|
+
""
|
|
4461
|
+
];
|
|
4462
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
4463
|
+
for (const comp of registry.components) {
|
|
4464
|
+
const list = grouped.get(comp.category) || [];
|
|
4465
|
+
list.push(comp);
|
|
4466
|
+
grouped.set(comp.category, list);
|
|
4467
|
+
}
|
|
4468
|
+
for (const [category, components] of grouped) {
|
|
4469
|
+
lines.push(`## ${category.charAt(0).toUpperCase() + category.slice(1)} (${components.length})`);
|
|
4470
|
+
lines.push("");
|
|
4471
|
+
lines.push("| Component | Path | Props | Tests | Story | Health |");
|
|
4472
|
+
lines.push("|-----------|------|-------|-------|-------|--------|");
|
|
4473
|
+
for (const comp of components) {
|
|
4474
|
+
const propsCount = comp.props.length;
|
|
4475
|
+
const tests = comp.health.hasTests ? "Yes" : "No";
|
|
4476
|
+
const story = comp.health.hasStory ? "Yes" : "No";
|
|
4477
|
+
const health = `${comp.health.score}/100`;
|
|
4478
|
+
lines.push(`| ${comp.name} | \`${comp.path}\` | ${propsCount} | ${tests} | ${story} | ${health} |`);
|
|
4479
|
+
}
|
|
4480
|
+
lines.push("");
|
|
4481
|
+
}
|
|
4482
|
+
return lines.join("\n");
|
|
4483
|
+
}
|
|
4484
|
+
|
|
3967
4485
|
// src/index.ts
|
|
3968
4486
|
var program = new Command();
|
|
3969
4487
|
program.name("ai-kit").description(
|
|
@@ -4112,5 +4630,16 @@ program.command("drift").description("Detect drift between component code and .a
|
|
|
4112
4630
|
process.exit(1);
|
|
4113
4631
|
}
|
|
4114
4632
|
});
|
|
4633
|
+
program.command("component-registry").description("Generate a component registry for AI agent discovery").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
|
|
4634
|
+
try {
|
|
4635
|
+
await componentRegistryCommand(targetPath);
|
|
4636
|
+
} catch (err) {
|
|
4637
|
+
if (err.name === "ExitPromptError") {
|
|
4638
|
+
process.exit(0);
|
|
4639
|
+
}
|
|
4640
|
+
console.error(err);
|
|
4641
|
+
process.exit(1);
|
|
4642
|
+
}
|
|
4643
|
+
});
|
|
4115
4644
|
program.parse();
|
|
4116
4645
|
//# sourceMappingURL=index.js.map
|