@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/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 VERSION = "1.1.0";
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 path15 from "path";
40
- import fs6 from "fs-extra";
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
- "visual-diff",
724
- "db-migrate",
725
- "dependency-graph"
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
- "visual-diff": "Visual regression testing plan for changed components and pages",
765
- "db-migrate": "Create safe database migrations with rollback strategies for Prisma, Drizzle, or SQL",
766
- "dependency-graph": "Map module dependencies, find circular imports, and analyze coupling metrics"
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 = path15.resolve(targetPath || process.cwd());
1023
+ const projectDir = path17.resolve(targetPath || process.cwd());
847
1024
  logSection("AI Kit \u2014 Project Setup");
848
1025
  logInfo(`Scanning: ${projectDir}`);
849
- const configPath = path15.join(projectDir, AI_KIT_CONFIG_FILE);
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, { strictness, customFragments });
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(path15.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path15.join(projectDir, GENERATED_FILES.cursorRules));
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 = path15.join(projectDir, GENERATED_FILES.claudeMd);
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 fs6.writeFile(claudeMdPath, content, "utf-8");
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 = path15.join(projectDir, GENERATED_FILES.cursorRules);
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 fs6.writeFile(cursorPath, content, "utf-8");
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 = path15.join(projectDir, GENERATED_FILES.cursorMdcDir);
1004
- await fs6.ensureDir(mdcDir);
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 fs6.writeFile(path15.join(mdcDir, mdc.filename), mdc.content, "utf-8");
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, { strictness: opts?.strictness, customFragments: opts?.customFragments });
1017
- await fs6.writeJson(
1018
- path15.join(projectDir, AI_KIT_CONFIG_FILE),
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: "Prettier not detected \u2014 install for code formatting:",
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 path16 from "path";
1095
- import fs7 from "fs-extra";
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 = path16.resolve(targetPath || process.cwd());
1100
- const configPath = path16.join(projectDir, AI_KIT_CONFIG_FILE);
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(path16.join(projectDir, GENERATED_FILES.claudeMd))) {
1131
- const claudeMdPath = path16.join(projectDir, GENERATED_FILES.claudeMd);
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 fs7.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
1364
+ await fs9.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
1136
1365
  } else {
1137
- await fs7.writeFile(claudeMdPath, newContent, "utf-8");
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(path16.join(projectDir, GENERATED_FILES.cursorRules))) {
1143
- const cursorRulesPath = path16.join(projectDir, GENERATED_FILES.cursorRules);
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 fs7.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
1376
+ await fs9.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
1148
1377
  } else {
1149
- await fs7.writeFile(cursorRulesPath, newContent, "utf-8");
1378
+ await fs9.writeFile(cursorRulesPath, newContent, "utf-8");
1150
1379
  }
1151
1380
  templates.push(".cursorrules");
1152
1381
  logSuccess(".cursorrules updated");
1153
- const mdcDir = path16.join(projectDir, GENERATED_FILES.cursorMdcDir);
1154
- await fs7.ensureDir(mdcDir);
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 fs7.writeFile(path16.join(mdcDir, mdc.filename), mdc.content, "utf-8");
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, genOpts);
1166
- await fs7.writeJson(configPath, config, { spaces: 2 });
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 path17 from "path";
1174
- import fs8 from "fs-extra";
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 = path17.resolve(targetPath || process.cwd());
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 = path17.join(projectDir, GENERATED_FILES.claudeMd);
1444
+ const claudeMdPath = path19.join(projectDir, GENERATED_FILES.claudeMd);
1199
1445
  if (fileExists(claudeMdPath)) {
1200
- await fs8.remove(claudeMdPath);
1446
+ await fs10.remove(claudeMdPath);
1201
1447
  removed.push(GENERATED_FILES.claudeMd);
1202
1448
  }
1203
- const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
1449
+ const cursorPath = path19.join(projectDir, GENERATED_FILES.cursorRules);
1204
1450
  if (fileExists(cursorPath)) {
1205
- await fs8.remove(cursorPath);
1451
+ await fs10.remove(cursorPath);
1206
1452
  removed.push(GENERATED_FILES.cursorRules);
1207
1453
  }
1208
- const cursorMdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
1454
+ const cursorMdcDir = path19.join(projectDir, GENERATED_FILES.cursorMdcDir);
1209
1455
  if (fileExists(cursorMdcDir)) {
1210
- await fs8.remove(cursorMdcDir);
1456
+ await fs10.remove(cursorMdcDir);
1211
1457
  removed.push(GENERATED_FILES.cursorMdcDir);
1212
1458
  }
1213
- const commandsDir = path17.join(projectDir, GENERATED_FILES.claudeCommands);
1459
+ const commandsDir = path19.join(projectDir, GENERATED_FILES.claudeCommands);
1214
1460
  if (fileExists(commandsDir)) {
1215
- await fs8.remove(commandsDir);
1461
+ await fs10.remove(commandsDir);
1216
1462
  removed.push(GENERATED_FILES.claudeCommands);
1217
1463
  }
1218
- const claudeSkillsDir = path17.join(projectDir, GENERATED_FILES.claudeSkills);
1464
+ const claudeSkillsDir = path19.join(projectDir, GENERATED_FILES.claudeSkills);
1219
1465
  if (fileExists(claudeSkillsDir)) {
1220
- await fs8.remove(claudeSkillsDir);
1466
+ await fs10.remove(claudeSkillsDir);
1221
1467
  removed.push(GENERATED_FILES.claudeSkills);
1222
1468
  }
1223
- const cursorSkillsDir = path17.join(projectDir, GENERATED_FILES.cursorSkills);
1469
+ const cursorSkillsDir = path19.join(projectDir, GENERATED_FILES.cursorSkills);
1224
1470
  if (fileExists(cursorSkillsDir)) {
1225
- await fs8.remove(cursorSkillsDir);
1471
+ await fs10.remove(cursorSkillsDir);
1226
1472
  removed.push(GENERATED_FILES.cursorSkills);
1227
1473
  }
1228
- const aiKitDir = path17.join(projectDir, "ai-kit");
1474
+ const aiKitDir = path19.join(projectDir, "ai-kit");
1229
1475
  if (fileExists(aiKitDir)) {
1230
- await fs8.remove(aiKitDir);
1476
+ await fs10.remove(aiKitDir);
1231
1477
  removed.push("ai-kit/");
1232
1478
  }
1233
- const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
1479
+ const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
1234
1480
  if (fileExists(configPath)) {
1235
- await fs8.remove(configPath);
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 path18 from "path";
1248
- import fs9 from "fs-extra";
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 = path18.join(os.homedir(), ".claude", "projects");
1259
- if (!fs9.existsSync(claudeDir)) return [];
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 = fs9.readdirSync(dir, { withFileTypes: true });
1509
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
1264
1510
  for (const entry of entries) {
1265
- const full = path18.join(dir, entry.name);
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 = fs9.readFileSync(filePath, "utf-8");
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 = fs9.statSync(filePath);
1563
+ const stat = fs11.statSync(filePath);
1318
1564
  sessionDate = stat.mtime.toISOString().slice(0, 10);
1319
1565
  }
1320
- const sessionId = path18.basename(filePath, ".jsonl");
1321
- const projectName = path18.basename(path18.dirname(filePath));
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 = path18.join(process.cwd(), "token-usage.csv");
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 fs9.writeFile(csvPath, csvHeader + csvRows, "utf-8");
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 = path18.join(outputDir, "token-data.json");
1594
- const dashboardSrc = path18.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
1595
- const dashboardDest = path18.join(outputDir, "token-dashboard.html");
1596
- await fs9.writeJson(dataPath, exportData, { spaces: 2 });
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 fs9.pathExists(dashboardSrc)) {
1599
- await fs9.copy(dashboardSrc, dashboardDest, { overwrite: true });
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 path19 from "path";
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 = path19.resolve(targetPath || process.cwd());
1621
- const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
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 = path19.join(projectDir, templateFile);
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 = path19.join(projectDir, GENERATED_FILES.claudeSkills, skill);
1665
- const cursorSkillPath = path19.join(projectDir, GENERATED_FILES.cursorSkills, skill);
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 = path19.join(projectDir, "ai-kit", "guides");
1925
+ const guidesDir = path21.join(projectDir, "ai-kit", "guides");
1680
1926
  const missingGuides = [];
1681
1927
  for (const guide of config.guides) {
1682
- const guidePath = path19.join(guidesDir, guide);
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 path20 from "path";
1821
- import fs10 from "fs-extra";
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 = path20.resolve(targetPath || process.cwd());
1826
- const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
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 = path20.join(projectDir, GENERATED_FILES.claudeSkills);
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 = path20.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
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 = path20.join(projectDir, filename);
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 (!fs10.existsSync(dirPath)) return 0;
2044
- const entries = fs10.readdirSync(dirPath);
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 path21 from "path";
2053
- import fs11 from "fs-extra";
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 = path21.resolve(targetPath || process.cwd());
2090
- const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
2091
- const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
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 = path21.join(projectDir, target.file);
2379
+ const outputPath = path23.join(projectDir, target.file);
2134
2380
  const transformed = transformer(claudeContent);
2135
- await fs11.writeFile(outputPath, transformed, "utf-8");
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 path22 from "path";
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 = path22.resolve(targetPath || process.cwd());
2255
- const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
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