@mikulgohil/ai-kit 1.1.0 → 1.2.1
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 +85 -12
- package/agents/build-resolver.md +54 -0
- package/agents/code-reviewer.md +60 -0
- package/agents/doc-updater.md +46 -0
- package/agents/e2e-runner.md +64 -0
- package/agents/planner.md +56 -0
- package/agents/refactor-cleaner.md +58 -0
- package/agents/security-reviewer.md +67 -0
- package/agents/sitecore-specialist.md +65 -0
- package/commands/checkpoint.md +78 -0
- package/commands/harness-audit.md +73 -0
- package/commands/orchestrate.md +67 -0
- package/commands/quality-gate.md +82 -0
- package/commands/resume-session.md +40 -0
- package/commands/save-session.md +65 -0
- package/contexts/dev.md +35 -0
- package/contexts/research.md +56 -0
- package/contexts/review.md +49 -0
- package/dist/index.js +509 -112
- package/dist/index.js.map +1 -1
- package/guides/getting-started.md +74 -21
- package/guides/hooks-and-agents.md +124 -0
- package/package.json +9 -4
- package/commands/ci-fix.md +0 -102
- package/commands/db-migrate.md +0 -138
- package/commands/dependency-graph.md +0 -138
- package/commands/docker-debug.md +0 -111
- package/commands/visual-diff.md +0 -131
package/dist/index.js
CHANGED
|
@@ -13,15 +13,20 @@ var TEMPLATES_DIR = path.join(PACKAGE_ROOT, "templates");
|
|
|
13
13
|
var COMMANDS_DIR = path.join(PACKAGE_ROOT, "commands");
|
|
14
14
|
var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
|
|
15
15
|
var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
|
|
16
|
-
var
|
|
16
|
+
var AGENTS_DIR = path.join(PACKAGE_ROOT, "agents");
|
|
17
|
+
var CONTEXTS_DIR = path.join(PACKAGE_ROOT, "contexts");
|
|
18
|
+
var VERSION = "1.2.0";
|
|
17
19
|
var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
|
|
18
20
|
var GENERATED_FILES = {
|
|
19
21
|
claudeMd: "CLAUDE.md",
|
|
20
22
|
cursorRules: ".cursorrules",
|
|
21
23
|
cursorMdcDir: ".cursor/rules",
|
|
22
24
|
claudeSettings: ".claude/settings.json",
|
|
25
|
+
claudeSettingsLocal: ".claude/settings.local.json",
|
|
23
26
|
claudeCommands: ".claude/commands",
|
|
24
27
|
claudeSkills: ".claude/skills",
|
|
28
|
+
claudeAgents: ".claude/agents",
|
|
29
|
+
claudeContexts: ".claude/contexts",
|
|
25
30
|
cursorSkills: ".cursor/skills"
|
|
26
31
|
};
|
|
27
32
|
var TEMPLATE_FRAGMENTS = [
|
|
@@ -36,8 +41,8 @@ var TEMPLATE_FRAGMENTS = [
|
|
|
36
41
|
];
|
|
37
42
|
|
|
38
43
|
// src/commands/init.ts
|
|
39
|
-
import
|
|
40
|
-
import
|
|
44
|
+
import path17 from "path";
|
|
45
|
+
import fs8 from "fs-extra";
|
|
41
46
|
import { select, confirm } from "@inquirer/prompts";
|
|
42
47
|
import ora from "ora";
|
|
43
48
|
|
|
@@ -307,6 +312,7 @@ function detectTools(projectPath, pkg) {
|
|
|
307
312
|
storybook: detectStorybook(projectPath, deps),
|
|
308
313
|
eslint: detectEslint(projectPath, deps),
|
|
309
314
|
prettier: detectPrettier(projectPath, deps),
|
|
315
|
+
biome: detectBiome(projectPath, deps),
|
|
310
316
|
axeCore: detectAxeCore(deps),
|
|
311
317
|
snyk: detectSnyk(projectPath, deps),
|
|
312
318
|
knip: detectKnip(projectPath, deps),
|
|
@@ -362,6 +368,12 @@ function detectPrettier(projectPath, deps) {
|
|
|
362
368
|
}
|
|
363
369
|
return false;
|
|
364
370
|
}
|
|
371
|
+
function detectBiome(projectPath, deps) {
|
|
372
|
+
if ("@biomejs/biome" in deps) return true;
|
|
373
|
+
if (fileExists(path8.join(projectPath, "biome.json"))) return true;
|
|
374
|
+
if (fileExists(path8.join(projectPath, "biome.jsonc"))) return true;
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
365
377
|
function detectAxeCore(deps) {
|
|
366
378
|
return "@axe-core/playwright" in deps || "axe-core" in deps;
|
|
367
379
|
}
|
|
@@ -675,11 +687,107 @@ function generateConfig(scan, templates, commands, guides, options) {
|
|
|
675
687
|
templates,
|
|
676
688
|
commands,
|
|
677
689
|
guides,
|
|
690
|
+
agents: options?.agents || [],
|
|
691
|
+
contexts: options?.contexts || [],
|
|
692
|
+
hooks: options?.hooks || false,
|
|
693
|
+
hookProfile: options?.hookProfile || "standard",
|
|
678
694
|
strictness: options?.strictness || "standard",
|
|
679
695
|
customFragments: options?.customFragments || []
|
|
680
696
|
};
|
|
681
697
|
}
|
|
682
698
|
|
|
699
|
+
// src/generator/hooks.ts
|
|
700
|
+
function generateHooks(scan, profile = "standard") {
|
|
701
|
+
const hooks = {};
|
|
702
|
+
const preToolUse = [];
|
|
703
|
+
const postToolUse = [];
|
|
704
|
+
const stop = [];
|
|
705
|
+
preToolUse.push({
|
|
706
|
+
matcher: "Bash(git push*)",
|
|
707
|
+
hooks: [
|
|
708
|
+
{
|
|
709
|
+
type: "command",
|
|
710
|
+
command: 'echo "\u26A0\uFE0F Review your changes before pushing. Run tests and type-check first."'
|
|
711
|
+
}
|
|
712
|
+
]
|
|
713
|
+
});
|
|
714
|
+
if (scan.tools.biome) {
|
|
715
|
+
postToolUse.push({
|
|
716
|
+
matcher: "Edit|Write",
|
|
717
|
+
hooks: [
|
|
718
|
+
{
|
|
719
|
+
type: "command",
|
|
720
|
+
command: `npx @biomejs/biome check --write --unsafe "$CLAUDE_FILE_PATH" 2>/dev/null || true`
|
|
721
|
+
}
|
|
722
|
+
]
|
|
723
|
+
});
|
|
724
|
+
} else if (scan.tools.prettier) {
|
|
725
|
+
postToolUse.push({
|
|
726
|
+
matcher: "Edit|Write",
|
|
727
|
+
hooks: [
|
|
728
|
+
{
|
|
729
|
+
type: "command",
|
|
730
|
+
command: `npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true`
|
|
731
|
+
}
|
|
732
|
+
]
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
if (scan.typescript && profile !== "minimal") {
|
|
736
|
+
postToolUse.push({
|
|
737
|
+
matcher: "Edit|Write",
|
|
738
|
+
hooks: [
|
|
739
|
+
{
|
|
740
|
+
type: "command",
|
|
741
|
+
command: 'case "$CLAUDE_FILE_PATH" in *.ts|*.tsx) npx tsc --noEmit --pretty 2>&1 | head -20 ;; esac'
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (scan.tools.eslint && profile === "strict") {
|
|
747
|
+
postToolUse.push({
|
|
748
|
+
matcher: "Edit|Write",
|
|
749
|
+
hooks: [
|
|
750
|
+
{
|
|
751
|
+
type: "command",
|
|
752
|
+
command: 'case "$CLAUDE_FILE_PATH" in *.ts|*.tsx|*.js|*.jsx) npx eslint "$CLAUDE_FILE_PATH" --max-warnings 0 2>&1 | head -15 ;; esac'
|
|
753
|
+
}
|
|
754
|
+
]
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
if (profile !== "minimal") {
|
|
758
|
+
postToolUse.push({
|
|
759
|
+
matcher: "Edit|Write",
|
|
760
|
+
hooks: [
|
|
761
|
+
{
|
|
762
|
+
type: "command",
|
|
763
|
+
command: 'case "$CLAUDE_FILE_PATH" in *.ts|*.tsx|*.js|*.jsx) grep -n "console\\.log" "$CLAUDE_FILE_PATH" && echo "\u26A0\uFE0F console.log detected \u2014 remove before committing" || true ;; esac'
|
|
764
|
+
}
|
|
765
|
+
]
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (profile === "strict") {
|
|
769
|
+
stop.push({
|
|
770
|
+
matcher: "",
|
|
771
|
+
hooks: [
|
|
772
|
+
{
|
|
773
|
+
type: "command",
|
|
774
|
+
command: 'git diff --name-only --diff-filter=M 2>/dev/null | grep -E "\\.(ts|tsx|js|jsx)$" | xargs grep -l "console\\.log" 2>/dev/null && echo "\u26A0\uFE0F console.log found in modified files" || true'
|
|
775
|
+
}
|
|
776
|
+
]
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
|
|
780
|
+
if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
|
|
781
|
+
if (stop.length > 0) hooks.Stop = stop;
|
|
782
|
+
return hooks;
|
|
783
|
+
}
|
|
784
|
+
function generateSettingsLocal(scan, profile = "standard") {
|
|
785
|
+
const hooks = generateHooks(scan, profile);
|
|
786
|
+
return {
|
|
787
|
+
hooks
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
683
791
|
// src/copier/skills.ts
|
|
684
792
|
import path12 from "path";
|
|
685
793
|
import fs3 from "fs-extra";
|
|
@@ -715,14 +823,16 @@ var AVAILABLE_SKILLS = [
|
|
|
715
823
|
"bundle-check",
|
|
716
824
|
"i18n-check",
|
|
717
825
|
"schema-gen",
|
|
718
|
-
"docker-debug",
|
|
719
|
-
"ci-fix",
|
|
720
826
|
"changelog",
|
|
721
827
|
"release",
|
|
722
828
|
"storybook-gen",
|
|
723
|
-
|
|
724
|
-
"
|
|
725
|
-
"
|
|
829
|
+
// New skills (v1.2.0) — hooks, agents, sessions, orchestration
|
|
830
|
+
"save-session",
|
|
831
|
+
"resume-session",
|
|
832
|
+
"checkpoint",
|
|
833
|
+
"orchestrate",
|
|
834
|
+
"quality-gate",
|
|
835
|
+
"harness-audit"
|
|
726
836
|
];
|
|
727
837
|
var SKILL_DESCRIPTIONS = {
|
|
728
838
|
"prompt-help": "Help developers write effective AI prompts with structured context",
|
|
@@ -756,14 +866,16 @@ var SKILL_DESCRIPTIONS = {
|
|
|
756
866
|
"bundle-check": "Analyze bundle size, find heavy imports, suggest tree-shaking and code splitting",
|
|
757
867
|
"i18n-check": "Find hardcoded strings, missing translation keys, and internationalization gaps",
|
|
758
868
|
"schema-gen": "Generate TypeScript types and Zod schemas from API responses, JSON, or GraphQL",
|
|
759
|
-
"docker-debug": "Analyze Dockerfiles for security, efficiency, and best practice violations",
|
|
760
|
-
"ci-fix": "Debug CI/CD pipeline failures, optimize caching, and improve build performance",
|
|
761
869
|
"changelog": "Generate formatted changelogs from git history following Keep a Changelog format",
|
|
762
870
|
"release": "Guided release workflow with versioning, changelog, tagging, and release notes",
|
|
763
871
|
"storybook-gen": "Generate Storybook stories with controls, play functions, and visual tests",
|
|
764
|
-
|
|
765
|
-
"
|
|
766
|
-
"
|
|
872
|
+
// New skills (v1.2.0) — hooks, agents, sessions, orchestration
|
|
873
|
+
"save-session": "Persist current session context, decisions, and pending work for later resumption",
|
|
874
|
+
"resume-session": "Restore context from a previous session and continue where you left off",
|
|
875
|
+
"checkpoint": "Create a verification snapshot \u2014 run all quality checks and record pass/fail status",
|
|
876
|
+
"orchestrate": "Multi-agent orchestration \u2014 break complex tasks into subtasks and delegate to agents",
|
|
877
|
+
"quality-gate": "Run comprehensive quality checks: types, lint, format, tests, bundle, a11y, security",
|
|
878
|
+
"harness-audit": "Audit AI agent configuration \u2014 check CLAUDE.md, hooks, agents, skills, MCP servers"
|
|
767
879
|
};
|
|
768
880
|
async function copySkills(targetDir) {
|
|
769
881
|
const copied = [];
|
|
@@ -802,7 +914,8 @@ var AVAILABLE_GUIDES = [
|
|
|
802
914
|
"prompt-playbook",
|
|
803
915
|
"when-to-use-ai",
|
|
804
916
|
"token-saving-tips",
|
|
805
|
-
"figma-workflow"
|
|
917
|
+
"figma-workflow",
|
|
918
|
+
"hooks-and-agents"
|
|
806
919
|
];
|
|
807
920
|
async function copyGuides(targetDir) {
|
|
808
921
|
const guidesTarget = path13.join(targetDir, "ai-kit", "guides");
|
|
@@ -841,12 +954,76 @@ async function scaffoldDocs(targetDir) {
|
|
|
841
954
|
return created;
|
|
842
955
|
}
|
|
843
956
|
|
|
957
|
+
// src/copier/agents.ts
|
|
958
|
+
import path15 from "path";
|
|
959
|
+
import fs6 from "fs-extra";
|
|
960
|
+
var UNIVERSAL_AGENTS = [
|
|
961
|
+
"planner",
|
|
962
|
+
"code-reviewer",
|
|
963
|
+
"security-reviewer",
|
|
964
|
+
"build-resolver",
|
|
965
|
+
"doc-updater",
|
|
966
|
+
"refactor-cleaner"
|
|
967
|
+
];
|
|
968
|
+
var CONDITIONAL_AGENTS = [
|
|
969
|
+
{
|
|
970
|
+
name: "e2e-runner",
|
|
971
|
+
condition: (scan) => scan.tools.playwright
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "sitecore-specialist",
|
|
975
|
+
condition: (scan) => scan.cms !== "none"
|
|
976
|
+
}
|
|
977
|
+
];
|
|
978
|
+
async function copyAgents(targetDir, scan) {
|
|
979
|
+
const agentsTarget = path15.join(targetDir, ".claude", "agents");
|
|
980
|
+
await fs6.ensureDir(agentsTarget);
|
|
981
|
+
const copied = [];
|
|
982
|
+
for (const agent of UNIVERSAL_AGENTS) {
|
|
983
|
+
const src = path15.join(AGENTS_DIR, `${agent}.md`);
|
|
984
|
+
if (!await fs6.pathExists(src)) continue;
|
|
985
|
+
await fs6.copy(src, path15.join(agentsTarget, `${agent}.md`), {
|
|
986
|
+
overwrite: true
|
|
987
|
+
});
|
|
988
|
+
copied.push(agent);
|
|
989
|
+
}
|
|
990
|
+
for (const { name, condition } of CONDITIONAL_AGENTS) {
|
|
991
|
+
if (!condition(scan)) continue;
|
|
992
|
+
const src = path15.join(AGENTS_DIR, `${name}.md`);
|
|
993
|
+
if (!await fs6.pathExists(src)) continue;
|
|
994
|
+
await fs6.copy(src, path15.join(agentsTarget, `${name}.md`), {
|
|
995
|
+
overwrite: true
|
|
996
|
+
});
|
|
997
|
+
copied.push(name);
|
|
998
|
+
}
|
|
999
|
+
return copied;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/copier/contexts.ts
|
|
1003
|
+
import path16 from "path";
|
|
1004
|
+
import fs7 from "fs-extra";
|
|
1005
|
+
var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
|
|
1006
|
+
async function copyContexts(targetDir) {
|
|
1007
|
+
const contextsTarget = path16.join(targetDir, ".claude", "contexts");
|
|
1008
|
+
await fs7.ensureDir(contextsTarget);
|
|
1009
|
+
const copied = [];
|
|
1010
|
+
for (const context of AVAILABLE_CONTEXTS) {
|
|
1011
|
+
const src = path16.join(CONTEXTS_DIR, `${context}.md`);
|
|
1012
|
+
if (!await fs7.pathExists(src)) continue;
|
|
1013
|
+
await fs7.copy(src, path16.join(contextsTarget, `${context}.md`), {
|
|
1014
|
+
overwrite: true
|
|
1015
|
+
});
|
|
1016
|
+
copied.push(context);
|
|
1017
|
+
}
|
|
1018
|
+
return copied;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
844
1021
|
// src/commands/init.ts
|
|
845
1022
|
async function initCommand(targetPath) {
|
|
846
|
-
const projectDir =
|
|
1023
|
+
const projectDir = path17.resolve(targetPath || process.cwd());
|
|
847
1024
|
logSection("AI Kit \u2014 Project Setup");
|
|
848
1025
|
logInfo(`Scanning: ${projectDir}`);
|
|
849
|
-
const configPath =
|
|
1026
|
+
const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
850
1027
|
if (fileExists(configPath)) {
|
|
851
1028
|
const overwrite = await confirm({
|
|
852
1029
|
message: "AI Kit is already configured in this project. Re-initialize?",
|
|
@@ -874,14 +1051,20 @@ async function initCommand(targetPath) {
|
|
|
874
1051
|
logInfo(`TypeScript: ${scan.typescript ? "Yes" : "No"}`);
|
|
875
1052
|
logInfo(`Monorepo: ${scan.monorepo ? `Yes (${scan.monorepoTool})` : "No"}`);
|
|
876
1053
|
logInfo(`Package Manager: ${scan.packageManager}`);
|
|
1054
|
+
logInfo(`Formatter: ${scan.tools.biome ? "Biome" : scan.tools.prettier ? "Prettier" : "None detected"}`);
|
|
877
1055
|
const clarifications = await askClarifications(scan);
|
|
878
1056
|
scan = applyClarifications(scan, clarifications);
|
|
879
1057
|
const tools = await selectTools();
|
|
880
1058
|
const strictness = await selectStrictness();
|
|
1059
|
+
const hookProfile = await selectHookProfile();
|
|
881
1060
|
const customFragments = loadCustomFragments(projectDir);
|
|
882
1061
|
const conflict = await selectConflictStrategy(projectDir);
|
|
883
1062
|
logSection("Generating Files");
|
|
884
|
-
const results = await generate(projectDir, scan, tools, conflict, {
|
|
1063
|
+
const results = await generate(projectDir, scan, tools, conflict, {
|
|
1064
|
+
strictness,
|
|
1065
|
+
customFragments,
|
|
1066
|
+
hookProfile
|
|
1067
|
+
});
|
|
885
1068
|
logSection("Setup Complete");
|
|
886
1069
|
if (results.claudeMd) logSuccess(`CLAUDE.md generated`);
|
|
887
1070
|
if (results.cursorRules) logSuccess(`.cursorrules generated`);
|
|
@@ -889,6 +1072,12 @@ async function initCommand(targetPath) {
|
|
|
889
1072
|
logSuccess(`${results.cursorMdcFiles} .cursor/rules/*.mdc files generated`);
|
|
890
1073
|
if (results.commands.length > 0)
|
|
891
1074
|
logSuccess(`${results.commands.length} skills generated (.claude/skills/ + .cursor/skills/)`);
|
|
1075
|
+
if (results.agents.length > 0)
|
|
1076
|
+
logSuccess(`${results.agents.length} agents generated (.claude/agents/)`);
|
|
1077
|
+
if (results.contexts.length > 0)
|
|
1078
|
+
logSuccess(`${results.contexts.length} context modes generated (.claude/contexts/)`);
|
|
1079
|
+
if (results.hooks)
|
|
1080
|
+
logSuccess(`Hooks configured (.claude/settings.local.json) \u2014 profile: ${hookProfile}`);
|
|
892
1081
|
if (results.guides.length > 0)
|
|
893
1082
|
logSuccess(`${results.guides.length} guides added to ai-kit/guides/`);
|
|
894
1083
|
if (results.docs.length > 0)
|
|
@@ -896,6 +1085,7 @@ async function initCommand(targetPath) {
|
|
|
896
1085
|
showRecommendations(scan);
|
|
897
1086
|
console.log("");
|
|
898
1087
|
logInfo("Run `ai-kit update` anytime to refresh configs after project changes.");
|
|
1088
|
+
logInfo("Run `ai-kit audit` to check your AI agent configuration health.");
|
|
899
1089
|
logInfo("Check ai-kit/guides/getting-started.md to get started.");
|
|
900
1090
|
}
|
|
901
1091
|
function formatFramework(scan) {
|
|
@@ -953,8 +1143,28 @@ async function selectStrictness() {
|
|
|
953
1143
|
default: "standard"
|
|
954
1144
|
});
|
|
955
1145
|
}
|
|
1146
|
+
async function selectHookProfile() {
|
|
1147
|
+
return select({
|
|
1148
|
+
message: "Hook automation profile (runs checks automatically as you code):",
|
|
1149
|
+
choices: [
|
|
1150
|
+
{
|
|
1151
|
+
name: "Standard \u2014 auto-format + typecheck + console.log warnings",
|
|
1152
|
+
value: "standard"
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
name: "Strict \u2014 all standard hooks + ESLint + stop checks",
|
|
1156
|
+
value: "strict"
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
name: "Minimal \u2014 auto-format + git push safety only",
|
|
1160
|
+
value: "minimal"
|
|
1161
|
+
}
|
|
1162
|
+
],
|
|
1163
|
+
default: "standard"
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
956
1166
|
async function selectConflictStrategy(projectDir) {
|
|
957
|
-
const hasExisting = fileExists(
|
|
1167
|
+
const hasExisting = fileExists(path17.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path17.join(projectDir, GENERATED_FILES.cursorRules));
|
|
958
1168
|
if (!hasExisting) return "overwrite";
|
|
959
1169
|
return select({
|
|
960
1170
|
message: "Existing AI config files detected. How should we handle conflicts?",
|
|
@@ -977,34 +1187,45 @@ async function generate(projectDir, scan, tools, conflict, opts) {
|
|
|
977
1187
|
cursorRules: false,
|
|
978
1188
|
cursorMdcFiles: 0,
|
|
979
1189
|
commands: [],
|
|
1190
|
+
agents: [],
|
|
1191
|
+
contexts: [],
|
|
1192
|
+
hooks: false,
|
|
980
1193
|
guides: [],
|
|
981
1194
|
docs: []
|
|
982
1195
|
};
|
|
983
1196
|
if (tools.claude) {
|
|
984
|
-
const claudeMdPath =
|
|
1197
|
+
const claudeMdPath = path17.join(projectDir, GENERATED_FILES.claudeMd);
|
|
985
1198
|
if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
|
|
986
1199
|
const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
987
|
-
await
|
|
1200
|
+
await fs8.writeFile(claudeMdPath, content, "utf-8");
|
|
988
1201
|
result.claudeMd = true;
|
|
989
1202
|
} else {
|
|
990
1203
|
logWarning("CLAUDE.md exists, skipping");
|
|
991
1204
|
}
|
|
992
1205
|
result.commands = await copySkills(projectDir);
|
|
1206
|
+
result.agents = await copyAgents(projectDir, scan);
|
|
1207
|
+
result.contexts = await copyContexts(projectDir);
|
|
1208
|
+
const hookProfile = opts?.hookProfile || "standard";
|
|
1209
|
+
const settingsLocalPath = path17.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
1210
|
+
const settingsLocal = generateSettingsLocal(scan, hookProfile);
|
|
1211
|
+
await fs8.ensureDir(path17.dirname(settingsLocalPath));
|
|
1212
|
+
await fs8.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
|
|
1213
|
+
result.hooks = true;
|
|
993
1214
|
}
|
|
994
1215
|
if (tools.cursor) {
|
|
995
|
-
const cursorPath =
|
|
1216
|
+
const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
|
|
996
1217
|
if (conflict === "overwrite" || !fileExists(cursorPath)) {
|
|
997
1218
|
const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
998
|
-
await
|
|
1219
|
+
await fs8.writeFile(cursorPath, content, "utf-8");
|
|
999
1220
|
result.cursorRules = true;
|
|
1000
1221
|
} else {
|
|
1001
1222
|
logWarning(".cursorrules exists, skipping");
|
|
1002
1223
|
}
|
|
1003
|
-
const mdcDir =
|
|
1004
|
-
await
|
|
1224
|
+
const mdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1225
|
+
await fs8.ensureDir(mdcDir);
|
|
1005
1226
|
const mdcFiles = generateMdcFiles(scan);
|
|
1006
1227
|
for (const mdc of mdcFiles) {
|
|
1007
|
-
await
|
|
1228
|
+
await fs8.writeFile(path17.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
1008
1229
|
}
|
|
1009
1230
|
result.cursorMdcFiles = mdcFiles.length;
|
|
1010
1231
|
}
|
|
@@ -1013,9 +1234,16 @@ async function generate(projectDir, scan, tools, conflict, opts) {
|
|
|
1013
1234
|
const templates = [];
|
|
1014
1235
|
if (result.claudeMd) templates.push("CLAUDE.md");
|
|
1015
1236
|
if (result.cursorRules) templates.push(".cursorrules");
|
|
1016
|
-
const config = generateConfig(scan, templates, result.commands, result.guides, {
|
|
1017
|
-
|
|
1018
|
-
|
|
1237
|
+
const config = generateConfig(scan, templates, result.commands, result.guides, {
|
|
1238
|
+
strictness: opts?.strictness,
|
|
1239
|
+
customFragments: opts?.customFragments,
|
|
1240
|
+
agents: result.agents,
|
|
1241
|
+
contexts: result.contexts,
|
|
1242
|
+
hooks: result.hooks,
|
|
1243
|
+
hookProfile: opts?.hookProfile
|
|
1244
|
+
});
|
|
1245
|
+
await fs8.writeJson(
|
|
1246
|
+
path17.join(projectDir, AI_KIT_CONFIG_FILE),
|
|
1019
1247
|
config,
|
|
1020
1248
|
{ spaces: 2 }
|
|
1021
1249
|
);
|
|
@@ -1034,9 +1262,9 @@ function showRecommendations(scan) {
|
|
|
1034
1262
|
hint: " npm install -D eslint @typescript-eslint/eslint-plugin"
|
|
1035
1263
|
},
|
|
1036
1264
|
{
|
|
1037
|
-
check: scan.tools.prettier,
|
|
1038
|
-
label: "
|
|
1039
|
-
hint: " npm install -D prettier"
|
|
1265
|
+
check: scan.tools.prettier || scan.tools.biome,
|
|
1266
|
+
label: "No formatter detected \u2014 install Prettier or Biome for auto-formatting hooks:",
|
|
1267
|
+
hint: " npm install -D prettier (or) npm install -D @biomejs/biome"
|
|
1040
1268
|
},
|
|
1041
1269
|
{
|
|
1042
1270
|
check: scan.tools.axeCore,
|
|
@@ -1091,13 +1319,13 @@ function showRecommendations(scan) {
|
|
|
1091
1319
|
}
|
|
1092
1320
|
|
|
1093
1321
|
// src/commands/update.ts
|
|
1094
|
-
import
|
|
1095
|
-
import
|
|
1322
|
+
import path18 from "path";
|
|
1323
|
+
import fs9 from "fs-extra";
|
|
1096
1324
|
import ora2 from "ora";
|
|
1097
1325
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
1098
1326
|
async function updateCommand(targetPath) {
|
|
1099
|
-
const projectDir =
|
|
1100
|
-
const configPath =
|
|
1327
|
+
const projectDir = path18.resolve(targetPath || process.cwd());
|
|
1328
|
+
const configPath = path18.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1101
1329
|
if (!fileExists(configPath)) {
|
|
1102
1330
|
logError("No ai-kit.config.json found. Run `ai-kit init` first.");
|
|
1103
1331
|
return;
|
|
@@ -1124,57 +1352,75 @@ async function updateCommand(targetPath) {
|
|
|
1124
1352
|
spinner.succeed("Project re-scanned");
|
|
1125
1353
|
logSection("Updating Files");
|
|
1126
1354
|
const strictness = existingConfig.strictness || "standard";
|
|
1355
|
+
const hookProfile = existingConfig.hookProfile || "standard";
|
|
1127
1356
|
const customFragments = loadCustomFragments(projectDir);
|
|
1128
1357
|
const genOpts = { strictness, customFragments };
|
|
1129
1358
|
const templates = [];
|
|
1130
|
-
if (existingConfig.templates.includes("CLAUDE.md") || fileExists(
|
|
1131
|
-
const claudeMdPath =
|
|
1359
|
+
if (existingConfig.templates.includes("CLAUDE.md") || fileExists(path18.join(projectDir, GENERATED_FILES.claudeMd))) {
|
|
1360
|
+
const claudeMdPath = path18.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1132
1361
|
const newContent = generateClaudeMd(scan, genOpts);
|
|
1133
1362
|
const existing = readFileSafe(claudeMdPath);
|
|
1134
1363
|
if (existing) {
|
|
1135
|
-
await
|
|
1364
|
+
await fs9.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1136
1365
|
} else {
|
|
1137
|
-
await
|
|
1366
|
+
await fs9.writeFile(claudeMdPath, newContent, "utf-8");
|
|
1138
1367
|
}
|
|
1139
1368
|
templates.push("CLAUDE.md");
|
|
1140
1369
|
logSuccess("CLAUDE.md updated");
|
|
1141
1370
|
}
|
|
1142
|
-
if (existingConfig.templates.includes(".cursorrules") || fileExists(
|
|
1143
|
-
const cursorRulesPath =
|
|
1371
|
+
if (existingConfig.templates.includes(".cursorrules") || fileExists(path18.join(projectDir, GENERATED_FILES.cursorRules))) {
|
|
1372
|
+
const cursorRulesPath = path18.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1144
1373
|
const newContent = generateCursorRules(scan, genOpts);
|
|
1145
1374
|
const existing = readFileSafe(cursorRulesPath);
|
|
1146
1375
|
if (existing) {
|
|
1147
|
-
await
|
|
1376
|
+
await fs9.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1148
1377
|
} else {
|
|
1149
|
-
await
|
|
1378
|
+
await fs9.writeFile(cursorRulesPath, newContent, "utf-8");
|
|
1150
1379
|
}
|
|
1151
1380
|
templates.push(".cursorrules");
|
|
1152
1381
|
logSuccess(".cursorrules updated");
|
|
1153
|
-
const mdcDir =
|
|
1154
|
-
await
|
|
1382
|
+
const mdcDir = path18.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1383
|
+
await fs9.ensureDir(mdcDir);
|
|
1155
1384
|
const mdcFiles = generateMdcFiles(scan);
|
|
1156
1385
|
for (const mdc of mdcFiles) {
|
|
1157
|
-
await
|
|
1386
|
+
await fs9.writeFile(path18.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
1158
1387
|
}
|
|
1159
1388
|
logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
|
|
1160
1389
|
}
|
|
1161
1390
|
const commands = await copySkills(projectDir);
|
|
1162
1391
|
logSuccess(`${commands.length} skills updated (.claude/skills/ + .cursor/skills/)`);
|
|
1392
|
+
const agents = await copyAgents(projectDir, scan);
|
|
1393
|
+
logSuccess(`${agents.length} agents updated (.claude/agents/)`);
|
|
1394
|
+
const contexts = await copyContexts(projectDir);
|
|
1395
|
+
logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
|
|
1396
|
+
if (existingConfig.hooks !== false) {
|
|
1397
|
+
const settingsLocalPath = path18.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
1398
|
+
const settingsLocal = generateSettingsLocal(scan, hookProfile);
|
|
1399
|
+
await fs9.ensureDir(path18.dirname(settingsLocalPath));
|
|
1400
|
+
await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
|
|
1401
|
+
logSuccess(`Hooks updated (profile: ${hookProfile})`);
|
|
1402
|
+
}
|
|
1163
1403
|
const guides = await copyGuides(projectDir);
|
|
1164
1404
|
logSuccess(`${guides.length} guides updated`);
|
|
1165
|
-
const config = generateConfig(scan, templates, commands, guides,
|
|
1166
|
-
|
|
1405
|
+
const config = generateConfig(scan, templates, commands, guides, {
|
|
1406
|
+
...genOpts,
|
|
1407
|
+
agents,
|
|
1408
|
+
contexts,
|
|
1409
|
+
hooks: existingConfig.hooks !== false,
|
|
1410
|
+
hookProfile
|
|
1411
|
+
});
|
|
1412
|
+
await fs9.writeJson(configPath, config, { spaces: 2 });
|
|
1167
1413
|
logSuccess("ai-kit.config.json updated");
|
|
1168
1414
|
console.log("");
|
|
1169
1415
|
logInfo("All AI configs refreshed with latest project scan.");
|
|
1170
1416
|
}
|
|
1171
1417
|
|
|
1172
1418
|
// src/commands/reset.ts
|
|
1173
|
-
import
|
|
1174
|
-
import
|
|
1419
|
+
import path19 from "path";
|
|
1420
|
+
import fs10 from "fs-extra";
|
|
1175
1421
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1176
1422
|
async function resetCommand(targetPath) {
|
|
1177
|
-
const projectDir =
|
|
1423
|
+
const projectDir = path19.resolve(targetPath || process.cwd());
|
|
1178
1424
|
logSection("AI Kit \u2014 Reset");
|
|
1179
1425
|
logWarning("This will remove all AI Kit generated files:");
|
|
1180
1426
|
logInfo(` - ${GENERATED_FILES.claudeMd}`);
|
|
@@ -1195,44 +1441,44 @@ async function resetCommand(targetPath) {
|
|
|
1195
1441
|
return;
|
|
1196
1442
|
}
|
|
1197
1443
|
const removed = [];
|
|
1198
|
-
const claudeMdPath =
|
|
1444
|
+
const claudeMdPath = path19.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1199
1445
|
if (fileExists(claudeMdPath)) {
|
|
1200
|
-
await
|
|
1446
|
+
await fs10.remove(claudeMdPath);
|
|
1201
1447
|
removed.push(GENERATED_FILES.claudeMd);
|
|
1202
1448
|
}
|
|
1203
|
-
const cursorPath =
|
|
1449
|
+
const cursorPath = path19.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1204
1450
|
if (fileExists(cursorPath)) {
|
|
1205
|
-
await
|
|
1451
|
+
await fs10.remove(cursorPath);
|
|
1206
1452
|
removed.push(GENERATED_FILES.cursorRules);
|
|
1207
1453
|
}
|
|
1208
|
-
const cursorMdcDir =
|
|
1454
|
+
const cursorMdcDir = path19.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1209
1455
|
if (fileExists(cursorMdcDir)) {
|
|
1210
|
-
await
|
|
1456
|
+
await fs10.remove(cursorMdcDir);
|
|
1211
1457
|
removed.push(GENERATED_FILES.cursorMdcDir);
|
|
1212
1458
|
}
|
|
1213
|
-
const commandsDir =
|
|
1459
|
+
const commandsDir = path19.join(projectDir, GENERATED_FILES.claudeCommands);
|
|
1214
1460
|
if (fileExists(commandsDir)) {
|
|
1215
|
-
await
|
|
1461
|
+
await fs10.remove(commandsDir);
|
|
1216
1462
|
removed.push(GENERATED_FILES.claudeCommands);
|
|
1217
1463
|
}
|
|
1218
|
-
const claudeSkillsDir =
|
|
1464
|
+
const claudeSkillsDir = path19.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
1219
1465
|
if (fileExists(claudeSkillsDir)) {
|
|
1220
|
-
await
|
|
1466
|
+
await fs10.remove(claudeSkillsDir);
|
|
1221
1467
|
removed.push(GENERATED_FILES.claudeSkills);
|
|
1222
1468
|
}
|
|
1223
|
-
const cursorSkillsDir =
|
|
1469
|
+
const cursorSkillsDir = path19.join(projectDir, GENERATED_FILES.cursorSkills);
|
|
1224
1470
|
if (fileExists(cursorSkillsDir)) {
|
|
1225
|
-
await
|
|
1471
|
+
await fs10.remove(cursorSkillsDir);
|
|
1226
1472
|
removed.push(GENERATED_FILES.cursorSkills);
|
|
1227
1473
|
}
|
|
1228
|
-
const aiKitDir =
|
|
1474
|
+
const aiKitDir = path19.join(projectDir, "ai-kit");
|
|
1229
1475
|
if (fileExists(aiKitDir)) {
|
|
1230
|
-
await
|
|
1476
|
+
await fs10.remove(aiKitDir);
|
|
1231
1477
|
removed.push("ai-kit/");
|
|
1232
1478
|
}
|
|
1233
|
-
const configPath =
|
|
1479
|
+
const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1234
1480
|
if (fileExists(configPath)) {
|
|
1235
|
-
await
|
|
1481
|
+
await fs10.remove(configPath);
|
|
1236
1482
|
removed.push(AI_KIT_CONFIG_FILE);
|
|
1237
1483
|
}
|
|
1238
1484
|
logSection("Reset Complete");
|
|
@@ -1244,8 +1490,8 @@ async function resetCommand(targetPath) {
|
|
|
1244
1490
|
}
|
|
1245
1491
|
|
|
1246
1492
|
// src/commands/tokens.ts
|
|
1247
|
-
import
|
|
1248
|
-
import
|
|
1493
|
+
import path20 from "path";
|
|
1494
|
+
import fs11 from "fs-extra";
|
|
1249
1495
|
import chalk2 from "chalk";
|
|
1250
1496
|
import ora3 from "ora";
|
|
1251
1497
|
import os from "os";
|
|
@@ -1255,14 +1501,14 @@ var PRICING = {
|
|
|
1255
1501
|
};
|
|
1256
1502
|
var PLAN_BUDGET = 20;
|
|
1257
1503
|
function findSessionFiles() {
|
|
1258
|
-
const claudeDir =
|
|
1259
|
-
if (!
|
|
1504
|
+
const claudeDir = path20.join(os.homedir(), ".claude", "projects");
|
|
1505
|
+
if (!fs11.existsSync(claudeDir)) return [];
|
|
1260
1506
|
const files = [];
|
|
1261
1507
|
function walkDir(dir) {
|
|
1262
1508
|
try {
|
|
1263
|
-
const entries =
|
|
1509
|
+
const entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
1264
1510
|
for (const entry of entries) {
|
|
1265
|
-
const full =
|
|
1511
|
+
const full = path20.join(dir, entry.name);
|
|
1266
1512
|
if (entry.isDirectory()) {
|
|
1267
1513
|
walkDir(full);
|
|
1268
1514
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -1277,7 +1523,7 @@ function findSessionFiles() {
|
|
|
1277
1523
|
}
|
|
1278
1524
|
function parseSessionFile(filePath) {
|
|
1279
1525
|
try {
|
|
1280
|
-
const content =
|
|
1526
|
+
const content = fs11.readFileSync(filePath, "utf-8");
|
|
1281
1527
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
1282
1528
|
const usage = {
|
|
1283
1529
|
inputTokens: 0,
|
|
@@ -1314,11 +1560,11 @@ function parseSessionFile(filePath) {
|
|
|
1314
1560
|
}
|
|
1315
1561
|
if (usage.inputTokens === 0 && usage.outputTokens === 0) return null;
|
|
1316
1562
|
if (!sessionDate) {
|
|
1317
|
-
const stat =
|
|
1563
|
+
const stat = fs11.statSync(filePath);
|
|
1318
1564
|
sessionDate = stat.mtime.toISOString().slice(0, 10);
|
|
1319
1565
|
}
|
|
1320
|
-
const sessionId =
|
|
1321
|
-
const projectName =
|
|
1566
|
+
const sessionId = path20.basename(filePath, ".jsonl");
|
|
1567
|
+
const projectName = path20.basename(path20.dirname(filePath));
|
|
1322
1568
|
return {
|
|
1323
1569
|
sessionId,
|
|
1324
1570
|
filePath,
|
|
@@ -1547,13 +1793,13 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
|
|
|
1547
1793
|
);
|
|
1548
1794
|
console.log("");
|
|
1549
1795
|
if (options.csv) {
|
|
1550
|
-
const csvPath =
|
|
1796
|
+
const csvPath = path20.join(process.cwd(), "token-usage.csv");
|
|
1551
1797
|
const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
|
|
1552
1798
|
const daily = aggregateByDate(sessions);
|
|
1553
1799
|
const csvRows = daily.map(
|
|
1554
1800
|
(d) => `${d.date},${d.sessions},${d.usage.inputTokens},${d.usage.outputTokens},${d.usage.cacheReadTokens},${d.cost.toFixed(2)}`
|
|
1555
1801
|
).join("\n");
|
|
1556
|
-
await
|
|
1802
|
+
await fs11.writeFile(csvPath, csvHeader + csvRows, "utf-8");
|
|
1557
1803
|
logSuccess(`CSV exported to ${csvPath}`);
|
|
1558
1804
|
}
|
|
1559
1805
|
if (options.export) {
|
|
@@ -1590,13 +1836,13 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1590
1836
|
}
|
|
1591
1837
|
};
|
|
1592
1838
|
const outputDir = process.cwd();
|
|
1593
|
-
const dataPath =
|
|
1594
|
-
const dashboardSrc =
|
|
1595
|
-
const dashboardDest =
|
|
1596
|
-
await
|
|
1839
|
+
const dataPath = path20.join(outputDir, "token-data.json");
|
|
1840
|
+
const dashboardSrc = path20.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
|
|
1841
|
+
const dashboardDest = path20.join(outputDir, "token-dashboard.html");
|
|
1842
|
+
await fs11.writeJson(dataPath, exportData, { spaces: 2 });
|
|
1597
1843
|
logInfo(`Token data written to ${dataPath}`);
|
|
1598
|
-
if (await
|
|
1599
|
-
await
|
|
1844
|
+
if (await fs11.pathExists(dashboardSrc)) {
|
|
1845
|
+
await fs11.copy(dashboardSrc, dashboardDest, { overwrite: true });
|
|
1600
1846
|
logInfo(`Dashboard copied to ${dashboardDest}`);
|
|
1601
1847
|
} else {
|
|
1602
1848
|
logWarning("Dashboard template not found. Skipping HTML export.");
|
|
@@ -1613,12 +1859,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1613
1859
|
}
|
|
1614
1860
|
|
|
1615
1861
|
// src/commands/doctor.ts
|
|
1616
|
-
import
|
|
1862
|
+
import path21 from "path";
|
|
1617
1863
|
import chalk3 from "chalk";
|
|
1618
1864
|
import ora4 from "ora";
|
|
1619
1865
|
async function doctorCommand(targetPath) {
|
|
1620
|
-
const projectDir =
|
|
1621
|
-
const configPath =
|
|
1866
|
+
const projectDir = path21.resolve(targetPath || process.cwd());
|
|
1867
|
+
const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1622
1868
|
let passed = 0;
|
|
1623
1869
|
let warnings = 0;
|
|
1624
1870
|
let issues = 0;
|
|
@@ -1650,7 +1896,7 @@ async function doctorCommand(targetPath) {
|
|
|
1650
1896
|
}
|
|
1651
1897
|
for (const template of config.templates) {
|
|
1652
1898
|
const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
|
|
1653
|
-
const templatePath =
|
|
1899
|
+
const templatePath = path21.join(projectDir, templateFile);
|
|
1654
1900
|
if (fileExists(templatePath)) {
|
|
1655
1901
|
logSuccess(`${template} exists and in sync`);
|
|
1656
1902
|
passed++;
|
|
@@ -1661,8 +1907,8 @@ async function doctorCommand(targetPath) {
|
|
|
1661
1907
|
}
|
|
1662
1908
|
const missingSkills = [];
|
|
1663
1909
|
for (const skill of config.commands) {
|
|
1664
|
-
const claudeSkillPath =
|
|
1665
|
-
const cursorSkillPath =
|
|
1910
|
+
const claudeSkillPath = path21.join(projectDir, GENERATED_FILES.claudeSkills, skill);
|
|
1911
|
+
const cursorSkillPath = path21.join(projectDir, GENERATED_FILES.cursorSkills, skill);
|
|
1666
1912
|
if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
|
|
1667
1913
|
missingSkills.push(skill);
|
|
1668
1914
|
}
|
|
@@ -1676,10 +1922,10 @@ async function doctorCommand(targetPath) {
|
|
|
1676
1922
|
);
|
|
1677
1923
|
issues++;
|
|
1678
1924
|
}
|
|
1679
|
-
const guidesDir =
|
|
1925
|
+
const guidesDir = path21.join(projectDir, "ai-kit", "guides");
|
|
1680
1926
|
const missingGuides = [];
|
|
1681
1927
|
for (const guide of config.guides) {
|
|
1682
|
-
const guidePath =
|
|
1928
|
+
const guidePath = path21.join(guidesDir, guide);
|
|
1683
1929
|
if (!fileExists(guidePath)) {
|
|
1684
1930
|
missingGuides.push(guide);
|
|
1685
1931
|
}
|
|
@@ -1817,13 +2063,13 @@ function compareScanResults(previous, current) {
|
|
|
1817
2063
|
}
|
|
1818
2064
|
|
|
1819
2065
|
// src/commands/diff.ts
|
|
1820
|
-
import
|
|
1821
|
-
import
|
|
2066
|
+
import path22 from "path";
|
|
2067
|
+
import fs12 from "fs-extra";
|
|
1822
2068
|
import chalk4 from "chalk";
|
|
1823
2069
|
import ora5 from "ora";
|
|
1824
2070
|
async function diffCommand(targetPath) {
|
|
1825
|
-
const projectDir =
|
|
1826
|
-
const configPath =
|
|
2071
|
+
const projectDir = path22.resolve(targetPath || process.cwd());
|
|
2072
|
+
const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1827
2073
|
console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
|
|
1828
2074
|
if (!fileExists(configPath)) {
|
|
1829
2075
|
logError("No ai-kit.config.json found. Run `ai-kit init` first.");
|
|
@@ -1890,11 +2136,11 @@ async function diffCommand(targetPath) {
|
|
|
1890
2136
|
if (cursorRulesStatus.status === "modified") modified++;
|
|
1891
2137
|
else if (cursorRulesStatus.status === "added") added++;
|
|
1892
2138
|
else unchanged++;
|
|
1893
|
-
const skillsDir =
|
|
2139
|
+
const skillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
1894
2140
|
const skillCount = countFilesInDir(skillsDir);
|
|
1895
2141
|
console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
|
|
1896
2142
|
unchanged++;
|
|
1897
|
-
const guidesDir =
|
|
2143
|
+
const guidesDir = path22.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
|
|
1898
2144
|
const guideCount = existingConfig.guides?.length || 0;
|
|
1899
2145
|
console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
|
|
1900
2146
|
unchanged++;
|
|
@@ -2003,7 +2249,7 @@ function diffStack(oldScan, newScan) {
|
|
|
2003
2249
|
return changes;
|
|
2004
2250
|
}
|
|
2005
2251
|
function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
|
|
2006
|
-
const filePath =
|
|
2252
|
+
const filePath = path22.join(projectDir, filename);
|
|
2007
2253
|
const currentContent = readFileSafe(filePath);
|
|
2008
2254
|
const newContent = generate2();
|
|
2009
2255
|
if (!currentContent) {
|
|
@@ -2040,8 +2286,8 @@ function logFileChange(result) {
|
|
|
2040
2286
|
}
|
|
2041
2287
|
function countFilesInDir(dirPath) {
|
|
2042
2288
|
try {
|
|
2043
|
-
if (!
|
|
2044
|
-
const entries =
|
|
2289
|
+
if (!fs12.existsSync(dirPath)) return 0;
|
|
2290
|
+
const entries = fs12.readdirSync(dirPath);
|
|
2045
2291
|
return entries.filter((e) => !e.startsWith(".")).length;
|
|
2046
2292
|
} catch {
|
|
2047
2293
|
return 0;
|
|
@@ -2049,8 +2295,8 @@ function countFilesInDir(dirPath) {
|
|
|
2049
2295
|
}
|
|
2050
2296
|
|
|
2051
2297
|
// src/commands/export.ts
|
|
2052
|
-
import
|
|
2053
|
-
import
|
|
2298
|
+
import path23 from "path";
|
|
2299
|
+
import fs13 from "fs-extra";
|
|
2054
2300
|
import ora6 from "ora";
|
|
2055
2301
|
import { select as select2 } from "@inquirer/prompts";
|
|
2056
2302
|
var EXPORT_TARGETS = {
|
|
@@ -2086,9 +2332,9 @@ function toCline(content) {
|
|
|
2086
2332
|
${stripped}`;
|
|
2087
2333
|
}
|
|
2088
2334
|
async function exportCommand(targetPath, options) {
|
|
2089
|
-
const projectDir =
|
|
2090
|
-
const configPath =
|
|
2091
|
-
const claudeMdPath =
|
|
2335
|
+
const projectDir = path23.resolve(targetPath || process.cwd());
|
|
2336
|
+
const configPath = path23.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2337
|
+
const claudeMdPath = path23.join(projectDir, GENERATED_FILES.claudeMd);
|
|
2092
2338
|
logSection("AI Kit \u2014 Export");
|
|
2093
2339
|
if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
|
|
2094
2340
|
logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
|
|
@@ -2130,9 +2376,9 @@ async function exportCommand(targetPath, options) {
|
|
|
2130
2376
|
for (const fmt2 of formats) {
|
|
2131
2377
|
const target = EXPORT_TARGETS[fmt2];
|
|
2132
2378
|
const transformer = transformers[fmt2];
|
|
2133
|
-
const outputPath =
|
|
2379
|
+
const outputPath = path23.join(projectDir, target.file);
|
|
2134
2380
|
const transformed = transformer(claudeContent);
|
|
2135
|
-
await
|
|
2381
|
+
await fs13.writeFile(outputPath, transformed, "utf-8");
|
|
2136
2382
|
exported++;
|
|
2137
2383
|
}
|
|
2138
2384
|
spinner.succeed("Export complete");
|
|
@@ -2148,7 +2394,7 @@ async function exportCommand(targetPath, options) {
|
|
|
2148
2394
|
}
|
|
2149
2395
|
|
|
2150
2396
|
// src/commands/stats.ts
|
|
2151
|
-
import
|
|
2397
|
+
import path24 from "path";
|
|
2152
2398
|
import chalk5 from "chalk";
|
|
2153
2399
|
var SKILL_CATEGORIES = {
|
|
2154
2400
|
"Getting Started": ["prompt-help", "understand"],
|
|
@@ -2251,8 +2497,8 @@ var MCP_DISPLAY_NAMES = {
|
|
|
2251
2497
|
perplexity: "Perplexity"
|
|
2252
2498
|
};
|
|
2253
2499
|
async function statsCommand(targetPath) {
|
|
2254
|
-
const projectDir =
|
|
2255
|
-
const configPath =
|
|
2500
|
+
const projectDir = path24.resolve(targetPath || process.cwd());
|
|
2501
|
+
const configPath = path24.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2256
2502
|
logSection("AI Kit \u2014 Project Stats");
|
|
2257
2503
|
console.log("");
|
|
2258
2504
|
if (!fileExists(configPath)) {
|
|
@@ -2345,6 +2591,146 @@ async function statsCommand(targetPath) {
|
|
|
2345
2591
|
console.log("");
|
|
2346
2592
|
}
|
|
2347
2593
|
|
|
2594
|
+
// src/commands/audit.ts
|
|
2595
|
+
import path25 from "path";
|
|
2596
|
+
import fs14 from "fs-extra";
|
|
2597
|
+
import chalk6 from "chalk";
|
|
2598
|
+
async function auditCommand(targetPath) {
|
|
2599
|
+
const projectDir = path25.resolve(targetPath || process.cwd());
|
|
2600
|
+
logSection("AI Kit \u2014 Security & Configuration Audit");
|
|
2601
|
+
logInfo(`Auditing: ${projectDir}`);
|
|
2602
|
+
const checks = [];
|
|
2603
|
+
const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2604
|
+
if (fileExists(configPath)) {
|
|
2605
|
+
checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
|
|
2606
|
+
} else {
|
|
2607
|
+
checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
|
|
2608
|
+
}
|
|
2609
|
+
const claudeMdPath = path25.join(projectDir, GENERATED_FILES.claudeMd);
|
|
2610
|
+
const claudeMd = readFileSafe(claudeMdPath);
|
|
2611
|
+
if (claudeMd) {
|
|
2612
|
+
if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
|
|
2613
|
+
checks.push({ name: "CLAUDE.md", status: "pass", message: "CLAUDE.md has AI-KIT markers" });
|
|
2614
|
+
} else {
|
|
2615
|
+
checks.push({ name: "CLAUDE.md", status: "warn", message: "CLAUDE.md exists but missing AI-KIT markers \u2014 may not update correctly" });
|
|
2616
|
+
}
|
|
2617
|
+
} else {
|
|
2618
|
+
checks.push({ name: "CLAUDE.md", status: "warn", message: "CLAUDE.md not found" });
|
|
2619
|
+
}
|
|
2620
|
+
if (claudeMd) {
|
|
2621
|
+
const secretPatterns = [
|
|
2622
|
+
/(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
|
|
2623
|
+
/(?:sk|pk|rk)[-_][a-zA-Z0-9]{20,}/,
|
|
2624
|
+
/ghp_[a-zA-Z0-9]{36}/,
|
|
2625
|
+
/xox[bpoas]-[a-zA-Z0-9-]+/
|
|
2626
|
+
];
|
|
2627
|
+
const hasSecrets = secretPatterns.some((p) => p.test(claudeMd));
|
|
2628
|
+
if (hasSecrets) {
|
|
2629
|
+
checks.push({ name: "Secrets in CLAUDE.md", status: "fail", message: "Potential secrets detected in CLAUDE.md \u2014 remove immediately" });
|
|
2630
|
+
} else {
|
|
2631
|
+
checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
const settingsLocalPath = path25.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
|
|
2635
|
+
if (fileExists(settingsLocalPath)) {
|
|
2636
|
+
const settings2 = readJsonSafe(settingsLocalPath);
|
|
2637
|
+
if (settings2?.hooks) {
|
|
2638
|
+
checks.push({ name: "Hooks", status: "pass", message: "Hooks configured in settings.local.json" });
|
|
2639
|
+
} else {
|
|
2640
|
+
checks.push({ name: "Hooks", status: "warn", message: "settings.local.json exists but no hooks defined" });
|
|
2641
|
+
}
|
|
2642
|
+
} else {
|
|
2643
|
+
checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
|
|
2644
|
+
}
|
|
2645
|
+
const agentsDir = path25.join(projectDir, GENERATED_FILES.claudeAgents);
|
|
2646
|
+
if (await fs14.pathExists(agentsDir)) {
|
|
2647
|
+
const agentFiles = (await fs14.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
|
|
2648
|
+
if (agentFiles.length > 0) {
|
|
2649
|
+
checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
|
|
2650
|
+
let invalidAgents = 0;
|
|
2651
|
+
for (const file of agentFiles) {
|
|
2652
|
+
const content = readFileSafe(path25.join(agentsDir, file));
|
|
2653
|
+
if (content && !content.startsWith("---")) {
|
|
2654
|
+
invalidAgents++;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
if (invalidAgents > 0) {
|
|
2658
|
+
checks.push({ name: "Agent frontmatter", status: "warn", message: `${invalidAgents} agent(s) missing YAML frontmatter` });
|
|
2659
|
+
}
|
|
2660
|
+
} else {
|
|
2661
|
+
checks.push({ name: "Agents", status: "warn", message: "Agents directory empty" });
|
|
2662
|
+
}
|
|
2663
|
+
} else {
|
|
2664
|
+
checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
|
|
2665
|
+
}
|
|
2666
|
+
const contextsDir = path25.join(projectDir, GENERATED_FILES.claudeContexts);
|
|
2667
|
+
if (await fs14.pathExists(contextsDir)) {
|
|
2668
|
+
const contextFiles = (await fs14.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
|
|
2669
|
+
checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
|
|
2670
|
+
} else {
|
|
2671
|
+
checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
|
|
2672
|
+
}
|
|
2673
|
+
const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
2674
|
+
if (await fs14.pathExists(skillsDir)) {
|
|
2675
|
+
const skillDirs = (await fs14.readdir(skillsDir)).filter(async (f) => {
|
|
2676
|
+
try {
|
|
2677
|
+
return (await fs14.stat(path25.join(skillsDir, f))).isDirectory();
|
|
2678
|
+
} catch {
|
|
2679
|
+
return false;
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
checks.push({ name: "Skills", status: "pass", message: `${skillDirs.length} skill(s) installed` });
|
|
2683
|
+
} else {
|
|
2684
|
+
checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
|
|
2685
|
+
}
|
|
2686
|
+
const gitignorePath = path25.join(projectDir, ".gitignore");
|
|
2687
|
+
const gitignore = readFileSafe(gitignorePath);
|
|
2688
|
+
if (gitignore) {
|
|
2689
|
+
const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
|
|
2690
|
+
if (envIgnored) {
|
|
2691
|
+
checks.push({ name: ".env gitignore", status: "pass", message: ".env files are gitignored" });
|
|
2692
|
+
} else {
|
|
2693
|
+
checks.push({ name: ".env gitignore", status: "fail", message: ".env files are NOT gitignored \u2014 add to .gitignore immediately" });
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
if (gitignore) {
|
|
2697
|
+
const settingsIgnored = gitignore.includes("settings.local.json");
|
|
2698
|
+
if (!settingsIgnored) {
|
|
2699
|
+
checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
const settingsPath = path25.join(projectDir, ".claude", "settings.json");
|
|
2703
|
+
const settings = readFileSafe(settingsPath);
|
|
2704
|
+
if (settings) {
|
|
2705
|
+
const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
|
|
2706
|
+
if (hasEnvVarsInSettings) {
|
|
2707
|
+
checks.push({ name: "MCP secrets", status: "fail", message: "Potential secrets in .claude/settings.json \u2014 use environment variables instead" });
|
|
2708
|
+
} else {
|
|
2709
|
+
checks.push({ name: "MCP secrets", status: "pass", message: "No hardcoded secrets in settings" });
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
logSection("Audit Results");
|
|
2713
|
+
const passed = checks.filter((c) => c.status === "pass");
|
|
2714
|
+
const warned = checks.filter((c) => c.status === "warn");
|
|
2715
|
+
const failed = checks.filter((c) => c.status === "fail");
|
|
2716
|
+
for (const check of checks) {
|
|
2717
|
+
const icon = check.status === "pass" ? chalk6.green("\u2713") : check.status === "warn" ? chalk6.yellow("\u26A0") : chalk6.red("\u2717");
|
|
2718
|
+
console.log(` ${icon} ${check.name}: ${check.message}`);
|
|
2719
|
+
}
|
|
2720
|
+
const total = checks.length;
|
|
2721
|
+
const score = Math.round((passed.length + warned.length * 0.5) / total * 100);
|
|
2722
|
+
const grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F";
|
|
2723
|
+
console.log("");
|
|
2724
|
+
logSection("Score");
|
|
2725
|
+
const gradeColor = grade <= "B" ? chalk6.green : grade <= "C" ? chalk6.yellow : chalk6.red;
|
|
2726
|
+
console.log(` ${gradeColor.bold(grade)} (${score}/100)`);
|
|
2727
|
+
console.log(` ${chalk6.green(String(passed.length))} passed \xB7 ${chalk6.yellow(String(warned.length))} warnings \xB7 ${chalk6.red(String(failed.length))} failures`);
|
|
2728
|
+
if (failed.length > 0) {
|
|
2729
|
+
console.log("");
|
|
2730
|
+
logError("Fix the failures above before proceeding.");
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2348
2734
|
// src/index.ts
|
|
2349
2735
|
var program = new Command();
|
|
2350
2736
|
program.name("ai-kit").description(
|
|
@@ -2438,5 +2824,16 @@ program.command("stats").description("Show project setup statistics and complexi
|
|
|
2438
2824
|
process.exit(1);
|
|
2439
2825
|
}
|
|
2440
2826
|
});
|
|
2827
|
+
program.command("audit").description("Security and configuration audit for AI agent setup").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
|
|
2828
|
+
try {
|
|
2829
|
+
await auditCommand(targetPath);
|
|
2830
|
+
} catch (err) {
|
|
2831
|
+
if (err.name === "ExitPromptError") {
|
|
2832
|
+
process.exit(0);
|
|
2833
|
+
}
|
|
2834
|
+
console.error(err);
|
|
2835
|
+
process.exit(1);
|
|
2836
|
+
}
|
|
2837
|
+
});
|
|
2441
2838
|
program.parse();
|
|
2442
2839
|
//# sourceMappingURL=index.js.map
|