@mikulgohil/ai-kit 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +85 -12
  2. package/agents/architect.md +79 -0
  3. package/agents/build-resolver.md +54 -0
  4. package/agents/code-reviewer.md +60 -0
  5. package/agents/doc-updater.md +46 -0
  6. package/agents/e2e-runner.md +64 -0
  7. package/agents/planner.md +56 -0
  8. package/agents/refactor-cleaner.md +58 -0
  9. package/agents/security-reviewer.md +67 -0
  10. package/agents/sitecore-specialist.md +96 -0
  11. package/agents/tdd-guide.md +115 -0
  12. package/commands/checkpoint.md +78 -0
  13. package/commands/harness-audit.md +73 -0
  14. package/commands/middleware.md +80 -0
  15. package/commands/orchestrate.md +67 -0
  16. package/commands/quality-gate-check.md +109 -0
  17. package/commands/quality-gate.md +82 -0
  18. package/commands/resume-session.md +40 -0
  19. package/commands/save-session.md +65 -0
  20. package/commands/search-first.md +60 -0
  21. package/commands/server-action.md +93 -0
  22. package/commands/sitecore-debug.md +58 -0
  23. package/contexts/dev.md +35 -0
  24. package/contexts/research.md +56 -0
  25. package/contexts/review.md +49 -0
  26. package/dist/index.js +891 -119
  27. package/dist/index.js.map +1 -1
  28. package/guides/getting-started.md +74 -21
  29. package/guides/hooks-and-agents.md +124 -0
  30. package/package.json +9 -4
  31. package/templates/claude-md/nextjs-app-router.md +35 -1
  32. package/templates/claude-md/sitecore-xmc.md +99 -2
  33. package/templates/claude-md/typescript.md +79 -1
  34. package/templates/cursorrules/nextjs-app-router.md +6 -1
  35. package/templates/cursorrules/sitecore-xmc.md +5 -1
  36. package/templates/cursorrules/typescript.md +6 -1
  37. package/commands/ci-fix.md +0 -102
  38. package/commands/db-migrate.md +0 -138
  39. package/commands/dependency-graph.md +0 -138
  40. package/commands/docker-debug.md +0 -111
  41. 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 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.3.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
 
@@ -129,8 +134,15 @@ function detectSitecore(pkg) {
129
134
  const jssNextjs = deps["@sitecore-jss/sitecore-jss-nextjs"];
130
135
  const jssReact = deps["@sitecore-jss/sitecore-jss-react"];
131
136
  const contentSdk = deps["@sitecore-content-sdk/nextjs"];
132
- if (contentSdk || jssNextjs) {
133
- const version = (contentSdk || jssNextjs || "").replace(/[\^~>=<]/g, "");
137
+ if (contentSdk) {
138
+ const version = contentSdk.replace(/[\^~>=<]/g, "");
139
+ return {
140
+ cms: "sitecore-xmc-v2",
141
+ sitecoreContentSdkVersion: version || void 0
142
+ };
143
+ }
144
+ if (jssNextjs) {
145
+ const version = jssNextjs.replace(/[\^~>=<]/g, "");
134
146
  return { cms: "sitecore-xmc", sitecorejssVersion: version || void 0 };
135
147
  }
136
148
  if (jssReact) {
@@ -307,6 +319,7 @@ function detectTools(projectPath, pkg) {
307
319
  storybook: detectStorybook(projectPath, deps),
308
320
  eslint: detectEslint(projectPath, deps),
309
321
  prettier: detectPrettier(projectPath, deps),
322
+ biome: detectBiome(projectPath, deps),
310
323
  axeCore: detectAxeCore(deps),
311
324
  snyk: detectSnyk(projectPath, deps),
312
325
  knip: detectKnip(projectPath, deps),
@@ -362,6 +375,12 @@ function detectPrettier(projectPath, deps) {
362
375
  }
363
376
  return false;
364
377
  }
378
+ function detectBiome(projectPath, deps) {
379
+ if ("@biomejs/biome" in deps) return true;
380
+ if (fileExists(path8.join(projectPath, "biome.json"))) return true;
381
+ if (fileExists(path8.join(projectPath, "biome.jsonc"))) return true;
382
+ return false;
383
+ }
365
384
  function detectAxeCore(deps) {
366
385
  return "@axe-core/playwright" in deps || "axe-core" in deps;
367
386
  }
@@ -545,9 +564,17 @@ function buildVariables(scan) {
545
564
  techStack.push(`Next.js ${scan.nextjsVersion || ""}`);
546
565
  }
547
566
  if (scan.cms !== "none") {
548
- techStack.push(
549
- scan.cms === "sitecore-xmc" ? `Sitecore XM Cloud${scan.sitecorejssVersion ? ` (JSS ${scan.sitecorejssVersion})` : ""}` : "Sitecore JSS"
550
- );
567
+ if (scan.cms === "sitecore-xmc-v2") {
568
+ techStack.push(
569
+ `Sitecore XM Cloud${scan.sitecoreContentSdkVersion ? ` (Content SDK ${scan.sitecoreContentSdkVersion})` : " (Content SDK v2)"}`
570
+ );
571
+ } else if (scan.cms === "sitecore-xmc") {
572
+ techStack.push(
573
+ `Sitecore XM Cloud${scan.sitecorejssVersion ? ` (JSS ${scan.sitecorejssVersion})` : ""}`
574
+ );
575
+ } else {
576
+ techStack.push("Sitecore JSS");
577
+ }
551
578
  }
552
579
  if (scan.typescript) techStack.push("TypeScript");
553
580
  if (scan.styling.includes("tailwind"))
@@ -585,7 +612,9 @@ function buildCursorVariables(scan) {
585
612
  techStack.push(`Next.js ${scan.nextjsVersion || ""}`);
586
613
  }
587
614
  if (scan.cms !== "none") {
588
- techStack.push(scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : "Sitecore JSS");
615
+ techStack.push(
616
+ scan.cms === "sitecore-xmc-v2" || scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : "Sitecore JSS"
617
+ );
589
618
  }
590
619
  if (scan.typescript) techStack.push("TypeScript");
591
620
  if (scan.styling.includes("tailwind")) techStack.push("Tailwind CSS");
@@ -675,11 +704,152 @@ function generateConfig(scan, templates, commands, guides, options) {
675
704
  templates,
676
705
  commands,
677
706
  guides,
707
+ agents: options?.agents || [],
708
+ contexts: options?.contexts || [],
709
+ hooks: options?.hooks || false,
710
+ hookProfile: options?.hookProfile || "standard",
678
711
  strictness: options?.strictness || "standard",
679
712
  customFragments: options?.customFragments || []
680
713
  };
681
714
  }
682
715
 
716
+ // src/generator/hooks.ts
717
+ function generateHooks(scan, profile = "standard") {
718
+ const hooks = {};
719
+ const preToolUse = [];
720
+ const postToolUse = [];
721
+ const stop = [];
722
+ preToolUse.push({
723
+ matcher: "Bash(git push*)",
724
+ hooks: [
725
+ {
726
+ type: "command",
727
+ command: 'echo "\u26A0\uFE0F Review your changes before pushing. Run tests and type-check first."'
728
+ }
729
+ ]
730
+ });
731
+ if (scan.tools.biome) {
732
+ postToolUse.push({
733
+ matcher: "Edit|Write",
734
+ hooks: [
735
+ {
736
+ type: "command",
737
+ command: `npx @biomejs/biome check --write --unsafe "$CLAUDE_FILE_PATH" 2>/dev/null || true`
738
+ }
739
+ ]
740
+ });
741
+ } else if (scan.tools.prettier) {
742
+ postToolUse.push({
743
+ matcher: "Edit|Write",
744
+ hooks: [
745
+ {
746
+ type: "command",
747
+ command: `npx prettier --write "$CLAUDE_FILE_PATH" 2>/dev/null || true`
748
+ }
749
+ ]
750
+ });
751
+ }
752
+ if (scan.typescript && profile !== "minimal") {
753
+ postToolUse.push({
754
+ matcher: "Edit|Write",
755
+ hooks: [
756
+ {
757
+ type: "command",
758
+ command: 'case "$CLAUDE_FILE_PATH" in *.ts|*.tsx) npx tsc --noEmit --pretty 2>&1 | head -20 ;; esac'
759
+ }
760
+ ]
761
+ });
762
+ }
763
+ if (scan.tools.eslint && profile === "strict") {
764
+ postToolUse.push({
765
+ matcher: "Edit|Write",
766
+ hooks: [
767
+ {
768
+ type: "command",
769
+ command: 'case "$CLAUDE_FILE_PATH" in *.ts|*.tsx|*.js|*.jsx) npx eslint "$CLAUDE_FILE_PATH" --max-warnings 0 2>&1 | head -15 ;; esac'
770
+ }
771
+ ]
772
+ });
773
+ }
774
+ if (profile !== "minimal") {
775
+ postToolUse.push({
776
+ matcher: "Edit|Write",
777
+ hooks: [
778
+ {
779
+ type: "command",
780
+ 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'
781
+ }
782
+ ]
783
+ });
784
+ }
785
+ if (profile !== "minimal") {
786
+ postToolUse.push({
787
+ matcher: "Bash",
788
+ hooks: [
789
+ {
790
+ type: "command",
791
+ command: [
792
+ 'if [ "$CLAUDE_TOOL_EXIT_CODE" != "0" ] && [ -n "$CLAUDE_TOOL_EXIT_CODE" ]; then',
793
+ ' OUTPUT="$CLAUDE_TOOL_OUTPUT"',
794
+ " IS_BUILD_ERROR=false",
795
+ ' case "$OUTPUT" in',
796
+ ' *"error TS"*|*"tsc"*) IS_BUILD_ERROR=true ;;',
797
+ ' *"ESLint"*|*"eslint"*|*"Lint error"*) IS_BUILD_ERROR=true ;;',
798
+ ' *"Build error"*|*"build failed"*|*"ELIFECYCLE"*) IS_BUILD_ERROR=true ;;',
799
+ ' *"Module not found"*|*"Cannot find module"*) IS_BUILD_ERROR=true ;;',
800
+ ' *"SyntaxError"*|*"TypeError"*) IS_BUILD_ERROR=true ;;',
801
+ " esac",
802
+ ' if [ "$IS_BUILD_ERROR" = "true" ]; then',
803
+ ' LOG_FILE="docs/mistakes-log.md"',
804
+ ' if [ -f "$LOG_FILE" ]; then',
805
+ ' DATE=$(date +"%Y-%m-%d %H:%M")',
806
+ ' ERROR_PREVIEW=$(echo "$OUTPUT" | grep -i "error" | head -3 | sed "s/^/ /")',
807
+ " {",
808
+ ' echo ""',
809
+ ' echo "### $DATE \u2014 Build/lint failure (auto-captured)"',
810
+ ' echo "- **What happened**: Command exited with code $CLAUDE_TOOL_EXIT_CODE"',
811
+ ' echo "- **Error preview**:"',
812
+ ' echo "\\`\\`\\`"',
813
+ ' echo "$ERROR_PREVIEW"',
814
+ ' echo "\\`\\`\\`"',
815
+ ' echo "- **Root cause**: <!-- TODO: Fill in after investigating -->"',
816
+ ' echo "- **Fix**: <!-- TODO: How was it resolved? -->"',
817
+ ' echo "- **Lesson**: <!-- TODO: What to do differently -->"',
818
+ ' echo ""',
819
+ ' echo "---"',
820
+ ' } >> "$LOG_FILE"',
821
+ ' echo "\u{1F4DD} Mistake auto-logged to docs/mistakes-log.md"',
822
+ " fi",
823
+ " fi",
824
+ "fi"
825
+ ].join("\n")
826
+ }
827
+ ]
828
+ });
829
+ }
830
+ if (profile === "strict") {
831
+ stop.push({
832
+ matcher: "",
833
+ hooks: [
834
+ {
835
+ type: "command",
836
+ 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'
837
+ }
838
+ ]
839
+ });
840
+ }
841
+ if (preToolUse.length > 0) hooks.PreToolUse = preToolUse;
842
+ if (postToolUse.length > 0) hooks.PostToolUse = postToolUse;
843
+ if (stop.length > 0) hooks.Stop = stop;
844
+ return hooks;
845
+ }
846
+ function generateSettingsLocal(scan, profile = "standard") {
847
+ const hooks = generateHooks(scan, profile);
848
+ return {
849
+ hooks
850
+ };
851
+ }
852
+
683
853
  // src/copier/skills.ts
684
854
  import path12 from "path";
685
855
  import fs3 from "fs-extra";
@@ -715,14 +885,20 @@ var AVAILABLE_SKILLS = [
715
885
  "bundle-check",
716
886
  "i18n-check",
717
887
  "schema-gen",
718
- "docker-debug",
719
- "ci-fix",
720
888
  "changelog",
721
889
  "release",
722
890
  "storybook-gen",
723
- "visual-diff",
724
- "db-migrate",
725
- "dependency-graph"
891
+ // New skills (v1.2.0) — hooks, agents, sessions, orchestration
892
+ "search-first",
893
+ "quality-gate-check",
894
+ "server-action",
895
+ "middleware",
896
+ "save-session",
897
+ "resume-session",
898
+ "checkpoint",
899
+ "orchestrate",
900
+ "quality-gate",
901
+ "harness-audit"
726
902
  ];
727
903
  var SKILL_DESCRIPTIONS = {
728
904
  "prompt-help": "Help developers write effective AI prompts with structured context",
@@ -756,14 +932,20 @@ var SKILL_DESCRIPTIONS = {
756
932
  "bundle-check": "Analyze bundle size, find heavy imports, suggest tree-shaking and code splitting",
757
933
  "i18n-check": "Find hardcoded strings, missing translation keys, and internationalization gaps",
758
934
  "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
935
  "changelog": "Generate formatted changelogs from git history following Keep a Changelog format",
762
936
  "release": "Guided release workflow with versioning, changelog, tagging, and release notes",
763
937
  "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"
938
+ // New skills (v1.2.0) hooks, agents, sessions, orchestration
939
+ "search-first": "Research-before-coding \u2014 search docs, existing patterns, and APIs before writing code",
940
+ "quality-gate-check": "Post-implementation quality checklist \u2014 type safety, a11y, security, performance, Sitecore",
941
+ "server-action": "Scaffold Next.js Server Actions with Zod validation, error handling, and revalidation",
942
+ "middleware": "Create or update Next.js middleware for auth, redirects, i18n, or Sitecore preview mode",
943
+ "save-session": "Persist current session context, decisions, and pending work for later resumption",
944
+ "resume-session": "Restore context from a previous session and continue where you left off",
945
+ "checkpoint": "Create a verification snapshot \u2014 run all quality checks and record pass/fail status",
946
+ "orchestrate": "Multi-agent orchestration \u2014 break complex tasks into subtasks and delegate to agents",
947
+ "quality-gate": "Run comprehensive quality checks: types, lint, format, tests, bundle, a11y, security",
948
+ "harness-audit": "Audit AI agent configuration \u2014 check CLAUDE.md, hooks, agents, skills, MCP servers"
767
949
  };
768
950
  async function copySkills(targetDir) {
769
951
  const copied = [];
@@ -802,7 +984,8 @@ var AVAILABLE_GUIDES = [
802
984
  "prompt-playbook",
803
985
  "when-to-use-ai",
804
986
  "token-saving-tips",
805
- "figma-workflow"
987
+ "figma-workflow",
988
+ "hooks-and-agents"
806
989
  ];
807
990
  async function copyGuides(targetDir) {
808
991
  const guidesTarget = path13.join(targetDir, "ai-kit", "guides");
@@ -841,12 +1024,81 @@ async function scaffoldDocs(targetDir) {
841
1024
  return created;
842
1025
  }
843
1026
 
1027
+ // src/copier/agents.ts
1028
+ import path15 from "path";
1029
+ import fs6 from "fs-extra";
1030
+ var UNIVERSAL_AGENTS = [
1031
+ "planner",
1032
+ "code-reviewer",
1033
+ "security-reviewer",
1034
+ "build-resolver",
1035
+ "doc-updater",
1036
+ "refactor-cleaner",
1037
+ "architect"
1038
+ ];
1039
+ var CONDITIONAL_AGENTS = [
1040
+ {
1041
+ name: "e2e-runner",
1042
+ condition: (scan) => scan.tools.playwright
1043
+ },
1044
+ {
1045
+ name: "sitecore-specialist",
1046
+ condition: (scan) => scan.cms !== "none"
1047
+ },
1048
+ {
1049
+ name: "tdd-guide",
1050
+ condition: (scan) => scan.tools.playwright || !!scan.scripts["test"]
1051
+ }
1052
+ ];
1053
+ async function copyAgents(targetDir, scan) {
1054
+ const agentsTarget = path15.join(targetDir, ".claude", "agents");
1055
+ await fs6.ensureDir(agentsTarget);
1056
+ const copied = [];
1057
+ for (const agent of UNIVERSAL_AGENTS) {
1058
+ const src = path15.join(AGENTS_DIR, `${agent}.md`);
1059
+ if (!await fs6.pathExists(src)) continue;
1060
+ await fs6.copy(src, path15.join(agentsTarget, `${agent}.md`), {
1061
+ overwrite: true
1062
+ });
1063
+ copied.push(agent);
1064
+ }
1065
+ for (const { name, condition } of CONDITIONAL_AGENTS) {
1066
+ if (!condition(scan)) continue;
1067
+ const src = path15.join(AGENTS_DIR, `${name}.md`);
1068
+ if (!await fs6.pathExists(src)) continue;
1069
+ await fs6.copy(src, path15.join(agentsTarget, `${name}.md`), {
1070
+ overwrite: true
1071
+ });
1072
+ copied.push(name);
1073
+ }
1074
+ return copied;
1075
+ }
1076
+
1077
+ // src/copier/contexts.ts
1078
+ import path16 from "path";
1079
+ import fs7 from "fs-extra";
1080
+ var AVAILABLE_CONTEXTS = ["dev", "review", "research"];
1081
+ async function copyContexts(targetDir) {
1082
+ const contextsTarget = path16.join(targetDir, ".claude", "contexts");
1083
+ await fs7.ensureDir(contextsTarget);
1084
+ const copied = [];
1085
+ for (const context of AVAILABLE_CONTEXTS) {
1086
+ const src = path16.join(CONTEXTS_DIR, `${context}.md`);
1087
+ if (!await fs7.pathExists(src)) continue;
1088
+ await fs7.copy(src, path16.join(contextsTarget, `${context}.md`), {
1089
+ overwrite: true
1090
+ });
1091
+ copied.push(context);
1092
+ }
1093
+ return copied;
1094
+ }
1095
+
844
1096
  // src/commands/init.ts
845
1097
  async function initCommand(targetPath) {
846
- const projectDir = path15.resolve(targetPath || process.cwd());
1098
+ const projectDir = path17.resolve(targetPath || process.cwd());
847
1099
  logSection("AI Kit \u2014 Project Setup");
848
1100
  logInfo(`Scanning: ${projectDir}`);
849
- const configPath = path15.join(projectDir, AI_KIT_CONFIG_FILE);
1101
+ const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
850
1102
  if (fileExists(configPath)) {
851
1103
  const overwrite = await confirm({
852
1104
  message: "AI Kit is already configured in this project. Re-initialize?",
@@ -874,14 +1126,20 @@ async function initCommand(targetPath) {
874
1126
  logInfo(`TypeScript: ${scan.typescript ? "Yes" : "No"}`);
875
1127
  logInfo(`Monorepo: ${scan.monorepo ? `Yes (${scan.monorepoTool})` : "No"}`);
876
1128
  logInfo(`Package Manager: ${scan.packageManager}`);
1129
+ logInfo(`Formatter: ${scan.tools.biome ? "Biome" : scan.tools.prettier ? "Prettier" : "None detected"}`);
877
1130
  const clarifications = await askClarifications(scan);
878
1131
  scan = applyClarifications(scan, clarifications);
879
1132
  const tools = await selectTools();
880
1133
  const strictness = await selectStrictness();
1134
+ const hookProfile = await selectHookProfile();
881
1135
  const customFragments = loadCustomFragments(projectDir);
882
1136
  const conflict = await selectConflictStrategy(projectDir);
883
1137
  logSection("Generating Files");
884
- const results = await generate(projectDir, scan, tools, conflict, { strictness, customFragments });
1138
+ const results = await generate(projectDir, scan, tools, conflict, {
1139
+ strictness,
1140
+ customFragments,
1141
+ hookProfile
1142
+ });
885
1143
  logSection("Setup Complete");
886
1144
  if (results.claudeMd) logSuccess(`CLAUDE.md generated`);
887
1145
  if (results.cursorRules) logSuccess(`.cursorrules generated`);
@@ -889,6 +1147,12 @@ async function initCommand(targetPath) {
889
1147
  logSuccess(`${results.cursorMdcFiles} .cursor/rules/*.mdc files generated`);
890
1148
  if (results.commands.length > 0)
891
1149
  logSuccess(`${results.commands.length} skills generated (.claude/skills/ + .cursor/skills/)`);
1150
+ if (results.agents.length > 0)
1151
+ logSuccess(`${results.agents.length} agents generated (.claude/agents/)`);
1152
+ if (results.contexts.length > 0)
1153
+ logSuccess(`${results.contexts.length} context modes generated (.claude/contexts/)`);
1154
+ if (results.hooks)
1155
+ logSuccess(`Hooks configured (.claude/settings.local.json) \u2014 profile: ${hookProfile}`);
892
1156
  if (results.guides.length > 0)
893
1157
  logSuccess(`${results.guides.length} guides added to ai-kit/guides/`);
894
1158
  if (results.docs.length > 0)
@@ -896,6 +1160,7 @@ async function initCommand(targetPath) {
896
1160
  showRecommendations(scan);
897
1161
  console.log("");
898
1162
  logInfo("Run `ai-kit update` anytime to refresh configs after project changes.");
1163
+ logInfo("Run `ai-kit audit` to check your AI agent configuration health.");
899
1164
  logInfo("Check ai-kit/guides/getting-started.md to get started.");
900
1165
  }
901
1166
  function formatFramework(scan) {
@@ -953,8 +1218,28 @@ async function selectStrictness() {
953
1218
  default: "standard"
954
1219
  });
955
1220
  }
1221
+ async function selectHookProfile() {
1222
+ return select({
1223
+ message: "Hook automation profile (runs checks automatically as you code):",
1224
+ choices: [
1225
+ {
1226
+ name: "Standard \u2014 auto-format + typecheck + console.log warnings",
1227
+ value: "standard"
1228
+ },
1229
+ {
1230
+ name: "Strict \u2014 all standard hooks + ESLint + stop checks",
1231
+ value: "strict"
1232
+ },
1233
+ {
1234
+ name: "Minimal \u2014 auto-format + git push safety only",
1235
+ value: "minimal"
1236
+ }
1237
+ ],
1238
+ default: "standard"
1239
+ });
1240
+ }
956
1241
  async function selectConflictStrategy(projectDir) {
957
- const hasExisting = fileExists(path15.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path15.join(projectDir, GENERATED_FILES.cursorRules));
1242
+ const hasExisting = fileExists(path17.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path17.join(projectDir, GENERATED_FILES.cursorRules));
958
1243
  if (!hasExisting) return "overwrite";
959
1244
  return select({
960
1245
  message: "Existing AI config files detected. How should we handle conflicts?",
@@ -977,34 +1262,45 @@ async function generate(projectDir, scan, tools, conflict, opts) {
977
1262
  cursorRules: false,
978
1263
  cursorMdcFiles: 0,
979
1264
  commands: [],
1265
+ agents: [],
1266
+ contexts: [],
1267
+ hooks: false,
980
1268
  guides: [],
981
1269
  docs: []
982
1270
  };
983
1271
  if (tools.claude) {
984
- const claudeMdPath = path15.join(projectDir, GENERATED_FILES.claudeMd);
1272
+ const claudeMdPath = path17.join(projectDir, GENERATED_FILES.claudeMd);
985
1273
  if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
986
1274
  const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
987
- await fs6.writeFile(claudeMdPath, content, "utf-8");
1275
+ await fs8.writeFile(claudeMdPath, content, "utf-8");
988
1276
  result.claudeMd = true;
989
1277
  } else {
990
1278
  logWarning("CLAUDE.md exists, skipping");
991
1279
  }
992
1280
  result.commands = await copySkills(projectDir);
1281
+ result.agents = await copyAgents(projectDir, scan);
1282
+ result.contexts = await copyContexts(projectDir);
1283
+ const hookProfile = opts?.hookProfile || "standard";
1284
+ const settingsLocalPath = path17.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1285
+ const settingsLocal = generateSettingsLocal(scan, hookProfile);
1286
+ await fs8.ensureDir(path17.dirname(settingsLocalPath));
1287
+ await fs8.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1288
+ result.hooks = true;
993
1289
  }
994
1290
  if (tools.cursor) {
995
- const cursorPath = path15.join(projectDir, GENERATED_FILES.cursorRules);
1291
+ const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
996
1292
  if (conflict === "overwrite" || !fileExists(cursorPath)) {
997
1293
  const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
998
- await fs6.writeFile(cursorPath, content, "utf-8");
1294
+ await fs8.writeFile(cursorPath, content, "utf-8");
999
1295
  result.cursorRules = true;
1000
1296
  } else {
1001
1297
  logWarning(".cursorrules exists, skipping");
1002
1298
  }
1003
- const mdcDir = path15.join(projectDir, GENERATED_FILES.cursorMdcDir);
1004
- await fs6.ensureDir(mdcDir);
1299
+ const mdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
1300
+ await fs8.ensureDir(mdcDir);
1005
1301
  const mdcFiles = generateMdcFiles(scan);
1006
1302
  for (const mdc of mdcFiles) {
1007
- await fs6.writeFile(path15.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1303
+ await fs8.writeFile(path17.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1008
1304
  }
1009
1305
  result.cursorMdcFiles = mdcFiles.length;
1010
1306
  }
@@ -1013,9 +1309,16 @@ async function generate(projectDir, scan, tools, conflict, opts) {
1013
1309
  const templates = [];
1014
1310
  if (result.claudeMd) templates.push("CLAUDE.md");
1015
1311
  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),
1312
+ const config = generateConfig(scan, templates, result.commands, result.guides, {
1313
+ strictness: opts?.strictness,
1314
+ customFragments: opts?.customFragments,
1315
+ agents: result.agents,
1316
+ contexts: result.contexts,
1317
+ hooks: result.hooks,
1318
+ hookProfile: opts?.hookProfile
1319
+ });
1320
+ await fs8.writeJson(
1321
+ path17.join(projectDir, AI_KIT_CONFIG_FILE),
1019
1322
  config,
1020
1323
  { spaces: 2 }
1021
1324
  );
@@ -1034,9 +1337,9 @@ function showRecommendations(scan) {
1034
1337
  hint: " npm install -D eslint @typescript-eslint/eslint-plugin"
1035
1338
  },
1036
1339
  {
1037
- check: scan.tools.prettier,
1038
- label: "Prettier not detected \u2014 install for code formatting:",
1039
- hint: " npm install -D prettier"
1340
+ check: scan.tools.prettier || scan.tools.biome,
1341
+ label: "No formatter detected \u2014 install Prettier or Biome for auto-formatting hooks:",
1342
+ hint: " npm install -D prettier (or) npm install -D @biomejs/biome"
1040
1343
  },
1041
1344
  {
1042
1345
  check: scan.tools.axeCore,
@@ -1091,13 +1394,13 @@ function showRecommendations(scan) {
1091
1394
  }
1092
1395
 
1093
1396
  // src/commands/update.ts
1094
- import path16 from "path";
1095
- import fs7 from "fs-extra";
1397
+ import path18 from "path";
1398
+ import fs9 from "fs-extra";
1096
1399
  import ora2 from "ora";
1097
1400
  import { confirm as confirm2 } from "@inquirer/prompts";
1098
1401
  async function updateCommand(targetPath) {
1099
- const projectDir = path16.resolve(targetPath || process.cwd());
1100
- const configPath = path16.join(projectDir, AI_KIT_CONFIG_FILE);
1402
+ const projectDir = path18.resolve(targetPath || process.cwd());
1403
+ const configPath = path18.join(projectDir, AI_KIT_CONFIG_FILE);
1101
1404
  if (!fileExists(configPath)) {
1102
1405
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
1103
1406
  return;
@@ -1124,57 +1427,75 @@ async function updateCommand(targetPath) {
1124
1427
  spinner.succeed("Project re-scanned");
1125
1428
  logSection("Updating Files");
1126
1429
  const strictness = existingConfig.strictness || "standard";
1430
+ const hookProfile = existingConfig.hookProfile || "standard";
1127
1431
  const customFragments = loadCustomFragments(projectDir);
1128
1432
  const genOpts = { strictness, customFragments };
1129
1433
  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);
1434
+ if (existingConfig.templates.includes("CLAUDE.md") || fileExists(path18.join(projectDir, GENERATED_FILES.claudeMd))) {
1435
+ const claudeMdPath = path18.join(projectDir, GENERATED_FILES.claudeMd);
1132
1436
  const newContent = generateClaudeMd(scan, genOpts);
1133
1437
  const existing = readFileSafe(claudeMdPath);
1134
1438
  if (existing) {
1135
- await fs7.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
1439
+ await fs9.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
1136
1440
  } else {
1137
- await fs7.writeFile(claudeMdPath, newContent, "utf-8");
1441
+ await fs9.writeFile(claudeMdPath, newContent, "utf-8");
1138
1442
  }
1139
1443
  templates.push("CLAUDE.md");
1140
1444
  logSuccess("CLAUDE.md updated");
1141
1445
  }
1142
- if (existingConfig.templates.includes(".cursorrules") || fileExists(path16.join(projectDir, GENERATED_FILES.cursorRules))) {
1143
- const cursorRulesPath = path16.join(projectDir, GENERATED_FILES.cursorRules);
1446
+ if (existingConfig.templates.includes(".cursorrules") || fileExists(path18.join(projectDir, GENERATED_FILES.cursorRules))) {
1447
+ const cursorRulesPath = path18.join(projectDir, GENERATED_FILES.cursorRules);
1144
1448
  const newContent = generateCursorRules(scan, genOpts);
1145
1449
  const existing = readFileSafe(cursorRulesPath);
1146
1450
  if (existing) {
1147
- await fs7.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
1451
+ await fs9.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
1148
1452
  } else {
1149
- await fs7.writeFile(cursorRulesPath, newContent, "utf-8");
1453
+ await fs9.writeFile(cursorRulesPath, newContent, "utf-8");
1150
1454
  }
1151
1455
  templates.push(".cursorrules");
1152
1456
  logSuccess(".cursorrules updated");
1153
- const mdcDir = path16.join(projectDir, GENERATED_FILES.cursorMdcDir);
1154
- await fs7.ensureDir(mdcDir);
1457
+ const mdcDir = path18.join(projectDir, GENERATED_FILES.cursorMdcDir);
1458
+ await fs9.ensureDir(mdcDir);
1155
1459
  const mdcFiles = generateMdcFiles(scan);
1156
1460
  for (const mdc of mdcFiles) {
1157
- await fs7.writeFile(path16.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1461
+ await fs9.writeFile(path18.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1158
1462
  }
1159
1463
  logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
1160
1464
  }
1161
1465
  const commands = await copySkills(projectDir);
1162
1466
  logSuccess(`${commands.length} skills updated (.claude/skills/ + .cursor/skills/)`);
1467
+ const agents = await copyAgents(projectDir, scan);
1468
+ logSuccess(`${agents.length} agents updated (.claude/agents/)`);
1469
+ const contexts = await copyContexts(projectDir);
1470
+ logSuccess(`${contexts.length} context modes updated (.claude/contexts/)`);
1471
+ if (existingConfig.hooks !== false) {
1472
+ const settingsLocalPath = path18.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
1473
+ const settingsLocal = generateSettingsLocal(scan, hookProfile);
1474
+ await fs9.ensureDir(path18.dirname(settingsLocalPath));
1475
+ await fs9.writeJson(settingsLocalPath, settingsLocal, { spaces: 2 });
1476
+ logSuccess(`Hooks updated (profile: ${hookProfile})`);
1477
+ }
1163
1478
  const guides = await copyGuides(projectDir);
1164
1479
  logSuccess(`${guides.length} guides updated`);
1165
- const config = generateConfig(scan, templates, commands, guides, genOpts);
1166
- await fs7.writeJson(configPath, config, { spaces: 2 });
1480
+ const config = generateConfig(scan, templates, commands, guides, {
1481
+ ...genOpts,
1482
+ agents,
1483
+ contexts,
1484
+ hooks: existingConfig.hooks !== false,
1485
+ hookProfile
1486
+ });
1487
+ await fs9.writeJson(configPath, config, { spaces: 2 });
1167
1488
  logSuccess("ai-kit.config.json updated");
1168
1489
  console.log("");
1169
1490
  logInfo("All AI configs refreshed with latest project scan.");
1170
1491
  }
1171
1492
 
1172
1493
  // src/commands/reset.ts
1173
- import path17 from "path";
1174
- import fs8 from "fs-extra";
1494
+ import path19 from "path";
1495
+ import fs10 from "fs-extra";
1175
1496
  import { confirm as confirm3 } from "@inquirer/prompts";
1176
1497
  async function resetCommand(targetPath) {
1177
- const projectDir = path17.resolve(targetPath || process.cwd());
1498
+ const projectDir = path19.resolve(targetPath || process.cwd());
1178
1499
  logSection("AI Kit \u2014 Reset");
1179
1500
  logWarning("This will remove all AI Kit generated files:");
1180
1501
  logInfo(` - ${GENERATED_FILES.claudeMd}`);
@@ -1195,44 +1516,44 @@ async function resetCommand(targetPath) {
1195
1516
  return;
1196
1517
  }
1197
1518
  const removed = [];
1198
- const claudeMdPath = path17.join(projectDir, GENERATED_FILES.claudeMd);
1519
+ const claudeMdPath = path19.join(projectDir, GENERATED_FILES.claudeMd);
1199
1520
  if (fileExists(claudeMdPath)) {
1200
- await fs8.remove(claudeMdPath);
1521
+ await fs10.remove(claudeMdPath);
1201
1522
  removed.push(GENERATED_FILES.claudeMd);
1202
1523
  }
1203
- const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
1524
+ const cursorPath = path19.join(projectDir, GENERATED_FILES.cursorRules);
1204
1525
  if (fileExists(cursorPath)) {
1205
- await fs8.remove(cursorPath);
1526
+ await fs10.remove(cursorPath);
1206
1527
  removed.push(GENERATED_FILES.cursorRules);
1207
1528
  }
1208
- const cursorMdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
1529
+ const cursorMdcDir = path19.join(projectDir, GENERATED_FILES.cursorMdcDir);
1209
1530
  if (fileExists(cursorMdcDir)) {
1210
- await fs8.remove(cursorMdcDir);
1531
+ await fs10.remove(cursorMdcDir);
1211
1532
  removed.push(GENERATED_FILES.cursorMdcDir);
1212
1533
  }
1213
- const commandsDir = path17.join(projectDir, GENERATED_FILES.claudeCommands);
1534
+ const commandsDir = path19.join(projectDir, GENERATED_FILES.claudeCommands);
1214
1535
  if (fileExists(commandsDir)) {
1215
- await fs8.remove(commandsDir);
1536
+ await fs10.remove(commandsDir);
1216
1537
  removed.push(GENERATED_FILES.claudeCommands);
1217
1538
  }
1218
- const claudeSkillsDir = path17.join(projectDir, GENERATED_FILES.claudeSkills);
1539
+ const claudeSkillsDir = path19.join(projectDir, GENERATED_FILES.claudeSkills);
1219
1540
  if (fileExists(claudeSkillsDir)) {
1220
- await fs8.remove(claudeSkillsDir);
1541
+ await fs10.remove(claudeSkillsDir);
1221
1542
  removed.push(GENERATED_FILES.claudeSkills);
1222
1543
  }
1223
- const cursorSkillsDir = path17.join(projectDir, GENERATED_FILES.cursorSkills);
1544
+ const cursorSkillsDir = path19.join(projectDir, GENERATED_FILES.cursorSkills);
1224
1545
  if (fileExists(cursorSkillsDir)) {
1225
- await fs8.remove(cursorSkillsDir);
1546
+ await fs10.remove(cursorSkillsDir);
1226
1547
  removed.push(GENERATED_FILES.cursorSkills);
1227
1548
  }
1228
- const aiKitDir = path17.join(projectDir, "ai-kit");
1549
+ const aiKitDir = path19.join(projectDir, "ai-kit");
1229
1550
  if (fileExists(aiKitDir)) {
1230
- await fs8.remove(aiKitDir);
1551
+ await fs10.remove(aiKitDir);
1231
1552
  removed.push("ai-kit/");
1232
1553
  }
1233
- const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
1554
+ const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
1234
1555
  if (fileExists(configPath)) {
1235
- await fs8.remove(configPath);
1556
+ await fs10.remove(configPath);
1236
1557
  removed.push(AI_KIT_CONFIG_FILE);
1237
1558
  }
1238
1559
  logSection("Reset Complete");
@@ -1244,8 +1565,8 @@ async function resetCommand(targetPath) {
1244
1565
  }
1245
1566
 
1246
1567
  // src/commands/tokens.ts
1247
- import path18 from "path";
1248
- import fs9 from "fs-extra";
1568
+ import path20 from "path";
1569
+ import fs11 from "fs-extra";
1249
1570
  import chalk2 from "chalk";
1250
1571
  import ora3 from "ora";
1251
1572
  import os from "os";
@@ -1255,14 +1576,14 @@ var PRICING = {
1255
1576
  };
1256
1577
  var PLAN_BUDGET = 20;
1257
1578
  function findSessionFiles() {
1258
- const claudeDir = path18.join(os.homedir(), ".claude", "projects");
1259
- if (!fs9.existsSync(claudeDir)) return [];
1579
+ const claudeDir = path20.join(os.homedir(), ".claude", "projects");
1580
+ if (!fs11.existsSync(claudeDir)) return [];
1260
1581
  const files = [];
1261
1582
  function walkDir(dir) {
1262
1583
  try {
1263
- const entries = fs9.readdirSync(dir, { withFileTypes: true });
1584
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
1264
1585
  for (const entry of entries) {
1265
- const full = path18.join(dir, entry.name);
1586
+ const full = path20.join(dir, entry.name);
1266
1587
  if (entry.isDirectory()) {
1267
1588
  walkDir(full);
1268
1589
  } else if (entry.name.endsWith(".jsonl")) {
@@ -1277,7 +1598,7 @@ function findSessionFiles() {
1277
1598
  }
1278
1599
  function parseSessionFile(filePath) {
1279
1600
  try {
1280
- const content = fs9.readFileSync(filePath, "utf-8");
1601
+ const content = fs11.readFileSync(filePath, "utf-8");
1281
1602
  const lines = content.split("\n").filter((l) => l.trim());
1282
1603
  const usage = {
1283
1604
  inputTokens: 0,
@@ -1314,11 +1635,11 @@ function parseSessionFile(filePath) {
1314
1635
  }
1315
1636
  if (usage.inputTokens === 0 && usage.outputTokens === 0) return null;
1316
1637
  if (!sessionDate) {
1317
- const stat = fs9.statSync(filePath);
1638
+ const stat = fs11.statSync(filePath);
1318
1639
  sessionDate = stat.mtime.toISOString().slice(0, 10);
1319
1640
  }
1320
- const sessionId = path18.basename(filePath, ".jsonl");
1321
- const projectName = path18.basename(path18.dirname(filePath));
1641
+ const sessionId = path20.basename(filePath, ".jsonl");
1642
+ const projectName = path20.basename(path20.dirname(filePath));
1322
1643
  return {
1323
1644
  sessionId,
1324
1645
  filePath,
@@ -1547,13 +1868,13 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
1547
1868
  );
1548
1869
  console.log("");
1549
1870
  if (options.csv) {
1550
- const csvPath = path18.join(process.cwd(), "token-usage.csv");
1871
+ const csvPath = path20.join(process.cwd(), "token-usage.csv");
1551
1872
  const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
1552
1873
  const daily = aggregateByDate(sessions);
1553
1874
  const csvRows = daily.map(
1554
1875
  (d) => `${d.date},${d.sessions},${d.usage.inputTokens},${d.usage.outputTokens},${d.usage.cacheReadTokens},${d.cost.toFixed(2)}`
1555
1876
  ).join("\n");
1556
- await fs9.writeFile(csvPath, csvHeader + csvRows, "utf-8");
1877
+ await fs11.writeFile(csvPath, csvHeader + csvRows, "utf-8");
1557
1878
  logSuccess(`CSV exported to ${csvPath}`);
1558
1879
  }
1559
1880
  if (options.export) {
@@ -1590,13 +1911,13 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
1590
1911
  }
1591
1912
  };
1592
1913
  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 });
1914
+ const dataPath = path20.join(outputDir, "token-data.json");
1915
+ const dashboardSrc = path20.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
1916
+ const dashboardDest = path20.join(outputDir, "token-dashboard.html");
1917
+ await fs11.writeJson(dataPath, exportData, { spaces: 2 });
1597
1918
  logInfo(`Token data written to ${dataPath}`);
1598
- if (await fs9.pathExists(dashboardSrc)) {
1599
- await fs9.copy(dashboardSrc, dashboardDest, { overwrite: true });
1919
+ if (await fs11.pathExists(dashboardSrc)) {
1920
+ await fs11.copy(dashboardSrc, dashboardDest, { overwrite: true });
1600
1921
  logInfo(`Dashboard copied to ${dashboardDest}`);
1601
1922
  } else {
1602
1923
  logWarning("Dashboard template not found. Skipping HTML export.");
@@ -1613,12 +1934,12 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
1613
1934
  }
1614
1935
 
1615
1936
  // src/commands/doctor.ts
1616
- import path19 from "path";
1937
+ import path21 from "path";
1617
1938
  import chalk3 from "chalk";
1618
1939
  import ora4 from "ora";
1619
1940
  async function doctorCommand(targetPath) {
1620
- const projectDir = path19.resolve(targetPath || process.cwd());
1621
- const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
1941
+ const projectDir = path21.resolve(targetPath || process.cwd());
1942
+ const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
1622
1943
  let passed = 0;
1623
1944
  let warnings = 0;
1624
1945
  let issues = 0;
@@ -1650,7 +1971,7 @@ async function doctorCommand(targetPath) {
1650
1971
  }
1651
1972
  for (const template of config.templates) {
1652
1973
  const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
1653
- const templatePath = path19.join(projectDir, templateFile);
1974
+ const templatePath = path21.join(projectDir, templateFile);
1654
1975
  if (fileExists(templatePath)) {
1655
1976
  logSuccess(`${template} exists and in sync`);
1656
1977
  passed++;
@@ -1661,8 +1982,8 @@ async function doctorCommand(targetPath) {
1661
1982
  }
1662
1983
  const missingSkills = [];
1663
1984
  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);
1985
+ const claudeSkillPath = path21.join(projectDir, GENERATED_FILES.claudeSkills, skill);
1986
+ const cursorSkillPath = path21.join(projectDir, GENERATED_FILES.cursorSkills, skill);
1666
1987
  if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
1667
1988
  missingSkills.push(skill);
1668
1989
  }
@@ -1676,10 +1997,10 @@ async function doctorCommand(targetPath) {
1676
1997
  );
1677
1998
  issues++;
1678
1999
  }
1679
- const guidesDir = path19.join(projectDir, "ai-kit", "guides");
2000
+ const guidesDir = path21.join(projectDir, "ai-kit", "guides");
1680
2001
  const missingGuides = [];
1681
2002
  for (const guide of config.guides) {
1682
- const guidePath = path19.join(guidesDir, guide);
2003
+ const guidePath = path21.join(guidesDir, guide);
1683
2004
  if (!fileExists(guidePath)) {
1684
2005
  missingGuides.push(guide);
1685
2006
  }
@@ -1817,13 +2138,13 @@ function compareScanResults(previous, current) {
1817
2138
  }
1818
2139
 
1819
2140
  // src/commands/diff.ts
1820
- import path20 from "path";
1821
- import fs10 from "fs-extra";
2141
+ import path22 from "path";
2142
+ import fs12 from "fs-extra";
1822
2143
  import chalk4 from "chalk";
1823
2144
  import ora5 from "ora";
1824
2145
  async function diffCommand(targetPath) {
1825
- const projectDir = path20.resolve(targetPath || process.cwd());
1826
- const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
2146
+ const projectDir = path22.resolve(targetPath || process.cwd());
2147
+ const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
1827
2148
  console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
1828
2149
  if (!fileExists(configPath)) {
1829
2150
  logError("No ai-kit.config.json found. Run `ai-kit init` first.");
@@ -1890,11 +2211,11 @@ async function diffCommand(targetPath) {
1890
2211
  if (cursorRulesStatus.status === "modified") modified++;
1891
2212
  else if (cursorRulesStatus.status === "added") added++;
1892
2213
  else unchanged++;
1893
- const skillsDir = path20.join(projectDir, GENERATED_FILES.claudeSkills);
2214
+ const skillsDir = path22.join(projectDir, GENERATED_FILES.claudeSkills);
1894
2215
  const skillCount = countFilesInDir(skillsDir);
1895
2216
  console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
1896
2217
  unchanged++;
1897
- const guidesDir = path20.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
2218
+ const guidesDir = path22.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
1898
2219
  const guideCount = existingConfig.guides?.length || 0;
1899
2220
  console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
1900
2221
  unchanged++;
@@ -2003,7 +2324,7 @@ function diffStack(oldScan, newScan) {
2003
2324
  return changes;
2004
2325
  }
2005
2326
  function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
2006
- const filePath = path20.join(projectDir, filename);
2327
+ const filePath = path22.join(projectDir, filename);
2007
2328
  const currentContent = readFileSafe(filePath);
2008
2329
  const newContent = generate2();
2009
2330
  if (!currentContent) {
@@ -2040,8 +2361,8 @@ function logFileChange(result) {
2040
2361
  }
2041
2362
  function countFilesInDir(dirPath) {
2042
2363
  try {
2043
- if (!fs10.existsSync(dirPath)) return 0;
2044
- const entries = fs10.readdirSync(dirPath);
2364
+ if (!fs12.existsSync(dirPath)) return 0;
2365
+ const entries = fs12.readdirSync(dirPath);
2045
2366
  return entries.filter((e) => !e.startsWith(".")).length;
2046
2367
  } catch {
2047
2368
  return 0;
@@ -2049,8 +2370,8 @@ function countFilesInDir(dirPath) {
2049
2370
  }
2050
2371
 
2051
2372
  // src/commands/export.ts
2052
- import path21 from "path";
2053
- import fs11 from "fs-extra";
2373
+ import path23 from "path";
2374
+ import fs13 from "fs-extra";
2054
2375
  import ora6 from "ora";
2055
2376
  import { select as select2 } from "@inquirer/prompts";
2056
2377
  var EXPORT_TARGETS = {
@@ -2086,9 +2407,9 @@ function toCline(content) {
2086
2407
  ${stripped}`;
2087
2408
  }
2088
2409
  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);
2410
+ const projectDir = path23.resolve(targetPath || process.cwd());
2411
+ const configPath = path23.join(projectDir, AI_KIT_CONFIG_FILE);
2412
+ const claudeMdPath = path23.join(projectDir, GENERATED_FILES.claudeMd);
2092
2413
  logSection("AI Kit \u2014 Export");
2093
2414
  if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
2094
2415
  logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
@@ -2130,9 +2451,9 @@ async function exportCommand(targetPath, options) {
2130
2451
  for (const fmt2 of formats) {
2131
2452
  const target = EXPORT_TARGETS[fmt2];
2132
2453
  const transformer = transformers[fmt2];
2133
- const outputPath = path21.join(projectDir, target.file);
2454
+ const outputPath = path23.join(projectDir, target.file);
2134
2455
  const transformed = transformer(claudeContent);
2135
- await fs11.writeFile(outputPath, transformed, "utf-8");
2456
+ await fs13.writeFile(outputPath, transformed, "utf-8");
2136
2457
  exported++;
2137
2458
  }
2138
2459
  spinner.succeed("Export complete");
@@ -2148,7 +2469,7 @@ async function exportCommand(targetPath, options) {
2148
2469
  }
2149
2470
 
2150
2471
  // src/commands/stats.ts
2151
- import path22 from "path";
2472
+ import path24 from "path";
2152
2473
  import chalk5 from "chalk";
2153
2474
  var SKILL_CATEGORIES = {
2154
2475
  "Getting Started": ["prompt-help", "understand"],
@@ -2196,7 +2517,7 @@ function calculateComplexity(config) {
2196
2517
  items.push({ label: frameworkLabel.trim(), active: hasFramework });
2197
2518
  const hasCms = scan.cms !== "none";
2198
2519
  if (hasCms) score += 2;
2199
- const cmsLabel = scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : scan.cms === "sitecore-jss" ? "Sitecore JSS" : "CMS";
2520
+ const cmsLabel = scan.cms === "sitecore-xmc-v2" ? "Sitecore XM Cloud (Content SDK v2)" : scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : scan.cms === "sitecore-jss" ? "Sitecore JSS" : "CMS";
2200
2521
  items.push({ label: cmsLabel, active: hasCms });
2201
2522
  if (scan.typescript) score += 1;
2202
2523
  items.push({
@@ -2251,8 +2572,8 @@ var MCP_DISPLAY_NAMES = {
2251
2572
  perplexity: "Perplexity"
2252
2573
  };
2253
2574
  async function statsCommand(targetPath) {
2254
- const projectDir = path22.resolve(targetPath || process.cwd());
2255
- const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
2575
+ const projectDir = path24.resolve(targetPath || process.cwd());
2576
+ const configPath = path24.join(projectDir, AI_KIT_CONFIG_FILE);
2256
2577
  logSection("AI Kit \u2014 Project Stats");
2257
2578
  console.log("");
2258
2579
  if (!fileExists(configPath)) {
@@ -2345,6 +2666,435 @@ async function statsCommand(targetPath) {
2345
2666
  console.log("");
2346
2667
  }
2347
2668
 
2669
+ // src/commands/audit.ts
2670
+ import path25 from "path";
2671
+ import fs14 from "fs-extra";
2672
+ import chalk6 from "chalk";
2673
+ async function auditCommand(targetPath) {
2674
+ const projectDir = path25.resolve(targetPath || process.cwd());
2675
+ logSection("AI Kit \u2014 Security & Configuration Audit");
2676
+ logInfo(`Auditing: ${projectDir}`);
2677
+ const checks = [];
2678
+ const configPath = path25.join(projectDir, AI_KIT_CONFIG_FILE);
2679
+ if (fileExists(configPath)) {
2680
+ checks.push({ name: "Config file", status: "pass", message: "ai-kit.config.json found" });
2681
+ } else {
2682
+ checks.push({ name: "Config file", status: "fail", message: "ai-kit.config.json missing \u2014 run `ai-kit init`" });
2683
+ }
2684
+ const claudeMdPath = path25.join(projectDir, GENERATED_FILES.claudeMd);
2685
+ const claudeMd = readFileSafe(claudeMdPath);
2686
+ if (claudeMd) {
2687
+ if (claudeMd.includes("AI-KIT:START") && claudeMd.includes("AI-KIT:END")) {
2688
+ checks.push({ name: "CLAUDE.md", status: "pass", message: "CLAUDE.md has AI-KIT markers" });
2689
+ } else {
2690
+ checks.push({ name: "CLAUDE.md", status: "warn", message: "CLAUDE.md exists but missing AI-KIT markers \u2014 may not update correctly" });
2691
+ }
2692
+ } else {
2693
+ checks.push({ name: "CLAUDE.md", status: "warn", message: "CLAUDE.md not found" });
2694
+ }
2695
+ if (claudeMd) {
2696
+ const secretPatterns = [
2697
+ /(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
2698
+ /(?:sk|pk|rk)[-_][a-zA-Z0-9]{20,}/,
2699
+ /ghp_[a-zA-Z0-9]{36}/,
2700
+ /xox[bpoas]-[a-zA-Z0-9-]+/
2701
+ ];
2702
+ const hasSecrets = secretPatterns.some((p) => p.test(claudeMd));
2703
+ if (hasSecrets) {
2704
+ checks.push({ name: "Secrets in CLAUDE.md", status: "fail", message: "Potential secrets detected in CLAUDE.md \u2014 remove immediately" });
2705
+ } else {
2706
+ checks.push({ name: "Secrets in CLAUDE.md", status: "pass", message: "No secrets detected" });
2707
+ }
2708
+ }
2709
+ const settingsLocalPath = path25.join(projectDir, GENERATED_FILES.claudeSettingsLocal);
2710
+ if (fileExists(settingsLocalPath)) {
2711
+ const settings2 = readJsonSafe(settingsLocalPath);
2712
+ if (settings2?.hooks) {
2713
+ checks.push({ name: "Hooks", status: "pass", message: "Hooks configured in settings.local.json" });
2714
+ } else {
2715
+ checks.push({ name: "Hooks", status: "warn", message: "settings.local.json exists but no hooks defined" });
2716
+ }
2717
+ } else {
2718
+ checks.push({ name: "Hooks", status: "warn", message: "No hooks configured \u2014 run `ai-kit init` to generate" });
2719
+ }
2720
+ const agentsDir = path25.join(projectDir, GENERATED_FILES.claudeAgents);
2721
+ if (await fs14.pathExists(agentsDir)) {
2722
+ const agentFiles = (await fs14.readdir(agentsDir)).filter((f) => f.endsWith(".md"));
2723
+ if (agentFiles.length > 0) {
2724
+ checks.push({ name: "Agents", status: "pass", message: `${agentFiles.length} agent(s) configured` });
2725
+ let invalidAgents = 0;
2726
+ for (const file of agentFiles) {
2727
+ const content = readFileSafe(path25.join(agentsDir, file));
2728
+ if (content && !content.startsWith("---")) {
2729
+ invalidAgents++;
2730
+ }
2731
+ }
2732
+ if (invalidAgents > 0) {
2733
+ checks.push({ name: "Agent frontmatter", status: "warn", message: `${invalidAgents} agent(s) missing YAML frontmatter` });
2734
+ }
2735
+ } else {
2736
+ checks.push({ name: "Agents", status: "warn", message: "Agents directory empty" });
2737
+ }
2738
+ } else {
2739
+ checks.push({ name: "Agents", status: "warn", message: "No agents directory \u2014 run `ai-kit init` to generate" });
2740
+ }
2741
+ const contextsDir = path25.join(projectDir, GENERATED_FILES.claudeContexts);
2742
+ if (await fs14.pathExists(contextsDir)) {
2743
+ const contextFiles = (await fs14.readdir(contextsDir)).filter((f) => f.endsWith(".md"));
2744
+ checks.push({ name: "Contexts", status: contextFiles.length > 0 ? "pass" : "warn", message: `${contextFiles.length} context mode(s) available` });
2745
+ } else {
2746
+ checks.push({ name: "Contexts", status: "warn", message: "No contexts directory" });
2747
+ }
2748
+ const skillsDir = path25.join(projectDir, GENERATED_FILES.claudeSkills);
2749
+ if (await fs14.pathExists(skillsDir)) {
2750
+ const skillDirs = (await fs14.readdir(skillsDir)).filter(async (f) => {
2751
+ try {
2752
+ return (await fs14.stat(path25.join(skillsDir, f))).isDirectory();
2753
+ } catch {
2754
+ return false;
2755
+ }
2756
+ });
2757
+ checks.push({ name: "Skills", status: "pass", message: `${skillDirs.length} skill(s) installed` });
2758
+ } else {
2759
+ checks.push({ name: "Skills", status: "warn", message: "No skills directory" });
2760
+ }
2761
+ const gitignorePath = path25.join(projectDir, ".gitignore");
2762
+ const gitignore = readFileSafe(gitignorePath);
2763
+ if (gitignore) {
2764
+ const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
2765
+ if (envIgnored) {
2766
+ checks.push({ name: ".env gitignore", status: "pass", message: ".env files are gitignored" });
2767
+ } else {
2768
+ checks.push({ name: ".env gitignore", status: "fail", message: ".env files are NOT gitignored \u2014 add to .gitignore immediately" });
2769
+ }
2770
+ }
2771
+ if (gitignore) {
2772
+ const settingsIgnored = gitignore.includes("settings.local.json");
2773
+ if (!settingsIgnored) {
2774
+ checks.push({ name: "Settings gitignore", status: "warn", message: "settings.local.json not in .gitignore \u2014 may leak local config" });
2775
+ }
2776
+ }
2777
+ const settingsPath = path25.join(projectDir, ".claude", "settings.json");
2778
+ const settings = readFileSafe(settingsPath);
2779
+ if (settings) {
2780
+ const hasEnvVarsInSettings = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settings);
2781
+ if (hasEnvVarsInSettings) {
2782
+ checks.push({ name: "MCP secrets", status: "fail", message: "Potential secrets in .claude/settings.json \u2014 use environment variables instead" });
2783
+ } else {
2784
+ checks.push({ name: "MCP secrets", status: "pass", message: "No hardcoded secrets in settings" });
2785
+ }
2786
+ }
2787
+ logSection("Audit Results");
2788
+ const passed = checks.filter((c) => c.status === "pass");
2789
+ const warned = checks.filter((c) => c.status === "warn");
2790
+ const failed = checks.filter((c) => c.status === "fail");
2791
+ for (const check of checks) {
2792
+ const icon = check.status === "pass" ? chalk6.green("\u2713") : check.status === "warn" ? chalk6.yellow("\u26A0") : chalk6.red("\u2717");
2793
+ console.log(` ${icon} ${check.name}: ${check.message}`);
2794
+ }
2795
+ const total = checks.length;
2796
+ const score = Math.round((passed.length + warned.length * 0.5) / total * 100);
2797
+ const grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F";
2798
+ console.log("");
2799
+ logSection("Score");
2800
+ const gradeColor2 = grade <= "B" ? chalk6.green : grade <= "C" ? chalk6.yellow : chalk6.red;
2801
+ console.log(` ${gradeColor2.bold(grade)} (${score}/100)`);
2802
+ console.log(` ${chalk6.green(String(passed.length))} passed \xB7 ${chalk6.yellow(String(warned.length))} warnings \xB7 ${chalk6.red(String(failed.length))} failures`);
2803
+ if (failed.length > 0) {
2804
+ console.log("");
2805
+ logError("Fix the failures above before proceeding.");
2806
+ }
2807
+ }
2808
+
2809
+ // src/commands/health.ts
2810
+ import path26 from "path";
2811
+ import fs15 from "fs-extra";
2812
+ import chalk7 from "chalk";
2813
+ import ora7 from "ora";
2814
+ function gradeFromScore(score) {
2815
+ if (score >= 90) return "A";
2816
+ if (score >= 80) return "B";
2817
+ if (score >= 70) return "C";
2818
+ if (score >= 60) return "D";
2819
+ return "F";
2820
+ }
2821
+ function gradeColor(grade) {
2822
+ if (grade <= "B") return chalk7.green;
2823
+ if (grade <= "C") return chalk7.yellow;
2824
+ return chalk7.red;
2825
+ }
2826
+ function statusIcon(status) {
2827
+ switch (status) {
2828
+ case "pass":
2829
+ return chalk7.green("\u2713");
2830
+ case "warn":
2831
+ return chalk7.yellow("\u26A0");
2832
+ case "fail":
2833
+ return chalk7.red("\u2717");
2834
+ }
2835
+ }
2836
+ function progressBar2(percent, width = 20) {
2837
+ const filled = Math.round(percent / 100 * width);
2838
+ const empty = width - filled;
2839
+ return chalk7.green("\u2588".repeat(filled)) + chalk7.gray("\u2591".repeat(empty));
2840
+ }
2841
+ function checkSetup(projectDir, config) {
2842
+ const checks = [];
2843
+ if (config.version === VERSION) {
2844
+ checks.push({ name: "Version", status: "pass", detail: `v${VERSION}` });
2845
+ } else {
2846
+ checks.push({
2847
+ name: "Version",
2848
+ status: "warn",
2849
+ detail: `config v${config.version} \u2260 CLI v${VERSION} \u2014 run \`ai-kit update\``
2850
+ });
2851
+ }
2852
+ const claudeMd = readFileSafe(path26.join(projectDir, GENERATED_FILES.claudeMd));
2853
+ if (claudeMd && claudeMd.includes("AI-KIT:START")) {
2854
+ checks.push({ name: "CLAUDE.md", status: "pass", detail: "Present with markers" });
2855
+ } else if (claudeMd) {
2856
+ checks.push({ name: "CLAUDE.md", status: "warn", detail: "Missing AI-KIT markers" });
2857
+ } else {
2858
+ checks.push({ name: "CLAUDE.md", status: "fail", detail: "Not found" });
2859
+ }
2860
+ if (fileExists(path26.join(projectDir, GENERATED_FILES.cursorRules))) {
2861
+ checks.push({ name: ".cursorrules", status: "pass", detail: "Present" });
2862
+ } else {
2863
+ checks.push({ name: ".cursorrules", status: "warn", detail: "Not generated" });
2864
+ }
2865
+ const skillsDir = path26.join(projectDir, GENERATED_FILES.claudeSkills);
2866
+ if (dirExists(skillsDir)) {
2867
+ const count = config.commands.length;
2868
+ checks.push({ name: "Skills", status: "pass", detail: `${count} installed` });
2869
+ } else {
2870
+ checks.push({ name: "Skills", status: "warn", detail: "No skills directory" });
2871
+ }
2872
+ const agentsDir = path26.join(projectDir, GENERATED_FILES.claudeAgents);
2873
+ if (dirExists(agentsDir)) {
2874
+ try {
2875
+ const agentFiles = fs15.readdirSync(agentsDir).filter((f) => f.endsWith(".md"));
2876
+ checks.push({ name: "Agents", status: "pass", detail: `${agentFiles.length} configured` });
2877
+ } catch {
2878
+ checks.push({ name: "Agents", status: "warn", detail: "Could not read agents" });
2879
+ }
2880
+ } else {
2881
+ checks.push({ name: "Agents", status: "warn", detail: "Not configured" });
2882
+ }
2883
+ const settingsLocal = readFileSafe(path26.join(projectDir, GENERATED_FILES.claudeSettingsLocal));
2884
+ if (settingsLocal && settingsLocal.includes('"hooks"')) {
2885
+ checks.push({ name: "Hooks", status: "pass", detail: "Configured" });
2886
+ } else {
2887
+ checks.push({ name: "Hooks", status: "warn", detail: "Not configured" });
2888
+ }
2889
+ return { title: "Setup Integrity", checks };
2890
+ }
2891
+ function checkSecurity(projectDir) {
2892
+ const checks = [];
2893
+ const claudeMd = readFileSafe(path26.join(projectDir, GENERATED_FILES.claudeMd));
2894
+ if (claudeMd) {
2895
+ const secretPatterns = [
2896
+ /(?:api[_-]?key|secret|token|password|credential)\s*[:=]\s*['"][^'"]+['"]/i,
2897
+ /(?:sk|pk|rk)[-_][a-zA-Z0-9]{20,}/,
2898
+ /ghp_[a-zA-Z0-9]{36}/,
2899
+ /xox[bpoas]-[a-zA-Z0-9-]+/
2900
+ ];
2901
+ const hasSecrets = secretPatterns.some((p) => p.test(claudeMd));
2902
+ checks.push({
2903
+ name: "Secrets in CLAUDE.md",
2904
+ status: hasSecrets ? "fail" : "pass",
2905
+ detail: hasSecrets ? "Potential secrets detected \u2014 remove immediately" : "Clean"
2906
+ });
2907
+ }
2908
+ const gitignore = readFileSafe(path26.join(projectDir, ".gitignore"));
2909
+ if (gitignore) {
2910
+ const envIgnored = gitignore.includes(".env") || gitignore.includes(".env.local");
2911
+ checks.push({
2912
+ name: ".env gitignore",
2913
+ status: envIgnored ? "pass" : "fail",
2914
+ detail: envIgnored ? "Protected" : "NOT gitignored \u2014 add .env to .gitignore"
2915
+ });
2916
+ }
2917
+ const settingsJson = readFileSafe(path26.join(projectDir, ".claude", "settings.json"));
2918
+ if (settingsJson) {
2919
+ const hasHardcoded = /(?:api[_-]?key|token|secret|password)\s*[:=]\s*"[^"]+"/i.test(settingsJson);
2920
+ checks.push({
2921
+ name: "MCP config",
2922
+ status: hasHardcoded ? "fail" : "pass",
2923
+ detail: hasHardcoded ? "Hardcoded secrets in settings.json" : "No hardcoded secrets"
2924
+ });
2925
+ }
2926
+ return { title: "Security", checks };
2927
+ }
2928
+ function checkStack(config) {
2929
+ const checks = [];
2930
+ const scan = config.scanResult;
2931
+ if (scan.framework !== "unknown") {
2932
+ const label = scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""} (${scan.routerType === "app" ? "App Router" : scan.routerType === "pages" ? "Pages Router" : "Hybrid"})` : scan.framework;
2933
+ checks.push({ name: "Framework", status: "pass", detail: label.trim() });
2934
+ } else {
2935
+ checks.push({ name: "Framework", status: "warn", detail: "Not detected" });
2936
+ }
2937
+ if (scan.cms !== "none") {
2938
+ const label = scan.cms === "sitecore-xmc-v2" ? "Sitecore XM Cloud (Content SDK v2)" : scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : scan.cms;
2939
+ checks.push({ name: "CMS", status: "pass", detail: label });
2940
+ }
2941
+ checks.push({
2942
+ name: "TypeScript",
2943
+ status: scan.typescript ? "pass" : "warn",
2944
+ detail: scan.typescript ? `Enabled${scan.typescriptStrict ? " (strict)" : ""}` : "Not detected"
2945
+ });
2946
+ if (scan.styling.length > 0) {
2947
+ checks.push({ name: "Styling", status: "pass", detail: scan.styling.join(", ") });
2948
+ }
2949
+ if (scan.monorepo) {
2950
+ checks.push({
2951
+ name: "Monorepo",
2952
+ status: "pass",
2953
+ detail: scan.monorepoTool || "Detected"
2954
+ });
2955
+ }
2956
+ return { title: "Stack Detection", checks };
2957
+ }
2958
+ function checkTools(scan) {
2959
+ const checks = [];
2960
+ const toolMap = {
2961
+ Playwright: { key: "playwright", hint: "npm i -D @playwright/test" },
2962
+ ESLint: { key: "eslint", hint: "npm i -D eslint" },
2963
+ Prettier: { key: "prettier", hint: "npm i -D prettier" },
2964
+ "axe-core": { key: "axeCore", hint: "npm i -D @axe-core/playwright" },
2965
+ Knip: { key: "knip", hint: "npm i -D knip" },
2966
+ "Bundle Analyzer": { key: "bundleAnalyzer", hint: "npm i -D @next/bundle-analyzer" },
2967
+ Storybook: { key: "storybook", hint: "npx storybook@latest init" }
2968
+ };
2969
+ for (const [name, { key, hint }] of Object.entries(toolMap)) {
2970
+ checks.push({
2971
+ name,
2972
+ status: scan.tools[key] ? "pass" : "warn",
2973
+ detail: scan.tools[key] ? "Detected" : `Missing \u2014 ${hint}`
2974
+ });
2975
+ }
2976
+ const mcpMap = {
2977
+ "GitHub MCP": "github",
2978
+ "Figma MCP": "figma",
2979
+ "Context7 MCP": "context7",
2980
+ "Perplexity MCP": "perplexity"
2981
+ };
2982
+ for (const [name, key] of Object.entries(mcpMap)) {
2983
+ checks.push({
2984
+ name,
2985
+ status: scan.mcpServers[key] ? "pass" : "warn",
2986
+ detail: scan.mcpServers[key] ? "Configured" : "Not configured"
2987
+ });
2988
+ }
2989
+ return { title: "Tools & MCP", checks };
2990
+ }
2991
+ function checkDocs(projectDir) {
2992
+ const checks = [];
2993
+ const docsToCheck = [
2994
+ { name: "Mistakes Log", path: "docs/mistakes-log.md" },
2995
+ { name: "Decisions Log", path: "docs/decisions-log.md" },
2996
+ { name: "Time Log", path: "docs/time-log.md" }
2997
+ ];
2998
+ for (const doc of docsToCheck) {
2999
+ const content = readFileSafe(path26.join(projectDir, doc.path));
3000
+ if (content) {
3001
+ const hasEntries = content.includes("## 20") || content.split("---").length > 2;
3002
+ checks.push({
3003
+ name: doc.name,
3004
+ status: hasEntries ? "pass" : "warn",
3005
+ detail: hasEntries ? "Has entries" : "Scaffolded but empty"
3006
+ });
3007
+ } else {
3008
+ checks.push({ name: doc.name, status: "warn", detail: "Not found" });
3009
+ }
3010
+ }
3011
+ return { title: "Documentation", checks };
3012
+ }
3013
+ async function healthCommand(targetPath) {
3014
+ const projectDir = path26.resolve(targetPath || process.cwd());
3015
+ const configPath = path26.join(projectDir, AI_KIT_CONFIG_FILE);
3016
+ console.log("");
3017
+ logSection("AI Kit \u2014 Project Health");
3018
+ console.log(chalk7.dim(` ${projectDir}`));
3019
+ console.log("");
3020
+ if (!fileExists(configPath)) {
3021
+ logError("ai-kit.config.json not found. Run `ai-kit init` first.");
3022
+ return;
3023
+ }
3024
+ const config = readJsonSafe(configPath);
3025
+ if (!config) {
3026
+ logError("ai-kit.config.json is corrupted. Run `ai-kit init` to re-initialize.");
3027
+ return;
3028
+ }
3029
+ const spinner = ora7("Scanning project...").start();
3030
+ let freshScan;
3031
+ try {
3032
+ freshScan = await scanProject(projectDir);
3033
+ spinner.succeed("Project scanned");
3034
+ } catch {
3035
+ spinner.fail("Scan failed \u2014 using cached data");
3036
+ freshScan = config.scanResult;
3037
+ }
3038
+ const sections = [
3039
+ checkSetup(projectDir, config),
3040
+ checkSecurity(projectDir),
3041
+ checkStack(config),
3042
+ checkTools(freshScan),
3043
+ checkDocs(projectDir)
3044
+ ];
3045
+ let totalPassed = 0;
3046
+ let totalWarnings = 0;
3047
+ let totalFailed = 0;
3048
+ for (const section of sections) {
3049
+ console.log(`
3050
+ ${chalk7.bold(section.title)}`);
3051
+ for (const check of section.checks) {
3052
+ const icon = statusIcon(check.status);
3053
+ console.log(` ${icon} ${chalk7.white(check.name)}: ${chalk7.dim(check.detail)}`);
3054
+ switch (check.status) {
3055
+ case "pass":
3056
+ totalPassed++;
3057
+ break;
3058
+ case "warn":
3059
+ totalWarnings++;
3060
+ break;
3061
+ case "fail":
3062
+ totalFailed++;
3063
+ break;
3064
+ }
3065
+ }
3066
+ }
3067
+ const total = totalPassed + totalWarnings + totalFailed;
3068
+ const score = total > 0 ? Math.round((totalPassed + totalWarnings * 0.5) / total * 100) : 0;
3069
+ const grade = gradeFromScore(score);
3070
+ const colorFn = gradeColor(grade);
3071
+ console.log("");
3072
+ console.log(
3073
+ ` ${chalk7.bold("Overall:")} ${colorFn.bold(grade)} ${chalk7.dim(`(${score}/100)`)}`
3074
+ );
3075
+ console.log(
3076
+ ` ${progressBar2(score)} ${chalk7.green(String(totalPassed))} passed \xB7 ${chalk7.yellow(String(totalWarnings))} warnings \xB7 ${chalk7.red(String(totalFailed))} failures`
3077
+ );
3078
+ if (totalFailed > 0 || totalWarnings > 2) {
3079
+ console.log("");
3080
+ console.log(chalk7.bold(" Recommendations:"));
3081
+ if (totalFailed > 0) {
3082
+ const failures = sections.flatMap((s) => s.checks.filter((c) => c.status === "fail"));
3083
+ for (const f of failures.slice(0, 3)) {
3084
+ console.log(` ${chalk7.red("\u2192")} Fix: ${f.name} \u2014 ${f.detail}`);
3085
+ }
3086
+ }
3087
+ const warnings = sections.flatMap((s) => s.checks.filter((c) => c.status === "warn"));
3088
+ for (const w of warnings.slice(0, 3)) {
3089
+ console.log(` ${chalk7.yellow("\u2192")} Improve: ${w.name} \u2014 ${w.detail}`);
3090
+ }
3091
+ }
3092
+ const scan = config.scanResult;
3093
+ console.log("");
3094
+ console.log(chalk7.dim(` v${config.version} \xB7 ${scan.projectName} \xB7 ${config.commands.length} skills \xB7 ${config.guides.length} guides \xB7 Generated ${config.generatedAt}`));
3095
+ console.log("");
3096
+ }
3097
+
2348
3098
  // src/index.ts
2349
3099
  var program = new Command();
2350
3100
  program.name("ai-kit").description(
@@ -2438,5 +3188,27 @@ program.command("stats").description("Show project setup statistics and complexi
2438
3188
  process.exit(1);
2439
3189
  }
2440
3190
  });
3191
+ program.command("audit").description("Security and configuration audit for AI agent setup").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
3192
+ try {
3193
+ await auditCommand(targetPath);
3194
+ } catch (err) {
3195
+ if (err.name === "ExitPromptError") {
3196
+ process.exit(0);
3197
+ }
3198
+ console.error(err);
3199
+ process.exit(1);
3200
+ }
3201
+ });
3202
+ program.command("health").description("One-glance project health \u2014 setup, security, stack, tools, and docs").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
3203
+ try {
3204
+ await healthCommand(targetPath);
3205
+ } catch (err) {
3206
+ if (err.name === "ExitPromptError") {
3207
+ process.exit(0);
3208
+ }
3209
+ console.error(err);
3210
+ process.exit(1);
3211
+ }
3212
+ });
2441
3213
  program.parse();
2442
3214
  //# sourceMappingURL=index.js.map