@hiveai/cli 0.9.10 → 0.9.12

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command49 } from "commander";
4
+ import { Command as Command51 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -198,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
198
198
  if (!f) continue;
199
199
  counts.set(f, (counts.get(f) ?? 0) + 1);
200
200
  }
201
- let entries = [...counts.entries()].map(([path46, changes]) => ({ path: path46, changes }));
201
+ let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
202
202
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
203
203
  if (lowerPaths.length > 0) {
204
204
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -308,12 +308,14 @@ function registerBriefing(program2) {
308
308
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
309
309
  const root = findProjectRoot(opts.dir);
310
310
  const paths = resolveHaivePaths(root);
311
+ const markerFiles = parseCsv(opts.files);
311
312
  if (existsSync(paths.haiveDir)) {
312
313
  await mkdir(paths.runtimeDir, { recursive: true });
313
314
  await writeBriefingMarker(paths, {
314
315
  task: opts.task ?? "CLI briefing",
315
316
  source: "haive-briefing-cli",
316
- sessionId: process.env.HAIVE_SESSION_ID
317
+ sessionId: process.env.HAIVE_SESSION_ID,
318
+ files: markerFiles
317
319
  }).catch(() => {
318
320
  });
319
321
  }
@@ -386,7 +388,7 @@ function registerBriefing(program2) {
386
388
  }
387
389
  }
388
390
  const all = ownMemories;
389
- const filePaths = parseCsv(opts.files);
391
+ const filePaths = markerFiles;
390
392
  const tokens = opts.task ? tokenizeQuery(opts.task) : null;
391
393
  const scopeFilter = opts.scope ?? "all";
392
394
  const recaps = all.filter(({ memory: mem }) => mem.frontmatter.type === "session_recap").sort(
@@ -498,6 +500,14 @@ function registerBriefing(program2) {
498
500
  if (ids.length > 0) {
499
501
  await trackReads(paths, ids).catch(() => {
500
502
  });
503
+ await writeBriefingMarker(paths, {
504
+ task: opts.task ?? "CLI briefing",
505
+ source: "haive-briefing-cli",
506
+ sessionId: process.env.HAIVE_SESSION_ID,
507
+ memoryIds: ids,
508
+ files: filePaths
509
+ }).catch(() => {
510
+ });
501
511
  }
502
512
  const radarForced = opts.radar === true;
503
513
  const radarAuto = opts.radar !== false && top.length < RADAR_AUTO_THRESHOLD;
@@ -720,23 +730,404 @@ function registerIndexCode(program2) {
720
730
  }
721
731
 
722
732
  // src/commands/init.ts
723
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
724
- import { existsSync as existsSync6 } from "fs";
725
- import path7 from "path";
726
- import { spawnSync } from "child_process";
733
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
734
+ import { existsSync as existsSync7 } from "fs";
735
+ import path8 from "path";
736
+ import { spawnSync as spawnSync2 } from "child_process";
727
737
  import "commander";
728
738
  import {
729
739
  AUTOPILOT_DEFAULTS,
730
740
  buildCodeMap as buildCodeMap2,
731
- resolveHaivePaths as resolveHaivePaths4,
741
+ resolveHaivePaths as resolveHaivePaths5,
732
742
  saveCodeMap as saveCodeMap2,
733
743
  saveConfig
734
744
  } from "@hiveai/core";
735
745
 
736
- // src/commands/init-bootstrap.ts
737
- import { readdir, readFile as readFile2 } from "fs/promises";
746
+ // src/commands/agent.ts
747
+ import { spawnSync } from "child_process";
748
+ import { existsSync as existsSync4 } from "fs";
749
+ import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
750
+ import os2 from "os";
751
+ import path5 from "path";
752
+ import { createInterface } from "readline/promises";
753
+ import "commander";
754
+ import { findProjectRoot as findProjectRoot5, resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
755
+
756
+ // src/commands/init-mcp-setup.ts
757
+ import { readFile as readFile2, writeFile, mkdir as mkdir2 } from "fs/promises";
738
758
  import { existsSync as existsSync3 } from "fs";
739
759
  import path4 from "path";
760
+ import os from "os";
761
+ var HOME = os.homedir();
762
+ var HAIVE_MCP_ENTRY = {
763
+ command: "haive",
764
+ args: ["mcp", "--stdio"]
765
+ };
766
+ function projectMcpEntry(root) {
767
+ return {
768
+ command: "haive",
769
+ args: ["mcp", "--stdio"],
770
+ env: { HAIVE_PROJECT_ROOT: root }
771
+ };
772
+ }
773
+ function cursorMcpPath() {
774
+ return path4.join(HOME, ".cursor", "mcp.json");
775
+ }
776
+ async function configureCursor() {
777
+ const mcpPath = cursorMcpPath();
778
+ const cursorDir = path4.join(HOME, ".cursor");
779
+ if (!existsSync3(cursorDir)) return { client: "Cursor", status: "not_installed" };
780
+ let config = {};
781
+ if (existsSync3(mcpPath)) {
782
+ try {
783
+ config = JSON.parse(await readFile2(mcpPath, "utf8"));
784
+ } catch {
785
+ }
786
+ }
787
+ config.mcpServers ??= {};
788
+ if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
789
+ config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
790
+ await mkdir2(cursorDir, { recursive: true });
791
+ await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
792
+ return { client: "Cursor", status: "configured", path: mcpPath };
793
+ }
794
+ function vscodeMcpPath() {
795
+ const candidates = [
796
+ path4.join(HOME, ".config", "Code", "User", "mcp.json"),
797
+ // Linux
798
+ path4.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
799
+ // macOS
800
+ path4.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
801
+ // Windows
802
+ path4.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
803
+ ];
804
+ for (const c of candidates) {
805
+ if (existsSync3(path4.dirname(c))) return c;
806
+ }
807
+ return null;
808
+ }
809
+ async function configureVSCode() {
810
+ const mcpPath = vscodeMcpPath();
811
+ if (!mcpPath) return { client: "VS Code", status: "not_installed" };
812
+ let config = {};
813
+ if (existsSync3(mcpPath)) {
814
+ try {
815
+ config = JSON.parse(await readFile2(mcpPath, "utf8"));
816
+ } catch {
817
+ }
818
+ }
819
+ config.servers ??= {};
820
+ if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
821
+ config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
822
+ await mkdir2(path4.dirname(mcpPath), { recursive: true });
823
+ await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
824
+ return { client: "VS Code", status: "configured", path: mcpPath };
825
+ }
826
+ function claudeConfigPath() {
827
+ const p = path4.join(HOME, ".claude.json");
828
+ if (existsSync3(p)) return p;
829
+ const p2 = path4.join(HOME, ".config", "claude", "claude.json");
830
+ if (existsSync3(path4.dirname(p2))) return p2;
831
+ return null;
832
+ }
833
+ async function configureClaude() {
834
+ const cfgPath = claudeConfigPath() ?? path4.join(HOME, ".claude.json");
835
+ if (!existsSync3(cfgPath) && !existsSync3(path4.join(HOME, ".claude"))) {
836
+ return { client: "Claude Code", status: "not_installed" };
837
+ }
838
+ let config = {};
839
+ if (existsSync3(cfgPath)) {
840
+ try {
841
+ config = JSON.parse(await readFile2(cfgPath, "utf8"));
842
+ } catch {
843
+ }
844
+ }
845
+ config.mcpServers ??= {};
846
+ if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
847
+ config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
848
+ await writeFile(cfgPath, JSON.stringify(config, null, 2), "utf8");
849
+ return { client: "Claude Code", status: "configured", path: cfgPath };
850
+ }
851
+ function windsurfMcpPath() {
852
+ const candidates = [
853
+ path4.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
854
+ path4.join(HOME, ".windsurf", "mcp.json")
855
+ ];
856
+ for (const c of candidates) {
857
+ if (existsSync3(path4.dirname(c))) return c;
858
+ }
859
+ return null;
860
+ }
861
+ async function configureWindsurf() {
862
+ const mcpPath = windsurfMcpPath();
863
+ if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
864
+ let config = {};
865
+ if (existsSync3(mcpPath)) {
866
+ try {
867
+ config = JSON.parse(await readFile2(mcpPath, "utf8"));
868
+ } catch {
869
+ }
870
+ }
871
+ config.mcpServers ??= {};
872
+ if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
873
+ config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
874
+ await mkdir2(path4.dirname(mcpPath), { recursive: true });
875
+ await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
876
+ return { client: "Windsurf", status: "configured", path: mcpPath };
877
+ }
878
+ async function autoConfigureMcpClients() {
879
+ const results = [];
880
+ const configurators = [configureCursor, configureVSCode, configureClaude, configureWindsurf];
881
+ for (const fn of configurators) {
882
+ try {
883
+ results.push(await fn());
884
+ } catch (err) {
885
+ const name = fn.name.replace("configure", "");
886
+ results.push({ client: name, status: "error", error: String(err) });
887
+ }
888
+ }
889
+ return results;
890
+ }
891
+ async function configureProjectMcpClients(root) {
892
+ const entry = projectMcpEntry(root);
893
+ const results = [];
894
+ try {
895
+ const cursorPath = path4.join(root, ".cursor", "mcp.json");
896
+ let config = {};
897
+ if (existsSync3(cursorPath)) {
898
+ try {
899
+ config = JSON.parse(await readFile2(cursorPath, "utf8"));
900
+ } catch {
901
+ }
902
+ }
903
+ config.mcpServers ??= {};
904
+ config.mcpServers["haive"] = entry;
905
+ await mkdir2(path4.dirname(cursorPath), { recursive: true });
906
+ await writeFile(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
907
+ results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
908
+ } catch (err) {
909
+ results.push({ client: "Cursor (project)", status: "error", error: String(err) });
910
+ }
911
+ try {
912
+ const vscodePath = path4.join(root, ".vscode", "mcp.json");
913
+ let config = {};
914
+ if (existsSync3(vscodePath)) {
915
+ try {
916
+ config = JSON.parse(await readFile2(vscodePath, "utf8"));
917
+ } catch {
918
+ }
919
+ }
920
+ config.servers ??= {};
921
+ config.servers["haive"] = { ...entry, type: "stdio" };
922
+ await mkdir2(path4.dirname(vscodePath), { recursive: true });
923
+ await writeFile(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
924
+ results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
925
+ } catch (err) {
926
+ results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
927
+ }
928
+ try {
929
+ const mcpPath = path4.join(root, ".mcp.json");
930
+ let config = {};
931
+ if (existsSync3(mcpPath)) {
932
+ try {
933
+ config = JSON.parse(await readFile2(mcpPath, "utf8"));
934
+ } catch {
935
+ }
936
+ }
937
+ config.mcpServers ??= {};
938
+ config.mcpServers["haive"] = { ...entry, type: "stdio" };
939
+ await writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
940
+ results.push({ client: "Claude Code (project)", status: "configured", path: mcpPath });
941
+ } catch (err) {
942
+ results.push({ client: "Claude Code (project)", status: "error", error: String(err) });
943
+ }
944
+ return results;
945
+ }
946
+
947
+ // src/commands/agent.ts
948
+ function registerAgent(program2) {
949
+ const agent = program2.command("agent").description("Detect, configure, and report the best hAIve mode for AI coding agents.");
950
+ agent.command("detect").description("Detect available AI agents and hAIve MCP/wrapper readiness.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
951
+ const detection = await detectAgentMode(opts.dir);
952
+ printDetection(detection, Boolean(opts.json));
953
+ });
954
+ agent.command("status").description("Alias for agent detect.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
955
+ const detection = await detectAgentMode(opts.dir);
956
+ printDetection(detection, Boolean(opts.json));
957
+ });
958
+ agent.command("setup").description("Configure hAIve project MCP, optional global MCP clients, and wrapper fallback metadata.").option("-d, --dir <dir>", "project root").option("-y, --yes", "approve user-level/global MCP configuration without prompting", false).option("--no-global", "skip user-level/global MCP configuration").option("--json", "emit JSON", false).action(async (opts) => {
959
+ const result = await setupAgentMode(opts.dir, {
960
+ yes: Boolean(opts.yes),
961
+ global: opts.global !== false && opts.noGlobal !== true,
962
+ interactive: process.stdin.isTTY
963
+ });
964
+ if (opts.json) {
965
+ console.log(JSON.stringify(result, null, 2));
966
+ return;
967
+ }
968
+ printSetupResult(result);
969
+ });
970
+ }
971
+ async function setupAgentMode(dir, opts = {}) {
972
+ const root = findProjectRoot5(dir);
973
+ const paths = resolveHaivePaths4(root);
974
+ const projectResults = await configureProjectMcpClients(root);
975
+ const detectionBeforeGlobal = await detectAgentMode(root);
976
+ let globalResults = [];
977
+ let globalSkippedReason;
978
+ const shouldConsiderGlobal = opts.global !== false;
979
+ if (shouldConsiderGlobal) {
980
+ const approved = opts.yes === true || (opts.interactive ? await confirmGlobalSetup() : false);
981
+ if (approved) {
982
+ globalResults = await autoConfigureMcpClients();
983
+ const codex = await configureCodexIfAvailable(root);
984
+ if (codex) globalResults.push(codex);
985
+ } else {
986
+ globalSkippedReason = opts.interactive ? "User declined user-level/global MCP configuration." : "Non-interactive shell; skipped user-level/global MCP configuration. Re-run `haive agent setup --yes` to apply it.";
987
+ }
988
+ } else {
989
+ globalSkippedReason = "User-level/global MCP configuration disabled.";
990
+ }
991
+ const detection = await detectAgentMode(root);
992
+ const modeFile = await writeAgentModeRecord(paths, detection, globalSkippedReason);
993
+ return {
994
+ detection,
995
+ project_results: projectResults,
996
+ global_results: globalResults,
997
+ mode_file: modeFile,
998
+ ...globalSkippedReason ? { global_skipped_reason: globalSkippedReason } : {}
999
+ };
1000
+ }
1001
+ async function detectAgentMode(dir) {
1002
+ const root = findProjectRoot5(dir);
1003
+ const paths = resolveHaivePaths4(root);
1004
+ const projectMcp = [
1005
+ { client: "Claude Code", path: path5.join(root, ".mcp.json"), present: existsSync4(path5.join(root, ".mcp.json")) },
1006
+ { client: "Cursor", path: path5.join(root, ".cursor", "mcp.json"), present: existsSync4(path5.join(root, ".cursor", "mcp.json")) },
1007
+ { client: "VS Code", path: path5.join(root, ".vscode", "mcp.json"), present: existsSync4(path5.join(root, ".vscode", "mcp.json")) }
1008
+ ];
1009
+ const installedAgents = [
1010
+ { agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
1011
+ { agent: "Claude", command: "claude", installed: commandExists("claude") },
1012
+ { agent: "Aider", command: "aider", installed: commandExists("aider") },
1013
+ { agent: "Cursor", command: "cursor", installed: commandExists("cursor") }
1014
+ ];
1015
+ const hasProjectMcp = projectMcp.some((item) => item.present);
1016
+ const hasNativeMcp = hasProjectMcp || installedAgents.some((a) => a.mcp_configured);
1017
+ const wrapperAgent = installedAgents.find((a) => a.installed && ["codex", "claude", "aider"].includes(a.command));
1018
+ const recommendedMode = hasNativeMcp ? "mcp" : wrapperAgent ? "wrapped" : "fallback";
1019
+ const recommendedCommand = recommendedMode === "mcp" ? "Restart your AI client, then call get_briefing before editing." : recommendedMode === "wrapped" && wrapperAgent ? `haive run -- ${wrapperAgent.command}` : 'haive briefing --task "..." --files "..."';
1020
+ return {
1021
+ root,
1022
+ initialized: existsSync4(paths.haiveDir),
1023
+ project_mcp: projectMcp,
1024
+ installed_agents: installedAgents,
1025
+ recommended_mode: recommendedMode,
1026
+ recommended_command: recommendedCommand
1027
+ };
1028
+ }
1029
+ async function writeAgentModeRecord(paths, detection, skippedReason) {
1030
+ const dir = path5.join(paths.runtimeDir, "enforcement");
1031
+ await mkdir3(dir, { recursive: true });
1032
+ const file = path5.join(dir, "agent-mode.json");
1033
+ const record = {
1034
+ selected_mode: detection.recommended_mode,
1035
+ recommended_command: detection.recommended_command,
1036
+ configured_at: (/* @__PURE__ */ new Date()).toISOString(),
1037
+ project_root: detection.root,
1038
+ notes: [
1039
+ "mcp = native hAIve MCP tools are available or project MCP config exists.",
1040
+ "wrapped = use haive run when native MCP is unavailable.",
1041
+ "fallback = use haive briefing/enforce manually.",
1042
+ ...skippedReason ? [skippedReason] : []
1043
+ ]
1044
+ };
1045
+ await writeFile2(file, JSON.stringify(record, null, 2) + "\n", "utf8");
1046
+ return file;
1047
+ }
1048
+ async function confirmGlobalSetup() {
1049
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1050
+ try {
1051
+ const answer = await rl.question(
1052
+ "Configure hAIve in user-level AI client configs (Cursor/VS Code/Claude/Codex when detected)? [y/N] "
1053
+ );
1054
+ return /^y(es)?$/i.test(answer.trim());
1055
+ } finally {
1056
+ rl.close();
1057
+ }
1058
+ }
1059
+ async function configureCodexIfAvailable(root) {
1060
+ if (!commandExists("codex")) return { client: "Codex", status: "not_installed" };
1061
+ if (codexMcpConfigured()) return { client: "Codex", status: "already_configured" };
1062
+ const result = spawnSync("codex", [
1063
+ "mcp",
1064
+ "add",
1065
+ "haive",
1066
+ "--env",
1067
+ `HAIVE_PROJECT_ROOT=${root}`,
1068
+ "--",
1069
+ "haive",
1070
+ "mcp",
1071
+ "--stdio"
1072
+ ], { encoding: "utf8" });
1073
+ if (result.status === 0) return { client: "Codex", status: "configured", path: path5.join(os2.homedir(), ".codex", "config.toml") };
1074
+ return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
1075
+ }
1076
+ function commandExists(command) {
1077
+ const result = spawnSync(process.platform === "win32" ? "where" : "which", [command], {
1078
+ encoding: "utf8",
1079
+ stdio: ["ignore", "pipe", "ignore"]
1080
+ });
1081
+ return result.status === 0;
1082
+ }
1083
+ function codexMcpConfigured() {
1084
+ if (!commandExists("codex")) return false;
1085
+ const result = spawnSync("codex", ["mcp", "get", "haive"], {
1086
+ encoding: "utf8",
1087
+ stdio: ["ignore", "pipe", "ignore"]
1088
+ });
1089
+ return result.status === 0;
1090
+ }
1091
+ function printDetection(detection, json) {
1092
+ if (json) {
1093
+ console.log(JSON.stringify(detection, null, 2));
1094
+ return;
1095
+ }
1096
+ console.log(ui.bold("hAIve agent status"));
1097
+ console.log(ui.dim(` root: ${detection.root}`));
1098
+ console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
1099
+ for (const cfg of detection.project_mcp) {
1100
+ console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path5.relative(detection.root, cfg.path))}`);
1101
+ }
1102
+ for (const agent of detection.installed_agents) {
1103
+ const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
1104
+ const mcp = agent.mcp_configured === true ? " + hAIve MCP" : "";
1105
+ console.log(`${marker} ${agent.agent} (${agent.command})${mcp}`);
1106
+ }
1107
+ console.log(ui.bold(`Recommended mode: ${detection.recommended_mode}`));
1108
+ console.log(` ${detection.recommended_command}`);
1109
+ }
1110
+ function printSetupResult(result) {
1111
+ for (const item of result.project_results) {
1112
+ if (item.status === "configured") ui.success(`${item.client} project MCP config written (${item.path})`);
1113
+ else if (item.status === "already_configured") ui.info(`${item.client} already configured`);
1114
+ else if (item.status === "error") ui.warn(`${item.client}: ${item.error}`);
1115
+ }
1116
+ for (const item of result.global_results) {
1117
+ if (item.status === "configured") ui.success(`${item.client} user-level MCP configured${item.path ? ` (${item.path})` : ""}`);
1118
+ else if (item.status === "already_configured") ui.info(`${item.client} user-level MCP already configured`);
1119
+ else if (item.status === "not_installed") ui.info(`${item.client} not detected`);
1120
+ else if (item.status === "error") ui.warn(`${item.client}: ${item.error}`);
1121
+ }
1122
+ if (result.global_skipped_reason) ui.warn(result.global_skipped_reason);
1123
+ ui.success(`Agent mode recorded at ${result.mode_file}`);
1124
+ printDetection(result.detection, false);
1125
+ }
1126
+
1127
+ // src/commands/init-bootstrap.ts
1128
+ import { readdir, readFile as readFile3 } from "fs/promises";
1129
+ import { existsSync as existsSync5 } from "fs";
1130
+ import path6 from "path";
740
1131
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
741
1132
  "node_modules",
742
1133
  "dist",
@@ -822,12 +1213,12 @@ function detectKeyDeps(allDeps) {
822
1213
  return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
823
1214
  }
824
1215
  function detectLanguage(root) {
825
- if (existsSync3(path4.join(root, "tsconfig.json"))) return "TypeScript";
826
- if (existsSync3(path4.join(root, "pyproject.toml")) || existsSync3(path4.join(root, "setup.py"))) return "Python";
827
- if (existsSync3(path4.join(root, "go.mod"))) return "Go";
828
- if (existsSync3(path4.join(root, "pom.xml")) || existsSync3(path4.join(root, "build.gradle"))) return "Java/Kotlin";
829
- if (existsSync3(path4.join(root, "Cargo.toml"))) return "Rust";
830
- if (existsSync3(path4.join(root, "package.json"))) return "JavaScript";
1216
+ if (existsSync5(path6.join(root, "tsconfig.json"))) return "TypeScript";
1217
+ if (existsSync5(path6.join(root, "pyproject.toml")) || existsSync5(path6.join(root, "setup.py"))) return "Python";
1218
+ if (existsSync5(path6.join(root, "go.mod"))) return "Go";
1219
+ if (existsSync5(path6.join(root, "pom.xml")) || existsSync5(path6.join(root, "build.gradle"))) return "Java/Kotlin";
1220
+ if (existsSync5(path6.join(root, "Cargo.toml"))) return "Rust";
1221
+ if (existsSync5(path6.join(root, "package.json"))) return "JavaScript";
831
1222
  return "Unknown";
832
1223
  }
833
1224
  function detectProjectType(frameworks, scripts, isMonorepo) {
@@ -844,7 +1235,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
844
1235
  if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
845
1236
  if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
846
1237
  if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
847
- if (existsSync3("pom.xml")) return "Java backend";
1238
+ if (existsSync5("pom.xml")) return "Java backend";
848
1239
  return "Application";
849
1240
  }
850
1241
  async function scanDirs(root, maxDepth = 2) {
@@ -860,9 +1251,9 @@ async function scanDirs(root, maxDepth = 2) {
860
1251
  for (const entry of entries) {
861
1252
  if (!entry.isDirectory()) continue;
862
1253
  if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
863
- const rel = path4.relative(root, path4.join(dir, entry.name));
1254
+ const rel = path6.relative(root, path6.join(dir, entry.name));
864
1255
  results.push(rel);
865
- await walk(path4.join(dir, entry.name), depth + 1);
1256
+ await walk(path6.join(dir, entry.name), depth + 1);
866
1257
  }
867
1258
  }
868
1259
  await walk(root, 0);
@@ -979,10 +1370,10 @@ function readmeExcerpt(readme) {
979
1370
  }
980
1371
  async function generateBootstrapContext(root) {
981
1372
  let pkg = {};
982
- const pkgPath = path4.join(root, "package.json");
983
- if (existsSync3(pkgPath)) {
1373
+ const pkgPath = path6.join(root, "package.json");
1374
+ if (existsSync5(pkgPath)) {
984
1375
  try {
985
- pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
1376
+ pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
986
1377
  } catch {
987
1378
  }
988
1379
  }
@@ -992,270 +1383,79 @@ async function generateBootstrapContext(root) {
992
1383
  const language = detectLanguage(root);
993
1384
  const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
994
1385
  const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
995
- const projectName = pkg.name ?? path4.basename(root);
1386
+ const projectName = pkg.name ?? path6.basename(root);
996
1387
  const projectDesc = pkg.description ?? "";
997
1388
  let readmeSummary = "";
998
1389
  for (const name of ["README.md", "readme.md", "README"]) {
999
- const p = path4.join(root, name);
1000
- if (existsSync3(p)) {
1390
+ const p = path6.join(root, name);
1391
+ if (existsSync5(p)) {
1001
1392
  try {
1002
- const content = await readFile2(p, "utf8");
1393
+ const content = await readFile3(p, "utf8");
1003
1394
  readmeSummary = readmeExcerpt(content);
1004
1395
  break;
1005
1396
  } catch {
1006
1397
  }
1007
- }
1008
- }
1009
- const dirs = await scanDirs(root, 2);
1010
- const moduleLines = inferModuleDescriptions(dirs, frameworks);
1011
- const scripts = pkg.scripts ?? {};
1012
- const scriptLines = Object.entries(scripts).filter(([k]) => ["build", "dev", "start", "test", "lint", "deploy"].includes(k)).map(([k, v]) => `- \`${k}\`: ${v}`).slice(0, 6);
1013
- const stackParts = [language];
1014
- if (frameworks.length) stackParts.push(...frameworks);
1015
- const techStack = stackParts.join(", ");
1016
- const notableDeps = Object.keys(allDeps).filter((d) => !d.startsWith("@types/") && !["typescript", "eslint", "prettier", "jest"].includes(d)).filter((d) => !["react", "react-dom", "next", "vue", "express"].includes(d)).slice(0, 10).map((d) => `\`${d}\``);
1017
- const lines = [
1018
- `# Project context \u2014 ${projectName}`,
1019
- "",
1020
- `> Auto-generated by \`haive init --bootstrap\`. Review and refine \u2014 especially the Architecture and Gotchas sections.`,
1021
- "",
1022
- `## Overview`,
1023
- `**Type:** ${projectType}`,
1024
- `**Tech stack:** ${techStack}`,
1025
- ...projectDesc ? [`**Description:** ${projectDesc}`] : [],
1026
- ...readmeSummary ? [`**From README:** ${readmeSummary}`] : [],
1027
- "",
1028
- `## Architecture`,
1029
- `TODO \u2014 fill in the high-level architecture (inferred structure below, verify manually):`,
1030
- "",
1031
- ...moduleLines.length ? moduleLines : ["TODO \u2014 no clear structure detected."],
1032
- "",
1033
- `## Key modules`,
1034
- `TODO \u2014 describe the purpose of the main modules. The directory scan found:`,
1035
- ...dirs.filter((d) => !d.includes("/")).slice(0, 8).map((d) => `- \`${d}/\``),
1036
- "",
1037
- `## Conventions`,
1038
- `TODO \u2014 fill in coding conventions (naming, patterns, file layout).`,
1039
- "",
1040
- ...scriptLines.length ? [
1041
- `**Available scripts:**`,
1042
- ...scriptLines,
1043
- ""
1044
- ] : [],
1045
- ...keyDeps.length ? [
1046
- `**Key dependencies in use:** ${keyDeps.map((d) => `\`${d}\``).join(", ")}`,
1047
- ""
1048
- ] : [],
1049
- ...notableDeps.length ? [
1050
- `**Other notable packages:** ${notableDeps.join(", ")}`,
1051
- ""
1052
- ] : [],
1053
- `## Glossary`,
1054
- `TODO \u2014 domain terms and what they mean here.`,
1055
- "",
1056
- `## Gotchas`,
1057
- `TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.`,
1058
- `(Run \`haive memory import-changelog\` or \`haive memory import README.md\` to seed these automatically.)`,
1059
- ""
1060
- ];
1061
- return lines.join("\n");
1062
- }
1063
-
1064
- // src/commands/init-mcp-setup.ts
1065
- import { readFile as readFile3, writeFile, mkdir as mkdir2 } from "fs/promises";
1066
- import { existsSync as existsSync4 } from "fs";
1067
- import path5 from "path";
1068
- import os from "os";
1069
- var HOME = os.homedir();
1070
- var HAIVE_MCP_ENTRY = {
1071
- command: "haive",
1072
- args: ["mcp", "--stdio"]
1073
- };
1074
- function projectMcpEntry(root) {
1075
- return {
1076
- command: "haive",
1077
- args: ["mcp", "--stdio"],
1078
- env: { HAIVE_PROJECT_ROOT: root }
1079
- };
1080
- }
1081
- function cursorMcpPath() {
1082
- return path5.join(HOME, ".cursor", "mcp.json");
1083
- }
1084
- async function configureCursor() {
1085
- const mcpPath = cursorMcpPath();
1086
- const cursorDir = path5.join(HOME, ".cursor");
1087
- if (!existsSync4(cursorDir)) return { client: "Cursor", status: "not_installed" };
1088
- let config = {};
1089
- if (existsSync4(mcpPath)) {
1090
- try {
1091
- config = JSON.parse(await readFile3(mcpPath, "utf8"));
1092
- } catch {
1093
- }
1094
- }
1095
- config.mcpServers ??= {};
1096
- if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
1097
- config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
1098
- await mkdir2(cursorDir, { recursive: true });
1099
- await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1100
- return { client: "Cursor", status: "configured", path: mcpPath };
1101
- }
1102
- function vscodeMcpPath() {
1103
- const candidates = [
1104
- path5.join(HOME, ".config", "Code", "User", "mcp.json"),
1105
- // Linux
1106
- path5.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
1107
- // macOS
1108
- path5.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
1109
- // Windows
1110
- path5.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
1111
- ];
1112
- for (const c of candidates) {
1113
- if (existsSync4(path5.dirname(c))) return c;
1114
- }
1115
- return null;
1116
- }
1117
- async function configureVSCode() {
1118
- const mcpPath = vscodeMcpPath();
1119
- if (!mcpPath) return { client: "VS Code", status: "not_installed" };
1120
- let config = {};
1121
- if (existsSync4(mcpPath)) {
1122
- try {
1123
- config = JSON.parse(await readFile3(mcpPath, "utf8"));
1124
- } catch {
1125
- }
1126
- }
1127
- config.servers ??= {};
1128
- if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
1129
- config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
1130
- await mkdir2(path5.dirname(mcpPath), { recursive: true });
1131
- await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1132
- return { client: "VS Code", status: "configured", path: mcpPath };
1133
- }
1134
- function claudeConfigPath() {
1135
- const p = path5.join(HOME, ".claude.json");
1136
- if (existsSync4(p)) return p;
1137
- const p2 = path5.join(HOME, ".config", "claude", "claude.json");
1138
- if (existsSync4(path5.dirname(p2))) return p2;
1139
- return null;
1140
- }
1141
- async function configureClaude() {
1142
- const cfgPath = claudeConfigPath() ?? path5.join(HOME, ".claude.json");
1143
- if (!existsSync4(cfgPath) && !existsSync4(path5.join(HOME, ".claude"))) {
1144
- return { client: "Claude Code", status: "not_installed" };
1145
- }
1146
- let config = {};
1147
- if (existsSync4(cfgPath)) {
1148
- try {
1149
- config = JSON.parse(await readFile3(cfgPath, "utf8"));
1150
- } catch {
1151
- }
1152
- }
1153
- config.mcpServers ??= {};
1154
- if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
1155
- config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
1156
- await writeFile(cfgPath, JSON.stringify(config, null, 2), "utf8");
1157
- return { client: "Claude Code", status: "configured", path: cfgPath };
1158
- }
1159
- function windsurfMcpPath() {
1160
- const candidates = [
1161
- path5.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
1162
- path5.join(HOME, ".windsurf", "mcp.json")
1163
- ];
1164
- for (const c of candidates) {
1165
- if (existsSync4(path5.dirname(c))) return c;
1166
- }
1167
- return null;
1168
- }
1169
- async function configureWindsurf() {
1170
- const mcpPath = windsurfMcpPath();
1171
- if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
1172
- let config = {};
1173
- if (existsSync4(mcpPath)) {
1174
- try {
1175
- config = JSON.parse(await readFile3(mcpPath, "utf8"));
1176
- } catch {
1177
- }
1178
- }
1179
- config.mcpServers ??= {};
1180
- if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
1181
- config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
1182
- await mkdir2(path5.dirname(mcpPath), { recursive: true });
1183
- await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1184
- return { client: "Windsurf", status: "configured", path: mcpPath };
1185
- }
1186
- async function autoConfigureMcpClients() {
1187
- const results = [];
1188
- const configurators = [configureCursor, configureVSCode, configureClaude, configureWindsurf];
1189
- for (const fn of configurators) {
1190
- try {
1191
- results.push(await fn());
1192
- } catch (err) {
1193
- const name = fn.name.replace("configure", "");
1194
- results.push({ client: name, status: "error", error: String(err) });
1195
- }
1196
- }
1197
- return results;
1198
- }
1199
- async function configureProjectMcpClients(root) {
1200
- const entry = projectMcpEntry(root);
1201
- const results = [];
1202
- try {
1203
- const cursorPath = path5.join(root, ".cursor", "mcp.json");
1204
- let config = {};
1205
- if (existsSync4(cursorPath)) {
1206
- try {
1207
- config = JSON.parse(await readFile3(cursorPath, "utf8"));
1208
- } catch {
1209
- }
1210
- }
1211
- config.mcpServers ??= {};
1212
- config.mcpServers["haive"] = entry;
1213
- await mkdir2(path5.dirname(cursorPath), { recursive: true });
1214
- await writeFile(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1215
- results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
1216
- } catch (err) {
1217
- results.push({ client: "Cursor (project)", status: "error", error: String(err) });
1218
- }
1219
- try {
1220
- const vscodePath = path5.join(root, ".vscode", "mcp.json");
1221
- let config = {};
1222
- if (existsSync4(vscodePath)) {
1223
- try {
1224
- config = JSON.parse(await readFile3(vscodePath, "utf8"));
1225
- } catch {
1226
- }
1227
- }
1228
- config.servers ??= {};
1229
- config.servers["haive"] = { ...entry, type: "stdio" };
1230
- await mkdir2(path5.dirname(vscodePath), { recursive: true });
1231
- await writeFile(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
1232
- results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
1233
- } catch (err) {
1234
- results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
1235
- }
1236
- try {
1237
- const mcpPath = path5.join(root, ".mcp.json");
1238
- let config = {};
1239
- if (existsSync4(mcpPath)) {
1240
- try {
1241
- config = JSON.parse(await readFile3(mcpPath, "utf8"));
1242
- } catch {
1243
- }
1244
- }
1245
- config.mcpServers ??= {};
1246
- config.mcpServers["haive"] = { ...entry, type: "stdio" };
1247
- await writeFile(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1248
- results.push({ client: "Claude Code (project)", status: "configured", path: mcpPath });
1249
- } catch (err) {
1250
- results.push({ client: "Claude Code (project)", status: "error", error: String(err) });
1398
+ }
1251
1399
  }
1252
- return results;
1400
+ const dirs = await scanDirs(root, 2);
1401
+ const moduleLines = inferModuleDescriptions(dirs, frameworks);
1402
+ const scripts = pkg.scripts ?? {};
1403
+ const scriptLines = Object.entries(scripts).filter(([k]) => ["build", "dev", "start", "test", "lint", "deploy"].includes(k)).map(([k, v]) => `- \`${k}\`: ${v}`).slice(0, 6);
1404
+ const stackParts = [language];
1405
+ if (frameworks.length) stackParts.push(...frameworks);
1406
+ const techStack = stackParts.join(", ");
1407
+ const notableDeps = Object.keys(allDeps).filter((d) => !d.startsWith("@types/") && !["typescript", "eslint", "prettier", "jest"].includes(d)).filter((d) => !["react", "react-dom", "next", "vue", "express"].includes(d)).slice(0, 10).map((d) => `\`${d}\``);
1408
+ const lines = [
1409
+ `# Project context \u2014 ${projectName}`,
1410
+ "",
1411
+ `> Auto-generated by \`haive init --bootstrap\`. Review and refine \u2014 especially the Architecture and Gotchas sections.`,
1412
+ "",
1413
+ `## Overview`,
1414
+ `**Type:** ${projectType}`,
1415
+ `**Tech stack:** ${techStack}`,
1416
+ ...projectDesc ? [`**Description:** ${projectDesc}`] : [],
1417
+ ...readmeSummary ? [`**From README:** ${readmeSummary}`] : [],
1418
+ "",
1419
+ `## Architecture`,
1420
+ `TODO \u2014 fill in the high-level architecture (inferred structure below, verify manually):`,
1421
+ "",
1422
+ ...moduleLines.length ? moduleLines : ["TODO \u2014 no clear structure detected."],
1423
+ "",
1424
+ `## Key modules`,
1425
+ `TODO \u2014 describe the purpose of the main modules. The directory scan found:`,
1426
+ ...dirs.filter((d) => !d.includes("/")).slice(0, 8).map((d) => `- \`${d}/\``),
1427
+ "",
1428
+ `## Conventions`,
1429
+ `TODO \u2014 fill in coding conventions (naming, patterns, file layout).`,
1430
+ "",
1431
+ ...scriptLines.length ? [
1432
+ `**Available scripts:**`,
1433
+ ...scriptLines,
1434
+ ""
1435
+ ] : [],
1436
+ ...keyDeps.length ? [
1437
+ `**Key dependencies in use:** ${keyDeps.map((d) => `\`${d}\``).join(", ")}`,
1438
+ ""
1439
+ ] : [],
1440
+ ...notableDeps.length ? [
1441
+ `**Other notable packages:** ${notableDeps.join(", ")}`,
1442
+ ""
1443
+ ] : [],
1444
+ `## Glossary`,
1445
+ `TODO \u2014 domain terms and what they mean here.`,
1446
+ "",
1447
+ `## Gotchas`,
1448
+ `TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.`,
1449
+ `(Run \`haive memory import-changelog\` or \`haive memory import README.md\` to seed these automatically.)`,
1450
+ ""
1451
+ ];
1452
+ return lines.join("\n");
1253
1453
  }
1254
1454
 
1255
1455
  // src/commands/init-stack-packs.ts
1256
- import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
1257
- import { existsSync as existsSync5 } from "fs";
1258
- import path6 from "path";
1456
+ import { mkdir as mkdir4, writeFile as writeFile3 } from "fs/promises";
1457
+ import { existsSync as existsSync6 } from "fs";
1458
+ import path7 from "path";
1259
1459
  import {
1260
1460
  buildFrontmatter,
1261
1461
  memoryFilePath,
@@ -1867,7 +2067,7 @@ function autoDetectStacks(deps) {
1867
2067
  async function seedStackPack(haivePaths, stack) {
1868
2068
  const memories = PACKS[stack];
1869
2069
  if (!memories) return 0;
1870
- await mkdir3(haivePaths.teamDir, { recursive: true });
2070
+ await mkdir4(haivePaths.teamDir, { recursive: true });
1871
2071
  let count = 0;
1872
2072
  for (const mem of memories) {
1873
2073
  const fm = buildFrontmatter({
@@ -1878,10 +2078,10 @@ async function seedStackPack(haivePaths, stack) {
1878
2078
  tags: mem.tags
1879
2079
  });
1880
2080
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
1881
- if (existsSync5(filePath)) continue;
2081
+ if (existsSync6(filePath)) continue;
1882
2082
  const content = serializeMemory({ frontmatter: fm, body: mem.body });
1883
- await mkdir3(path6.dirname(filePath), { recursive: true });
1884
- await writeFile2(filePath, content, "utf8");
2083
+ await mkdir4(path7.dirname(filePath), { recursive: true });
2084
+ await writeFile3(filePath, content, "utf8");
1885
2085
  count++;
1886
2086
  }
1887
2087
  return count;
@@ -2095,38 +2295,42 @@ function registerInit(program2) {
2095
2295
  ).option(
2096
2296
  "--no-mcp-setup",
2097
2297
  "skip auto-configuring haive MCP (haive mcp --stdio) in Cursor / VS Code / Claude Code"
2298
+ ).option(
2299
+ "-y, --yes",
2300
+ "approve user-level AI client configuration prompts during agent setup",
2301
+ false
2098
2302
  ).action(async (opts) => {
2099
- const root = path7.resolve(opts.dir);
2100
- const paths = resolveHaivePaths4(root);
2303
+ const root = path8.resolve(opts.dir);
2304
+ const paths = resolveHaivePaths5(root);
2101
2305
  const autopilot = opts.manual !== true;
2102
2306
  const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
2103
2307
  const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
2104
- if (existsSync6(paths.haiveDir)) {
2308
+ if (existsSync7(paths.haiveDir)) {
2105
2309
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
2106
2310
  }
2107
- await mkdir4(paths.personalDir, { recursive: true });
2108
- await mkdir4(paths.teamDir, { recursive: true });
2109
- await mkdir4(paths.moduleDir, { recursive: true });
2110
- await mkdir4(paths.modulesContextDir, { recursive: true });
2311
+ await mkdir5(paths.personalDir, { recursive: true });
2312
+ await mkdir5(paths.teamDir, { recursive: true });
2313
+ await mkdir5(paths.moduleDir, { recursive: true });
2314
+ await mkdir5(paths.modulesContextDir, { recursive: true });
2111
2315
  await ensureAiRuntimeLayout(paths.runtimeDir);
2112
- if (!existsSync6(paths.projectContext)) {
2316
+ if (!existsSync7(paths.projectContext)) {
2113
2317
  if (wantBootstrap) {
2114
2318
  ui.info("Bootstrapping project context from local files\u2026");
2115
2319
  try {
2116
2320
  const context = await generateBootstrapContext(root);
2117
- await writeFile3(paths.projectContext, context, "utf8");
2321
+ await writeFile4(paths.projectContext, context, "utf8");
2118
2322
  ui.success("Created .ai/project-context.md (auto-bootstrapped from local files)");
2119
2323
  } catch (err) {
2120
2324
  ui.warn(`Bootstrap failed (${String(err)}) \u2014 writing default template instead`);
2121
- await writeFile3(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
2325
+ await writeFile4(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
2122
2326
  }
2123
2327
  } else {
2124
- await writeFile3(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
2125
- ui.success(`Created ${path7.relative(root, paths.projectContext)}`);
2328
+ await writeFile4(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
2329
+ ui.success(`Created ${path8.relative(root, paths.projectContext)}`);
2126
2330
  }
2127
2331
  }
2128
- const configExists = existsSync6(
2129
- path7.join(paths.haiveDir, "haive.config.json")
2332
+ const configExists = existsSync7(
2333
+ path8.join(paths.haiveDir, "haive.config.json")
2130
2334
  );
2131
2335
  if (!configExists) {
2132
2336
  await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
@@ -2137,7 +2341,7 @@ function registerInit(program2) {
2137
2341
  if (opts.bridges) {
2138
2342
  await writeBridge(root, "CLAUDE.md");
2139
2343
  await writeBridge(root, ".cursorrules");
2140
- await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
2344
+ await writeBridge(root, path8.join(".github", "copilot-instructions.md"));
2141
2345
  await writeCursorHaiveRule(root);
2142
2346
  }
2143
2347
  const stacksToSeed = await resolveStacksToSeed(root, wantStack);
@@ -2160,18 +2364,18 @@ function registerInit(program2) {
2160
2364
  }
2161
2365
  const wantCi = opts.withCi || autopilot;
2162
2366
  if (wantCi) {
2163
- const ciPath = path7.join(root, ".github", "workflows", "haive-sync.yml");
2164
- if (existsSync6(ciPath)) {
2367
+ const ciPath = path8.join(root, ".github", "workflows", "haive-sync.yml");
2368
+ if (existsSync7(ciPath)) {
2165
2369
  ui.info("CI workflow already exists \u2014 skipped");
2166
2370
  } else {
2167
- await mkdir4(path7.dirname(ciPath), { recursive: true });
2168
- await writeFile3(ciPath, CI_WORKFLOW, "utf8");
2169
- ui.success(`Created ${path7.relative(root, ciPath)}`);
2371
+ await mkdir5(path8.dirname(ciPath), { recursive: true });
2372
+ await writeFile4(ciPath, CI_WORKFLOW, "utf8");
2373
+ ui.success(`Created ${path8.relative(root, ciPath)}`);
2170
2374
  }
2171
2375
  }
2172
2376
  if (autopilot) {
2173
2377
  const haiveBin = process.argv[1];
2174
- const enforcementResult = spawnSync(
2378
+ const enforcementResult = spawnSync2(
2175
2379
  process.execPath,
2176
2380
  [haiveBin, "enforce", "install", "--dir", root],
2177
2381
  { encoding: "utf8" }
@@ -2191,36 +2395,28 @@ function registerInit(program2) {
2191
2395
  }
2192
2396
  }
2193
2397
  if (opts.mcpSetup !== false) {
2194
- const mcpResults = await autoConfigureMcpClients();
2195
- const configured = mcpResults.filter((r) => r.status === "configured");
2196
- const alreadyOk = mcpResults.filter((r) => r.status === "already_configured");
2197
- for (const r of configured) {
2198
- ui.success(`haive MCP configured in ${r.client} (${r.path})`);
2199
- }
2200
- for (const r of alreadyOk) {
2201
- ui.info(`haive MCP already configured in ${r.client} \u2014 skipped`);
2202
- }
2203
- if (configured.length === 0 && alreadyOk.length === 0) {
2204
- ui.warn(
2205
- 'No supported AI client detected (Cursor, VS Code, Claude Code, Windsurf).\n Configure manually: command "haive", args ["mcp", "--stdio"].\n See: https://github.com/Doucs91/hAIve#mcp-setup'
2206
- );
2398
+ const agentSetup = await setupAgentMode(root, {
2399
+ yes: opts.yes === true,
2400
+ global: true,
2401
+ interactive: process.stdin.isTTY
2402
+ });
2403
+ for (const r of agentSetup.project_results) {
2404
+ if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path8.relative(root, r.path)})`);
2405
+ else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
2207
2406
  }
2208
- const projectMcpResults = await configureProjectMcpClients(root);
2209
- for (const r of projectMcpResults) {
2210
- if (r.status === "configured") {
2211
- ui.success(`haive MCP project config written (${path7.relative(root, r.path)})`);
2212
- }
2407
+ for (const r of agentSetup.global_results) {
2408
+ if (r.status === "configured") ui.success(`haive MCP configured in ${r.client}${r.path ? ` (${r.path})` : ""}`);
2409
+ else if (r.status === "already_configured") ui.info(`haive MCP already configured in ${r.client} \u2014 skipped`);
2213
2410
  }
2411
+ if (agentSetup.global_skipped_reason) ui.warn(agentSetup.global_skipped_reason);
2412
+ ui.info(`Recommended agent mode: ${agentSetup.detection.recommended_mode}`);
2413
+ ui.info(agentSetup.detection.recommended_command);
2214
2414
  await ensureGitignoreEntries(root, [
2215
2415
  ".cursor/mcp.json",
2216
2416
  ".vscode/mcp.json",
2217
2417
  ".mcp.json"
2218
2418
  ]);
2219
- if (configured.length > 0 || projectMcpResults.some((r) => r.status === "configured")) {
2220
- ui.info(
2221
- ui.dim(" \u2192 Restart your AI client for MCP changes to take effect.")
2222
- );
2223
- }
2419
+ ui.info(ui.dim(" \u2192 Restart your AI client for MCP changes to take effect."));
2224
2420
  }
2225
2421
  ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
2226
2422
  console.log();
@@ -2269,8 +2465,8 @@ function registerInit(program2) {
2269
2465
  async function resolveStacksToSeed(root, stackOpt) {
2270
2466
  if (!stackOpt) return [];
2271
2467
  if (stackOpt === "auto") {
2272
- const pkgPath = path7.join(root, "package.json");
2273
- if (!existsSync6(pkgPath)) return [];
2468
+ const pkgPath = path8.join(root, "package.json");
2469
+ if (!existsSync7(pkgPath)) return [];
2274
2470
  try {
2275
2471
  const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
2276
2472
  const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -2283,23 +2479,23 @@ async function resolveStacksToSeed(root, stackOpt) {
2283
2479
  }
2284
2480
  async function writeCursorHaiveRule(root) {
2285
2481
  const relPath = ".cursor/rules/haive-mcp-required.mdc";
2286
- const target = path7.join(root, relPath);
2287
- if (existsSync6(target)) {
2482
+ const target = path8.join(root, relPath);
2483
+ if (existsSync7(target)) {
2288
2484
  ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
2289
2485
  return;
2290
2486
  }
2291
- await mkdir4(path7.dirname(target), { recursive: true });
2292
- await writeFile3(target, CURSOR_HAIVE_RULE_MDC, "utf8");
2487
+ await mkdir5(path8.dirname(target), { recursive: true });
2488
+ await writeFile4(target, CURSOR_HAIVE_RULE_MDC, "utf8");
2293
2489
  ui.success(`Created Cursor rule ${relPath}`);
2294
2490
  }
2295
2491
  async function writeBridge(root, relPath) {
2296
- const target = path7.join(root, relPath);
2297
- if (existsSync6(target)) {
2492
+ const target = path8.join(root, relPath);
2493
+ if (existsSync7(target)) {
2298
2494
  ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
2299
2495
  return;
2300
2496
  }
2301
- await mkdir4(path7.dirname(target), { recursive: true });
2302
- await writeFile3(target, BRIDGE_BODY, "utf8");
2497
+ await mkdir5(path8.dirname(target), { recursive: true });
2498
+ await writeFile4(target, BRIDGE_BODY, "utf8");
2303
2499
  ui.success(`Created bridge ${relPath}`);
2304
2500
  }
2305
2501
  var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
@@ -2315,43 +2511,43 @@ var RUNTIME_GITIGNORE_BODY = `*
2315
2511
  !README.md
2316
2512
  `;
2317
2513
  async function ensureAiRuntimeLayout(runtimeDir) {
2318
- await mkdir4(runtimeDir, { recursive: true });
2319
- const gi = path7.join(runtimeDir, ".gitignore");
2320
- if (!existsSync6(gi)) {
2321
- await writeFile3(gi, RUNTIME_GITIGNORE_BODY, "utf8");
2514
+ await mkdir5(runtimeDir, { recursive: true });
2515
+ const gi = path8.join(runtimeDir, ".gitignore");
2516
+ if (!existsSync7(gi)) {
2517
+ await writeFile4(gi, RUNTIME_GITIGNORE_BODY, "utf8");
2322
2518
  }
2323
- const readme = path7.join(runtimeDir, "README.md");
2324
- if (!existsSync6(readme)) {
2325
- await writeFile3(readme, RUNTIME_README_BODY, "utf8");
2519
+ const readme = path8.join(runtimeDir, "README.md");
2520
+ if (!existsSync7(readme)) {
2521
+ await writeFile4(readme, RUNTIME_README_BODY, "utf8");
2326
2522
  }
2327
2523
  }
2328
2524
  async function ensureGitignoreEntries(root, patterns) {
2329
2525
  try {
2330
- const gitignorePath = path7.join(root, ".gitignore");
2526
+ const gitignorePath = path8.join(root, ".gitignore");
2331
2527
  let existing = "";
2332
- if (existsSync6(gitignorePath)) {
2528
+ if (existsSync7(gitignorePath)) {
2333
2529
  existing = await readFile4(gitignorePath, "utf8");
2334
2530
  }
2335
2531
  const lines = existing.split("\n");
2336
2532
  const missing = patterns.filter((p) => !lines.some((l) => l.trim() === p));
2337
2533
  if (missing.length === 0) return;
2338
2534
  const toAppend = (existing.endsWith("\n") || existing === "" ? "" : "\n") + "# hAIve project-level MCP configs (machine-specific absolute paths)\n" + missing.join("\n") + "\n";
2339
- await writeFile3(gitignorePath, existing + toAppend, "utf8");
2535
+ await writeFile4(gitignorePath, existing + toAppend, "utf8");
2340
2536
  } catch {
2341
2537
  }
2342
2538
  }
2343
2539
 
2344
2540
  // src/commands/install-hooks.ts
2345
- import { mkdir as mkdir6, writeFile as writeFile5, chmod, readFile as readFile6 } from "fs/promises";
2346
- import { existsSync as existsSync8 } from "fs";
2347
- import path9 from "path";
2541
+ import { mkdir as mkdir7, writeFile as writeFile6, chmod, readFile as readFile6 } from "fs/promises";
2542
+ import { existsSync as existsSync9 } from "fs";
2543
+ import path10 from "path";
2348
2544
  import "commander";
2349
- import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
2545
+ import { findProjectRoot as findProjectRoot7 } from "@hiveai/core";
2350
2546
 
2351
2547
  // src/utils/claude-hooks.ts
2352
- import { existsSync as existsSync7 } from "fs";
2353
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2354
- import path8 from "path";
2548
+ import { existsSync as existsSync8 } from "fs";
2549
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
2550
+ import path9 from "path";
2355
2551
  var HAIVE_HOOK_TAG = "haive-enforcement";
2356
2552
  var POST_TOOL_USE_GROUP = {
2357
2553
  matcher: "Edit|Write|Bash",
@@ -2437,7 +2633,7 @@ function unpatchClaudeSettings(input) {
2437
2633
  async function installClaudeHooksAtPath(settingsPath) {
2438
2634
  let raw = null;
2439
2635
  let created = false;
2440
- if (existsSync7(settingsPath)) {
2636
+ if (existsSync8(settingsPath)) {
2441
2637
  try {
2442
2638
  raw = JSON.parse(await readFile5(settingsPath, "utf8"));
2443
2639
  } catch {
@@ -2447,25 +2643,25 @@ async function installClaudeHooksAtPath(settingsPath) {
2447
2643
  created = true;
2448
2644
  }
2449
2645
  const patched = patchClaudeSettings(raw);
2450
- await mkdir5(path8.dirname(settingsPath), { recursive: true });
2451
- await writeFile4(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
2646
+ await mkdir6(path9.dirname(settingsPath), { recursive: true });
2647
+ await writeFile5(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
2452
2648
  return { settingsPath, created };
2453
2649
  }
2454
2650
  async function uninstallClaudeHooksAtPath(settingsPath) {
2455
- if (!existsSync7(settingsPath)) {
2651
+ if (!existsSync8(settingsPath)) {
2456
2652
  return { settingsPath, created: false };
2457
2653
  }
2458
2654
  const raw = JSON.parse(await readFile5(settingsPath, "utf8"));
2459
2655
  const cleaned = unpatchClaudeSettings(raw);
2460
- await writeFile4(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
2656
+ await writeFile5(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
2461
2657
  return { settingsPath, created: false };
2462
2658
  }
2463
2659
  function defaultClaudeSettingsPath(scope, projectRoot) {
2464
2660
  if (scope === "user") {
2465
2661
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2466
- return path8.join(home, ".claude", "settings.json");
2662
+ return path9.join(home, ".claude", "settings.json");
2467
2663
  }
2468
- return path8.join(projectRoot, ".claude", "settings.local.json");
2664
+ return path9.join(projectRoot, ".claude", "settings.local.json");
2469
2665
  }
2470
2666
 
2471
2667
  // src/commands/install-hooks.ts
@@ -2521,20 +2717,20 @@ fi
2521
2717
  }
2522
2718
  ];
2523
2719
  async function installGitHooks(opts) {
2524
- const root = findProjectRoot6(opts.dir);
2525
- const gitDir = path9.join(root, ".git");
2526
- if (!existsSync8(gitDir)) {
2720
+ const root = findProjectRoot7(opts.dir);
2721
+ const gitDir = path10.join(root, ".git");
2722
+ if (!existsSync9(gitDir)) {
2527
2723
  ui.error(`No .git directory at ${root}.`);
2528
2724
  process.exitCode = 1;
2529
2725
  return;
2530
2726
  }
2531
- const hooksDir = path9.join(gitDir, "hooks");
2532
- await mkdir6(hooksDir, { recursive: true });
2727
+ const hooksDir = path10.join(gitDir, "hooks");
2728
+ await mkdir7(hooksDir, { recursive: true });
2533
2729
  let installed = 0;
2534
2730
  let skipped = 0;
2535
2731
  for (const { name, body } of HOOKS) {
2536
- const file = path9.join(hooksDir, name);
2537
- if (existsSync8(file) && !opts.force) {
2732
+ const file = path10.join(hooksDir, name);
2733
+ if (existsSync9(file) && !opts.force) {
2538
2734
  const existing = await readFile6(file, "utf8");
2539
2735
  if (!existing.includes(HOOK_MARKER)) {
2540
2736
  ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
@@ -2542,7 +2738,7 @@ async function installGitHooks(opts) {
2542
2738
  continue;
2543
2739
  }
2544
2740
  }
2545
- await writeFile5(file, body, "utf8");
2741
+ await writeFile6(file, body, "utf8");
2546
2742
  await chmod(file, 493);
2547
2743
  installed++;
2548
2744
  }
@@ -2552,7 +2748,7 @@ async function installGitHooks(opts) {
2552
2748
  ui.info("pre-push: haive enforce check blocks pushes that bypass briefing/session recap policy.");
2553
2749
  }
2554
2750
  async function installClaudeHooks(opts) {
2555
- const root = findProjectRoot6(opts.dir);
2751
+ const root = findProjectRoot7(opts.dir);
2556
2752
  const scope = opts.scope ?? "user";
2557
2753
  const settingsPath = opts.settings ?? defaultClaudeSettingsPath(scope, root);
2558
2754
  if (opts.uninstall) {
@@ -2597,11 +2793,11 @@ function registerInstallHooks(program2) {
2597
2793
  }
2598
2794
 
2599
2795
  // src/commands/observe.ts
2600
- import { appendFile, mkdir as mkdir7 } from "fs/promises";
2601
- import { existsSync as existsSync9 } from "fs";
2602
- import path10 from "path";
2796
+ import { appendFile, mkdir as mkdir8 } from "fs/promises";
2797
+ import { existsSync as existsSync10 } from "fs";
2798
+ import path11 from "path";
2603
2799
  import "commander";
2604
- import { findProjectRoot as findProjectRoot7, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
2800
+ import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
2605
2801
  var MAX_STDIN_BYTES = 256 * 1024;
2606
2802
  var TRUNCATE_FIELD = 800;
2607
2803
  function truncate(s, max = TRUNCATE_FIELD) {
@@ -2671,14 +2867,14 @@ function registerObserve(program2) {
2671
2867
  }
2672
2868
  const root = (() => {
2673
2869
  try {
2674
- return findProjectRoot7(opts.dir ?? payload.cwd);
2870
+ return findProjectRoot8(opts.dir ?? payload.cwd);
2675
2871
  } catch {
2676
2872
  return null;
2677
2873
  }
2678
2874
  })();
2679
2875
  if (!root) return;
2680
- const paths = resolveHaivePaths5(root);
2681
- if (!existsSync9(paths.haiveDir)) return;
2876
+ const paths = resolveHaivePaths6(root);
2877
+ if (!existsSync10(paths.haiveDir)) return;
2682
2878
  const observation = {
2683
2879
  ts: (/* @__PURE__ */ new Date()).toISOString(),
2684
2880
  session_id: payload.session_id,
@@ -2687,10 +2883,10 @@ function registerObserve(program2) {
2687
2883
  summary: buildSummary(payload),
2688
2884
  files: extractFiles(payload)
2689
2885
  };
2690
- const cacheDir = path10.join(paths.haiveDir, ".cache");
2691
- await mkdir7(cacheDir, { recursive: true });
2886
+ const cacheDir = path11.join(paths.haiveDir, ".cache");
2887
+ await mkdir8(cacheDir, { recursive: true });
2692
2888
  await appendFile(
2693
- path10.join(cacheDir, "observations.jsonl"),
2889
+ path11.join(cacheDir, "observations.jsonl"),
2694
2890
  JSON.stringify(observation) + "\n",
2695
2891
  "utf8"
2696
2892
  );
@@ -2701,15 +2897,15 @@ function registerObserve(program2) {
2701
2897
 
2702
2898
  // src/commands/mcp.ts
2703
2899
  import "commander";
2704
- import { findProjectRoot as findProjectRoot9 } from "@hiveai/core";
2900
+ import { findProjectRoot as findProjectRoot10 } from "@hiveai/core";
2705
2901
 
2706
2902
  // ../mcp/dist/server.js
2707
2903
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2708
2904
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2709
- import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
2710
- import { mkdir as mkdir8, writeFile as writeFile6 } from "fs/promises";
2711
- import { existsSync as existsSync10 } from "fs";
2712
- import path11 from "path";
2905
+ import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
2906
+ import { mkdir as mkdir9, writeFile as writeFile7 } from "fs/promises";
2907
+ import { existsSync as existsSync11 } from "fs";
2908
+ import path12 from "path";
2713
2909
  import { z } from "zod";
2714
2910
  import { readFile as readFile7, readdir as readdir2 } from "fs/promises";
2715
2911
  import { existsSync as existsSync22 } from "fs";
@@ -2793,7 +2989,7 @@ import {
2793
2989
  } from "@hiveai/core";
2794
2990
  import { z as z10 } from "zod";
2795
2991
  import { writeFile as writeFile52 } from "fs/promises";
2796
- import { existsSync as existsSync11 } from "fs";
2992
+ import { existsSync as existsSync112 } from "fs";
2797
2993
  import { loadMemoriesFromDir as loadMemoriesFromDir9, serializeMemory as serializeMemory4 } from "@hiveai/core";
2798
2994
  import { z as z11 } from "zod";
2799
2995
  import { existsSync as existsSync12 } from "fs";
@@ -2810,7 +3006,7 @@ import {
2810
3006
  serializeMemory as serializeMemory5
2811
3007
  } from "@hiveai/core";
2812
3008
  import { z as z13 } from "zod";
2813
- import { mkdir as mkdir32, writeFile as writeFile7 } from "fs/promises";
3009
+ import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
2814
3010
  import { existsSync as existsSync14 } from "fs";
2815
3011
  import path52 from "path";
2816
3012
  import {
@@ -2971,8 +3167,8 @@ import { loadConfigSync } from "@hiveai/core";
2971
3167
  function createContext(options = {}) {
2972
3168
  const env = options.env ?? process.env;
2973
3169
  const cwd = options.cwd ?? process.cwd();
2974
- const root = options.root ?? env.HAIVE_PROJECT_ROOT ?? findProjectRoot8(cwd);
2975
- return { paths: resolveHaivePaths6(root) };
3170
+ const root = options.root ?? env.HAIVE_PROJECT_ROOT ?? findProjectRoot9(cwd);
3171
+ return { paths: resolveHaivePaths7(root) };
2976
3172
  }
2977
3173
  var BootstrapProjectSaveInputSchema = {
2978
3174
  content: z.string().min(1).describe("Full Markdown content for the project (or module) context file"),
@@ -2982,15 +3178,15 @@ var BootstrapProjectSaveInputSchema = {
2982
3178
  overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
2983
3179
  };
2984
3180
  async function bootstrapProjectSave(input, ctx) {
2985
- const target = input.module ? path11.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
2986
- const exists = existsSync10(target);
3181
+ const target = input.module ? path12.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
3182
+ const exists = existsSync11(target);
2987
3183
  if (exists && !input.overwrite) {
2988
3184
  throw new Error(
2989
3185
  `${target} already exists. Pass overwrite=true to replace it.`
2990
3186
  );
2991
3187
  }
2992
- await mkdir8(path11.dirname(target), { recursive: true });
2993
- await writeFile6(target, input.content, "utf8");
3188
+ await mkdir9(path12.dirname(target), { recursive: true });
3189
+ await writeFile7(target, input.content, "utf8");
2994
3190
  return {
2995
3191
  file_path: target,
2996
3192
  action: exists ? "overwritten" : "created"
@@ -3744,7 +3940,7 @@ var MemUpdateInputSchema = {
3744
3940
  author: z11.string().optional().describe("New author handle or email")
3745
3941
  };
3746
3942
  async function memUpdate(input, ctx) {
3747
- if (!existsSync11(ctx.paths.memoriesDir)) {
3943
+ if (!existsSync112(ctx.paths.memoriesDir)) {
3748
3944
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
3749
3945
  }
3750
3946
  const memories = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
@@ -3880,7 +4076,7 @@ async function memTried(input, ctx) {
3880
4076
  if (existsSync14(file)) {
3881
4077
  throw new Error(`Memory already exists at ${file}`);
3882
4078
  }
3883
- await writeFile7(file, serializeMemory6({ frontmatter, body }), "utf8");
4079
+ await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
3884
4080
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
3885
4081
  }
3886
4082
  var MemObserveInputSchema = {
@@ -6072,7 +6268,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6072
6268
  };
6073
6269
  }
6074
6270
  var SERVER_NAME = "haive";
6075
- var SERVER_VERSION = "0.9.10";
6271
+ var SERVER_VERSION = "0.9.12";
6076
6272
  function jsonResult(data) {
6077
6273
  return {
6078
6274
  content: [
@@ -6086,14 +6282,11 @@ function jsonResult(data) {
6086
6282
  var ENFORCEMENT_PROFILE_TOOLS = /* @__PURE__ */ new Set([
6087
6283
  "get_briefing",
6088
6284
  "mem_save",
6089
- "mem_tried",
6090
6285
  "mem_search",
6091
- "mem_get",
6092
- "mem_update",
6093
6286
  "mem_verify",
6094
6287
  "mem_relevant_to",
6095
- "code_map",
6096
- "pre_commit_check"
6288
+ "pre_commit_check",
6289
+ "mem_session_end"
6097
6290
  ]);
6098
6291
  var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]);
6099
6292
  var MUTATING_TOOLS = /* @__PURE__ */ new Set([
@@ -6984,7 +7177,7 @@ function registerMcp(program2) {
6984
7177
  ).option("-d, --dir <dir>", "project root (walks up from here for .ai/ / .git/)").option("-r, --root <dir>", "same as --dir (parity with legacy haive-mcp --root)").option("--stdio", "optional marker for client configs \u2014 transport is always stdio", false).action(async (opts) => {
6985
7178
  void opts.stdio;
6986
7179
  const raw = opts.root ?? opts.dir;
6987
- const root = raw ? findProjectRoot9(raw) : findProjectRoot9();
7180
+ const root = raw ? findProjectRoot10(raw) : findProjectRoot10();
6988
7181
  try {
6989
7182
  await runHaiveMcpStdio({ root });
6990
7183
  } catch (err) {
@@ -6995,15 +7188,15 @@ function registerMcp(program2) {
6995
7188
  }
6996
7189
 
6997
7190
  // src/commands/sync.ts
6998
- import { spawnSync as spawnSync2 } from "child_process";
6999
- import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir9 } from "fs/promises";
7191
+ import { spawnSync as spawnSync3 } from "child_process";
7192
+ import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
7000
7193
  import { existsSync as existsSync29 } from "fs";
7001
- import path12 from "path";
7194
+ import path13 from "path";
7002
7195
  import "commander";
7003
7196
  import {
7004
7197
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
7005
7198
  buildFrontmatter as buildFrontmatter6,
7006
- findProjectRoot as findProjectRoot10,
7199
+ findProjectRoot as findProjectRoot11,
7007
7200
  getUsage as getUsage10,
7008
7201
  isAutoPromoteEligible as isAutoPromoteEligible2,
7009
7202
  isDecaying as isDecaying2,
@@ -7012,7 +7205,7 @@ import {
7012
7205
  loadMemoriesFromDir as loadMemoriesFromDir23,
7013
7206
  loadUsageIndex as loadUsageIndex12,
7014
7207
  pullCrossRepoSources,
7015
- resolveHaivePaths as resolveHaivePaths7,
7208
+ resolveHaivePaths as resolveHaivePaths8,
7016
7209
  resolveManifestFiles,
7017
7210
  serializeMemory as serializeMemory11,
7018
7211
  trackDependencies,
@@ -7031,8 +7224,8 @@ function registerSync(program2) {
7031
7224
  "--inject-bridge",
7032
7225
  "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
7033
7226
  ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
7034
- const root = findProjectRoot10(opts.dir);
7035
- const paths = resolveHaivePaths7(root);
7227
+ const root = findProjectRoot11(opts.dir);
7228
+ const paths = resolveHaivePaths8(root);
7036
7229
  if (!existsSync29(paths.memoriesDir)) {
7037
7230
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7038
7231
  process.exitCode = 1;
@@ -7165,7 +7358,7 @@ function registerSync(program2) {
7165
7358
  );
7166
7359
  }
7167
7360
  if (opts.injectBridge) {
7168
- const bridgeFile = opts.bridgeFile ? path12.resolve(opts.bridgeFile) : path12.join(root, "CLAUDE.md");
7361
+ const bridgeFile = opts.bridgeFile ? path13.resolve(opts.bridgeFile) : path13.join(root, "CLAUDE.md");
7169
7362
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
7170
7363
  await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
7171
7364
  }
@@ -7271,10 +7464,10 @@ Attends une **confirmation explicite** avant d'agir.
7271
7464
  paths: [result.file],
7272
7465
  topic: `dep-bump-${slugParts}`
7273
7466
  });
7274
- const teamDir = path12.join(paths.memoriesDir, "team");
7275
- await mkdir9(teamDir, { recursive: true });
7467
+ const teamDir = path13.join(paths.memoriesDir, "team");
7468
+ await mkdir10(teamDir, { recursive: true });
7276
7469
  await writeFile13(
7277
- path12.join(teamDir, `${fm.id}.md`),
7470
+ path13.join(teamDir, `${fm.id}.md`),
7278
7471
  serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7279
7472
  "utf8"
7280
7473
  );
@@ -7338,10 +7531,10 @@ Attends une **confirmation explicite** avant d'agir.
7338
7531
  paths: [diff.file],
7339
7532
  topic: `contract-breaking-${diff.contract}`
7340
7533
  });
7341
- const teamDir = path12.join(paths.memoriesDir, "team");
7342
- await mkdir9(teamDir, { recursive: true });
7534
+ const teamDir = path13.join(paths.memoriesDir, "team");
7535
+ await mkdir10(teamDir, { recursive: true });
7343
7536
  await writeFile13(
7344
- path12.join(teamDir, `${fm.id}.md`),
7537
+ path13.join(teamDir, `${fm.id}.md`),
7345
7538
  serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7346
7539
  "utf8"
7347
7540
  );
@@ -7355,7 +7548,7 @@ Attends une **confirmation explicite** avant d'agir.
7355
7548
  const existingMap = await loadCodeMap4(paths);
7356
7549
  if (existingMap) {
7357
7550
  const mapAge = new Date(existingMap.generated_at).getTime();
7358
- const gitResult = spawnSync2(
7551
+ const gitResult = spawnSync3(
7359
7552
  "git",
7360
7553
  [
7361
7554
  "diff",
@@ -7434,11 +7627,11 @@ ${BRIDGE_END}`;
7434
7627
  const startIdx = existing.indexOf(BRIDGE_START);
7435
7628
  const endIdx = existing.indexOf(BRIDGE_END);
7436
7629
  if (startIdx !== -1 && endIdx === -1) {
7437
- ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
7630
+ ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
7438
7631
  return;
7439
7632
  }
7440
7633
  if (startIdx === -1 && endIdx !== -1) {
7441
- ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
7634
+ ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
7442
7635
  return;
7443
7636
  }
7444
7637
  let updated;
@@ -7446,19 +7639,19 @@ ${BRIDGE_END}`;
7446
7639
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
7447
7640
  } else {
7448
7641
  if (!fileExists && !quiet) {
7449
- ui.info(`Creating ${path12.relative(root, bridgeFile)} with haive memory block.`);
7642
+ ui.info(`Creating ${path13.relative(root, bridgeFile)} with haive memory block.`);
7450
7643
  }
7451
7644
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
7452
7645
  }
7453
7646
  await writeFile13(bridgeFile, updated, "utf8");
7454
7647
  if (!quiet) {
7455
7648
  console.log(
7456
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path12.relative(root, bridgeFile)}`)
7649
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path13.relative(root, bridgeFile)}`)
7457
7650
  );
7458
7651
  }
7459
7652
  }
7460
7653
  function collectSinceChanges(root, ref) {
7461
- const result = spawnSync2(
7654
+ const result = spawnSync3(
7462
7655
  "git",
7463
7656
  ["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
7464
7657
  { encoding: "utf8" }
@@ -7478,17 +7671,17 @@ function collectSinceChanges(root, ref) {
7478
7671
 
7479
7672
  // src/commands/memory-add.ts
7480
7673
  import { createHash as createHash2 } from "crypto";
7481
- import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7674
+ import { mkdir as mkdir11, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7482
7675
  import { existsSync as existsSync30 } from "fs";
7483
- import path13 from "path";
7676
+ import path14 from "path";
7484
7677
  import "commander";
7485
7678
  import {
7486
7679
  buildFrontmatter as buildFrontmatter7,
7487
- findProjectRoot as findProjectRoot11,
7680
+ findProjectRoot as findProjectRoot12,
7488
7681
  inferModulesFromPaths as inferModulesFromPaths3,
7489
7682
  loadMemoriesFromDir as loadMemoriesFromDir24,
7490
7683
  memoryFilePath as memoryFilePath6,
7491
- resolveHaivePaths as resolveHaivePaths8,
7684
+ resolveHaivePaths as resolveHaivePaths9,
7492
7685
  serializeMemory as serializeMemory12
7493
7686
  } from "@hiveai/core";
7494
7687
  function registerMemoryAdd(memory2) {
@@ -7516,8 +7709,8 @@ function registerMemoryAdd(memory2) {
7516
7709
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
7517
7710
  `
7518
7711
  ).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: personal, or team in autopilot)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7519
- const root = findProjectRoot11(opts.dir);
7520
- const paths = resolveHaivePaths8(root);
7712
+ const root = findProjectRoot12(opts.dir);
7713
+ const paths = resolveHaivePaths9(root);
7521
7714
  if (!existsSync30(paths.haiveDir)) {
7522
7715
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
7523
7716
  process.exitCode = 1;
@@ -7529,7 +7722,7 @@ function registerMemoryAdd(memory2) {
7529
7722
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
7530
7723
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
7531
7724
  if (anchorPaths.length > 0) {
7532
- const missing = anchorPaths.filter((p) => !existsSync30(path13.resolve(root, p)));
7725
+ const missing = anchorPaths.filter((p) => !existsSync30(path14.resolve(root, p)));
7533
7726
  if (missing.length > 0) {
7534
7727
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
7535
7728
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -7594,7 +7787,7 @@ TODO \u2014 write the memory body.
7594
7787
  }
7595
7788
  };
7596
7789
  await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
7597
- ui.success(`Updated (topic upsert) ${path13.relative(root, topicMatch.filePath)}`);
7790
+ ui.success(`Updated (topic upsert) ${path14.relative(root, topicMatch.filePath)}`);
7598
7791
  ui.info(`id=${fm.id} revision=${revisionCount}`);
7599
7792
  return;
7600
7793
  }
@@ -7613,7 +7806,7 @@ TODO \u2014 write the memory body.
7613
7806
  topic: opts.topic
7614
7807
  });
7615
7808
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
7616
- await mkdir10(path13.dirname(file), { recursive: true });
7809
+ await mkdir11(path14.dirname(file), { recursive: true });
7617
7810
  if (existsSync30(file)) {
7618
7811
  ui.error(`Memory already exists at ${file}`);
7619
7812
  process.exitCode = 1;
@@ -7632,7 +7825,7 @@ TODO \u2014 write the memory body.
7632
7825
  }
7633
7826
  }
7634
7827
  await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
7635
- ui.success(`Created ${path13.relative(root, file)}`);
7828
+ ui.success(`Created ${path14.relative(root, file)}`);
7636
7829
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
7637
7830
  if (inferredTags.length > 0) {
7638
7831
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
@@ -7663,9 +7856,9 @@ function parseCsv2(value) {
7663
7856
 
7664
7857
  // src/commands/memory-list.ts
7665
7858
  import { existsSync as existsSync31 } from "fs";
7666
- import path14 from "path";
7859
+ import path15 from "path";
7667
7860
  import "commander";
7668
- import { findProjectRoot as findProjectRoot12, resolveHaivePaths as resolveHaivePaths9 } from "@hiveai/core";
7861
+ import { findProjectRoot as findProjectRoot13, resolveHaivePaths as resolveHaivePaths10 } from "@hiveai/core";
7669
7862
 
7670
7863
  // src/utils/fs.ts
7671
7864
  import {
@@ -7677,8 +7870,8 @@ import {
7677
7870
  // src/commands/memory-list.ts
7678
7871
  function registerMemoryList(memory2) {
7679
7872
  memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7680
- const root = findProjectRoot12(opts.dir);
7681
- const paths = resolveHaivePaths9(root);
7873
+ const root = findProjectRoot13(opts.dir);
7874
+ const paths = resolveHaivePaths10(root);
7682
7875
  if (!existsSync31(paths.memoriesDir)) {
7683
7876
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
7684
7877
  process.exitCode = 1;
@@ -7711,7 +7904,7 @@ function registerMemoryList(memory2) {
7711
7904
  console.log(
7712
7905
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
7713
7906
  );
7714
- console.log(` ${ui.dim(path14.relative(root, filePath))}`);
7907
+ console.log(` ${ui.dim(path15.relative(root, filePath))}`);
7715
7908
  }
7716
7909
  console.log(ui.dim(`
7717
7910
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
@@ -7746,20 +7939,20 @@ function matchesFilters(loaded, opts) {
7746
7939
  }
7747
7940
 
7748
7941
  // src/commands/memory-promote.ts
7749
- import { mkdir as mkdir11, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
7942
+ import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
7750
7943
  import { existsSync as existsSync33 } from "fs";
7751
- import path15 from "path";
7944
+ import path16 from "path";
7752
7945
  import "commander";
7753
7946
  import {
7754
- findProjectRoot as findProjectRoot13,
7947
+ findProjectRoot as findProjectRoot14,
7755
7948
  memoryFilePath as memoryFilePath7,
7756
- resolveHaivePaths as resolveHaivePaths10,
7949
+ resolveHaivePaths as resolveHaivePaths11,
7757
7950
  serializeMemory as serializeMemory13
7758
7951
  } from "@hiveai/core";
7759
7952
  function registerMemoryPromote(memory2) {
7760
7953
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
7761
- const root = findProjectRoot13(opts.dir);
7762
- const paths = resolveHaivePaths10(root);
7954
+ const root = findProjectRoot14(opts.dir);
7955
+ const paths = resolveHaivePaths11(root);
7763
7956
  if (!existsSync33(paths.memoriesDir)) {
7764
7957
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
7765
7958
  process.exitCode = 1;
@@ -7795,11 +7988,11 @@ function registerMemoryPromote(memory2) {
7795
7988
  body: found.memory.body
7796
7989
  };
7797
7990
  const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
7798
- await mkdir11(path15.dirname(newPath), { recursive: true });
7991
+ await mkdir12(path16.dirname(newPath), { recursive: true });
7799
7992
  await writeFile15(newPath, serializeMemory13(updated), "utf8");
7800
7993
  await unlink2(found.filePath);
7801
7994
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
7802
- ui.info(`Now at ${path15.relative(root, newPath)}`);
7995
+ ui.info(`Now at ${path16.relative(root, newPath)}`);
7803
7996
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
7804
7997
  });
7805
7998
  }
@@ -7807,17 +8000,17 @@ function registerMemoryPromote(memory2) {
7807
8000
  // src/commands/memory-approve.ts
7808
8001
  import { existsSync as existsSync34 } from "fs";
7809
8002
  import { writeFile as writeFile16 } from "fs/promises";
7810
- import path16 from "path";
8003
+ import path17 from "path";
7811
8004
  import "commander";
7812
8005
  import {
7813
- findProjectRoot as findProjectRoot14,
7814
- resolveHaivePaths as resolveHaivePaths11,
8006
+ findProjectRoot as findProjectRoot15,
8007
+ resolveHaivePaths as resolveHaivePaths12,
7815
8008
  serializeMemory as serializeMemory14
7816
8009
  } from "@hiveai/core";
7817
8010
  function registerMemoryApprove(memory2) {
7818
8011
  memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
7819
- const root = findProjectRoot14(opts.dir);
7820
- const paths = resolveHaivePaths11(root);
8012
+ const root = findProjectRoot15(opts.dir);
8013
+ const paths = resolveHaivePaths12(root);
7821
8014
  if (!existsSync34(paths.memoriesDir)) {
7822
8015
  ui.error(`No .ai/memories at ${root}.`);
7823
8016
  process.exitCode = 1;
@@ -7871,24 +8064,24 @@ function registerMemoryApprove(memory2) {
7871
8064
  };
7872
8065
  await writeFile16(found.filePath, serializeMemory14(next), "utf8");
7873
8066
  ui.success(`Approved ${id} (status=validated)`);
7874
- ui.info(path16.relative(root, found.filePath));
8067
+ ui.info(path17.relative(root, found.filePath));
7875
8068
  });
7876
8069
  }
7877
8070
 
7878
8071
  // src/commands/memory-update.ts
7879
8072
  import { writeFile as writeFile17 } from "fs/promises";
7880
8073
  import { existsSync as existsSync35 } from "fs";
7881
- import path17 from "path";
8074
+ import path18 from "path";
7882
8075
  import "commander";
7883
8076
  import {
7884
- findProjectRoot as findProjectRoot15,
7885
- resolveHaivePaths as resolveHaivePaths12,
8077
+ findProjectRoot as findProjectRoot16,
8078
+ resolveHaivePaths as resolveHaivePaths13,
7886
8079
  serializeMemory as serializeMemory15
7887
8080
  } from "@hiveai/core";
7888
8081
  function registerMemoryUpdate(memory2) {
7889
8082
  memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
7890
- const root = findProjectRoot15(opts.dir);
7891
- const paths = resolveHaivePaths12(root);
8083
+ const root = findProjectRoot16(opts.dir);
8084
+ const paths = resolveHaivePaths13(root);
7892
8085
  if (!existsSync35(paths.memoriesDir)) {
7893
8086
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7894
8087
  process.exitCode = 1;
@@ -7941,7 +8134,7 @@ function registerMemoryUpdate(memory2) {
7941
8134
  serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
7942
8135
  "utf8"
7943
8136
  );
7944
- ui.success(`Updated ${path17.relative(root, loaded.filePath)}`);
8137
+ ui.success(`Updated ${path18.relative(root, loaded.filePath)}`);
7945
8138
  ui.info(`fields: ${updated.join(", ")}`);
7946
8139
  });
7947
8140
  }
@@ -7962,15 +8155,15 @@ function parseCsv3(value) {
7962
8155
  // src/commands/memory-auto-promote.ts
7963
8156
  import { writeFile as writeFile18 } from "fs/promises";
7964
8157
  import { existsSync as existsSync36 } from "fs";
7965
- import path18 from "path";
8158
+ import path19 from "path";
7966
8159
  import "commander";
7967
8160
  import {
7968
8161
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
7969
- findProjectRoot as findProjectRoot16,
8162
+ findProjectRoot as findProjectRoot17,
7970
8163
  getUsage as getUsage11,
7971
8164
  isAutoPromoteEligible as isAutoPromoteEligible3,
7972
8165
  loadUsageIndex as loadUsageIndex13,
7973
- resolveHaivePaths as resolveHaivePaths13,
8166
+ resolveHaivePaths as resolveHaivePaths14,
7974
8167
  serializeMemory as serializeMemory16
7975
8168
  } from "@hiveai/core";
7976
8169
  function registerMemoryAutoPromote(memory2) {
@@ -7979,8 +8172,8 @@ function registerMemoryAutoPromote(memory2) {
7979
8172
  "memories with more rejections than this are skipped",
7980
8173
  String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
7981
8174
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7982
- const root = findProjectRoot16(opts.dir);
7983
- const paths = resolveHaivePaths13(root);
8175
+ const root = findProjectRoot17(opts.dir);
8176
+ const paths = resolveHaivePaths14(root);
7984
8177
  if (!existsSync36(paths.memoriesDir)) {
7985
8178
  ui.error(`No .ai/memories at ${root}.`);
7986
8179
  process.exitCode = 1;
@@ -8007,7 +8200,7 @@ function registerMemoryAutoPromote(memory2) {
8007
8200
  console.log(
8008
8201
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8009
8202
  );
8010
- console.log(` ${ui.dim(path18.relative(root, filePath))}`);
8203
+ console.log(` ${ui.dim(path19.relative(root, filePath))}`);
8011
8204
  if (opts.apply) {
8012
8205
  const next = {
8013
8206
  frontmatter: { ...mem.frontmatter, status: "validated" },
@@ -8026,17 +8219,17 @@ function registerMemoryAutoPromote(memory2) {
8026
8219
  import { spawn as spawn3 } from "child_process";
8027
8220
  import { existsSync as existsSync37 } from "fs";
8028
8221
  import { readFile as readFile10 } from "fs/promises";
8029
- import path19 from "path";
8222
+ import path20 from "path";
8030
8223
  import "commander";
8031
8224
  import {
8032
- findProjectRoot as findProjectRoot17,
8225
+ findProjectRoot as findProjectRoot18,
8033
8226
  parseMemory,
8034
- resolveHaivePaths as resolveHaivePaths14
8227
+ resolveHaivePaths as resolveHaivePaths15
8035
8228
  } from "@hiveai/core";
8036
8229
  function registerMemoryEdit(memory2) {
8037
8230
  memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8038
- const root = findProjectRoot17(opts.dir);
8039
- const paths = resolveHaivePaths14(root);
8231
+ const root = findProjectRoot18(opts.dir);
8232
+ const paths = resolveHaivePaths15(root);
8040
8233
  if (!existsSync37(paths.memoriesDir)) {
8041
8234
  ui.error(`No .ai/memories at ${root}.`);
8042
8235
  process.exitCode = 1;
@@ -8050,7 +8243,7 @@ function registerMemoryEdit(memory2) {
8050
8243
  return;
8051
8244
  }
8052
8245
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
8053
- ui.info(`Opening ${path19.relative(root, found.filePath)} with ${editor}\u2026`);
8246
+ ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
8054
8247
  const code = await runEditor(editor, found.filePath);
8055
8248
  if (code !== 0) {
8056
8249
  ui.warn(`Editor exited with status ${code}.`);
@@ -8078,21 +8271,21 @@ function runEditor(editor, file) {
8078
8271
 
8079
8272
  // src/commands/memory-for-files.ts
8080
8273
  import { existsSync as existsSync38 } from "fs";
8081
- import path20 from "path";
8274
+ import path21 from "path";
8082
8275
  import "commander";
8083
8276
  import {
8084
8277
  deriveConfidence as deriveConfidence9,
8085
- findProjectRoot as findProjectRoot18,
8278
+ findProjectRoot as findProjectRoot19,
8086
8279
  getUsage as getUsage12,
8087
8280
  inferModulesFromPaths as inferModulesFromPaths4,
8088
8281
  loadUsageIndex as loadUsageIndex14,
8089
8282
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
8090
- resolveHaivePaths as resolveHaivePaths15
8283
+ resolveHaivePaths as resolveHaivePaths16
8091
8284
  } from "@hiveai/core";
8092
8285
  function registerMemoryForFiles(memory2) {
8093
8286
  memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
8094
- const root = findProjectRoot18(opts.dir);
8095
- const paths = resolveHaivePaths15(root);
8287
+ const root = findProjectRoot19(opts.dir);
8288
+ const paths = resolveHaivePaths16(root);
8096
8289
  if (!existsSync38(paths.memoriesDir)) {
8097
8290
  ui.error(`No .ai/memories at ${root}.`);
8098
8291
  process.exitCode = 1;
@@ -8200,24 +8393,24 @@ function printGroup(root, label, loaded, usage) {
8200
8393
  const u = getUsage12(usage, fm.id);
8201
8394
  const conf = deriveConfidence9(fm, u);
8202
8395
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
8203
- console.log(` ${ui.dim(path20.relative(root, filePath))}`);
8396
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
8204
8397
  }
8205
8398
  }
8206
8399
 
8207
8400
  // src/commands/memory-hot.ts
8208
8401
  import { existsSync as existsSync39 } from "fs";
8209
- import path21 from "path";
8402
+ import path23 from "path";
8210
8403
  import "commander";
8211
8404
  import {
8212
- findProjectRoot as findProjectRoot19,
8405
+ findProjectRoot as findProjectRoot20,
8213
8406
  getUsage as getUsage13,
8214
8407
  loadUsageIndex as loadUsageIndex15,
8215
- resolveHaivePaths as resolveHaivePaths16
8408
+ resolveHaivePaths as resolveHaivePaths17
8216
8409
  } from "@hiveai/core";
8217
8410
  function registerMemoryHot(memory2) {
8218
8411
  memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8219
- const root = findProjectRoot19(opts.dir);
8220
- const paths = resolveHaivePaths16(root);
8412
+ const root = findProjectRoot20(opts.dir);
8413
+ const paths = resolveHaivePaths17(root);
8221
8414
  if (!existsSync39(paths.memoriesDir)) {
8222
8415
  ui.error(`No .ai/memories at ${root}.`);
8223
8416
  process.exitCode = 1;
@@ -8246,7 +8439,7 @@ function registerMemoryHot(memory2) {
8246
8439
  console.log(
8247
8440
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8248
8441
  );
8249
- console.log(` ${ui.dim(path21.relative(root, filePath))}`);
8442
+ console.log(` ${ui.dim(path23.relative(root, filePath))}`);
8250
8443
  }
8251
8444
  ui.info(
8252
8445
  `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
@@ -8255,15 +8448,15 @@ function registerMemoryHot(memory2) {
8255
8448
  }
8256
8449
 
8257
8450
  // src/commands/memory-tried.ts
8258
- import { mkdir as mkdir12, writeFile as writeFile19 } from "fs/promises";
8451
+ import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
8259
8452
  import { existsSync as existsSync40 } from "fs";
8260
- import path23 from "path";
8453
+ import path24 from "path";
8261
8454
  import "commander";
8262
8455
  import {
8263
8456
  buildFrontmatter as buildFrontmatter8,
8264
- findProjectRoot as findProjectRoot20,
8457
+ findProjectRoot as findProjectRoot21,
8265
8458
  memoryFilePath as memoryFilePath8,
8266
- resolveHaivePaths as resolveHaivePaths17,
8459
+ resolveHaivePaths as resolveHaivePaths18,
8267
8460
  serializeMemory as serializeMemory17
8268
8461
  } from "@hiveai/core";
8269
8462
  function registerMemoryTried(memory2) {
@@ -8283,8 +8476,8 @@ function registerMemoryTried(memory2) {
8283
8476
  --paths packages/cli/src/index.ts
8284
8477
  `
8285
8478
  ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
8286
- const root = findProjectRoot20(opts.dir);
8287
- const paths = resolveHaivePaths17(root);
8479
+ const root = findProjectRoot21(opts.dir);
8480
+ const paths = resolveHaivePaths18(root);
8288
8481
  if (!existsSync40(paths.haiveDir)) {
8289
8482
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8290
8483
  process.exitCode = 1;
@@ -8308,14 +8501,14 @@ function registerMemoryTried(memory2) {
8308
8501
  }
8309
8502
  const body = lines.join("\n") + "\n";
8310
8503
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8311
- await mkdir12(path23.dirname(file), { recursive: true });
8504
+ await mkdir13(path24.dirname(file), { recursive: true });
8312
8505
  if (existsSync40(file)) {
8313
8506
  ui.error(`Memory already exists at ${file}`);
8314
8507
  process.exitCode = 1;
8315
8508
  return;
8316
8509
  }
8317
8510
  await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
8318
- ui.success(`Recorded: ${path23.relative(root, file)}`);
8511
+ ui.success(`Recorded: ${path24.relative(root, file)}`);
8319
8512
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
8320
8513
  });
8321
8514
  }
@@ -8326,18 +8519,18 @@ function parseCsv4(value) {
8326
8519
 
8327
8520
  // src/commands/memory-pending.ts
8328
8521
  import { existsSync as existsSync41 } from "fs";
8329
- import path24 from "path";
8522
+ import path25 from "path";
8330
8523
  import "commander";
8331
8524
  import {
8332
- findProjectRoot as findProjectRoot21,
8525
+ findProjectRoot as findProjectRoot22,
8333
8526
  getUsage as getUsage14,
8334
8527
  loadUsageIndex as loadUsageIndex16,
8335
- resolveHaivePaths as resolveHaivePaths18
8528
+ resolveHaivePaths as resolveHaivePaths19
8336
8529
  } from "@hiveai/core";
8337
8530
  function registerMemoryPending(memory2) {
8338
8531
  memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8339
- const root = findProjectRoot21(opts.dir);
8340
- const paths = resolveHaivePaths18(root);
8532
+ const root = findProjectRoot22(opts.dir);
8533
+ const paths = resolveHaivePaths19(root);
8341
8534
  if (!existsSync41(paths.memoriesDir)) {
8342
8535
  ui.error(`No .ai/memories at ${root}.`);
8343
8536
  process.exitCode = 1;
@@ -8366,7 +8559,7 @@ function registerMemoryPending(memory2) {
8366
8559
  console.log(
8367
8560
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
8368
8561
  );
8369
- console.log(` ${ui.dim(path24.relative(root, filePath))}`);
8562
+ console.log(` ${ui.dim(path25.relative(root, filePath))}`);
8370
8563
  }
8371
8564
  ui.info(`${proposed.length} pending`);
8372
8565
  });
@@ -8374,22 +8567,22 @@ function registerMemoryPending(memory2) {
8374
8567
 
8375
8568
  // src/commands/memory-query.ts
8376
8569
  import { existsSync as existsSync43 } from "fs";
8377
- import path25 from "path";
8570
+ import path26 from "path";
8378
8571
  import "commander";
8379
8572
  import {
8380
8573
  extractSnippet as extractSnippet2,
8381
- findProjectRoot as findProjectRoot22,
8574
+ findProjectRoot as findProjectRoot23,
8382
8575
  literalMatchesAllTokens as literalMatchesAllTokens3,
8383
8576
  literalMatchesAnyToken as literalMatchesAnyToken4,
8384
8577
  pickSnippetNeedle as pickSnippetNeedle2,
8385
- resolveHaivePaths as resolveHaivePaths19,
8578
+ resolveHaivePaths as resolveHaivePaths20,
8386
8579
  tokenizeQuery as tokenizeQuery6,
8387
8580
  trackReads as trackReads4
8388
8581
  } from "@hiveai/core";
8389
8582
  function registerMemoryQuery(memory2) {
8390
8583
  memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
8391
- const root = findProjectRoot22(opts.dir);
8392
- const paths = resolveHaivePaths19(root);
8584
+ const root = findProjectRoot23(opts.dir);
8585
+ const paths = resolveHaivePaths20(root);
8393
8586
  if (!existsSync43(paths.memoriesDir)) {
8394
8587
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8395
8588
  process.exitCode = 1;
@@ -8431,7 +8624,7 @@ function registerMemoryQuery(memory2) {
8431
8624
  const fm = mem.frontmatter;
8432
8625
  const statusBadge = ui.statusBadge(fm.status);
8433
8626
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
8434
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
8627
+ console.log(` ${ui.dim(path26.relative(root, filePath))}`);
8435
8628
  const snippet = extractSnippet2(mem.body, snippetNeedle);
8436
8629
  if (snippet) console.log(` ${snippet}`);
8437
8630
  }
@@ -8452,17 +8645,17 @@ import { writeFile as writeFile20 } from "fs/promises";
8452
8645
  import { existsSync as existsSync44 } from "fs";
8453
8646
  import "commander";
8454
8647
  import {
8455
- findProjectRoot as findProjectRoot23,
8648
+ findProjectRoot as findProjectRoot24,
8456
8649
  loadUsageIndex as loadUsageIndex17,
8457
8650
  recordRejection as recordRejection2,
8458
- resolveHaivePaths as resolveHaivePaths20,
8651
+ resolveHaivePaths as resolveHaivePaths21,
8459
8652
  saveUsageIndex as saveUsageIndex3,
8460
8653
  serializeMemory as serializeMemory18
8461
8654
  } from "@hiveai/core";
8462
8655
  function registerMemoryReject(memory2) {
8463
8656
  memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8464
- const root = findProjectRoot23(opts.dir);
8465
- const paths = resolveHaivePaths20(root);
8657
+ const root = findProjectRoot24(opts.dir);
8658
+ const paths = resolveHaivePaths21(root);
8466
8659
  if (!existsSync44(paths.memoriesDir)) {
8467
8660
  ui.error(`No .ai/memories at ${root}.`);
8468
8661
  process.exitCode = 1;
@@ -8501,19 +8694,19 @@ function registerMemoryReject(memory2) {
8501
8694
  // src/commands/memory-rm.ts
8502
8695
  import { existsSync as existsSync45 } from "fs";
8503
8696
  import { unlink as unlink3 } from "fs/promises";
8504
- import path26 from "path";
8505
- import { createInterface } from "readline/promises";
8697
+ import path27 from "path";
8698
+ import { createInterface as createInterface2 } from "readline/promises";
8506
8699
  import "commander";
8507
8700
  import {
8508
- findProjectRoot as findProjectRoot24,
8701
+ findProjectRoot as findProjectRoot25,
8509
8702
  loadUsageIndex as loadUsageIndex18,
8510
- resolveHaivePaths as resolveHaivePaths21,
8703
+ resolveHaivePaths as resolveHaivePaths22,
8511
8704
  saveUsageIndex as saveUsageIndex4
8512
8705
  } from "@hiveai/core";
8513
8706
  function registerMemoryRm(memory2) {
8514
8707
  memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8515
- const root = findProjectRoot24(opts.dir);
8516
- const paths = resolveHaivePaths21(root);
8708
+ const root = findProjectRoot25(opts.dir);
8709
+ const paths = resolveHaivePaths22(root);
8517
8710
  if (!existsSync45(paths.memoriesDir)) {
8518
8711
  ui.error(`No .ai/memories at ${root}.`);
8519
8712
  process.exitCode = 1;
@@ -8526,9 +8719,9 @@ function registerMemoryRm(memory2) {
8526
8719
  process.exitCode = 1;
8527
8720
  return;
8528
8721
  }
8529
- const rel = path26.relative(root, found.filePath);
8722
+ const rel = path27.relative(root, found.filePath);
8530
8723
  if (!opts.yes) {
8531
- const rl = createInterface({ input: process.stdin, output: process.stdout });
8724
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
8532
8725
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
8533
8726
  rl.close();
8534
8727
  if (answer !== "y" && answer !== "yes") {
@@ -8552,19 +8745,19 @@ function registerMemoryRm(memory2) {
8552
8745
  // src/commands/memory-show.ts
8553
8746
  import { existsSync as existsSync46 } from "fs";
8554
8747
  import { readFile as readFile11 } from "fs/promises";
8555
- import path27 from "path";
8748
+ import path28 from "path";
8556
8749
  import "commander";
8557
8750
  import {
8558
8751
  deriveConfidence as deriveConfidence10,
8559
- findProjectRoot as findProjectRoot25,
8752
+ findProjectRoot as findProjectRoot26,
8560
8753
  getUsage as getUsage15,
8561
8754
  loadUsageIndex as loadUsageIndex19,
8562
- resolveHaivePaths as resolveHaivePaths22
8755
+ resolveHaivePaths as resolveHaivePaths23
8563
8756
  } from "@hiveai/core";
8564
8757
  function registerMemoryShow(memory2) {
8565
8758
  memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8566
- const root = findProjectRoot25(opts.dir);
8567
- const paths = resolveHaivePaths22(root);
8759
+ const root = findProjectRoot26(opts.dir);
8760
+ const paths = resolveHaivePaths23(root);
8568
8761
  if (!existsSync46(paths.memoriesDir)) {
8569
8762
  ui.error(`No .ai/memories at ${root}.`);
8570
8763
  process.exitCode = 1;
@@ -8594,7 +8787,7 @@ function registerMemoryShow(memory2) {
8594
8787
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
8595
8788
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
8596
8789
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
8597
- console.log(`${ui.dim("file:")} ${path27.relative(root, found.filePath)}`);
8790
+ console.log(`${ui.dim("file:")} ${path28.relative(root, found.filePath)}`);
8598
8791
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
8599
8792
  console.log(ui.dim("anchor:"));
8600
8793
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -8610,19 +8803,19 @@ function registerMemoryShow(memory2) {
8610
8803
 
8611
8804
  // src/commands/memory-stats.ts
8612
8805
  import { existsSync as existsSync47 } from "fs";
8613
- import path28 from "path";
8806
+ import path29 from "path";
8614
8807
  import "commander";
8615
8808
  import {
8616
8809
  deriveConfidence as deriveConfidence11,
8617
- findProjectRoot as findProjectRoot26,
8810
+ findProjectRoot as findProjectRoot27,
8618
8811
  getUsage as getUsage16,
8619
8812
  loadUsageIndex as loadUsageIndex20,
8620
- resolveHaivePaths as resolveHaivePaths23
8813
+ resolveHaivePaths as resolveHaivePaths24
8621
8814
  } from "@hiveai/core";
8622
8815
  function registerMemoryStats(memory2) {
8623
8816
  memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
8624
- const root = findProjectRoot26(opts.dir);
8625
- const paths = resolveHaivePaths23(root);
8817
+ const root = findProjectRoot27(opts.dir);
8818
+ const paths = resolveHaivePaths24(root);
8626
8819
  if (!existsSync47(paths.memoriesDir)) {
8627
8820
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8628
8821
  process.exitCode = 1;
@@ -8648,7 +8841,7 @@ function registerMemoryStats(memory2) {
8648
8841
  console.log(
8649
8842
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
8650
8843
  );
8651
- console.log(` ${ui.dim(path28.relative(root, filePath))}`);
8844
+ console.log(` ${ui.dim(path29.relative(root, filePath))}`);
8652
8845
  }
8653
8846
  });
8654
8847
  }
@@ -8656,11 +8849,11 @@ function registerMemoryStats(memory2) {
8656
8849
  // src/commands/memory-verify.ts
8657
8850
  import { writeFile as writeFile21 } from "fs/promises";
8658
8851
  import { existsSync as existsSync48 } from "fs";
8659
- import path29 from "path";
8852
+ import path30 from "path";
8660
8853
  import "commander";
8661
8854
  import {
8662
- findProjectRoot as findProjectRoot27,
8663
- resolveHaivePaths as resolveHaivePaths24,
8855
+ findProjectRoot as findProjectRoot28,
8856
+ resolveHaivePaths as resolveHaivePaths25,
8664
8857
  serializeMemory as serializeMemory19,
8665
8858
  verifyAnchor as verifyAnchor3
8666
8859
  } from "@hiveai/core";
@@ -8668,8 +8861,8 @@ function registerMemoryVerify(memory2) {
8668
8861
  memory2.command("verify").description(
8669
8862
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
8670
8863
  ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
8671
- const root = findProjectRoot27(opts.dir);
8672
- const paths = resolveHaivePaths24(root);
8864
+ const root = findProjectRoot28(opts.dir);
8865
+ const paths = resolveHaivePaths25(root);
8673
8866
  if (!existsSync48(paths.memoriesDir)) {
8674
8867
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8675
8868
  process.exitCode = 1;
@@ -8693,7 +8886,7 @@ function registerMemoryVerify(memory2) {
8693
8886
  anchorlessIds.push(mem.frontmatter.id);
8694
8887
  continue;
8695
8888
  }
8696
- const rel = path29.relative(root, filePath);
8889
+ const rel = path30.relative(root, filePath);
8697
8890
  if (result.stale) {
8698
8891
  staleCount++;
8699
8892
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -8760,15 +8953,15 @@ import { readFile as readFile12 } from "fs/promises";
8760
8953
  import { existsSync as existsSync49 } from "fs";
8761
8954
  import "commander";
8762
8955
  import {
8763
- findProjectRoot as findProjectRoot28,
8764
- resolveHaivePaths as resolveHaivePaths25
8956
+ findProjectRoot as findProjectRoot29,
8957
+ resolveHaivePaths as resolveHaivePaths26
8765
8958
  } from "@hiveai/core";
8766
8959
  function registerMemoryImport(memory2) {
8767
8960
  memory2.command("import").description(
8768
8961
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
8769
8962
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
8770
- const root = findProjectRoot28(opts.dir);
8771
- const paths = resolveHaivePaths25(root);
8963
+ const root = findProjectRoot29(opts.dir);
8964
+ const paths = resolveHaivePaths26(root);
8772
8965
  if (!existsSync49(paths.haiveDir)) {
8773
8966
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8774
8967
  process.exitCode = 1;
@@ -8808,21 +9001,21 @@ function registerMemoryImport(memory2) {
8808
9001
 
8809
9002
  // src/commands/memory-import-changelog.ts
8810
9003
  import { existsSync as existsSync50 } from "fs";
8811
- import { readFile as readFile13, mkdir as mkdir13, writeFile as writeFile23 } from "fs/promises";
8812
- import path30 from "path";
9004
+ import { readFile as readFile13, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
9005
+ import path31 from "path";
8813
9006
  import "commander";
8814
9007
  import {
8815
9008
  buildFrontmatter as buildFrontmatter9,
8816
- findProjectRoot as findProjectRoot29,
8817
- resolveHaivePaths as resolveHaivePaths26,
9009
+ findProjectRoot as findProjectRoot30,
9010
+ resolveHaivePaths as resolveHaivePaths27,
8818
9011
  serializeMemory as serializeMemory20
8819
9012
  } from "@hiveai/core";
8820
9013
  function parseChangelog(content) {
8821
9014
  const entries = [];
8822
9015
  const versionRe = /^#{1,3}\s+(?:\[?)([0-9]+\.[0-9]+[.0-9]*)/m;
8823
9016
  const sections = content.split(/^#{1,3}\s+/m).slice(1);
8824
- for (const section of sections) {
8825
- const versionMatch = section.match(/^(?:\[?)([0-9]+\.[0-9]+[.0-9]*)/);
9017
+ for (const section2 of sections) {
9018
+ const versionMatch = section2.match(/^(?:\[?)([0-9]+\.[0-9]+[.0-9]*)/);
8826
9019
  const version = versionMatch?.[1];
8827
9020
  if (!version) continue;
8828
9021
  const entry = {
@@ -8833,7 +9026,7 @@ function parseChangelog(content) {
8833
9026
  fixed: [],
8834
9027
  added: []
8835
9028
  };
8836
- const subSections = section.split(/^#{2,4}\s+/m);
9029
+ const subSections = section2.split(/^#{2,4}\s+/m);
8837
9030
  for (const sub of subSections) {
8838
9031
  const firstLine = (sub.split("\n")[0] ?? "").toLowerCase().trim();
8839
9032
  const items = sub.split("\n").slice(1).filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*")).map((l) => l.replace(/^[\s\-*]+/, "").trim()).filter(Boolean);
@@ -8859,7 +9052,7 @@ function parseChangelog(content) {
8859
9052
  }
8860
9053
  }
8861
9054
  if (entry.breaking.length === 0) {
8862
- for (const line of section.split("\n")) {
9055
+ for (const line of section2.split("\n")) {
8863
9056
  if (/breaking|⚠|deprecated|removed/.test(line.toLowerCase())) {
8864
9057
  const item = line.replace(/^[\s\-*#]+/, "").trim();
8865
9058
  if (item) entry.breaking.push(item);
@@ -8879,9 +9072,9 @@ function registerMemoryImportChangelog(memory2) {
8879
9072
  "--versions <csv>",
8880
9073
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
8881
9074
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
8882
- const root = findProjectRoot29(opts.dir);
8883
- const paths = resolveHaivePaths26(root);
8884
- const changelogPath = path30.resolve(root, opts.fromChangelog);
9075
+ const root = findProjectRoot30(opts.dir);
9076
+ const paths = resolveHaivePaths27(root);
9077
+ const changelogPath = path31.resolve(root, opts.fromChangelog);
8885
9078
  if (!existsSync50(changelogPath)) {
8886
9079
  ui.error(`CHANGELOG not found: ${changelogPath}`);
8887
9080
  process.exitCode = 1;
@@ -8902,10 +9095,10 @@ function registerMemoryImportChangelog(memory2) {
8902
9095
  entries = entries.filter((e) => requested.includes(e.version));
8903
9096
  }
8904
9097
  }
8905
- const pkgName = opts.package ?? path30.basename(path30.dirname(changelogPath));
9098
+ const pkgName = opts.package ?? path31.basename(path31.dirname(changelogPath));
8906
9099
  const scope = opts.scope ?? "team";
8907
- const teamDir = path30.join(paths.memoriesDir, scope);
8908
- await mkdir13(teamDir, { recursive: true });
9100
+ const teamDir = path31.join(paths.memoriesDir, scope);
9101
+ await mkdir14(teamDir, { recursive: true });
8909
9102
  let saved = 0;
8910
9103
  for (const entry of entries) {
8911
9104
  const lines = [];
@@ -8927,7 +9120,7 @@ function registerMemoryImportChangelog(memory2) {
8927
9120
  lines.push("");
8928
9121
  }
8929
9122
  lines.push(
8930
- `**Source:** \`${path30.relative(root, changelogPath)}\`
9123
+ `**Source:** \`${path31.relative(root, changelogPath)}\`
8931
9124
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
8932
9125
  );
8933
9126
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -8942,11 +9135,11 @@ function registerMemoryImportChangelog(memory2) {
8942
9135
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
8943
9136
  `v${entry.version}`
8944
9137
  ],
8945
- paths: [path30.relative(root, changelogPath)],
9138
+ paths: [path31.relative(root, changelogPath)],
8946
9139
  topic: `changelog-${pkgName}-${entry.version}`
8947
9140
  });
8948
9141
  await writeFile23(
8949
- path30.join(teamDir, `${fm.id}.md`),
9142
+ path31.join(teamDir, `${fm.id}.md`),
8950
9143
  serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
8951
9144
  "utf8"
8952
9145
  );
@@ -8971,15 +9164,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
8971
9164
  // src/commands/memory-digest.ts
8972
9165
  import { existsSync as existsSync51 } from "fs";
8973
9166
  import { writeFile as writeFile24 } from "fs/promises";
8974
- import path31 from "path";
9167
+ import path33 from "path";
8975
9168
  import "commander";
8976
9169
  import {
8977
9170
  deriveConfidence as deriveConfidence12,
8978
- findProjectRoot as findProjectRoot30,
9171
+ findProjectRoot as findProjectRoot31,
8979
9172
  getUsage as getUsage17,
8980
9173
  loadMemoriesFromDir as loadMemoriesFromDir26,
8981
9174
  loadUsageIndex as loadUsageIndex21,
8982
- resolveHaivePaths as resolveHaivePaths27
9175
+ resolveHaivePaths as resolveHaivePaths28
8983
9176
  } from "@hiveai/core";
8984
9177
  var CONFIDENCE_EMOJI = {
8985
9178
  unverified: "\u2B1C",
@@ -8992,8 +9185,8 @@ function registerMemoryDigest(program2) {
8992
9185
  program2.command("digest").description(
8993
9186
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
8994
9187
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
8995
- const root = findProjectRoot30(opts.dir);
8996
- const paths = resolveHaivePaths27(root);
9188
+ const root = findProjectRoot31(opts.dir);
9189
+ const paths = resolveHaivePaths28(root);
8997
9190
  if (!existsSync51(paths.memoriesDir)) {
8998
9191
  ui.error("No .ai/memories found. Run `haive init` first.");
8999
9192
  process.exitCode = 1;
@@ -9066,7 +9259,7 @@ function registerMemoryDigest(program2) {
9066
9259
  );
9067
9260
  const digest = lines.join("\n");
9068
9261
  if (opts.out) {
9069
- const outPath = path31.resolve(process.cwd(), opts.out);
9262
+ const outPath = path33.resolve(process.cwd(), opts.out);
9070
9263
  await writeFile24(outPath, digest, "utf8");
9071
9264
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
9072
9265
  } else {
@@ -9076,20 +9269,20 @@ function registerMemoryDigest(program2) {
9076
9269
  }
9077
9270
 
9078
9271
  // src/commands/session-end.ts
9079
- import { writeFile as writeFile25, mkdir as mkdir14, readFile as readFile14, rm as rm2 } from "fs/promises";
9272
+ import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile14, rm as rm2 } from "fs/promises";
9080
9273
  import { existsSync as existsSync53 } from "fs";
9081
- import path33 from "path";
9274
+ import path34 from "path";
9082
9275
  import "commander";
9083
9276
  import {
9084
9277
  buildFrontmatter as buildFrontmatter10,
9085
- findProjectRoot as findProjectRoot31,
9278
+ findProjectRoot as findProjectRoot32,
9086
9279
  loadMemoriesFromDir as loadMemoriesFromDir27,
9087
9280
  memoryFilePath as memoryFilePath9,
9088
- resolveHaivePaths as resolveHaivePaths28,
9281
+ resolveHaivePaths as resolveHaivePaths29,
9089
9282
  serializeMemory as serializeMemory21
9090
9283
  } from "@hiveai/core";
9091
9284
  async function buildAutoRecap(paths) {
9092
- const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
9285
+ const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9093
9286
  if (!existsSync53(obsFile)) return null;
9094
9287
  const raw = await readFile14(obsFile, "utf8").catch(() => "");
9095
9288
  if (!raw.trim()) return null;
@@ -9169,8 +9362,8 @@ function registerSessionEnd(session2) {
9169
9362
  --next "Add integration tests for webhook signature validation"
9170
9363
  `
9171
9364
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9172
- const root = findProjectRoot31(opts.dir);
9173
- const paths = resolveHaivePaths28(root);
9365
+ const root = findProjectRoot32(opts.dir);
9366
+ const paths = resolveHaivePaths29(root);
9174
9367
  if (!existsSync53(paths.haiveDir)) {
9175
9368
  if (opts.auto || opts.quiet) return;
9176
9369
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
@@ -9203,14 +9396,14 @@ function registerSessionEnd(session2) {
9203
9396
  });
9204
9397
  const topic = recapTopic2(scope, opts.module);
9205
9398
  const filesTouched = parseCsv5(resolvedFiles);
9206
- const missingPaths = filesTouched.filter((p) => !existsSync53(path33.resolve(root, p)));
9399
+ const missingPaths = filesTouched.filter((p) => !existsSync53(path34.resolve(root, p)));
9207
9400
  if (missingPaths.length > 0 && !opts.quiet) {
9208
9401
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
9209
9402
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
9210
9403
  }
9211
9404
  const cleanupObservations = async () => {
9212
9405
  if (!opts.auto) return;
9213
- const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
9406
+ const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9214
9407
  if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
9215
9408
  });
9216
9409
  };
@@ -9234,7 +9427,7 @@ function registerSessionEnd(session2) {
9234
9427
  await cleanupObservations();
9235
9428
  if (!opts.quiet) {
9236
9429
  ui.success(`Session recap updated (revision #${revisionCount})`);
9237
- ui.info(`id=${fm.id} file=${path33.relative(root, topicMatch.filePath)}`);
9430
+ ui.info(`id=${fm.id} file=${path34.relative(root, topicMatch.filePath)}`);
9238
9431
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
9239
9432
  }
9240
9433
  return;
@@ -9251,12 +9444,12 @@ function registerSessionEnd(session2) {
9251
9444
  status: "validated"
9252
9445
  });
9253
9446
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9254
- await mkdir14(path33.dirname(file), { recursive: true });
9447
+ await mkdir15(path34.dirname(file), { recursive: true });
9255
9448
  await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
9256
9449
  await cleanupObservations();
9257
9450
  if (!opts.quiet) {
9258
9451
  ui.success(`Session recap created`);
9259
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path33.relative(root, file)}`);
9452
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path34.relative(root, file)}`);
9260
9453
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
9261
9454
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
9262
9455
  }
@@ -9270,13 +9463,13 @@ function parseCsv5(value) {
9270
9463
  // src/commands/snapshot.ts
9271
9464
  import { existsSync as existsSync54 } from "fs";
9272
9465
  import { readdir as readdir4 } from "fs/promises";
9273
- import path34 from "path";
9466
+ import path35 from "path";
9274
9467
  import "commander";
9275
9468
  import {
9276
9469
  diffContract,
9277
- findProjectRoot as findProjectRoot32,
9470
+ findProjectRoot as findProjectRoot33,
9278
9471
  loadConfig as loadConfig5,
9279
- resolveHaivePaths as resolveHaivePaths29,
9472
+ resolveHaivePaths as resolveHaivePaths30,
9280
9473
  snapshotContract
9281
9474
  } from "@hiveai/core";
9282
9475
  function registerSnapshot(program2) {
@@ -9301,15 +9494,15 @@ function registerSnapshot(program2) {
9301
9494
  "--format <format>",
9302
9495
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
9303
9496
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
9304
- const root = findProjectRoot32(opts.dir);
9305
- const paths = resolveHaivePaths29(root);
9497
+ const root = findProjectRoot33(opts.dir);
9498
+ const paths = resolveHaivePaths30(root);
9306
9499
  if (!existsSync54(paths.haiveDir)) {
9307
9500
  ui.error("No .ai/ found. Run `haive init` first.");
9308
9501
  process.exitCode = 1;
9309
9502
  return;
9310
9503
  }
9311
9504
  if (opts.list) {
9312
- const contractsDir = path34.join(paths.haiveDir, "contracts");
9505
+ const contractsDir = path35.join(paths.haiveDir, "contracts");
9313
9506
  if (!existsSync54(contractsDir)) {
9314
9507
  console.log(ui.dim("No contract snapshots found."));
9315
9508
  return;
@@ -9365,7 +9558,7 @@ function registerSnapshot(program2) {
9365
9558
  return;
9366
9559
  }
9367
9560
  const contractPath = opts.contract;
9368
- const name = opts.name ?? path34.basename(contractPath, path34.extname(contractPath));
9561
+ const name = opts.name ?? path35.basename(contractPath, path35.extname(contractPath));
9369
9562
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
9370
9563
  const contract = { name, path: contractPath, format };
9371
9564
  try {
@@ -9420,8 +9613,8 @@ async function runDiff(root, haiveDir, contract) {
9420
9613
  }
9421
9614
  }
9422
9615
  function detectFormat(filePath) {
9423
- const ext = path34.extname(filePath).toLowerCase();
9424
- const base = path34.basename(filePath).toLowerCase();
9616
+ const ext = path35.extname(filePath).toLowerCase();
9617
+ const base = path35.basename(filePath).toLowerCase();
9425
9618
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
9426
9619
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
9427
9620
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -9435,15 +9628,15 @@ function detectFormat(filePath) {
9435
9628
 
9436
9629
  // src/commands/hub.ts
9437
9630
  import { existsSync as existsSync55 } from "fs";
9438
- import { mkdir as mkdir15, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9439
- import path35 from "path";
9440
- import { spawnSync as spawnSync3 } from "child_process";
9631
+ import { mkdir as mkdir16, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9632
+ import path36 from "path";
9633
+ import { spawnSync as spawnSync4 } from "child_process";
9441
9634
  import "commander";
9442
9635
  import {
9443
- findProjectRoot as findProjectRoot33,
9636
+ findProjectRoot as findProjectRoot34,
9444
9637
  loadConfig as loadConfig6,
9445
9638
  loadMemoriesFromDir as loadMemoriesFromDir28,
9446
- resolveHaivePaths as resolveHaivePaths30,
9639
+ resolveHaivePaths as resolveHaivePaths31,
9447
9640
  saveConfig as saveConfig2,
9448
9641
  serializeMemory as serializeMemory23
9449
9642
  } from "@hiveai/core";
@@ -9455,21 +9648,21 @@ function registerHub(program2) {
9455
9648
  hub.command("init <hubPath>").description(
9456
9649
  "Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
9457
9650
  ).action(async (hubPath) => {
9458
- const absPath = path35.resolve(hubPath);
9459
- await mkdir15(absPath, { recursive: true });
9460
- const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9651
+ const absPath = path36.resolve(hubPath);
9652
+ await mkdir16(absPath, { recursive: true });
9653
+ const gitCheck = spawnSync4("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9461
9654
  if (gitCheck.status !== 0) {
9462
- const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
9655
+ const init = spawnSync4("git", ["init"], { cwd: absPath, encoding: "utf8" });
9463
9656
  if (init.status !== 0) {
9464
9657
  ui.error(`git init failed: ${init.stderr}`);
9465
9658
  process.exitCode = 1;
9466
9659
  return;
9467
9660
  }
9468
9661
  }
9469
- const sharedDir = path35.join(absPath, ".ai", "memories", "shared");
9470
- await mkdir15(sharedDir, { recursive: true });
9662
+ const sharedDir = path36.join(absPath, ".ai", "memories", "shared");
9663
+ await mkdir16(sharedDir, { recursive: true });
9471
9664
  await writeFile26(
9472
- path35.join(absPath, ".ai", "README.md"),
9665
+ path36.join(absPath, ".ai", "README.md"),
9473
9666
  `# hAIve Team Knowledge Hub
9474
9667
 
9475
9668
  This repo is a shared knowledge hub for hAIve.
@@ -9491,12 +9684,12 @@ haive hub pull # import into a project
9491
9684
  "utf8"
9492
9685
  );
9493
9686
  await writeFile26(
9494
- path35.join(absPath, ".gitignore"),
9687
+ path36.join(absPath, ".gitignore"),
9495
9688
  ".ai/.cache/\n.ai/memories/personal/\n",
9496
9689
  "utf8"
9497
9690
  );
9498
- spawnSync3("git", ["add", "."], { cwd: absPath });
9499
- spawnSync3("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
9691
+ spawnSync4("git", ["add", "."], { cwd: absPath });
9692
+ spawnSync4("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
9500
9693
  cwd: absPath,
9501
9694
  encoding: "utf8"
9502
9695
  });
@@ -9506,7 +9699,7 @@ haive hub pull # import into a project
9506
9699
  `
9507
9700
  Next steps:
9508
9701
  1. Add hubPath to your project's .ai/haive.config.json:
9509
- { "hubPath": "${path35.relative(process.cwd(), absPath)}" }
9702
+ { "hubPath": "${path36.relative(process.cwd(), absPath)}" }
9510
9703
  2. Run \`haive hub push\` to publish your shared memories
9511
9704
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
9512
9705
  `
@@ -9525,8 +9718,8 @@ Next steps:
9525
9718
  haive hub push --commit --message "feat: add payment API contract memories"
9526
9719
  `
9527
9720
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
9528
- const root = findProjectRoot33(opts.dir);
9529
- const paths = resolveHaivePaths30(root);
9721
+ const root = findProjectRoot34(opts.dir);
9722
+ const paths = resolveHaivePaths31(root);
9530
9723
  const config = await loadConfig6(paths);
9531
9724
  if (!config.hubPath) {
9532
9725
  ui.error(
@@ -9535,15 +9728,15 @@ Next steps:
9535
9728
  process.exitCode = 1;
9536
9729
  return;
9537
9730
  }
9538
- const hubRoot = path35.resolve(root, config.hubPath);
9731
+ const hubRoot = path36.resolve(root, config.hubPath);
9539
9732
  if (!existsSync55(hubRoot)) {
9540
9733
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
9541
9734
  process.exitCode = 1;
9542
9735
  return;
9543
9736
  }
9544
- const projectName = path35.basename(root);
9545
- const destDir = path35.join(hubRoot, ".ai", "memories", "shared", projectName);
9546
- await mkdir15(destDir, { recursive: true });
9737
+ const projectName = path36.basename(root);
9738
+ const destDir = path36.join(hubRoot, ".ai", "memories", "shared", projectName);
9739
+ await mkdir16(destDir, { recursive: true });
9547
9740
  const all = await loadMemoriesFromDir28(paths.memoriesDir);
9548
9741
  const shared = all.filter(
9549
9742
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
@@ -9561,7 +9754,7 @@ Next steps:
9561
9754
  for (const { memory: memory2 } of shared) {
9562
9755
  const fm = memory2.frontmatter;
9563
9756
  const fileName = `${fm.id}.md`;
9564
- const destPath = path35.join(destDir, fileName);
9757
+ const destPath = path36.join(destDir, fileName);
9565
9758
  await writeFile26(destPath, serializeMemory23(memory2), "utf8");
9566
9759
  pushed++;
9567
9760
  }
@@ -9569,10 +9762,10 @@ Next steps:
9569
9762
  console.log(ui.dim(` Location: ${destDir}`));
9570
9763
  if (opts.commit) {
9571
9764
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
9572
- spawnSync3("git", ["add", path35.join(".ai", "memories", "shared", projectName)], {
9765
+ spawnSync4("git", ["add", path36.join(".ai", "memories", "shared", projectName)], {
9573
9766
  cwd: hubRoot
9574
9767
  });
9575
- const commit = spawnSync3("git", ["commit", "-m", message], {
9768
+ const commit = spawnSync4("git", ["commit", "-m", message], {
9576
9769
  cwd: hubRoot,
9577
9770
  encoding: "utf8"
9578
9771
  });
@@ -9594,8 +9787,8 @@ Next steps:
9594
9787
  hub.command("pull").description(
9595
9788
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
9596
9789
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
9597
- const root = findProjectRoot33(opts.dir);
9598
- const paths = resolveHaivePaths30(root);
9790
+ const root = findProjectRoot34(opts.dir);
9791
+ const paths = resolveHaivePaths31(root);
9599
9792
  const config = await loadConfig6(paths);
9600
9793
  if (!config.hubPath) {
9601
9794
  ui.error(
@@ -9604,15 +9797,15 @@ Next steps:
9604
9797
  process.exitCode = 1;
9605
9798
  return;
9606
9799
  }
9607
- const hubRoot = path35.resolve(root, config.hubPath);
9608
- const hubSharedDir = path35.join(hubRoot, ".ai", "memories", "shared");
9800
+ const hubRoot = path36.resolve(root, config.hubPath);
9801
+ const hubSharedDir = path36.join(hubRoot, ".ai", "memories", "shared");
9609
9802
  if (!existsSync55(hubSharedDir)) {
9610
9803
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
9611
9804
  return;
9612
9805
  }
9613
- const projectName = path35.basename(root);
9614
- const { readdir: readdir5 } = await import("fs/promises");
9615
- const projectDirs = (await readdir5(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
9806
+ const projectName = path36.basename(root);
9807
+ const { readdir: readdir6 } = await import("fs/promises");
9808
+ const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
9616
9809
  if (projectDirs.length === 0) {
9617
9810
  console.log(ui.dim("No other projects have pushed to the hub yet."));
9618
9811
  return;
@@ -9620,16 +9813,16 @@ Next steps:
9620
9813
  let totalImported = 0;
9621
9814
  let totalUpdated = 0;
9622
9815
  for (const sourceName of projectDirs) {
9623
- const sourceDir = path35.join(hubSharedDir, sourceName);
9624
- const destDir = path35.join(paths.memoriesDir, "shared", sourceName);
9625
- await mkdir15(destDir, { recursive: true });
9626
- const sourceFiles = (await readdir5(sourceDir)).filter((f) => f.endsWith(".md"));
9816
+ const sourceDir = path36.join(hubSharedDir, sourceName);
9817
+ const destDir = path36.join(paths.memoriesDir, "shared", sourceName);
9818
+ await mkdir16(destDir, { recursive: true });
9819
+ const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
9627
9820
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
9628
9821
  const existingInDest = await loadDir(destDir);
9629
9822
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
9630
9823
  for (const file of sourceFiles) {
9631
- const srcPath = path35.join(sourceDir, file);
9632
- const destPath = path35.join(destDir, file);
9824
+ const srcPath = path36.join(sourceDir, file);
9825
+ const destPath = path36.join(destDir, file);
9633
9826
  const fileContent = await readFile15(srcPath, "utf8");
9634
9827
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
9635
9828
  if (!alreadyTagged) {
@@ -9653,21 +9846,21 @@ Next steps:
9653
9846
  );
9654
9847
  });
9655
9848
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
9656
- const root = findProjectRoot33(opts.dir);
9657
- const paths = resolveHaivePaths30(root);
9849
+ const root = findProjectRoot34(opts.dir);
9850
+ const paths = resolveHaivePaths31(root);
9658
9851
  const config = await loadConfig6(paths);
9659
9852
  console.log(ui.bold("Hub status"));
9660
9853
  console.log(
9661
9854
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
9662
9855
  );
9663
- const sharedDir = path35.join(paths.memoriesDir, "shared");
9856
+ const sharedDir = path36.join(paths.memoriesDir, "shared");
9664
9857
  if (existsSync55(sharedDir)) {
9665
- const { readdir: readdir5 } = await import("fs/promises");
9666
- const sources = (await readdir5(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
9858
+ const { readdir: readdir6 } = await import("fs/promises");
9859
+ const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
9667
9860
  console.log(`
9668
9861
  Imported from ${sources.length} source(s):`);
9669
9862
  for (const src of sources) {
9670
- const files = (await readdir5(path35.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
9863
+ const files = (await readdir6(path36.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
9671
9864
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
9672
9865
  }
9673
9866
  } else {
@@ -9691,16 +9884,16 @@ Next steps:
9691
9884
  // src/commands/stats.ts
9692
9885
  import "commander";
9693
9886
  import { existsSync as existsSync56 } from "fs";
9694
- import { mkdir as mkdir16, writeFile as writeFile27 } from "fs/promises";
9695
- import path36 from "path";
9887
+ import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
9888
+ import path37 from "path";
9696
9889
  import {
9697
9890
  aggregateUsage,
9698
- findProjectRoot as findProjectRoot34,
9891
+ findProjectRoot as findProjectRoot35,
9699
9892
  loadMemoriesFromDir as loadMemoriesFromDir29,
9700
9893
  loadUsageIndex as loadUsageIndex23,
9701
9894
  parseSince,
9702
9895
  readUsageEvents as readUsageEvents2,
9703
- resolveHaivePaths as resolveHaivePaths31,
9896
+ resolveHaivePaths as resolveHaivePaths32,
9704
9897
  usageLogSize
9705
9898
  } from "@hiveai/core";
9706
9899
  function registerStats(program2) {
@@ -9709,8 +9902,8 @@ function registerStats(program2) {
9709
9902
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
9710
9903
  void 0
9711
9904
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
9712
- const root = findProjectRoot34(opts.dir);
9713
- const paths = resolveHaivePaths31(root);
9905
+ const root = findProjectRoot35(opts.dir);
9906
+ const paths = resolveHaivePaths32(root);
9714
9907
  if (opts.exportReport) {
9715
9908
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
9716
9909
  return;
@@ -9764,7 +9957,7 @@ function registerStats(program2) {
9764
9957
  });
9765
9958
  }
9766
9959
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
9767
- const outAbs = path36.isAbsolute(outRelative) ? path36.resolve(outRelative) : path36.resolve(root, outRelative);
9960
+ const outAbs = path37.isAbsolute(outRelative) ? path37.resolve(outRelative) : path37.resolve(root, outRelative);
9768
9961
  const size = await usageLogSize(paths);
9769
9962
  let events = await readUsageEvents2(paths);
9770
9963
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
@@ -9799,7 +9992,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
9799
9992
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
9800
9993
  events = [];
9801
9994
  }
9802
- await mkdir16(path36.dirname(outAbs), { recursive: true });
9995
+ await mkdir17(path37.dirname(outAbs), { recursive: true });
9803
9996
  const payload = {
9804
9997
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
9805
9998
  project_root: root,
@@ -9863,13 +10056,13 @@ import { performance } from "perf_hooks";
9863
10056
  import "commander";
9864
10057
  import {
9865
10058
  estimateTokens as estimateTokens3,
9866
- findProjectRoot as findProjectRoot35,
9867
- resolveHaivePaths as resolveHaivePaths32
10059
+ findProjectRoot as findProjectRoot36,
10060
+ resolveHaivePaths as resolveHaivePaths33
9868
10061
  } from "@hiveai/core";
9869
10062
  function registerBench(program2) {
9870
10063
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
9871
- const root = findProjectRoot35(opts.dir);
9872
- const paths = resolveHaivePaths32(root);
10064
+ const root = findProjectRoot36(opts.dir);
10065
+ const paths = resolveHaivePaths33(root);
9873
10066
  const ctx = { paths };
9874
10067
  const task = opts.task ?? "audit dependencies for security risks";
9875
10068
  const scenarios = [
@@ -9987,20 +10180,165 @@ function summarize(name, t0, payload, notes) {
9987
10180
  };
9988
10181
  }
9989
10182
 
9990
- // src/commands/memory-suggest.ts
9991
- import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
10183
+ // src/commands/benchmark.ts
9992
10184
  import { existsSync as existsSync57 } from "fs";
9993
- import path37 from "path";
10185
+ import { readdir as readdir5, readFile as readFile16, writeFile as writeFile28 } from "fs/promises";
10186
+ import path38 from "path";
10187
+ import "commander";
10188
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot37 } from "@hiveai/core";
10189
+ function registerBenchmark(program2) {
10190
+ const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
10191
+ benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
10192
+ const root = resolveBenchmarkRoot(opts.dir);
10193
+ const rows = await collectRows(root);
10194
+ const summary = summarizeRows(rows);
10195
+ if (opts.json) {
10196
+ console.log(JSON.stringify({ root, summary, rows }, null, 2));
10197
+ return;
10198
+ }
10199
+ const markdown = renderMarkdown(root, summary, rows);
10200
+ if (opts.out) {
10201
+ const outFile = path38.isAbsolute(opts.out) ? opts.out : path38.join(root, opts.out);
10202
+ await writeFile28(outFile, markdown, "utf8");
10203
+ ui.success(`wrote ${path38.relative(process.cwd(), outFile)}`);
10204
+ return;
10205
+ }
10206
+ console.log(markdown);
10207
+ });
10208
+ benchmark.command("demo").description("Print the recommended protocol for running a hAIve vs plain agent benchmark.").action(() => {
10209
+ console.log([
10210
+ "# hAIve Agent Benchmark Demo",
10211
+ "",
10212
+ "1. Create paired fixtures: one `*-haive`, one `*-plain`.",
10213
+ "2. Put the same failing tests in both fixtures.",
10214
+ "3. Add precise `.ai/memories/team/*.md` policy memories only to the hAIve fixture.",
10215
+ "4. Run equal agents in parallel:",
10216
+ " - hAIve agents must run `haive briefing --files ... --task ...` first.",
10217
+ " - Plain agents must not read `.ai` or call hAIve.",
10218
+ "5. Require every agent to write `BENCHMARK_AGENT_REPORT.md`.",
10219
+ "6. Run `haive benchmark report --dir <benchmark-root> --out RESULTS.md`.",
10220
+ "",
10221
+ "Recommended metrics: pass rate, test iterations, files read, files changed, visible artifacts, decision quality, and token proxy."
10222
+ ].join("\n"));
10223
+ });
10224
+ }
10225
+ function resolveBenchmarkRoot(dir) {
10226
+ const candidate = dir ?? "benchmarks/agent-benchmark";
10227
+ if (path38.isAbsolute(candidate)) return candidate;
10228
+ const projectRoot = findProjectRoot37(process.cwd());
10229
+ return path38.join(projectRoot, candidate);
10230
+ }
10231
+ async function collectRows(root) {
10232
+ if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
10233
+ const entries = await readdir5(root, { withFileTypes: true });
10234
+ const rows = [];
10235
+ for (const entry of entries) {
10236
+ if (!entry.isDirectory()) continue;
10237
+ const fixtureDir = path38.join(root, entry.name);
10238
+ const reportFile = path38.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
10239
+ if (!existsSync57(reportFile)) continue;
10240
+ const report = await readFile16(reportFile, "utf8");
10241
+ rows.push(parseAgentReport(entry.name, report));
10242
+ }
10243
+ return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
10244
+ }
10245
+ function parseAgentReport(fixture, report) {
10246
+ const group = fixture.endsWith("-haive") ? "haive" : fixture.endsWith("-plain") ? "plain" : "unknown";
10247
+ return {
10248
+ fixture,
10249
+ group,
10250
+ commands: sectionBulletCount(report, "Commands"),
10251
+ files_read: sectionBulletCount(report, "Files Read"),
10252
+ files_modified: sectionBulletCount(report, "Files Modified"),
10253
+ test_iterations: countMatches(section(report, "Test Iterations"), /Iteration\s+\d+|^- /gim),
10254
+ terminal_failures: countMatches(section(report, "Terminal Errors"), /fail|error|not raised|exited with code 1/gi),
10255
+ decision_mentions: sectionBulletCount(report, "Key Decisions"),
10256
+ token_proxy: estimateTokens4(report),
10257
+ haive_impact: /hAIve Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report)
10258
+ };
10259
+ }
10260
+ function summarizeRows(rows) {
10261
+ const byGroup = (group) => rows.filter((r) => r.group === group);
10262
+ return {
10263
+ fixtures: rows.length,
10264
+ haive: summarizeGroup(byGroup("haive")),
10265
+ plain: summarizeGroup(byGroup("plain"))
10266
+ };
10267
+ }
10268
+ function summarizeGroup(rows) {
10269
+ const sum = (key) => rows.reduce((total, row) => total + Number(row[key] ?? 0), 0);
10270
+ return {
10271
+ fixtures: rows.length,
10272
+ commands: sum("commands"),
10273
+ files_read: sum("files_read"),
10274
+ files_modified: sum("files_modified"),
10275
+ test_iterations: sum("test_iterations"),
10276
+ terminal_failures: sum("terminal_failures"),
10277
+ decision_mentions: sum("decision_mentions"),
10278
+ token_proxy: sum("token_proxy"),
10279
+ haive_impact_count: rows.filter((r) => r.haive_impact).length
10280
+ };
10281
+ }
10282
+ function renderMarkdown(root, summary, rows) {
10283
+ const lines = [
10284
+ "# hAIve Agent Benchmark Report",
10285
+ "",
10286
+ `Benchmark root: \`${root}\``,
10287
+ "",
10288
+ "## Summary",
10289
+ "",
10290
+ "| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Token proxy | hAIve impact |",
10291
+ "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
10292
+ groupLine("hAIve", summary.haive),
10293
+ groupLine("Plain", summary.plain),
10294
+ "",
10295
+ "## Fixtures",
10296
+ "",
10297
+ "| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions | Token proxy | hAIve impact |",
10298
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
10299
+ ...rows.map(
10300
+ (row) => `| \`${row.fixture}\` | ${row.group} | ${row.commands} | ${row.files_read} | ${row.files_modified} | ${row.test_iterations} | ${row.terminal_failures} | ${row.decision_mentions} | ${row.token_proxy} | ${row.haive_impact ? "yes" : "no"} |`
10301
+ ),
10302
+ "",
10303
+ "## Reading",
10304
+ "",
10305
+ "The token proxy is estimated from the agent report size, not from private model billing data.",
10306
+ "Use this report to compare relative effort and decision quality, then pair it with final test results and a human review of the diffs.",
10307
+ ""
10308
+ ];
10309
+ return lines.join("\n");
10310
+ }
10311
+ function groupLine(label, group) {
10312
+ return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.token_proxy} | ${group.haive_impact_count} |`;
10313
+ }
10314
+ function sectionBulletCount(markdown, title) {
10315
+ return countMatches(section(markdown, title), /^- |^\d+\.\s/gm);
10316
+ }
10317
+ function section(markdown, title) {
10318
+ const re = new RegExp(`##\\s+[^\\n]*${escapeRegExp(title)}[^\\n]*\\n([\\s\\S]*?)(?=\\n##\\s+|$)`, "i");
10319
+ return re.exec(markdown)?.[1] ?? "";
10320
+ }
10321
+ function countMatches(text, re) {
10322
+ return [...text.matchAll(re)].length;
10323
+ }
10324
+ function escapeRegExp(value) {
10325
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10326
+ }
10327
+
10328
+ // src/commands/memory-suggest.ts
10329
+ import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
10330
+ import { existsSync as existsSync58 } from "fs";
10331
+ import path39 from "path";
9994
10332
  import "commander";
9995
10333
  import {
9996
10334
  aggregateUsage as aggregateUsage2,
9997
10335
  buildFrontmatter as buildFrontmatter11,
9998
- findProjectRoot as findProjectRoot36,
10336
+ findProjectRoot as findProjectRoot38,
9999
10337
  loadMemoriesFromDir as loadMemoriesFromDir30,
10000
10338
  memoryFilePath as memoryFilePath10,
10001
10339
  parseSince as parseSince2,
10002
10340
  readUsageEvents as readUsageEvents3,
10003
- resolveHaivePaths as resolveHaivePaths33,
10341
+ resolveHaivePaths as resolveHaivePaths34,
10004
10342
  serializeMemory as serializeMemory24
10005
10343
  } from "@hiveai/core";
10006
10344
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -10013,8 +10351,8 @@ function registerMemorySuggest(memory2) {
10013
10351
  memory2.command("suggest").description(
10014
10352
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
10015
10353
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10016
- const root = findProjectRoot36(opts.dir);
10017
- const paths = resolveHaivePaths33(root);
10354
+ const root = findProjectRoot38(opts.dir);
10355
+ const paths = resolveHaivePaths34(root);
10018
10356
  const events = await readUsageEvents3(paths);
10019
10357
  if (events.length === 0) {
10020
10358
  if (opts.json) {
@@ -10060,7 +10398,7 @@ function registerMemorySuggest(memory2) {
10060
10398
  }
10061
10399
  const created = [];
10062
10400
  const skipped = [];
10063
- const existing = existsSync57(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
10401
+ const existing = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
10064
10402
  for (const s of top) {
10065
10403
  const slug = slugify(s.query);
10066
10404
  if (!slug) {
@@ -10083,13 +10421,13 @@ function registerMemorySuggest(memory2) {
10083
10421
  fm.status = "draft";
10084
10422
  const body = renderTemplate(s);
10085
10423
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
10086
- await mkdir17(path37.dirname(file), { recursive: true });
10087
- if (existsSync57(file)) {
10088
- skipped.push({ query: s.query, reason: `file already exists at ${path37.relative(root, file)}` });
10424
+ await mkdir18(path39.dirname(file), { recursive: true });
10425
+ if (existsSync58(file)) {
10426
+ skipped.push({ query: s.query, reason: `file already exists at ${path39.relative(root, file)}` });
10089
10427
  continue;
10090
10428
  }
10091
- await writeFile28(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
10092
- created.push({ id: fm.id, file: path37.relative(root, file), query: s.query });
10429
+ await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
10430
+ created.push({ id: fm.id, file: path39.relative(root, file), query: s.query });
10093
10431
  }
10094
10432
  if (opts.json) {
10095
10433
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -10182,16 +10520,16 @@ function truncate2(text, max) {
10182
10520
  }
10183
10521
 
10184
10522
  // src/commands/memory-archive.ts
10185
- import { existsSync as existsSync58 } from "fs";
10186
- import { writeFile as writeFile29 } from "fs/promises";
10187
- import path38 from "path";
10523
+ import { existsSync as existsSync59 } from "fs";
10524
+ import { writeFile as writeFile30 } from "fs/promises";
10525
+ import path40 from "path";
10188
10526
  import "commander";
10189
10527
  import {
10190
- findProjectRoot as findProjectRoot37,
10528
+ findProjectRoot as findProjectRoot39,
10191
10529
  getUsage as getUsage18,
10192
10530
  loadMemoriesFromDir as loadMemoriesFromDir31,
10193
10531
  loadUsageIndex as loadUsageIndex24,
10194
- resolveHaivePaths as resolveHaivePaths34,
10532
+ resolveHaivePaths as resolveHaivePaths35,
10195
10533
  serializeMemory as serializeMemory25
10196
10534
  } from "@hiveai/core";
10197
10535
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -10199,9 +10537,9 @@ function registerMemoryArchive(memory2) {
10199
10537
  memory2.command("archive").description(
10200
10538
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
10201
10539
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10202
- const root = findProjectRoot37(opts.dir);
10203
- const paths = resolveHaivePaths34(root);
10204
- if (!existsSync58(paths.memoriesDir)) {
10540
+ const root = findProjectRoot39(opts.dir);
10541
+ const paths = resolveHaivePaths35(root);
10542
+ if (!existsSync59(paths.memoriesDir)) {
10205
10543
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10206
10544
  process.exitCode = 1;
10207
10545
  return;
@@ -10222,7 +10560,7 @@ function registerMemoryArchive(memory2) {
10222
10560
  if (typeFilter && fm.type !== typeFilter) continue;
10223
10561
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
10224
10562
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
10225
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync58(path38.join(paths.root, p)));
10563
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(path40.join(paths.root, p)));
10226
10564
  const isAnchorless = !hasAnyAnchor;
10227
10565
  if (!isAnchorless && !allPathsGone) continue;
10228
10566
  const u = getUsage18(usage, fm.id);
@@ -10270,7 +10608,7 @@ function registerMemoryArchive(memory2) {
10270
10608
  if (!found) continue;
10271
10609
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
10272
10610
  try {
10273
- await writeFile29(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
10611
+ await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
10274
10612
  archived++;
10275
10613
  } catch (err) {
10276
10614
  if (!opts.json) {
@@ -10296,31 +10634,31 @@ function parseDays(input) {
10296
10634
  }
10297
10635
 
10298
10636
  // src/commands/doctor.ts
10299
- import { existsSync as existsSync59 } from "fs";
10637
+ import { existsSync as existsSync60 } from "fs";
10300
10638
  import { stat } from "fs/promises";
10301
- import path39 from "path";
10639
+ import path41 from "path";
10302
10640
  import { execSync as execSync3 } from "child_process";
10303
10641
  import "commander";
10304
10642
  import {
10305
10643
  codeMapPath as codeMapPath2,
10306
- findProjectRoot as findProjectRoot38,
10644
+ findProjectRoot as findProjectRoot40,
10307
10645
  getUsage as getUsage19,
10308
10646
  loadCodeMap as loadCodeMap5,
10309
10647
  loadConfig as loadConfig7,
10310
10648
  loadMemoriesFromDir as loadMemoriesFromDir32,
10311
10649
  loadUsageIndex as loadUsageIndex25,
10312
10650
  readUsageEvents as readUsageEvents4,
10313
- resolveHaivePaths as resolveHaivePaths35
10651
+ resolveHaivePaths as resolveHaivePaths36
10314
10652
  } from "@hiveai/core";
10315
10653
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
10316
10654
  function registerDoctor(program2) {
10317
10655
  program2.command("doctor").description(
10318
10656
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
10319
10657
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10320
- const root = findProjectRoot38(opts.dir);
10321
- const paths = resolveHaivePaths35(root);
10658
+ const root = findProjectRoot40(opts.dir);
10659
+ const paths = resolveHaivePaths36(root);
10322
10660
  const findings = [];
10323
- if (!existsSync59(paths.haiveDir)) {
10661
+ if (!existsSync60(paths.haiveDir)) {
10324
10662
  findings.push({
10325
10663
  severity: "error",
10326
10664
  code: "not-initialized",
@@ -10329,7 +10667,7 @@ function registerDoctor(program2) {
10329
10667
  });
10330
10668
  return emit(findings, opts);
10331
10669
  }
10332
- if (!existsSync59(paths.projectContext)) {
10670
+ if (!existsSync60(paths.projectContext)) {
10333
10671
  findings.push({
10334
10672
  severity: "warn",
10335
10673
  code: "no-project-context",
@@ -10337,8 +10675,8 @@ function registerDoctor(program2) {
10337
10675
  fix: "haive init"
10338
10676
  });
10339
10677
  } else {
10340
- const { readFile: readFile17 } = await import("fs/promises");
10341
- const content = await readFile17(paths.projectContext, "utf8");
10678
+ const { readFile: readFile18 } = await import("fs/promises");
10679
+ const content = await readFile18(paths.projectContext, "utf8");
10342
10680
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10343
10681
  if (isTemplate) {
10344
10682
  findings.push({
@@ -10349,7 +10687,7 @@ function registerDoctor(program2) {
10349
10687
  });
10350
10688
  }
10351
10689
  }
10352
- const memories = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
10690
+ const memories = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
10353
10691
  const now = Date.now();
10354
10692
  if (memories.length === 0) {
10355
10693
  findings.push({
@@ -10460,12 +10798,12 @@ function registerDoctor(program2) {
10460
10798
  }
10461
10799
  const config = await loadConfig7(paths);
10462
10800
  if (config.enforcement?.requireBriefingFirst) {
10463
- const claudeSettings = path39.join(root, ".claude", "settings.local.json");
10801
+ const claudeSettings = path41.join(root, ".claude", "settings.local.json");
10464
10802
  let hasClaudeEnforcement = false;
10465
- if (existsSync59(claudeSettings)) {
10803
+ if (existsSync60(claudeSettings)) {
10466
10804
  try {
10467
- const { readFile: readFile17 } = await import("fs/promises");
10468
- const raw = await readFile17(claudeSettings, "utf8");
10805
+ const { readFile: readFile18 } = await import("fs/promises");
10806
+ const raw = await readFile18(claudeSettings, "utf8");
10469
10807
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
10470
10808
  } catch {
10471
10809
  hasClaudeEnforcement = false;
@@ -10494,7 +10832,7 @@ function registerDoctor(program2) {
10494
10832
  timeout: 3e3,
10495
10833
  stdio: ["ignore", "pipe", "ignore"]
10496
10834
  }).trim();
10497
- const cliVersion = "0.9.10";
10835
+ const cliVersion = "0.9.12";
10498
10836
  if (legacyRaw && legacyRaw !== cliVersion) {
10499
10837
  findings.push({
10500
10838
  severity: "warn",
@@ -10543,22 +10881,22 @@ function isSearchTool(name) {
10543
10881
  }
10544
10882
 
10545
10883
  // src/commands/playback.ts
10546
- import { existsSync as existsSync60 } from "fs";
10884
+ import { existsSync as existsSync61 } from "fs";
10547
10885
  import "commander";
10548
10886
  import {
10549
- findProjectRoot as findProjectRoot39,
10887
+ findProjectRoot as findProjectRoot41,
10550
10888
  loadMemoriesFromDir as loadMemoriesFromDir33,
10551
10889
  parseSince as parseSince3,
10552
10890
  readUsageEvents as readUsageEvents5,
10553
- resolveHaivePaths as resolveHaivePaths36
10891
+ resolveHaivePaths as resolveHaivePaths37
10554
10892
  } from "@hiveai/core";
10555
10893
  var MS_PER_MINUTE = 6e4;
10556
10894
  function registerPlayback(program2) {
10557
10895
  program2.command("playback").description(
10558
10896
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
10559
10897
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10560
- const root = findProjectRoot39(opts.dir);
10561
- const paths = resolveHaivePaths36(root);
10898
+ const root = findProjectRoot41(opts.dir);
10899
+ const paths = resolveHaivePaths37(root);
10562
10900
  const events = await readUsageEvents5(paths);
10563
10901
  if (events.length === 0) {
10564
10902
  if (opts.json) {
@@ -10573,7 +10911,7 @@ function registerPlayback(program2) {
10573
10911
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
10574
10912
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
10575
10913
  const sessions = bucketSessions(filtered, gapMs);
10576
- const all = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10914
+ const all = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10577
10915
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
10578
10916
  const enriched = sessions.map((s, i) => {
10579
10917
  const startMs = Date.parse(s.start);
@@ -10663,8 +11001,8 @@ function truncate3(text, max) {
10663
11001
  import { spawn as spawn4 } from "child_process";
10664
11002
  import "commander";
10665
11003
  import {
10666
- findProjectRoot as findProjectRoot40,
10667
- resolveHaivePaths as resolveHaivePaths37
11004
+ findProjectRoot as findProjectRoot42,
11005
+ resolveHaivePaths as resolveHaivePaths38
10668
11006
  } from "@hiveai/core";
10669
11007
  function registerPrecommit(program2) {
10670
11008
  program2.command("precommit").description(
@@ -10674,8 +11012,8 @@ function registerPrecommit(program2) {
10674
11012
  "'any' | 'high-confidence' (default) | 'never' (report only)",
10675
11013
  "high-confidence"
10676
11014
  ).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
10677
- const root = findProjectRoot40(opts.dir);
10678
- const paths = resolveHaivePaths37(root);
11015
+ const root = findProjectRoot42(opts.dir);
11016
+ const paths = resolveHaivePaths38(root);
10679
11017
  const ctx = { paths };
10680
11018
  let diff = "";
10681
11019
  let touchedPaths = opts.paths ?? [];
@@ -10767,12 +11105,12 @@ function runCommand3(cmd, args, cwd) {
10767
11105
  }
10768
11106
 
10769
11107
  // src/commands/welcome.ts
10770
- import { existsSync as existsSync61 } from "fs";
11108
+ import { existsSync as existsSync63 } from "fs";
10771
11109
  import "commander";
10772
11110
  import {
10773
- findProjectRoot as findProjectRoot41,
11111
+ findProjectRoot as findProjectRoot43,
10774
11112
  loadMemoriesFromDir as loadMemoriesFromDir34,
10775
- resolveHaivePaths as resolveHaivePaths38
11113
+ resolveHaivePaths as resolveHaivePaths39
10776
11114
  } from "@hiveai/core";
10777
11115
  var TYPE_RANK = {
10778
11116
  decision: 0,
@@ -10786,9 +11124,9 @@ function registerWelcome(program2) {
10786
11124
  program2.command("welcome").description(
10787
11125
  "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
10788
11126
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
10789
- const root = findProjectRoot41(opts.dir);
10790
- const paths = resolveHaivePaths38(root);
10791
- if (!existsSync61(paths.memoriesDir)) {
11127
+ const root = findProjectRoot43(opts.dir);
11128
+ const paths = resolveHaivePaths39(root);
11129
+ if (!existsSync63(paths.memoriesDir)) {
10792
11130
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
10793
11131
  process.exitCode = 1;
10794
11132
  return;
@@ -10829,17 +11167,17 @@ function registerWelcome(program2) {
10829
11167
  }
10830
11168
 
10831
11169
  // src/commands/memory-lint.ts
10832
- import { existsSync as existsSync63 } from "fs";
11170
+ import { existsSync as existsSync64 } from "fs";
10833
11171
  import "commander";
10834
11172
  import {
10835
- findProjectRoot as findProjectRoot42,
11173
+ findProjectRoot as findProjectRoot44,
10836
11174
  loadMemoriesFromDir as loadMemoriesFromDir35,
10837
- resolveHaivePaths as resolveHaivePaths39
11175
+ resolveHaivePaths as resolveHaivePaths40
10838
11176
  } from "@hiveai/core";
10839
11177
  async function lintMemoriesAsync(root) {
10840
- const paths = resolveHaivePaths39(root);
11178
+ const paths = resolveHaivePaths40(root);
10841
11179
  const out = [];
10842
- if (!existsSync63(paths.memoriesDir)) return out;
11180
+ if (!existsSync64(paths.memoriesDir)) return out;
10843
11181
  const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
10844
11182
  const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
10845
11183
  for (const { filePath, memory: memory2 } of loaded) {
@@ -10900,7 +11238,7 @@ function registerMemoryLint(parent) {
10900
11238
  parent.command("lint").description(
10901
11239
  "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
10902
11240
  ).option("--json", "emit findings as JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10903
- const root = findProjectRoot42(opts.dir);
11241
+ const root = findProjectRoot44(opts.dir);
10904
11242
  const findings = await lintMemoriesAsync(root);
10905
11243
  if (opts.json) {
10906
11244
  console.log(JSON.stringify({ findings_count: findings.length, findings }, null, 2));
@@ -10946,27 +11284,27 @@ function registerMemorySuggestTopic(memory2) {
10946
11284
  }
10947
11285
 
10948
11286
  // src/commands/resolve-project.ts
10949
- import path40 from "path";
11287
+ import path43 from "path";
10950
11288
  import "commander";
10951
11289
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
10952
11290
  function registerResolveProject(program2) {
10953
11291
  program2.command("resolve-project").description(
10954
11292
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
10955
11293
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
10956
- const info = resolveProjectInfo2({ cwd: path40.resolve(opts.dir) });
11294
+ const info = resolveProjectInfo2({ cwd: path43.resolve(opts.dir) });
10957
11295
  console.log(JSON.stringify({ ok: true, info }, null, 2));
10958
11296
  });
10959
11297
  }
10960
11298
 
10961
11299
  // src/commands/runtime-journal.ts
10962
- import { existsSync as existsSync64 } from "fs";
10963
- import path41 from "path";
11300
+ import { existsSync as existsSync65 } from "fs";
11301
+ import path44 from "path";
10964
11302
  import "commander";
10965
11303
  import {
10966
11304
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
10967
- findProjectRoot as findProjectRoot43,
11305
+ findProjectRoot as findProjectRoot45,
10968
11306
  readRuntimeJournalTail as readRuntimeJournalTail2,
10969
- resolveHaivePaths as resolveHaivePaths40
11307
+ resolveHaivePaths as resolveHaivePaths41
10970
11308
  } from "@hiveai/core";
10971
11309
  function registerRuntime(program2) {
10972
11310
  const runtime = program2.command("runtime").description(
@@ -10974,18 +11312,18 @@ function registerRuntime(program2) {
10974
11312
  );
10975
11313
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
10976
11314
  journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
10977
- const root = path41.resolve(opts.dir ?? process.cwd());
10978
- const paths = resolveHaivePaths40(findProjectRoot43(root));
11315
+ const root = path44.resolve(opts.dir ?? process.cwd());
11316
+ const paths = resolveHaivePaths41(findProjectRoot45(root));
10979
11317
  const raw = opts.kind ?? "note";
10980
11318
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
10981
11319
  await appendRuntimeJournalEntry3(paths, { kind, message });
10982
- ui.success(`Appended to ${path41.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
11320
+ ui.success(`Appended to ${path44.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
10983
11321
  });
10984
11322
  journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
10985
- const root = path41.resolve(opts.dir ?? process.cwd());
10986
- const paths = resolveHaivePaths40(findProjectRoot43(root));
11323
+ const root = path44.resolve(opts.dir ?? process.cwd());
11324
+ const paths = resolveHaivePaths41(findProjectRoot45(root));
10987
11325
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
10988
- if (!existsSync64(paths.haiveDir)) {
11326
+ if (!existsSync65(paths.haiveDir)) {
10989
11327
  ui.error("No .ai/ \u2014 run `haive init` first.");
10990
11328
  process.exitCode = 1;
10991
11329
  return;
@@ -11000,13 +11338,13 @@ function registerRuntime(program2) {
11000
11338
  }
11001
11339
 
11002
11340
  // src/commands/memory-timeline.ts
11003
- import { existsSync as existsSync65 } from "fs";
11004
- import path43 from "path";
11341
+ import { existsSync as existsSync66 } from "fs";
11342
+ import path45 from "path";
11005
11343
  import "commander";
11006
11344
  import {
11007
11345
  collectTimelineEntries as collectTimelineEntries2,
11008
- findProjectRoot as findProjectRoot44,
11009
- resolveHaivePaths as resolveHaivePaths41
11346
+ findProjectRoot as findProjectRoot46,
11347
+ resolveHaivePaths as resolveHaivePaths42
11010
11348
  } from "@hiveai/core";
11011
11349
  function registerMemoryTimeline(memory2) {
11012
11350
  memory2.command("timeline").description(
@@ -11017,9 +11355,9 @@ function registerMemoryTimeline(memory2) {
11017
11355
  process.exitCode = 1;
11018
11356
  return;
11019
11357
  }
11020
- const root = path43.resolve(opts.dir ?? process.cwd());
11021
- const paths = resolveHaivePaths41(findProjectRoot44(root));
11022
- if (!existsSync65(paths.memoriesDir)) {
11358
+ const root = path45.resolve(opts.dir ?? process.cwd());
11359
+ const paths = resolveHaivePaths42(findProjectRoot46(root));
11360
+ if (!existsSync66(paths.memoriesDir)) {
11023
11361
  ui.error("No memories \u2014 run `haive init`.");
11024
11362
  process.exitCode = 1;
11025
11363
  return;
@@ -11037,14 +11375,14 @@ function registerMemoryTimeline(memory2) {
11037
11375
  }
11038
11376
 
11039
11377
  // src/commands/memory-conflict-candidates.ts
11040
- import { existsSync as existsSync66 } from "fs";
11041
- import path44 from "path";
11378
+ import { existsSync as existsSync67 } from "fs";
11379
+ import path46 from "path";
11042
11380
  import "commander";
11043
11381
  import {
11044
11382
  findLexicalConflictPairs as findLexicalConflictPairs2,
11045
11383
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
11046
- findProjectRoot as findProjectRoot45,
11047
- resolveHaivePaths as resolveHaivePaths42
11384
+ findProjectRoot as findProjectRoot47,
11385
+ resolveHaivePaths as resolveHaivePaths43
11048
11386
  } from "@hiveai/core";
11049
11387
  function parseTypes(csv) {
11050
11388
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -11060,9 +11398,9 @@ function registerMemoryConflictCandidates(memory2) {
11060
11398
  "decision,architecture,convention,gotcha (lexical scan)",
11061
11399
  "decision,architecture"
11062
11400
  ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
11063
- const root = path44.resolve(opts.dir ?? process.cwd());
11064
- const paths = resolveHaivePaths42(findProjectRoot45(root));
11065
- if (!existsSync66(paths.memoriesDir)) {
11401
+ const root = path46.resolve(opts.dir ?? process.cwd());
11402
+ const paths = resolveHaivePaths43(findProjectRoot47(root));
11403
+ if (!existsSync67(paths.memoriesDir)) {
11066
11404
  ui.error("No memories \u2014 run `haive init`.");
11067
11405
  process.exitCode = 1;
11068
11406
  return;
@@ -11098,18 +11436,20 @@ function registerMemoryConflictCandidates(memory2) {
11098
11436
 
11099
11437
  // src/commands/enforce.ts
11100
11438
  import { spawn as spawn5 } from "child_process";
11101
- import { existsSync as existsSync67 } from "fs";
11102
- import { chmod as chmod2, mkdir as mkdir18, readFile as readFile16, writeFile as writeFile30 } from "fs/promises";
11103
- import path45 from "path";
11439
+ import { existsSync as existsSync68 } from "fs";
11440
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile17, rm as rm3, writeFile as writeFile31 } from "fs/promises";
11441
+ import path47 from "path";
11104
11442
  import "commander";
11105
11443
  import {
11106
- findProjectRoot as findProjectRoot46,
11444
+ findProjectRoot as findProjectRoot48,
11107
11445
  hasRecentBriefingMarker,
11108
11446
  isFreshIsoDate,
11109
11447
  loadConfig as loadConfig8,
11110
11448
  loadMemoriesFromDir as loadMemoriesFromDir36,
11449
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
11450
+ readRecentBriefingMarker,
11111
11451
  resolveBriefingBudget as resolveBriefingBudget3,
11112
- resolveHaivePaths as resolveHaivePaths43,
11452
+ resolveHaivePaths as resolveHaivePaths44,
11113
11453
  saveConfig as saveConfig3,
11114
11454
  SESSION_RECAP_TTL_MS,
11115
11455
  verifyAnchor as verifyAnchor4,
@@ -11122,9 +11462,9 @@ function registerEnforce(program2) {
11122
11462
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
11123
11463
  );
11124
11464
  enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
11125
- const root = findProjectRoot46(opts.dir);
11126
- const paths = resolveHaivePaths43(root);
11127
- await mkdir18(paths.haiveDir, { recursive: true });
11465
+ const root = findProjectRoot48(opts.dir);
11466
+ const paths = resolveHaivePaths44(root);
11467
+ await mkdir19(paths.haiveDir, { recursive: true });
11128
11468
  const current = await loadConfig8(paths);
11129
11469
  await saveConfig3(paths, {
11130
11470
  ...current,
@@ -11135,7 +11475,11 @@ function registerEnforce(program2) {
11135
11475
  requireSessionRecap: true,
11136
11476
  requireMemoryVerify: true,
11137
11477
  blockStaleDecisionChanges: true,
11138
- toolProfile: "enforcement"
11478
+ requireDecisionCoverage: true,
11479
+ scoreThreshold: 85,
11480
+ cleanupGeneratedArtifacts: true,
11481
+ toolProfile: "enforcement",
11482
+ policyPacks: ["architecture", "gotchas", "security", "domain", "release"]
11139
11483
  }
11140
11484
  });
11141
11485
  ui.success("hAIve strict enforcement enabled in .ai/haive.config.json");
@@ -11144,7 +11488,7 @@ function registerEnforce(program2) {
11144
11488
  if (opts.claude !== false) {
11145
11489
  try {
11146
11490
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
11147
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path45.relative(root, result.settingsPath)})`);
11491
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path47.relative(root, result.settingsPath)})`);
11148
11492
  } catch (err) {
11149
11493
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
11150
11494
  }
@@ -11162,6 +11506,23 @@ function registerEnforce(program2) {
11162
11506
  printReport(report, Boolean(opts.json));
11163
11507
  if (report.should_block) process.exit(2);
11164
11508
  });
11509
+ enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
11510
+ const root = findProjectRoot48(opts.dir);
11511
+ const paths = resolveHaivePaths44(root);
11512
+ const targets = [
11513
+ path47.join(paths.haiveDir, ".cache"),
11514
+ path47.join(paths.haiveDir, ".runtime")
11515
+ ];
11516
+ for (const target of targets) {
11517
+ if (!existsSync68(target)) continue;
11518
+ const rel = path47.relative(root, target);
11519
+ if (opts.dryRun) ui.info(`would remove ${rel}`);
11520
+ else {
11521
+ await rm3(target, { recursive: true, force: true });
11522
+ ui.success(`removed ${rel}`);
11523
+ }
11524
+ }
11525
+ });
11165
11526
  enforce.command("ci").description("CI entrypoint: fail if the repository violates hAIve enforcement policy.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
11166
11527
  const report = await buildEnforcementReport(opts.dir, "ci");
11167
11528
  printReport(report, Boolean(opts.json));
@@ -11171,16 +11532,11 @@ function registerEnforce(program2) {
11171
11532
  const payload = await readHookPayload();
11172
11533
  const root = resolveRoot(opts.dir, payload);
11173
11534
  if (!root) return;
11174
- const paths = resolveHaivePaths43(root);
11175
- if (!existsSync67(paths.haiveDir)) return;
11176
- await mkdir18(paths.runtimeDir, { recursive: true });
11535
+ const paths = resolveHaivePaths44(root);
11536
+ if (!existsSync68(paths.haiveDir)) return;
11537
+ await mkdir19(paths.runtimeDir, { recursive: true });
11177
11538
  const sessionId = opts.sessionId ?? payload.session_id;
11178
11539
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
11179
- await writeBriefingMarker2(paths, {
11180
- sessionId,
11181
- task,
11182
- source: opts.source ?? "claude-session-start"
11183
- });
11184
11540
  const budget = resolveBriefingBudget3("quick", {
11185
11541
  max_tokens: 2500,
11186
11542
  max_memories: 5,
@@ -11204,6 +11560,12 @@ function registerEnforce(program2) {
11204
11560
  },
11205
11561
  { paths }
11206
11562
  );
11563
+ await writeBriefingMarker2(paths, {
11564
+ sessionId,
11565
+ task,
11566
+ source: opts.source ?? "claude-session-start",
11567
+ memoryIds: briefing.memories.map((m) => m.id)
11568
+ });
11207
11569
  console.log("hAIve briefing loaded. Agents must consult this before editing.");
11208
11570
  if (briefing.last_session) {
11209
11571
  console.log(`
@@ -11232,8 +11594,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
11232
11594
  const payload = await readHookPayload();
11233
11595
  const root = resolveRoot(opts.dir, payload);
11234
11596
  if (!root) return;
11235
- const paths = resolveHaivePaths43(root);
11236
- if (!existsSync67(paths.haiveDir)) return;
11597
+ const paths = resolveHaivePaths44(root);
11598
+ if (!existsSync68(paths.haiveDir)) return;
11237
11599
  if (!isWriteLikeTool(payload)) return;
11238
11600
  const ok = await hasRecentBriefingMarker(paths, payload.session_id);
11239
11601
  if (ok) return;
@@ -11255,9 +11617,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
11255
11617
  });
11256
11618
  }
11257
11619
  async function runWithEnforcement(command, args, opts) {
11258
- const root = findProjectRoot46(opts.dir);
11259
- const paths = resolveHaivePaths43(root);
11260
- if (!existsSync67(paths.haiveDir)) {
11620
+ const root = findProjectRoot48(opts.dir);
11621
+ const paths = resolveHaivePaths44(root);
11622
+ if (!existsSync68(paths.haiveDir)) {
11261
11623
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11262
11624
  process.exit(1);
11263
11625
  }
@@ -11276,7 +11638,7 @@ async function runWithEnforcement(command, args, opts) {
11276
11638
  process.exit(2);
11277
11639
  }
11278
11640
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
11279
- ui.info(`Briefing written to ${path45.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
11641
+ ui.info(`Briefing written to ${path47.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
11280
11642
  const child = spawn5(command, args, {
11281
11643
  cwd: root,
11282
11644
  stdio: "inherit",
@@ -11319,9 +11681,15 @@ async function writeWrapperBriefing(paths, sessionId, task) {
11319
11681
  min_semantic_score: 0.25,
11320
11682
  budget_preset: "quick"
11321
11683
  }, { paths });
11322
- const dir = path45.join(paths.runtimeDir, "enforcement", "briefings");
11323
- await mkdir18(dir, { recursive: true });
11324
- const file = path45.join(dir, `${sessionId}.md`);
11684
+ await writeBriefingMarker2(paths, {
11685
+ sessionId,
11686
+ task,
11687
+ source: "haive-run",
11688
+ memoryIds: briefing.memories.map((m) => m.id)
11689
+ });
11690
+ const dir = path47.join(paths.runtimeDir, "enforcement", "briefings");
11691
+ await mkdir19(dir, { recursive: true });
11692
+ const file = path47.join(dir, `${sessionId}.md`);
11325
11693
  const parts = [
11326
11694
  "# hAIve Briefing",
11327
11695
  "",
@@ -11339,13 +11707,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
11339
11707
  if (briefing.setup_warnings.length > 0) {
11340
11708
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
11341
11709
  }
11342
- await writeFile30(file, parts.join("\n") + "\n", "utf8");
11710
+ await writeFile31(file, parts.join("\n") + "\n", "utf8");
11343
11711
  return file;
11344
11712
  }
11345
11713
  async function buildEnforcementReport(dir, stage, sessionId) {
11346
- const root = findProjectRoot46(dir);
11347
- const paths = resolveHaivePaths43(root);
11348
- const initialized = existsSync67(paths.haiveDir);
11714
+ const root = findProjectRoot48(dir);
11715
+ const paths = resolveHaivePaths44(root);
11716
+ const initialized = existsSync68(paths.haiveDir);
11349
11717
  const config = initialized ? await loadConfig8(paths) : {};
11350
11718
  const mode = config.enforcement?.mode ?? "strict";
11351
11719
  const findings = [];
@@ -11354,12 +11722,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
11354
11722
  root,
11355
11723
  initialized,
11356
11724
  mode,
11725
+ score: buildScore([], config.enforcement?.scoreThreshold),
11357
11726
  should_block: true,
11358
11727
  findings: [{
11359
11728
  severity: "error",
11360
11729
  code: "not-initialized",
11361
11730
  message: "This repository is not initialized with hAIve.",
11362
- fix: "Run `haive init` or `haive enforce install`."
11731
+ fix: "Run `haive init` or `haive enforce install`.",
11732
+ impact: 100
11363
11733
  }]
11364
11734
  };
11365
11735
  }
@@ -11368,6 +11738,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
11368
11738
  root,
11369
11739
  initialized,
11370
11740
  mode,
11741
+ score: buildScore([], config.enforcement?.scoreThreshold),
11371
11742
  should_block: false,
11372
11743
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
11373
11744
  };
@@ -11378,42 +11749,67 @@ async function buildEnforcementReport(dir, stage, sessionId) {
11378
11749
  severity: "error",
11379
11750
  code: "briefing-missing",
11380
11751
  message: "No recent hAIve briefing marker was found for this workflow.",
11381
- fix: 'Run `haive briefing --task "..."`, `haive enforce session-start`, or wrap the agent with `haive run -- <agent>`.'
11752
+ fix: 'Run `haive briefing --task "..."`, `haive enforce session-start`, or wrap the agent with `haive run -- <agent>`.',
11753
+ impact: 35
11382
11754
  });
11383
11755
  }
11384
11756
  if (config.enforcement?.requireSessionRecap !== false && (stage === "pre-push" || stage === "ci")) {
11385
11757
  const hasRecap = await hasRecentSessionRecap(paths);
11386
- findings.push(hasRecap ? { severity: "ok", code: "session-recap-present", message: "A recent session_recap memory exists." } : {
11758
+ findings.push(hasRecap ? { severity: "ok", code: "session-recap-present", message: "A recent session_recap memory exists." } : stage === "ci" ? {
11759
+ severity: "warn",
11760
+ code: "session-recap-missing",
11761
+ message: "No recent session_recap memory was found. CI reports this as a warning because personal recaps are usually not committed.",
11762
+ fix: "Run `haive session end --scope team --goal ... --accomplished ...` if you want a team recap visible in CI.",
11763
+ impact: 5
11764
+ } : {
11387
11765
  severity: "error",
11388
11766
  code: "session-recap-missing",
11389
11767
  message: "No recent session_recap memory was found.",
11390
- fix: "Run `haive session end --goal ... --accomplished ...` before pushing."
11768
+ fix: "Run `haive session end --goal ... --accomplished ...` before pushing.",
11769
+ impact: 20
11391
11770
  });
11392
11771
  }
11393
11772
  if (config.enforcement?.requireMemoryVerify !== false) {
11394
11773
  findings.push(...await verifyMemoryPolicy(paths, config));
11395
11774
  }
11775
+ if (config.enforcement?.requireDecisionCoverage !== false) {
11776
+ findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
11777
+ }
11396
11778
  if (stage === "pre-commit" || stage === "ci") {
11397
11779
  findings.push(...await runPrecommitPolicy(paths));
11398
11780
  }
11781
+ if (config.enforcement?.cleanupGeneratedArtifacts !== false) {
11782
+ findings.push(...await findGeneratedArtifacts(paths));
11783
+ }
11784
+ const score = buildScore(findings, config.enforcement?.scoreThreshold);
11785
+ if (score.score < score.threshold) {
11786
+ findings.push({
11787
+ severity: "error",
11788
+ code: "enforcement-score-below-threshold",
11789
+ message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}%.`,
11790
+ fix: "Load the relevant briefing, address policy findings, then rerun `haive enforce check`.",
11791
+ impact: 0
11792
+ });
11793
+ }
11399
11794
  const hasErrors = findings.some((f) => f.severity === "error");
11400
11795
  return {
11401
11796
  root,
11402
11797
  initialized,
11403
11798
  mode,
11799
+ score: buildScore(findings, config.enforcement?.scoreThreshold),
11404
11800
  should_block: mode === "strict" && hasErrors,
11405
11801
  findings
11406
11802
  };
11407
11803
  }
11408
11804
  async function hasRecentSessionRecap(paths) {
11409
- if (!existsSync67(paths.memoriesDir)) return false;
11805
+ if (!existsSync68(paths.memoriesDir)) return false;
11410
11806
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
11411
11807
  return all.some(
11412
11808
  ({ memory: memory2 }) => memory2.frontmatter.type === "session_recap" && memory2.frontmatter.status !== "rejected" && isFreshIsoDate(memory2.frontmatter.created_at, SESSION_RECAP_TTL_MS)
11413
11809
  );
11414
11810
  }
11415
11811
  async function verifyMemoryPolicy(paths, config) {
11416
- if (!existsSync67(paths.memoriesDir)) return [];
11812
+ if (!existsSync68(paths.memoriesDir)) return [];
11417
11813
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
11418
11814
  const findings = [];
11419
11815
  const staleImportant = [];
@@ -11444,11 +11840,51 @@ async function verifyMemoryPolicy(paths, config) {
11444
11840
  severity: "error",
11445
11841
  code: "stale-important-memories",
11446
11842
  message: `${staleImportant.length} important anchored memories are stale: ${staleImportant.slice(0, 8).join(", ")}`,
11447
- fix: "Run `haive memory verify --update`, then update or delete stale decisions/gotchas before merging."
11843
+ fix: "Run `haive memory verify --update`, then update or delete stale decisions/gotchas before merging.",
11844
+ impact: 40
11448
11845
  });
11449
11846
  }
11450
11847
  return findings;
11451
11848
  }
11849
+ async function verifyDecisionCoverage(paths, stage, sessionId) {
11850
+ if (!existsSync68(paths.memoriesDir)) return [];
11851
+ const changedFiles = await getChangedFiles(paths.root, stage);
11852
+ if (changedFiles.length === 0) {
11853
+ return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
11854
+ }
11855
+ const all = await loadMemoriesFromDir36(paths.memoriesDir);
11856
+ const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
11857
+ const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
11858
+ const fm = memory2.frontmatter;
11859
+ if (!policyTypes.has(fm.type)) return false;
11860
+ if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
11861
+ return memoryMatchesAnchorPaths6(memory2, changedFiles);
11862
+ });
11863
+ if (relevant.length === 0) {
11864
+ return [{
11865
+ severity: "ok",
11866
+ code: "decision-coverage-none-required",
11867
+ message: `No anchored decisions or policies matched ${changedFiles.length} changed file(s).`
11868
+ }];
11869
+ }
11870
+ const marker = await readRecentBriefingMarker(paths, sessionId);
11871
+ const consulted = new Set(marker?.memory_ids ?? []);
11872
+ const missing = relevant.filter((memory2) => !consulted.has(memory2.frontmatter.id));
11873
+ if (missing.length === 0) {
11874
+ return [{
11875
+ severity: "ok",
11876
+ code: "decision-coverage-pass",
11877
+ message: `Relevant decisions/policies were surfaced for ${changedFiles.length} changed file(s): ${relevant.length}/${relevant.length}.`
11878
+ }];
11879
+ }
11880
+ return [{
11881
+ severity: stage === "local" ? "warn" : "error",
11882
+ code: "decision-coverage-missing",
11883
+ message: `${missing.length}/${relevant.length} relevant anchored decisions/policies were not present in the latest briefing: ${missing.slice(0, 6).map((m) => m.frontmatter.id).join(", ")}`,
11884
+ fix: `Run \`haive briefing --files "${changedFiles.slice(0, 10).join(",")}" --task "..."\` before committing.`,
11885
+ impact: Math.min(35, 10 + missing.length * 5)
11886
+ }];
11887
+ }
11452
11888
  async function runPrecommitPolicy(paths) {
11453
11889
  const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
11454
11890
  const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
@@ -11473,16 +11909,66 @@ async function runPrecommitPolicy(paths) {
11473
11909
  severity: "error",
11474
11910
  code: "precommit-policy-block",
11475
11911
  message: `Pre-commit policy matched ${result.summary.anti_patterns} anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
11476
- fix: "Review the hAIve warnings, then update the code or the relevant memories."
11912
+ fix: "Review the hAIve warnings, then update the code or the relevant memories.",
11913
+ impact: 45
11914
+ }];
11915
+ }
11916
+ async function findGeneratedArtifacts(paths) {
11917
+ const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
11918
+ const generated = dirty.split("\n").map((line) => line.trim()).filter(Boolean).filter(
11919
+ (line) => line.includes(".ai/.cache/") || line.includes(".ai/.runtime/") || line.includes("__pycache__/") || line.endsWith(".pyc")
11920
+ );
11921
+ if (generated.length === 0) {
11922
+ return [{ severity: "ok", code: "generated-artifacts-clean", message: "No generated runtime/cache artifacts are visible to git." }];
11923
+ }
11924
+ return [{
11925
+ severity: "warn",
11926
+ code: "generated-artifacts-visible",
11927
+ message: `${generated.length} generated artifact(s) are visible in git status.`,
11928
+ fix: "Run `haive enforce cleanup`, update .gitignore, or remove test/runtime outputs before committing.",
11929
+ impact: 10
11477
11930
  }];
11478
11931
  }
11932
+ async function getChangedFiles(root, stage) {
11933
+ const commands = stage === "pre-commit" ? [["diff", "--cached", "--name-only"]] : [
11934
+ ["diff", "--cached", "--name-only"],
11935
+ ["diff", "--name-only"]
11936
+ ];
11937
+ const files = /* @__PURE__ */ new Set();
11938
+ for (const args of commands) {
11939
+ const out = await runCommand4("git", args, root).catch(() => "");
11940
+ for (const line of out.split("\n")) {
11941
+ const file = line.trim();
11942
+ if (file) files.add(file);
11943
+ }
11944
+ }
11945
+ return [...files].filter((file) => !file.startsWith(".ai/.runtime/") && !file.startsWith(".ai/.cache/"));
11946
+ }
11947
+ function buildScore(findings, threshold = 80) {
11948
+ const checks = {
11949
+ total: findings.length,
11950
+ ok: findings.filter((f) => f.severity === "ok").length,
11951
+ warn: findings.filter((f) => f.severity === "warn").length,
11952
+ error: findings.filter((f) => f.severity === "error").length
11953
+ };
11954
+ const penalty = findings.reduce((sum, f) => {
11955
+ if (f.severity === "error") return sum + (f.impact ?? 25);
11956
+ if (f.severity === "warn") return sum + (f.impact ?? 8);
11957
+ return sum;
11958
+ }, 0);
11959
+ return {
11960
+ score: Math.max(0, Math.min(100, 100 - penalty)),
11961
+ threshold,
11962
+ checks
11963
+ };
11964
+ }
11479
11965
  async function installGitEnforcement(root) {
11480
- const hooksDir = path45.join(root, ".git", "hooks");
11481
- if (!existsSync67(path45.join(root, ".git"))) {
11966
+ const hooksDir = path47.join(root, ".git", "hooks");
11967
+ if (!existsSync68(path47.join(root, ".git"))) {
11482
11968
  ui.warn("No .git directory found; git enforcement hooks skipped.");
11483
11969
  return;
11484
11970
  }
11485
- await mkdir18(hooksDir, { recursive: true });
11971
+ await mkdir19(hooksDir, { recursive: true });
11486
11972
  const hooks = [
11487
11973
  {
11488
11974
  name: "pre-commit",
@@ -11500,31 +11986,31 @@ haive enforce check --stage pre-push --dir . || exit $?
11500
11986
  }
11501
11987
  ];
11502
11988
  for (const hook of hooks) {
11503
- const file = path45.join(hooksDir, hook.name);
11504
- if (existsSync67(file)) {
11505
- const current = await readFile16(file, "utf8").catch(() => "");
11989
+ const file = path47.join(hooksDir, hook.name);
11990
+ if (existsSync68(file)) {
11991
+ const current = await readFile17(file, "utf8").catch(() => "");
11506
11992
  if (current.includes(ENFORCE_HOOK_MARKER)) {
11507
- await writeFile30(file, hook.body, "utf8");
11993
+ await writeFile31(file, hook.body, "utf8");
11508
11994
  } else {
11509
- await writeFile30(file, `${current.trimEnd()}
11995
+ await writeFile31(file, `${current.trimEnd()}
11510
11996
 
11511
11997
  ${hook.body}`, "utf8");
11512
11998
  }
11513
11999
  } else {
11514
- await writeFile30(file, hook.body, "utf8");
12000
+ await writeFile31(file, hook.body, "utf8");
11515
12001
  }
11516
12002
  await chmod2(file, 493);
11517
12003
  }
11518
12004
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
11519
12005
  }
11520
12006
  async function installCiEnforcement(root) {
11521
- const workflowPath = path45.join(root, ".github", "workflows", "haive-enforcement.yml");
11522
- await mkdir18(path45.dirname(workflowPath), { recursive: true });
11523
- if (existsSync67(workflowPath)) {
12007
+ const workflowPath = path47.join(root, ".github", "workflows", "haive-enforcement.yml");
12008
+ await mkdir19(path47.dirname(workflowPath), { recursive: true });
12009
+ if (existsSync68(workflowPath)) {
11524
12010
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
11525
12011
  return;
11526
12012
  }
11527
- await writeFile30(workflowPath, `name: haive-enforcement
12013
+ await writeFile31(workflowPath, `name: haive-enforcement
11528
12014
 
11529
12015
  on:
11530
12016
  pull_request:
@@ -11548,7 +12034,7 @@ jobs:
11548
12034
  - name: Enforce hAIve policy
11549
12035
  run: haive enforce ci
11550
12036
  `, "utf8");
11551
- ui.success(`Created ${path45.relative(root, workflowPath)}`);
12037
+ ui.success(`Created ${path47.relative(root, workflowPath)}`);
11552
12038
  }
11553
12039
  function printReport(report, json) {
11554
12040
  if (json) {
@@ -11557,6 +12043,7 @@ function printReport(report, json) {
11557
12043
  }
11558
12044
  console.log(ui.bold(`hAIve enforcement \u2014 ${report.mode}`));
11559
12045
  console.log(ui.dim(` root: ${report.root}`));
12046
+ console.log(ui.dim(` score: ${report.score.score}% / threshold ${report.score.threshold}%`));
11560
12047
  for (const finding of report.findings) {
11561
12048
  const marker = finding.severity === "error" ? ui.red("\u2717") : finding.severity === "warn" ? ui.yellow("\u26A0") : finding.severity === "ok" ? ui.green("\u2713") : ui.dim("\u2022");
11562
12049
  console.log(`${marker} ${finding.code}: ${finding.message}`);
@@ -11576,7 +12063,7 @@ async function readHookPayload() {
11576
12063
  }
11577
12064
  function resolveRoot(dir, payload) {
11578
12065
  try {
11579
- return findProjectRoot46(dir ?? payload.cwd);
12066
+ return findProjectRoot48(dir ?? payload.cwd);
11580
12067
  } catch {
11581
12068
  return null;
11582
12069
  }
@@ -11645,14 +12132,15 @@ function registerRun(program2) {
11645
12132
  }
11646
12133
 
11647
12134
  // src/index.ts
11648
- var program = new Command49();
11649
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.10");
12135
+ var program = new Command51();
12136
+ program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.12");
11650
12137
  registerInit(program);
11651
12138
  registerWelcome(program);
11652
12139
  registerResolveProject(program);
11653
12140
  registerRuntime(program);
11654
12141
  registerEnforce(program);
11655
12142
  registerRun(program);
12143
+ registerAgent(program);
11656
12144
  registerMcp(program);
11657
12145
  registerBriefing(program);
11658
12146
  registerTui(program);
@@ -11694,6 +12182,7 @@ registerSnapshot(program);
11694
12182
  registerHub(program);
11695
12183
  registerStats(program);
11696
12184
  registerBench(program);
12185
+ registerBenchmark(program);
11697
12186
  registerDoctor(program);
11698
12187
  registerPlayback(program);
11699
12188
  registerPrecommit(program);