@hiveai/cli 0.21.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command as Command64 } from "commander";
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync as existsSync3 } from "fs";
8
8
  import { mkdir, readFile as readFile2 } from "fs/promises";
9
- import path3 from "path";
9
+ import path4 from "path";
10
10
  import "commander";
11
11
  import {
12
12
  classifyMemoryPriority,
@@ -67,6 +67,7 @@ var ui = {
67
67
 
68
68
  // src/utils/briefing-radar.ts
69
69
  import { execFile } from "child_process";
70
+ import path from "path";
70
71
  import { promisify } from "util";
71
72
  var exec = promisify(execFile);
72
73
  var DEFAULT_DAYS_BACK = 14;
@@ -94,8 +95,11 @@ var SOURCE_GLOBS = [
94
95
  ];
95
96
  async function isGitRepo(root) {
96
97
  try {
97
- await exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root });
98
- return true;
98
+ const { stdout } = await exec("git", ["rev-parse", "--show-toplevel"], { cwd: root });
99
+ const toplevel = path.resolve(stdout.trim());
100
+ const resolvedRoot = path.resolve(root);
101
+ if (toplevel === resolvedRoot) return true;
102
+ return toplevel.startsWith(resolvedRoot + path.sep);
99
103
  } catch {
100
104
  return false;
101
105
  }
@@ -208,7 +212,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
208
212
  if (!f) continue;
209
213
  counts.set(f, (counts.get(f) ?? 0) + 1);
210
214
  }
211
- let entries = [...counts.entries()].map(([path60, changes]) => ({ path: path60, changes }));
215
+ let entries = [...counts.entries()].map(([path63, changes]) => ({ path: path63, changes }));
212
216
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
213
217
  if (lowerPaths.length > 0) {
214
218
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -238,7 +242,7 @@ function radarHasContent(r) {
238
242
  // src/utils/autopilot.ts
239
243
  import { existsSync as existsSync2 } from "fs";
240
244
  import { readFile, writeFile as writeFile2 } from "fs/promises";
241
- import path2 from "path";
245
+ import path3 from "path";
242
246
  import {
243
247
  AUTOPILOT_DEFAULTS,
244
248
  buildCodeMap,
@@ -253,7 +257,7 @@ import {
253
257
  import { existsSync } from "fs";
254
258
  import { writeFile } from "fs/promises";
255
259
  import { spawnSync } from "child_process";
256
- import path from "path";
260
+ import path2 from "path";
257
261
  import "commander";
258
262
  import {
259
263
  findProjectRoot,
@@ -441,7 +445,7 @@ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
441
445
  for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
442
446
  const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
443
447
  if (!candidate || candidate.startsWith("http")) continue;
444
- if (existsSync(path.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
448
+ if (existsSync(path2.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
445
449
  paths.add(candidate);
446
450
  }
447
451
  }
@@ -704,7 +708,7 @@ async function projectContextVersionStatus(root, paths) {
704
708
  if (!existsSync2(paths.projectContext)) {
705
709
  return { mismatch: false, canSync: false };
706
710
  }
707
- const packagePath = path2.join(root, "package.json");
711
+ const packagePath = path3.join(root, "package.json");
708
712
  if (!existsSync2(packagePath)) {
709
713
  return { mismatch: false, canSync: false };
710
714
  }
@@ -940,7 +944,7 @@ function registerBriefing(program2) {
940
944
  continue;
941
945
  }
942
946
  const otherMemories = await loadMemoriesFromDir3(otherPaths.memoriesDir);
943
- const tag = path3.basename(otherRoot);
947
+ const tag = path4.basename(otherRoot);
944
948
  for (const m of otherMemories) {
945
949
  ownMemories.push({ ...m, origin: tag });
946
950
  }
@@ -1066,7 +1070,7 @@ function registerBriefing(program2) {
1066
1070
  status: item.memory.frontmatter.status,
1067
1071
  priority: priorities[i],
1068
1072
  score: item.score,
1069
- file: path3.relative(root, item.filePath),
1073
+ file: path4.relative(root, item.filePath),
1070
1074
  summary: (item.memory.body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "").slice(0, 140)
1071
1075
  }))
1072
1076
  }, null, 2));
@@ -1088,7 +1092,7 @@ function registerBriefing(program2) {
1088
1092
  `${ui.bold(fm.id)} ${priorityBadge(priority)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
1089
1093
  );
1090
1094
  if (opts.explainSource) {
1091
- const relPath = path3.relative(root, item.filePath);
1095
+ const relPath = path4.relative(root, item.filePath);
1092
1096
  const anchorPaths = fm.anchor?.paths ?? [];
1093
1097
  const anchorSymbols = fm.anchor?.symbols ?? [];
1094
1098
  const parts = [`source: ${relPath}`];
@@ -1212,7 +1216,7 @@ function registerTui(program2) {
1212
1216
 
1213
1217
  // src/commands/embeddings.ts
1214
1218
  import { existsSync as existsSync4 } from "fs";
1215
- import path4 from "path";
1219
+ import path5 from "path";
1216
1220
  import "commander";
1217
1221
  import { findProjectRoot as findProjectRoot4, resolveHaivePaths as resolveHaivePaths3 } from "@hiveai/core";
1218
1222
  function registerEmbeddings(program2) {
@@ -1254,7 +1258,7 @@ function registerEmbeddings(program2) {
1254
1258
  for (const hit of result.hits) {
1255
1259
  const score = hit.score.toFixed(3);
1256
1260
  console.log(`${ui.bold(score)} ${hit.id}`);
1257
- console.log(` ${ui.dim(path4.relative(root, hit.file_path))}`);
1261
+ console.log(` ${ui.dim(path5.relative(root, hit.file_path))}`);
1258
1262
  }
1259
1263
  });
1260
1264
  embeddings.command("status").description("Show the embeddings index status").option("-d, --dir <dir>", "project root").action(async (opts) => {
@@ -1285,7 +1289,7 @@ async function loadEmbeddings() {
1285
1289
 
1286
1290
  // src/commands/index-code.ts
1287
1291
  import { existsSync as existsSync5, statSync } from "fs";
1288
- import path5 from "path";
1292
+ import path6 from "path";
1289
1293
  import "commander";
1290
1294
  import {
1291
1295
  buildCodeMap as buildCodeMap2,
@@ -1334,7 +1338,7 @@ function registerIndexCode(program2) {
1334
1338
  const fileCount = Object.keys(map.files).length;
1335
1339
  const exportCount = Object.values(map.files).reduce((s, f) => s + f.exports.length, 0);
1336
1340
  ui.success(
1337
- `Indexed ${fileCount} file(s) with ${exportCount} export(s) \u2192 ${path5.relative(root, codeMapPath(paths))}`
1341
+ `Indexed ${fileCount} file(s) with ${exportCount} export(s) \u2192 ${path6.relative(root, codeMapPath(paths))}`
1338
1342
  );
1339
1343
  });
1340
1344
  idx.command("code-search").description(
@@ -1371,12 +1375,12 @@ async function reportIndexStatus(root, paths, asJson) {
1371
1375
  const fileCount = map ? Object.keys(map.files).length : 0;
1372
1376
  const exportCount = map ? Object.values(map.files).reduce((s, f) => s + f.exports.length, 0) : 0;
1373
1377
  const mapMtime = existsSync5(mapFile) ? statSync(mapFile).mtime.toISOString() : null;
1374
- const searchIndexFile = path5.join(paths.haiveDir, ".cache", "embeddings", "code-embeddings-index.json");
1378
+ const searchIndexFile = path6.join(paths.haiveDir, ".cache", "embeddings", "code-embeddings-index.json");
1375
1379
  const searchIndexPresent = existsSync5(searchIndexFile);
1376
1380
  const status = {
1377
1381
  code_map: {
1378
1382
  present: map !== null,
1379
- path: path5.relative(root, mapFile),
1383
+ path: path6.relative(root, mapFile),
1380
1384
  files: fileCount,
1381
1385
  exports: exportCount,
1382
1386
  generated_at: map?.generated_at ?? null,
@@ -1384,7 +1388,7 @@ async function reportIndexStatus(root, paths, asJson) {
1384
1388
  },
1385
1389
  code_search_index: {
1386
1390
  present: searchIndexPresent,
1387
- path: path5.relative(root, searchIndexFile)
1391
+ path: path6.relative(root, searchIndexFile)
1388
1392
  }
1389
1393
  };
1390
1394
  if (asJson) {
@@ -1409,7 +1413,7 @@ async function reportIndexStatus(root, paths, asJson) {
1409
1413
  import { execFile as execFile2 } from "child_process";
1410
1414
  import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, writeFile as writeFile7 } from "fs/promises";
1411
1415
  import { existsSync as existsSync11 } from "fs";
1412
- import path11 from "path";
1416
+ import path12 from "path";
1413
1417
  import { spawnSync as spawnSync3 } from "child_process";
1414
1418
  import { promisify as promisify2 } from "util";
1415
1419
  import "commander";
@@ -1430,7 +1434,7 @@ import {
1430
1434
  // src/utils/bridge-files.ts
1431
1435
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
1432
1436
  import { existsSync as existsSync6 } from "fs";
1433
- import path6 from "path";
1437
+ import path7 from "path";
1434
1438
  import {
1435
1439
  BRIDGE_MARKERS,
1436
1440
  generateBridges,
@@ -1457,14 +1461,14 @@ async function writeBridgeFiles(root, paths, opts) {
1457
1461
  const maxMemories = Math.max(1, opts.maxMemories ?? 8);
1458
1462
  const outputs = generateBridges(memories, sensors, { maxMemories, targets: opts.targets });
1459
1463
  for (const output of outputs) {
1460
- const targetFile = path6.join(root, output.path);
1464
+ const targetFile = path7.join(root, output.path);
1461
1465
  const fileExists = existsSync6(targetFile);
1462
1466
  if (opts.onlyExisting && !fileExists) continue;
1463
1467
  if (opts.dryRun) {
1464
1468
  (fileExists ? result.updated : result.created).push(output.path);
1465
1469
  continue;
1466
1470
  }
1467
- await mkdir2(path6.dirname(targetFile), { recursive: true });
1471
+ await mkdir2(path7.dirname(targetFile), { recursive: true });
1468
1472
  if (!fileExists) {
1469
1473
  await writeFile3(targetFile, output.content, "utf8");
1470
1474
  result.created.push(output.path);
@@ -1513,7 +1517,7 @@ import { spawnSync as spawnSync2 } from "child_process";
1513
1517
  import { existsSync as existsSync8 } from "fs";
1514
1518
  import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
1515
1519
  import os2 from "os";
1516
- import path8 from "path";
1520
+ import path9 from "path";
1517
1521
  import { createInterface } from "readline/promises";
1518
1522
  import "commander";
1519
1523
  import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
@@ -1521,7 +1525,7 @@ import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaiveP
1521
1525
  // src/commands/init-mcp-setup.ts
1522
1526
  import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
1523
1527
  import { existsSync as existsSync7 } from "fs";
1524
- import path7 from "path";
1528
+ import path8 from "path";
1525
1529
  import os from "os";
1526
1530
  var HOME = os.homedir();
1527
1531
  var HAIVE_MCP_ENTRY = {
@@ -1536,11 +1540,11 @@ function projectMcpEntry(root) {
1536
1540
  };
1537
1541
  }
1538
1542
  function cursorMcpPath() {
1539
- return path7.join(HOME, ".cursor", "mcp.json");
1543
+ return path8.join(HOME, ".cursor", "mcp.json");
1540
1544
  }
1541
1545
  async function configureCursor() {
1542
1546
  const mcpPath = cursorMcpPath();
1543
- const cursorDir = path7.join(HOME, ".cursor");
1547
+ const cursorDir = path8.join(HOME, ".cursor");
1544
1548
  if (!existsSync7(cursorDir)) return { client: "Cursor", status: "not_installed" };
1545
1549
  let config = {};
1546
1550
  if (existsSync7(mcpPath)) {
@@ -1558,16 +1562,16 @@ async function configureCursor() {
1558
1562
  }
1559
1563
  function vscodeMcpPath() {
1560
1564
  const candidates = [
1561
- path7.join(HOME, ".config", "Code", "User", "mcp.json"),
1565
+ path8.join(HOME, ".config", "Code", "User", "mcp.json"),
1562
1566
  // Linux
1563
- path7.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
1567
+ path8.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
1564
1568
  // macOS
1565
- path7.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
1569
+ path8.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
1566
1570
  // Windows
1567
- path7.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
1571
+ path8.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
1568
1572
  ];
1569
1573
  for (const c of candidates) {
1570
- if (existsSync7(path7.dirname(c))) return c;
1574
+ if (existsSync7(path8.dirname(c))) return c;
1571
1575
  }
1572
1576
  return null;
1573
1577
  }
@@ -1584,20 +1588,20 @@ async function configureVSCode() {
1584
1588
  config.servers ??= {};
1585
1589
  if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
1586
1590
  config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
1587
- await mkdir3(path7.dirname(mcpPath), { recursive: true });
1591
+ await mkdir3(path8.dirname(mcpPath), { recursive: true });
1588
1592
  await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
1589
1593
  return { client: "VS Code", status: "configured", path: mcpPath };
1590
1594
  }
1591
1595
  function claudeConfigPath() {
1592
- const p = path7.join(HOME, ".claude.json");
1596
+ const p = path8.join(HOME, ".claude.json");
1593
1597
  if (existsSync7(p)) return p;
1594
- const p2 = path7.join(HOME, ".config", "claude", "claude.json");
1595
- if (existsSync7(path7.dirname(p2))) return p2;
1598
+ const p2 = path8.join(HOME, ".config", "claude", "claude.json");
1599
+ if (existsSync7(path8.dirname(p2))) return p2;
1596
1600
  return null;
1597
1601
  }
1598
1602
  async function configureClaude() {
1599
- const cfgPath = claudeConfigPath() ?? path7.join(HOME, ".claude.json");
1600
- if (!existsSync7(cfgPath) && !existsSync7(path7.join(HOME, ".claude"))) {
1603
+ const cfgPath = claudeConfigPath() ?? path8.join(HOME, ".claude.json");
1604
+ if (!existsSync7(cfgPath) && !existsSync7(path8.join(HOME, ".claude"))) {
1601
1605
  return { client: "Claude Code", status: "not_installed" };
1602
1606
  }
1603
1607
  let config = {};
@@ -1615,11 +1619,11 @@ async function configureClaude() {
1615
1619
  }
1616
1620
  function windsurfMcpPath() {
1617
1621
  const candidates = [
1618
- path7.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
1619
- path7.join(HOME, ".windsurf", "mcp.json")
1622
+ path8.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
1623
+ path8.join(HOME, ".windsurf", "mcp.json")
1620
1624
  ];
1621
1625
  for (const c of candidates) {
1622
- if (existsSync7(path7.dirname(c))) return c;
1626
+ if (existsSync7(path8.dirname(c))) return c;
1623
1627
  }
1624
1628
  return null;
1625
1629
  }
@@ -1636,7 +1640,7 @@ async function configureWindsurf() {
1636
1640
  config.mcpServers ??= {};
1637
1641
  if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
1638
1642
  config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
1639
- await mkdir3(path7.dirname(mcpPath), { recursive: true });
1643
+ await mkdir3(path8.dirname(mcpPath), { recursive: true });
1640
1644
  await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
1641
1645
  return { client: "Windsurf", status: "configured", path: mcpPath };
1642
1646
  }
@@ -1657,7 +1661,7 @@ async function configureProjectMcpClients(root) {
1657
1661
  const entry = projectMcpEntry(root);
1658
1662
  const results = [];
1659
1663
  try {
1660
- const cursorPath = path7.join(root, ".cursor", "mcp.json");
1664
+ const cursorPath = path8.join(root, ".cursor", "mcp.json");
1661
1665
  let config = {};
1662
1666
  if (existsSync7(cursorPath)) {
1663
1667
  try {
@@ -1667,14 +1671,14 @@ async function configureProjectMcpClients(root) {
1667
1671
  }
1668
1672
  config.mcpServers ??= {};
1669
1673
  config.mcpServers["haive"] = entry;
1670
- await mkdir3(path7.dirname(cursorPath), { recursive: true });
1674
+ await mkdir3(path8.dirname(cursorPath), { recursive: true });
1671
1675
  await writeFile4(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1672
1676
  results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
1673
1677
  } catch (err) {
1674
1678
  results.push({ client: "Cursor (project)", status: "error", error: String(err) });
1675
1679
  }
1676
1680
  try {
1677
- const vscodePath = path7.join(root, ".vscode", "mcp.json");
1681
+ const vscodePath = path8.join(root, ".vscode", "mcp.json");
1678
1682
  let config = {};
1679
1683
  if (existsSync7(vscodePath)) {
1680
1684
  try {
@@ -1684,14 +1688,14 @@ async function configureProjectMcpClients(root) {
1684
1688
  }
1685
1689
  config.servers ??= {};
1686
1690
  config.servers["haive"] = { ...entry, type: "stdio" };
1687
- await mkdir3(path7.dirname(vscodePath), { recursive: true });
1691
+ await mkdir3(path8.dirname(vscodePath), { recursive: true });
1688
1692
  await writeFile4(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
1689
1693
  results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
1690
1694
  } catch (err) {
1691
1695
  results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
1692
1696
  }
1693
1697
  try {
1694
- const mcpPath = path7.join(root, ".mcp.json");
1698
+ const mcpPath = path8.join(root, ".mcp.json");
1695
1699
  let config = {};
1696
1700
  if (existsSync7(mcpPath)) {
1697
1701
  try {
@@ -1767,9 +1771,9 @@ async function detectAgentMode(dir) {
1767
1771
  const root = findProjectRoot6(dir);
1768
1772
  const paths = resolveHaivePaths5(root);
1769
1773
  const projectMcp = [
1770
- { client: "Claude Code", path: path8.join(root, ".mcp.json"), present: existsSync8(path8.join(root, ".mcp.json")) },
1771
- { client: "Cursor", path: path8.join(root, ".cursor", "mcp.json"), present: existsSync8(path8.join(root, ".cursor", "mcp.json")) },
1772
- { client: "VS Code", path: path8.join(root, ".vscode", "mcp.json"), present: existsSync8(path8.join(root, ".vscode", "mcp.json")) }
1774
+ { client: "Claude Code", path: path9.join(root, ".mcp.json"), present: existsSync8(path9.join(root, ".mcp.json")) },
1775
+ { client: "Cursor", path: path9.join(root, ".cursor", "mcp.json"), present: existsSync8(path9.join(root, ".cursor", "mcp.json")) },
1776
+ { client: "VS Code", path: path9.join(root, ".vscode", "mcp.json"), present: existsSync8(path9.join(root, ".vscode", "mcp.json")) }
1773
1777
  ];
1774
1778
  const installedAgents = [
1775
1779
  { agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
@@ -1792,9 +1796,9 @@ async function detectAgentMode(dir) {
1792
1796
  };
1793
1797
  }
1794
1798
  async function writeAgentModeRecord(paths, detection, skippedReason) {
1795
- const dir = path8.join(paths.runtimeDir, "enforcement");
1799
+ const dir = path9.join(paths.runtimeDir, "enforcement");
1796
1800
  await mkdir4(dir, { recursive: true });
1797
- const file = path8.join(dir, "agent-mode.json");
1801
+ const file = path9.join(dir, "agent-mode.json");
1798
1802
  const record = {
1799
1803
  selected_mode: detection.recommended_mode,
1800
1804
  recommended_command: detection.recommended_command,
@@ -1835,7 +1839,7 @@ async function configureCodexIfAvailable(root) {
1835
1839
  "mcp",
1836
1840
  "--stdio"
1837
1841
  ], { encoding: "utf8" });
1838
- if (result.status === 0) return { client: "Codex", status: "configured", path: path8.join(os2.homedir(), ".codex", "config.toml") };
1842
+ if (result.status === 0) return { client: "Codex", status: "configured", path: path9.join(os2.homedir(), ".codex", "config.toml") };
1839
1843
  return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
1840
1844
  }
1841
1845
  function commandExists(command) {
@@ -1862,7 +1866,7 @@ function printDetection(detection, json) {
1862
1866
  console.log(ui.dim(` root: ${detection.root}`));
1863
1867
  console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
1864
1868
  for (const cfg of detection.project_mcp) {
1865
- console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path8.relative(detection.root, cfg.path))}`);
1869
+ console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path9.relative(detection.root, cfg.path))}`);
1866
1870
  }
1867
1871
  for (const agent of detection.installed_agents) {
1868
1872
  const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
@@ -1892,7 +1896,7 @@ function printSetupResult(result) {
1892
1896
  // src/commands/init-bootstrap.ts
1893
1897
  import { readdir, readFile as readFile5 } from "fs/promises";
1894
1898
  import { existsSync as existsSync9, readdirSync } from "fs";
1895
- import path9 from "path";
1899
+ import path10 from "path";
1896
1900
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
1897
1901
  "node_modules",
1898
1902
  "dist",
@@ -1978,12 +1982,12 @@ function detectKeyDeps(allDeps) {
1978
1982
  return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
1979
1983
  }
1980
1984
  function detectLanguage(root) {
1981
- if (existsSync9(path9.join(root, "tsconfig.json"))) return "TypeScript";
1982
- if (existsSync9(path9.join(root, "pyproject.toml")) || existsSync9(path9.join(root, "setup.py"))) return "Python";
1983
- if (existsSync9(path9.join(root, "go.mod"))) return "Go";
1984
- if (existsSync9(path9.join(root, "pom.xml")) || existsSync9(path9.join(root, "build.gradle"))) return "Java/Kotlin";
1985
- if (existsSync9(path9.join(root, "Cargo.toml"))) return "Rust";
1986
- if (existsSync9(path9.join(root, "package.json"))) {
1985
+ if (existsSync9(path10.join(root, "tsconfig.json"))) return "TypeScript";
1986
+ if (existsSync9(path10.join(root, "pyproject.toml")) || existsSync9(path10.join(root, "setup.py"))) return "Python";
1987
+ if (existsSync9(path10.join(root, "go.mod"))) return "Go";
1988
+ if (existsSync9(path10.join(root, "pom.xml")) || existsSync9(path10.join(root, "build.gradle"))) return "Java/Kotlin";
1989
+ if (existsSync9(path10.join(root, "Cargo.toml"))) return "Rust";
1990
+ if (existsSync9(path10.join(root, "package.json"))) {
1987
1991
  return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
1988
1992
  }
1989
1993
  return "Unknown";
@@ -2003,7 +2007,7 @@ function hasSourceWithExt(root, exts) {
2003
2007
  if (depth <= 0) return false;
2004
2008
  for (const entry of entries) {
2005
2009
  if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
2006
- if (scanDir(path9.join(dir, entry.name), depth - 1)) return true;
2010
+ if (scanDir(path10.join(dir, entry.name), depth - 1)) return true;
2007
2011
  }
2008
2012
  }
2009
2013
  return false;
@@ -2040,9 +2044,9 @@ async function scanDirs(root, maxDepth = 2) {
2040
2044
  for (const entry of entries) {
2041
2045
  if (!entry.isDirectory()) continue;
2042
2046
  if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
2043
- const rel = path9.relative(root, path9.join(dir, entry.name));
2047
+ const rel = path10.relative(root, path10.join(dir, entry.name));
2044
2048
  results.push(rel);
2045
- await walk(path9.join(dir, entry.name), depth + 1);
2049
+ await walk(path10.join(dir, entry.name), depth + 1);
2046
2050
  }
2047
2051
  }
2048
2052
  await walk(root, 0);
@@ -2159,7 +2163,7 @@ function readmeExcerpt(readme) {
2159
2163
  }
2160
2164
  async function generateBootstrapContext(root) {
2161
2165
  let pkg = {};
2162
- const pkgPath = path9.join(root, "package.json");
2166
+ const pkgPath = path10.join(root, "package.json");
2163
2167
  if (existsSync9(pkgPath)) {
2164
2168
  try {
2165
2169
  pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
@@ -2172,11 +2176,11 @@ async function generateBootstrapContext(root) {
2172
2176
  const language = detectLanguage(root);
2173
2177
  const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
2174
2178
  const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
2175
- const projectName = pkg.name ?? path9.basename(root);
2179
+ const projectName = pkg.name ?? path10.basename(root);
2176
2180
  const projectDesc = pkg.description ?? "";
2177
2181
  let readmeSummary = "";
2178
2182
  for (const name of ["README.md", "readme.md", "README"]) {
2179
- const p = path9.join(root, name);
2183
+ const p = path10.join(root, name);
2180
2184
  if (existsSync9(p)) {
2181
2185
  try {
2182
2186
  const content = await readFile5(p, "utf8");
@@ -2245,7 +2249,7 @@ async function generateBootstrapContext(root) {
2245
2249
  // src/commands/init-stack-packs.ts
2246
2250
  import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
2247
2251
  import { existsSync as existsSync10 } from "fs";
2248
- import path10 from "path";
2252
+ import path11 from "path";
2249
2253
  import {
2250
2254
  buildFrontmatter,
2251
2255
  loadMemoriesFromDir as loadMemoriesFromDir5,
@@ -3374,7 +3378,7 @@ ${mem.body}`;
3374
3378
  const content = serializeMemory2({ frontmatter: fm, body: `${titledBody}
3375
3379
 
3376
3380
  ${SEED_FOOTER(stack)}` });
3377
- await mkdir5(path10.dirname(filePath), { recursive: true });
3381
+ await mkdir5(path11.dirname(filePath), { recursive: true });
3378
3382
  await writeFile6(filePath, content, "utf8");
3379
3383
  existingTopics.add(topic);
3380
3384
  existingSignatures.add(signature);
@@ -3386,7 +3390,7 @@ ${SEED_FOOTER(stack)}` });
3386
3390
 
3387
3391
  // src/commands/init.ts
3388
3392
  var execFileAsync = promisify2(execFile2);
3389
- var HAIVE_GITHUB_ACTION_REF = `v${"0.21.0"}`;
3393
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.24.0"}`;
3390
3394
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3391
3395
 
3392
3396
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -3614,7 +3618,7 @@ function registerInit(program2) {
3614
3618
  "approve user-level AI client configuration prompts during agent setup",
3615
3619
  false
3616
3620
  ).option("--json", "emit a machine-readable summary on stdout (human logs go to stderr)", false).action(async (opts) => {
3617
- const root = path11.resolve(opts.dir);
3621
+ const root = path12.resolve(opts.dir);
3618
3622
  const paths = resolveHaivePaths6(root);
3619
3623
  const autopilot = opts.manual !== true;
3620
3624
  const json = opts.json === true;
@@ -3641,7 +3645,7 @@ function registerInit(program2) {
3641
3645
  await mkdir6(paths.moduleDir, { recursive: true });
3642
3646
  await mkdir6(paths.modulesContextDir, { recursive: true });
3643
3647
  await ensureAiRuntimeLayout(paths.runtimeDir);
3644
- await ensureAiCacheLayout(path11.join(paths.haiveDir, ".cache"));
3648
+ await ensureAiCacheLayout(path12.join(paths.haiveDir, ".cache"));
3645
3649
  if (!existsSync11(paths.projectContext)) {
3646
3650
  if (wantBootstrap) {
3647
3651
  ui.info("Bootstrapping project context from local files\u2026");
@@ -3655,11 +3659,11 @@ function registerInit(program2) {
3655
3659
  }
3656
3660
  } else {
3657
3661
  await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
3658
- ui.success(`Created ${path11.relative(root, paths.projectContext)}`);
3662
+ ui.success(`Created ${path12.relative(root, paths.projectContext)}`);
3659
3663
  }
3660
3664
  }
3661
3665
  const configExists = existsSync11(
3662
- path11.join(paths.haiveDir, "haive.config.json")
3666
+ path12.join(paths.haiveDir, "haive.config.json")
3663
3667
  );
3664
3668
  if (!configExists) {
3665
3669
  await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
@@ -3726,13 +3730,13 @@ function registerInit(program2) {
3726
3730
  }
3727
3731
  const wantCi = opts.withCi || autopilot;
3728
3732
  if (wantCi) {
3729
- const ciPath = path11.join(root, ".github", "workflows", "haive-sync.yml");
3733
+ const ciPath = path12.join(root, ".github", "workflows", "haive-sync.yml");
3730
3734
  if (existsSync11(ciPath)) {
3731
3735
  ui.info("CI workflow already exists \u2014 skipped");
3732
3736
  } else {
3733
- await mkdir6(path11.dirname(ciPath), { recursive: true });
3737
+ await mkdir6(path12.dirname(ciPath), { recursive: true });
3734
3738
  await writeFile7(ciPath, CI_WORKFLOW, "utf8");
3735
- ui.success(`Created ${path11.relative(root, ciPath)}`);
3739
+ ui.success(`Created ${path12.relative(root, ciPath)}`);
3736
3740
  }
3737
3741
  }
3738
3742
  if (autopilot) {
@@ -3772,7 +3776,7 @@ function registerInit(program2) {
3772
3776
  interactive: process.stdin.isTTY
3773
3777
  });
3774
3778
  for (const r of agentSetup.project_results) {
3775
- if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path11.relative(root, r.path)})`);
3779
+ if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path12.relative(root, r.path)})`);
3776
3780
  else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
3777
3781
  }
3778
3782
  for (const r of agentSetup.global_results) {
@@ -3871,7 +3875,7 @@ async function autoDetectStacksFromRoot(root) {
3871
3875
  let requirementsTxt;
3872
3876
  let goMod;
3873
3877
  let pomXml;
3874
- const pkgPath = path11.join(root, "package.json");
3878
+ const pkgPath = path12.join(root, "package.json");
3875
3879
  if (existsSync11(pkgPath)) {
3876
3880
  try {
3877
3881
  const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
@@ -3880,7 +3884,7 @@ async function autoDetectStacksFromRoot(root) {
3880
3884
  }
3881
3885
  }
3882
3886
  for (const name of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
3883
- const reqPath = path11.join(root, name);
3887
+ const reqPath = path12.join(root, name);
3884
3888
  if (existsSync11(reqPath)) {
3885
3889
  try {
3886
3890
  requirementsTxt = await readFile6(reqPath, "utf8");
@@ -3889,14 +3893,14 @@ async function autoDetectStacksFromRoot(root) {
3889
3893
  }
3890
3894
  }
3891
3895
  }
3892
- const goModPath = path11.join(root, "go.mod");
3896
+ const goModPath = path12.join(root, "go.mod");
3893
3897
  if (existsSync11(goModPath)) {
3894
3898
  try {
3895
3899
  goMod = await readFile6(goModPath, "utf8");
3896
3900
  } catch {
3897
3901
  }
3898
3902
  }
3899
- const pomPath = path11.join(root, "pom.xml");
3903
+ const pomPath = path12.join(root, "pom.xml");
3900
3904
  if (existsSync11(pomPath)) {
3901
3905
  try {
3902
3906
  pomXml = await readFile6(pomPath, "utf8");
@@ -3904,7 +3908,7 @@ async function autoDetectStacksFromRoot(root) {
3904
3908
  }
3905
3909
  }
3906
3910
  let composerJson;
3907
- const composerPath = path11.join(root, "composer.json");
3911
+ const composerPath = path12.join(root, "composer.json");
3908
3912
  if (existsSync11(composerPath)) {
3909
3913
  try {
3910
3914
  composerJson = await readFile6(composerPath, "utf8");
@@ -3912,16 +3916,16 @@ async function autoDetectStacksFromRoot(root) {
3912
3916
  }
3913
3917
  }
3914
3918
  let gemfile;
3915
- const gemfilePath = path11.join(root, "Gemfile");
3919
+ const gemfilePath = path12.join(root, "Gemfile");
3916
3920
  if (existsSync11(gemfilePath)) {
3917
3921
  try {
3918
3922
  gemfile = await readFile6(gemfilePath, "utf8");
3919
3923
  } catch {
3920
3924
  }
3921
3925
  }
3922
- const hasDockerfile = existsSync11(path11.join(root, "Dockerfile"));
3923
- const hasTurboJson = existsSync11(path11.join(root, "turbo.json"));
3924
- const hasNxJson = existsSync11(path11.join(root, "nx.json"));
3926
+ const hasDockerfile = existsSync11(path12.join(root, "Dockerfile"));
3927
+ const hasTurboJson = existsSync11(path12.join(root, "turbo.json"));
3928
+ const hasNxJson = existsSync11(path12.join(root, "nx.json"));
3925
3929
  let hasCsproj = false;
3926
3930
  try {
3927
3931
  const entries = await readdir2(root);
@@ -3974,7 +3978,7 @@ _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delet
3974
3978
  `;
3975
3979
  const file = memoryFilePath2(paths, fm.scope, fm.id, fm.module);
3976
3980
  if (existsSync11(file)) continue;
3977
- await mkdir6(path11.dirname(file), { recursive: true });
3981
+ await mkdir6(path12.dirname(file), { recursive: true });
3978
3982
  await writeFile7(file, serializeMemory3({ frontmatter: fm, body }), "utf8");
3979
3983
  written++;
3980
3984
  }
@@ -4036,12 +4040,12 @@ function printInitReport(r) {
4036
4040
  }
4037
4041
  async function writeCursorHaiveRule(root) {
4038
4042
  const relPath = ".cursor/rules/haive-mcp-required.mdc";
4039
- const target = path11.join(root, relPath);
4043
+ const target = path12.join(root, relPath);
4040
4044
  if (existsSync11(target)) {
4041
4045
  ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
4042
4046
  return;
4043
4047
  }
4044
- await mkdir6(path11.dirname(target), { recursive: true });
4048
+ await mkdir6(path12.dirname(target), { recursive: true });
4045
4049
  await writeFile7(target, CURSOR_HAIVE_RULE_MDC, "utf8");
4046
4050
  ui.success(`Created Cursor rule ${relPath}`);
4047
4051
  }
@@ -4070,25 +4074,25 @@ var RUNTIME_GITIGNORE_BODY = `*
4070
4074
  `;
4071
4075
  async function ensureAiRuntimeLayout(runtimeDir) {
4072
4076
  await mkdir6(runtimeDir, { recursive: true });
4073
- const gi = path11.join(runtimeDir, ".gitignore");
4077
+ const gi = path12.join(runtimeDir, ".gitignore");
4074
4078
  if (!existsSync11(gi)) {
4075
4079
  await writeFile7(gi, RUNTIME_GITIGNORE_BODY, "utf8");
4076
4080
  }
4077
- const readme = path11.join(runtimeDir, "README.md");
4081
+ const readme = path12.join(runtimeDir, "README.md");
4078
4082
  if (!existsSync11(readme)) {
4079
4083
  await writeFile7(readme, RUNTIME_README_BODY, "utf8");
4080
4084
  }
4081
4085
  }
4082
4086
  async function ensureAiCacheLayout(cacheDir) {
4083
4087
  await mkdir6(cacheDir, { recursive: true });
4084
- const gi = path11.join(cacheDir, ".gitignore");
4088
+ const gi = path12.join(cacheDir, ".gitignore");
4085
4089
  if (!existsSync11(gi)) {
4086
4090
  await writeFile7(gi, "*\n!.gitignore\n", "utf8");
4087
4091
  }
4088
4092
  }
4089
4093
  async function ensureGitignoreEntries(root, patterns) {
4090
4094
  try {
4091
- const gitignorePath = path11.join(root, ".gitignore");
4095
+ const gitignorePath = path12.join(root, ".gitignore");
4092
4096
  let existing = "";
4093
4097
  if (existsSync11(gitignorePath)) {
4094
4098
  existing = await readFile6(gitignorePath, "utf8");
@@ -4105,14 +4109,14 @@ async function ensureGitignoreEntries(root, patterns) {
4105
4109
  // src/commands/install-hooks.ts
4106
4110
  import { mkdir as mkdir8, writeFile as writeFile9, chmod, readFile as readFile8 } from "fs/promises";
4107
4111
  import { existsSync as existsSync13 } from "fs";
4108
- import path13 from "path";
4112
+ import path14 from "path";
4109
4113
  import "commander";
4110
4114
  import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
4111
4115
 
4112
4116
  // src/utils/claude-hooks.ts
4113
4117
  import { existsSync as existsSync12 } from "fs";
4114
4118
  import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
4115
- import path12 from "path";
4119
+ import path13 from "path";
4116
4120
  var HAIVE_HOOK_TAG = "haive-enforcement";
4117
4121
  var POST_TOOL_USE_GROUP = {
4118
4122
  matcher: "Edit|Write|Bash",
@@ -4208,7 +4212,7 @@ async function installClaudeHooksAtPath(settingsPath) {
4208
4212
  created = true;
4209
4213
  }
4210
4214
  const patched = patchClaudeSettings(raw);
4211
- await mkdir7(path12.dirname(settingsPath), { recursive: true });
4215
+ await mkdir7(path13.dirname(settingsPath), { recursive: true });
4212
4216
  await writeFile8(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
4213
4217
  return { settingsPath, created };
4214
4218
  }
@@ -4224,9 +4228,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
4224
4228
  function defaultClaudeSettingsPath(scope, projectRoot) {
4225
4229
  if (scope === "user") {
4226
4230
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4227
- return path12.join(home, ".claude", "settings.json");
4231
+ return path13.join(home, ".claude", "settings.json");
4228
4232
  }
4229
- return path12.join(projectRoot, ".claude", "settings.local.json");
4233
+ return path13.join(projectRoot, ".claude", "settings.local.json");
4230
4234
  }
4231
4235
 
4232
4236
  // src/commands/install-hooks.ts
@@ -4283,18 +4287,18 @@ fi
4283
4287
  ];
4284
4288
  async function installGitHooks(opts) {
4285
4289
  const root = findProjectRoot8(opts.dir);
4286
- const gitDir = path13.join(root, ".git");
4290
+ const gitDir = path14.join(root, ".git");
4287
4291
  if (!existsSync13(gitDir)) {
4288
4292
  ui.error(`No .git directory at ${root}.`);
4289
4293
  process.exitCode = 1;
4290
4294
  return;
4291
4295
  }
4292
- const hooksDir = path13.join(gitDir, "hooks");
4296
+ const hooksDir = path14.join(gitDir, "hooks");
4293
4297
  await mkdir8(hooksDir, { recursive: true });
4294
4298
  let installed = 0;
4295
4299
  let skipped = 0;
4296
4300
  for (const { name, body } of HOOKS) {
4297
- const file = path13.join(hooksDir, name);
4301
+ const file = path14.join(hooksDir, name);
4298
4302
  if (existsSync13(file) && !opts.force) {
4299
4303
  const existing = await readFile8(file, "utf8");
4300
4304
  if (!existing.includes(HOOK_MARKER)) {
@@ -4360,7 +4364,7 @@ function registerInstallHooks(program2) {
4360
4364
  // src/commands/observe.ts
4361
4365
  import { appendFile, mkdir as mkdir9 } from "fs/promises";
4362
4366
  import { existsSync as existsSync14 } from "fs";
4363
- import path14 from "path";
4367
+ import path15 from "path";
4364
4368
  import "commander";
4365
4369
  import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
4366
4370
  var MAX_STDIN_BYTES = 256 * 1024;
@@ -4478,10 +4482,10 @@ function registerObserve(program2) {
4478
4482
  files: extractFiles(payload),
4479
4483
  ...failureHint ? { failure_hint: true } : {}
4480
4484
  };
4481
- const cacheDir = path14.join(paths.haiveDir, ".cache");
4485
+ const cacheDir = path15.join(paths.haiveDir, ".cache");
4482
4486
  await mkdir9(cacheDir, { recursive: true });
4483
4487
  await appendFile(
4484
- path14.join(cacheDir, "observations.jsonl"),
4488
+ path15.join(cacheDir, "observations.jsonl"),
4485
4489
  JSON.stringify(observation) + "\n",
4486
4490
  "utf8"
4487
4491
  );
@@ -4500,7 +4504,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
4500
4504
  import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
4501
4505
  import { mkdir as mkdir10, writeFile as writeFile10 } from "fs/promises";
4502
4506
  import { existsSync as existsSync15 } from "fs";
4503
- import path15 from "path";
4507
+ import path16 from "path";
4504
4508
  import { z } from "zod";
4505
4509
  import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
4506
4510
  import { existsSync as existsSync22 } from "fs";
@@ -4670,6 +4674,7 @@ import path82 from "path";
4670
4674
  import { execSync } from "child_process";
4671
4675
  import { readFile as readFile52, writeFile as writeFile13 } from "fs/promises";
4672
4676
  import { existsSync as existsSync21 } from "fs";
4677
+ import path112 from "path";
4673
4678
  import {
4674
4679
  allocateBudget,
4675
4680
  briefingProofLine,
@@ -4729,7 +4734,7 @@ import { z as z23 } from "zod";
4729
4734
  import { z as z24 } from "zod";
4730
4735
  import { existsSync as existsSync24 } from "fs";
4731
4736
  import { spawn } from "child_process";
4732
- import path112 from "path";
4737
+ import path122 from "path";
4733
4738
  import {
4734
4739
  deriveConfidence as deriveConfidence5,
4735
4740
  getUsage as getUsage7,
@@ -4742,7 +4747,6 @@ import { z as z25 } from "zod";
4742
4747
  import { existsSync as existsSync25 } from "fs";
4743
4748
  import {
4744
4749
  addedLinesFromDiff,
4745
- appendPreventionEvent,
4746
4750
  BRIDGE_TARGET_PATH,
4747
4751
  buildDocFrequency,
4748
4752
  CODE_STOPWORDS,
@@ -4754,9 +4758,8 @@ import {
4754
4758
  loadUsageIndex as loadUsageIndex10,
4755
4759
  literalMatchesAnyToken as literalMatchesAnyToken3,
4756
4760
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
4757
- recordPrevention,
4761
+ recordPreventionHits,
4758
4762
  runSensors,
4759
- saveUsageIndex as saveUsageIndex4,
4760
4763
  sensorTargetsFromDiff,
4761
4764
  tokenizeQuery as tokenizeQuery3
4762
4765
  } from "@hiveai/core";
@@ -4790,7 +4793,7 @@ import { z as z29 } from "zod";
4790
4793
  import { z as z30 } from "zod";
4791
4794
  import { mkdir as mkdir82, writeFile as writeFile14 } from "fs/promises";
4792
4795
  import { existsSync as existsSync29 } from "fs";
4793
- import path122 from "path";
4796
+ import path132 from "path";
4794
4797
  import { execSync as execSync2 } from "child_process";
4795
4798
  import {
4796
4799
  buildFrontmatter as buildFrontmatter5,
@@ -4803,7 +4806,8 @@ import { existsSync as existsSync30 } from "fs";
4803
4806
  import {
4804
4807
  findLexicalConflictPairs,
4805
4808
  findTopicStatusConflictPairs,
4806
- loadMemoriesFromDir as loadMemoriesFromDir23
4809
+ loadMemoriesFromDir as loadMemoriesFromDir23,
4810
+ planConflictResolution
4807
4811
  } from "@hiveai/core";
4808
4812
  import { z as z32 } from "zod";
4809
4813
  import { resolveProjectInfo } from "@hiveai/core";
@@ -4835,14 +4839,14 @@ var BootstrapProjectSaveInputSchema = {
4835
4839
  overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
4836
4840
  };
4837
4841
  async function bootstrapProjectSave(input, ctx) {
4838
- const target = input.module ? path15.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
4842
+ const target = input.module ? path16.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
4839
4843
  const exists = existsSync15(target);
4840
4844
  if (exists && !input.overwrite) {
4841
4845
  throw new Error(
4842
4846
  `${target} already exists. Pass overwrite=true to replace it.`
4843
4847
  );
4844
4848
  }
4845
- await mkdir10(path15.dirname(target), { recursive: true });
4849
+ await mkdir10(path16.dirname(target), { recursive: true });
4846
4850
  await writeFile10(target, input.content, "utf8");
4847
4851
  return {
4848
4852
  file_path: target,
@@ -5807,7 +5811,15 @@ async function memTried(input, ctx) {
5807
5811
  throw new Error(`Memory already exists at ${file}`);
5808
5812
  }
5809
5813
  await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
5810
- return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
5814
+ const sensorGenerated = Boolean(sensor);
5815
+ const hint = sensorGenerated ? void 0 : input.paths.length === 0 ? "No sensor was generated (no `paths` given), so this lesson is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives to close the loop." : "No sensor could be derived from the wording (no distinctive code token). The lesson is briefed but not enforced; add a concrete forbidden token/value, or attach a sensor manually, to make the gate block the repeat.";
5816
+ return {
5817
+ id: frontmatter.id,
5818
+ scope: frontmatter.scope,
5819
+ file_path: file,
5820
+ sensor_generated: sensorGenerated,
5821
+ ...hint ? { hint } : {}
5822
+ };
5811
5823
  }
5812
5824
  var IngestFindingsInputSchema = {
5813
5825
  format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
@@ -6572,6 +6584,7 @@ async function getBriefing(input, ctx) {
6572
6584
  const topSymbols = Object.entries(codeMap.files).flatMap(
6573
6585
  ([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
6574
6586
  ).slice(0, 15).join(", ");
6587
+ const commands = await detectRunCommands(ctx.paths.root);
6575
6588
  projectContext = `# Project context (auto-generated by hAIve)
6576
6589
 
6577
6590
  > \u26A0 This is a minimal auto-generated context based on the code-map. Invoke the \`bootstrap_project\` MCP prompt to replace it with a full analysis.
@@ -6581,7 +6594,10 @@ async function getBriefing(input, ctx) {
6581
6594
  - **Main file types:** ${topExts}
6582
6595
  - **Generated at:** ${codeMap.generated_at}
6583
6596
 
6584
- ## Key exports (sample)
6597
+ ` + (commands ? `## Commands
6598
+ ${commands}
6599
+
6600
+ ` : "") + `## Key exports (sample)
6585
6601
  ` + topSymbols + "\n";
6586
6602
  autoContextGenerated = true;
6587
6603
  setupWarnings.push(
@@ -6861,6 +6877,19 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
6861
6877
  }
6862
6878
  };
6863
6879
  }
6880
+ async function detectRunCommands(root) {
6881
+ const pkgPath = path112.join(root, "package.json");
6882
+ if (!existsSync21(pkgPath)) return null;
6883
+ try {
6884
+ const pkg = JSON.parse(await readFile52(pkgPath, "utf8"));
6885
+ const scripts = pkg.scripts ?? {};
6886
+ const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
6887
+ const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
6888
+ return lines.length > 0 ? lines.join("\n") : null;
6889
+ } catch {
6890
+ return null;
6891
+ }
6892
+ }
6864
6893
  var CodeMapInputSchema = {
6865
6894
  file: z20.string().optional().describe("Filter to files whose path contains this substring"),
6866
6895
  symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
@@ -7084,7 +7113,7 @@ var WhyThisFileInputSchema = {
7084
7113
  memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
7085
7114
  };
7086
7115
  async function whyThisFile(input, ctx) {
7087
- const fileExists = existsSync24(path112.join(ctx.paths.root, input.path));
7116
+ const fileExists = existsSync24(path122.join(ctx.paths.root, input.path));
7088
7117
  const [commits, memories, codeMap] = await Promise.all([
7089
7118
  runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
7090
7119
  collectAnchoredMemories(ctx, input.path, input.memory_limit),
@@ -7358,19 +7387,7 @@ async function antiPatternsCheck(input, ctx) {
7358
7387
  const strongCatches = warnings.filter(
7359
7388
  (w) => w.reasons.includes("sensor") || w.reasons.includes("anchor") && w.reasons.includes("literal")
7360
7389
  );
7361
- if (strongCatches.length > 0) {
7362
- const recordedIds = [];
7363
- for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
7364
- if (recordedIds.length > 0) {
7365
- await saveUsageIndex4(ctx.paths, usage).catch(() => {
7366
- });
7367
- const at = (/* @__PURE__ */ new Date()).toISOString();
7368
- for (const id of recordedIds) {
7369
- await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
7370
- });
7371
- }
7372
- }
7373
- }
7390
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
7374
7391
  return {
7375
7392
  scanned: negative.length,
7376
7393
  warnings
@@ -8128,13 +8145,13 @@ async function patternDetect(input, ctx) {
8128
8145
  try {
8129
8146
  const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
8130
8147
  const configFiles = changedFiles.filter(
8131
- (f) => CONFIG_PATTERNS.some((p) => path122.basename(f.toLowerCase()).includes(p))
8148
+ (f) => CONFIG_PATTERNS.some((p) => path132.basename(f.toLowerCase()).includes(p))
8132
8149
  );
8133
8150
  for (const file of configFiles.slice(0, 5)) {
8134
8151
  const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
8135
8152
  if (!diff) continue;
8136
- const parentDir = path122.basename(path122.dirname(file));
8137
- const baseName = path122.basename(file).replace(/\.[^.]+$/, "");
8153
+ const parentDir = path132.basename(path132.dirname(file));
8154
+ const baseName = path132.basename(file).replace(/\.[^.]+$/, "");
8138
8155
  const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
8139
8156
  matches.push({
8140
8157
  kind: "config_change",
@@ -8198,7 +8215,7 @@ async function patternDetect(input, ctx) {
8198
8215
  for (const [p, { count, tools }] of pathCounts) {
8199
8216
  if (count < HOT_FILE_MIN) continue;
8200
8217
  if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
8201
- if (CONFIG_PATTERNS.some((cp2) => path122.basename(p).includes(cp2))) continue;
8218
+ if (CONFIG_PATTERNS.some((cp2) => path132.basename(p).includes(cp2))) continue;
8202
8219
  const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
8203
8220
  matches.push({
8204
8221
  kind: "hot_file",
@@ -8245,7 +8262,7 @@ async function patternDetect(input, ctx) {
8245
8262
  void 0
8246
8263
  );
8247
8264
  if (existsSync29(file)) continue;
8248
- await mkdir82(path122.dirname(file), { recursive: true });
8265
+ await mkdir82(path132.dirname(file), { recursive: true });
8249
8266
  await writeFile14(
8250
8267
  file,
8251
8268
  serializeMemory12({ frontmatter: fm, body: match.proposed_body }),
@@ -8288,6 +8305,18 @@ function gitFileDiff(root, file, sinceDays) {
8288
8305
  return null;
8289
8306
  }
8290
8307
  }
8308
+ function suggestResolution(byId, idA, idB) {
8309
+ const a = byId.get(idA);
8310
+ const b = byId.get(idB);
8311
+ if (!a || !b) return null;
8312
+ const plan = planConflictResolution(a, b);
8313
+ return {
8314
+ keep_id: plan.keep_id,
8315
+ supersede_id: plan.supersede_id,
8316
+ reason: plan.reason,
8317
+ command: `haive memory resolve-conflict ${plan.keep_id} ${plan.supersede_id} --yes`
8318
+ };
8319
+ }
8291
8320
  var MemConflictCandidatesInputSchema = {
8292
8321
  since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
8293
8322
  types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
@@ -8309,6 +8338,7 @@ async function memConflictCandidates(input, ctx) {
8309
8338
  };
8310
8339
  }
8311
8340
  const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
8341
+ const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
8312
8342
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
8313
8343
  sinceDays: input.since_days,
8314
8344
  types: input.types,
@@ -8317,8 +8347,22 @@ async function memConflictCandidates(input, ctx) {
8317
8347
  maxScan: input.max_scan
8318
8348
  });
8319
8349
  const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
8350
+ const enrichedPairs = pairs.map((p) => ({
8351
+ ...p,
8352
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
8353
+ }));
8354
+ const enrichedTopicStatusPairs = topicStatusPairs.map((p) => ({
8355
+ ...p,
8356
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
8357
+ }));
8320
8358
  const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
8321
- return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
8359
+ return {
8360
+ pairs: enrichedPairs,
8361
+ topic_status_pairs: enrichedTopicStatusPairs,
8362
+ scanned,
8363
+ truncated,
8364
+ notice
8365
+ };
8322
8366
  }
8323
8367
  var MemResolveProjectInputSchema = {
8324
8368
  cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
@@ -8632,7 +8676,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
8632
8676
  };
8633
8677
  }
8634
8678
  var SERVER_NAME = "haive";
8635
- var SERVER_VERSION = "0.21.0";
8679
+ var SERVER_VERSION = "0.24.0";
8636
8680
  function jsonResult(data) {
8637
8681
  return {
8638
8682
  content: [
@@ -9666,7 +9710,7 @@ function registerMcp(program2) {
9666
9710
  import { spawnSync as spawnSync4 } from "child_process";
9667
9711
  import { readFile as readFile10, writeFile as writeFile15, mkdir as mkdir11 } from "fs/promises";
9668
9712
  import { existsSync as existsSync33 } from "fs";
9669
- import path16 from "path";
9713
+ import path17 from "path";
9670
9714
  import "commander";
9671
9715
  import {
9672
9716
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
@@ -9860,10 +9904,10 @@ function registerSync(program2) {
9860
9904
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
9861
9905
  let bridgeTargets;
9862
9906
  if (opts.bridgeFile) {
9863
- bridgeTargets = [path16.resolve(opts.bridgeFile)];
9907
+ bridgeTargets = [path17.resolve(opts.bridgeFile)];
9864
9908
  } else {
9865
- const agentsMd = path16.join(root, "AGENTS.md");
9866
- bridgeTargets = [path16.join(root, "CLAUDE.md")];
9909
+ const agentsMd = path17.join(root, "AGENTS.md");
9910
+ bridgeTargets = [path17.join(root, "CLAUDE.md")];
9867
9911
  if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
9868
9912
  }
9869
9913
  for (const bridgeFile of bridgeTargets) {
@@ -9995,10 +10039,10 @@ Wait for **explicit confirmation** before acting.
9995
10039
  topic: `dep-bump-${slugParts}`
9996
10040
  });
9997
10041
  if (!dryRun) {
9998
- const teamDir = path16.join(paths.memoriesDir, "team");
10042
+ const teamDir = path17.join(paths.memoriesDir, "team");
9999
10043
  await mkdir11(teamDir, { recursive: true });
10000
10044
  await writeFile15(
10001
- path16.join(teamDir, `${fm.id}.md`),
10045
+ path17.join(teamDir, `${fm.id}.md`),
10002
10046
  serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
10003
10047
  "utf8"
10004
10048
  );
@@ -10064,10 +10108,10 @@ Wait for **explicit confirmation** before acting.
10064
10108
  topic: `contract-breaking-${diff.contract}`
10065
10109
  });
10066
10110
  if (!dryRun) {
10067
- const teamDir = path16.join(paths.memoriesDir, "team");
10111
+ const teamDir = path17.join(paths.memoriesDir, "team");
10068
10112
  await mkdir11(teamDir, { recursive: true });
10069
10113
  await writeFile15(
10070
- path16.join(teamDir, `${fm.id}.md`),
10114
+ path17.join(teamDir, `${fm.id}.md`),
10071
10115
  serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
10072
10116
  "utf8"
10073
10117
  );
@@ -10190,11 +10234,11 @@ ${BRIDGE_END}`;
10190
10234
  const startIdx = existing.indexOf(BRIDGE_START);
10191
10235
  const endIdx = existing.indexOf(BRIDGE_END);
10192
10236
  if (startIdx !== -1 && endIdx === -1) {
10193
- ui.warn(`${path16.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
10237
+ ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
10194
10238
  return;
10195
10239
  }
10196
10240
  if (startIdx === -1 && endIdx !== -1) {
10197
- ui.warn(`${path16.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
10241
+ ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
10198
10242
  return;
10199
10243
  }
10200
10244
  let updated;
@@ -10202,14 +10246,14 @@ ${BRIDGE_END}`;
10202
10246
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
10203
10247
  } else {
10204
10248
  if (!fileExists && !quiet) {
10205
- ui.info(`Creating ${path16.relative(root, bridgeFile)} with haive memory block.`);
10249
+ ui.info(`Creating ${path17.relative(root, bridgeFile)} with haive memory block.`);
10206
10250
  }
10207
10251
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
10208
10252
  }
10209
10253
  await writeFile15(bridgeFile, updated, "utf8");
10210
10254
  if (!quiet) {
10211
10255
  console.log(
10212
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path16.relative(root, bridgeFile)}`)
10256
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path17.relative(root, bridgeFile)}`)
10213
10257
  );
10214
10258
  }
10215
10259
  }
@@ -10236,7 +10280,7 @@ function collectSinceChanges(root, ref) {
10236
10280
  import { createHash as createHash2 } from "crypto";
10237
10281
  import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile16 } from "fs/promises";
10238
10282
  import { existsSync as existsSync34 } from "fs";
10239
- import path17 from "path";
10283
+ import path18 from "path";
10240
10284
  import "commander";
10241
10285
  import {
10242
10286
  buildFrontmatter as buildFrontmatter7,
@@ -10294,7 +10338,7 @@ function registerMemoryAdd(memory2) {
10294
10338
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
10295
10339
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
10296
10340
  if (anchorPaths.length > 0) {
10297
- const missing = anchorPaths.filter((p) => !existsSync34(path17.resolve(root, p)));
10341
+ const missing = anchorPaths.filter((p) => !existsSync34(path18.resolve(root, p)));
10298
10342
  if (missing.length > 0) {
10299
10343
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
10300
10344
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -10358,7 +10402,7 @@ TODO \u2014 write the memory body.
10358
10402
  const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
10359
10403
  if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
10360
10404
  await writeFile16(topicMatch.filePath, serializeMemory14({ frontmatter: newFrontmatter, body }), "utf8");
10361
- ui.success(`Updated (topic upsert) ${path17.relative(root, topicMatch.filePath)}`);
10405
+ ui.success(`Updated (topic upsert) ${path18.relative(root, topicMatch.filePath)}`);
10362
10406
  ui.info(`id=${fm.id} revision=${revisionCount}`);
10363
10407
  if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
10364
10408
  await runPostMemoryAutopilot(root, paths, config);
@@ -10382,7 +10426,7 @@ TODO \u2014 write the memory body.
10382
10426
  activation
10383
10427
  });
10384
10428
  const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
10385
- await mkdir12(path17.dirname(file), { recursive: true });
10429
+ await mkdir12(path18.dirname(file), { recursive: true });
10386
10430
  if (existsSync34(file)) {
10387
10431
  ui.error(`Memory already exists at ${file}`);
10388
10432
  process.exitCode = 1;
@@ -10401,7 +10445,7 @@ TODO \u2014 write the memory body.
10401
10445
  }
10402
10446
  }
10403
10447
  await writeFile16(file, serializeMemory14({ frontmatter, body }), "utf8");
10404
- ui.success(`Created ${path17.relative(root, file)}`);
10448
+ ui.success(`Created ${path18.relative(root, file)}`);
10405
10449
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
10406
10450
  if (frontmatter.sensor?.autogen) {
10407
10451
  ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(frontmatter.sensor.pattern)}`);
@@ -10481,7 +10525,7 @@ function slugify(value) {
10481
10525
 
10482
10526
  // src/commands/memory-list.ts
10483
10527
  import { existsSync as existsSync35 } from "fs";
10484
- import path18 from "path";
10528
+ import path19 from "path";
10485
10529
  import "commander";
10486
10530
  import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
10487
10531
 
@@ -10534,7 +10578,7 @@ function registerMemoryList(memory2) {
10534
10578
  );
10535
10579
  const title = mem.body.match(/^#\s+(.+)$/m)?.[1]?.trim();
10536
10580
  if (title && title !== fm.id) console.log(` ${title}`);
10537
- console.log(` ${ui.dim(path18.relative(root, filePath))}`);
10581
+ console.log(` ${ui.dim(path19.relative(root, filePath))}`);
10538
10582
  }
10539
10583
  const totalLabel = clipped > 0 ? `
10540
10584
  ${displayed.length} of ${filtered.length} memories shown (use --limit to adjust)` : `
@@ -10573,7 +10617,7 @@ function matchesFilters(loaded, opts) {
10573
10617
  // src/commands/memory-promote.ts
10574
10618
  import { mkdir as mkdir13, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
10575
10619
  import { existsSync as existsSync36 } from "fs";
10576
- import path19 from "path";
10620
+ import path20 from "path";
10577
10621
  import "commander";
10578
10622
  import {
10579
10623
  findProjectRoot as findProjectRoot15,
@@ -10620,11 +10664,11 @@ function registerMemoryPromote(memory2) {
10620
10664
  body: found.memory.body
10621
10665
  };
10622
10666
  const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
10623
- await mkdir13(path19.dirname(newPath), { recursive: true });
10667
+ await mkdir13(path20.dirname(newPath), { recursive: true });
10624
10668
  await writeFile17(newPath, serializeMemory15(updated), "utf8");
10625
10669
  await unlink2(found.filePath);
10626
10670
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
10627
- ui.info(`Now at ${path19.relative(root, newPath)}`);
10671
+ ui.info(`Now at ${path20.relative(root, newPath)}`);
10628
10672
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
10629
10673
  });
10630
10674
  }
@@ -10632,7 +10676,7 @@ function registerMemoryPromote(memory2) {
10632
10676
  // src/commands/memory-approve.ts
10633
10677
  import { existsSync as existsSync37 } from "fs";
10634
10678
  import { writeFile as writeFile18 } from "fs/promises";
10635
- import path20 from "path";
10679
+ import path21 from "path";
10636
10680
  import "commander";
10637
10681
  import {
10638
10682
  findProjectRoot as findProjectRoot16,
@@ -10696,14 +10740,14 @@ function registerMemoryApprove(memory2) {
10696
10740
  };
10697
10741
  await writeFile18(found.filePath, serializeMemory16(next), "utf8");
10698
10742
  ui.success(`Approved ${id} (status=validated)`);
10699
- ui.info(path20.relative(root, found.filePath));
10743
+ ui.info(path21.relative(root, found.filePath));
10700
10744
  });
10701
10745
  }
10702
10746
 
10703
10747
  // src/commands/memory-update.ts
10704
10748
  import { readFile as readFile12, writeFile as writeFile19 } from "fs/promises";
10705
10749
  import { existsSync as existsSync38 } from "fs";
10706
- import path21 from "path";
10750
+ import path23 from "path";
10707
10751
  import "commander";
10708
10752
  import {
10709
10753
  findProjectRoot as findProjectRoot17,
@@ -10782,7 +10826,7 @@ function registerMemoryUpdate(memory2) {
10782
10826
  serializeMemory17({ frontmatter: newFrontmatter, body: newBody }),
10783
10827
  "utf8"
10784
10828
  );
10785
- ui.success(`Updated ${path21.relative(root, loaded.filePath)}`);
10829
+ ui.success(`Updated ${path23.relative(root, loaded.filePath)}`);
10786
10830
  ui.info(`fields: ${updated.join(", ")}`);
10787
10831
  });
10788
10832
  }
@@ -10803,7 +10847,7 @@ function parseCsv3(value) {
10803
10847
  // src/commands/memory-auto-promote.ts
10804
10848
  import { writeFile as writeFile20 } from "fs/promises";
10805
10849
  import { existsSync as existsSync39 } from "fs";
10806
- import path23 from "path";
10850
+ import path24 from "path";
10807
10851
  import "commander";
10808
10852
  import {
10809
10853
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
@@ -10848,7 +10892,7 @@ function registerMemoryAutoPromote(memory2) {
10848
10892
  console.log(
10849
10893
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
10850
10894
  );
10851
- console.log(` ${ui.dim(path23.relative(root, filePath))}`);
10895
+ console.log(` ${ui.dim(path24.relative(root, filePath))}`);
10852
10896
  if (opts.apply) {
10853
10897
  const next = {
10854
10898
  frontmatter: { ...mem.frontmatter, status: "validated" },
@@ -10867,7 +10911,7 @@ function registerMemoryAutoPromote(memory2) {
10867
10911
  import { spawn as spawn3 } from "child_process";
10868
10912
  import { existsSync as existsSync40 } from "fs";
10869
10913
  import { readFile as readFile13 } from "fs/promises";
10870
- import path24 from "path";
10914
+ import path25 from "path";
10871
10915
  import "commander";
10872
10916
  import {
10873
10917
  findProjectRoot as findProjectRoot19,
@@ -10891,7 +10935,7 @@ function registerMemoryEdit(memory2) {
10891
10935
  return;
10892
10936
  }
10893
10937
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
10894
- ui.info(`Opening ${path24.relative(root, found.filePath)} with ${editor}\u2026`);
10938
+ ui.info(`Opening ${path25.relative(root, found.filePath)} with ${editor}\u2026`);
10895
10939
  const code = await runEditor(editor, found.filePath);
10896
10940
  if (code !== 0) {
10897
10941
  ui.warn(`Editor exited with status ${code}.`);
@@ -10919,7 +10963,7 @@ function runEditor(editor, file) {
10919
10963
 
10920
10964
  // src/commands/memory-for-files.ts
10921
10965
  import { existsSync as existsSync41 } from "fs";
10922
- import path25 from "path";
10966
+ import path26 from "path";
10923
10967
  import "commander";
10924
10968
  import {
10925
10969
  deriveConfidence as deriveConfidence9,
@@ -11041,13 +11085,13 @@ function printGroup(root, label, loaded, usage) {
11041
11085
  const u = getUsage13(usage, fm.id);
11042
11086
  const conf = deriveConfidence9(fm, u);
11043
11087
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
11044
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
11088
+ console.log(` ${ui.dim(path26.relative(root, filePath))}`);
11045
11089
  }
11046
11090
  }
11047
11091
 
11048
11092
  // src/commands/memory-hot.ts
11049
11093
  import { existsSync as existsSync43 } from "fs";
11050
- import path26 from "path";
11094
+ import path27 from "path";
11051
11095
  import "commander";
11052
11096
  import {
11053
11097
  findProjectRoot as findProjectRoot21,
@@ -11089,7 +11133,7 @@ function registerMemoryHot(memory2) {
11089
11133
  console.log(
11090
11134
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
11091
11135
  );
11092
- console.log(` ${ui.dim(path26.relative(root, filePath))}`);
11136
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
11093
11137
  }
11094
11138
  ui.info(
11095
11139
  `${candidates.length} hot (read \u2265${threshold}\xD7) \u2014 agents rely on these; promote with \`haive memory promote <id>\`.
@@ -11101,7 +11145,7 @@ function registerMemoryHot(memory2) {
11101
11145
  // src/commands/memory-tried.ts
11102
11146
  import { mkdir as mkdir14, writeFile as writeFile21 } from "fs/promises";
11103
11147
  import { existsSync as existsSync44 } from "fs";
11104
- import path27 from "path";
11148
+ import path28 from "path";
11105
11149
  import "commander";
11106
11150
  import {
11107
11151
  buildFrontmatter as buildFrontmatter8,
@@ -11157,16 +11201,22 @@ function registerMemoryTried(memory2) {
11157
11201
  frontmatter.sensor = sensor;
11158
11202
  }
11159
11203
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
11160
- await mkdir14(path27.dirname(file), { recursive: true });
11204
+ await mkdir14(path28.dirname(file), { recursive: true });
11161
11205
  if (existsSync44(file)) {
11162
11206
  ui.error(`Memory already exists at ${file}`);
11163
11207
  process.exitCode = 1;
11164
11208
  return;
11165
11209
  }
11166
11210
  await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
11167
- ui.success(`Recorded: ${path27.relative(root, file)}`);
11211
+ ui.success(`Recorded: ${path28.relative(root, file)}`);
11168
11212
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
11169
- if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
11213
+ if (sensor) {
11214
+ ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
11215
+ } else {
11216
+ ui.warn(
11217
+ frontmatter.anchor.paths.length === 0 ? "No sensor generated (no --paths) \u2014 lesson is briefed but NOT enforced. Add --paths to close the loop." : "No sensor could be derived from the wording \u2014 lesson is briefed but NOT enforced. Use a concrete forbidden token to enable a gate block."
11218
+ );
11219
+ }
11170
11220
  });
11171
11221
  }
11172
11222
  function parseCsv4(value) {
@@ -11177,7 +11227,7 @@ function parseCsv4(value) {
11177
11227
  // src/commands/memory-seed.ts
11178
11228
  import { readFile as readFile14 } from "fs/promises";
11179
11229
  import { existsSync as existsSync45 } from "fs";
11180
- import path28 from "path";
11230
+ import path29 from "path";
11181
11231
  import "commander";
11182
11232
  import {
11183
11233
  findProjectRoot as findProjectRoot23,
@@ -11186,7 +11236,7 @@ import {
11186
11236
  } from "@hiveai/core";
11187
11237
  async function readDependencyMap(root) {
11188
11238
  try {
11189
- const raw = await readFile14(path28.join(root, "package.json"), "utf8");
11239
+ const raw = await readFile14(path29.join(root, "package.json"), "utf8");
11190
11240
  const pkg = JSON.parse(raw);
11191
11241
  return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
11192
11242
  } catch {
@@ -11273,7 +11323,7 @@ function registerMemorySeed(memory2) {
11273
11323
 
11274
11324
  // src/commands/memory-pending.ts
11275
11325
  import { existsSync as existsSync46 } from "fs";
11276
- import path29 from "path";
11326
+ import path30 from "path";
11277
11327
  import "commander";
11278
11328
  import {
11279
11329
  findProjectRoot as findProjectRoot24,
@@ -11319,7 +11369,7 @@ function registerMemoryPending(memory2) {
11319
11369
  console.log(
11320
11370
  ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
11321
11371
  );
11322
- console.log(` ${ui.dim(path29.relative(root, filePath))}`);
11372
+ console.log(` ${ui.dim(path30.relative(root, filePath))}`);
11323
11373
  }
11324
11374
  if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
11325
11375
  console.log();
@@ -11334,7 +11384,7 @@ function registerMemoryPending(memory2) {
11334
11384
  console.log(
11335
11385
  ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
11336
11386
  );
11337
- console.log(` ${ui.dim(path29.relative(root, filePath))}`);
11387
+ console.log(` ${ui.dim(path30.relative(root, filePath))}`);
11338
11388
  }
11339
11389
  console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
11340
11390
  }
@@ -11344,7 +11394,7 @@ function registerMemoryPending(memory2) {
11344
11394
 
11345
11395
  // src/commands/memory-query.ts
11346
11396
  import { existsSync as existsSync47 } from "fs";
11347
- import path30 from "path";
11397
+ import path31 from "path";
11348
11398
  import "commander";
11349
11399
  import {
11350
11400
  extractSnippet as extractSnippet2,
@@ -11401,7 +11451,7 @@ function registerMemoryQuery(memory2) {
11401
11451
  const fm = mem.frontmatter;
11402
11452
  const statusBadge = ui.statusBadge(fm.status);
11403
11453
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
11404
- console.log(` ${ui.dim(path30.relative(root, filePath))}`);
11454
+ console.log(` ${ui.dim(path31.relative(root, filePath))}`);
11405
11455
  const snippet = extractSnippet2(mem.body, snippetNeedle);
11406
11456
  if (snippet) console.log(` ${snippet}`);
11407
11457
  }
@@ -11426,7 +11476,7 @@ import {
11426
11476
  loadUsageIndex as loadUsageIndex18,
11427
11477
  recordRejection as recordRejection3,
11428
11478
  resolveHaivePaths as resolveHaivePaths23,
11429
- saveUsageIndex as saveUsageIndex5,
11479
+ saveUsageIndex as saveUsageIndex4,
11430
11480
  serializeMemory as serializeMemory20
11431
11481
  } from "@hiveai/core";
11432
11482
  function registerMemoryReject(memory2) {
@@ -11459,7 +11509,7 @@ function registerMemoryReject(memory2) {
11459
11509
  );
11460
11510
  const idx = await loadUsageIndex18(paths);
11461
11511
  recordRejection3(idx, id, opts.reason ?? null);
11462
- await saveUsageIndex5(paths, idx);
11512
+ await saveUsageIndex4(paths, idx);
11463
11513
  const u = idx.by_id[id];
11464
11514
  ui.success(
11465
11515
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -11471,14 +11521,14 @@ function registerMemoryReject(memory2) {
11471
11521
  // src/commands/memory-rm.ts
11472
11522
  import { existsSync as existsSync49 } from "fs";
11473
11523
  import { unlink as unlink3 } from "fs/promises";
11474
- import path31 from "path";
11524
+ import path33 from "path";
11475
11525
  import { createInterface as createInterface2 } from "readline/promises";
11476
11526
  import "commander";
11477
11527
  import {
11478
11528
  findProjectRoot as findProjectRoot27,
11479
11529
  loadUsageIndex as loadUsageIndex19,
11480
11530
  resolveHaivePaths as resolveHaivePaths24,
11481
- saveUsageIndex as saveUsageIndex6
11531
+ saveUsageIndex as saveUsageIndex5
11482
11532
  } from "@hiveai/core";
11483
11533
  function registerMemoryRm(memory2) {
11484
11534
  memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").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) => {
@@ -11496,7 +11546,7 @@ function registerMemoryRm(memory2) {
11496
11546
  process.exitCode = 1;
11497
11547
  return;
11498
11548
  }
11499
- const rel = path31.relative(root, found.filePath);
11549
+ const rel = path33.relative(root, found.filePath);
11500
11550
  if (!opts.yes) {
11501
11551
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
11502
11552
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -11512,7 +11562,7 @@ function registerMemoryRm(memory2) {
11512
11562
  const idx = await loadUsageIndex19(paths);
11513
11563
  if (idx.by_id[id]) {
11514
11564
  delete idx.by_id[id];
11515
- await saveUsageIndex6(paths, idx);
11565
+ await saveUsageIndex5(paths, idx);
11516
11566
  ui.info("Removed usage entry");
11517
11567
  }
11518
11568
  }
@@ -11522,7 +11572,7 @@ function registerMemoryRm(memory2) {
11522
11572
  // src/commands/memory-show.ts
11523
11573
  import { existsSync as existsSync50 } from "fs";
11524
11574
  import { readFile as readFile15 } from "fs/promises";
11525
- import path33 from "path";
11575
+ import path34 from "path";
11526
11576
  import "commander";
11527
11577
  import {
11528
11578
  deriveConfidence as deriveConfidence10,
@@ -11564,7 +11614,7 @@ function registerMemoryShow(memory2) {
11564
11614
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
11565
11615
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
11566
11616
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
11567
- console.log(`${ui.dim("file:")} ${path33.relative(root, found.filePath)}`);
11617
+ console.log(`${ui.dim("file:")} ${path34.relative(root, found.filePath)}`);
11568
11618
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
11569
11619
  console.log(ui.dim("anchor:"));
11570
11620
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -11580,7 +11630,7 @@ function registerMemoryShow(memory2) {
11580
11630
 
11581
11631
  // src/commands/memory-stats.ts
11582
11632
  import { existsSync as existsSync51 } from "fs";
11583
- import path34 from "path";
11633
+ import path35 from "path";
11584
11634
  import "commander";
11585
11635
  import {
11586
11636
  deriveConfidence as deriveConfidence11,
@@ -11618,7 +11668,7 @@ function registerMemoryStats(memory2) {
11618
11668
  console.log(
11619
11669
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
11620
11670
  );
11621
- console.log(` ${ui.dim(path34.relative(root, filePath))}`);
11671
+ console.log(` ${ui.dim(path35.relative(root, filePath))}`);
11622
11672
  }
11623
11673
  });
11624
11674
  }
@@ -11725,7 +11775,7 @@ import {
11725
11775
  recordRejection as recordRejection4,
11726
11776
  recommendFeedbackAdjustment as recommendFeedbackAdjustment2,
11727
11777
  resolveHaivePaths as resolveHaivePaths28,
11728
- saveUsageIndex as saveUsageIndex7,
11778
+ saveUsageIndex as saveUsageIndex6,
11729
11779
  serializeMemory as serializeMemory21
11730
11780
  } from "@hiveai/core";
11731
11781
  function registerMemoryFeedback(memory2) {
@@ -11755,7 +11805,7 @@ function registerMemoryFeedback(memory2) {
11755
11805
  const outcome = opts.applied ? "applied" : "rejected";
11756
11806
  if (opts.applied) recordApplied2(index, id);
11757
11807
  else recordRejection4(index, id, opts.reason ?? null);
11758
- await saveUsageIndex7(paths, index);
11808
+ await saveUsageIndex6(paths, index);
11759
11809
  const usage = getUsage19(index, id);
11760
11810
  const adjustment = opts.rejected ? recommendFeedbackAdjustment2(target.memory.frontmatter, usage) : { action: "none", reason: "No automatic adjustment needed." };
11761
11811
  const adjustedFrontmatter = applyFeedbackAdjustment2(target.memory.frontmatter, adjustment);
@@ -11781,7 +11831,7 @@ function registerMemoryFeedback(memory2) {
11781
11831
  // src/commands/memory-verify.ts
11782
11832
  import { writeFile as writeFile25 } from "fs/promises";
11783
11833
  import { existsSync as existsSync55 } from "fs";
11784
- import path35 from "path";
11834
+ import path36 from "path";
11785
11835
  import "commander";
11786
11836
  import {
11787
11837
  findProjectRoot as findProjectRoot32,
@@ -11823,7 +11873,7 @@ function registerMemoryVerify(memory2) {
11823
11873
  for (const { memory: mem, filePath } of targets) {
11824
11874
  const result = await verifyAnchor3(mem, { projectRoot: root });
11825
11875
  const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
11826
- const rel = path35.relative(root, filePath);
11876
+ const rel = path36.relative(root, filePath);
11827
11877
  if (!isAnchored) {
11828
11878
  anchorlessIds.push(mem.frontmatter.id);
11829
11879
  entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
@@ -11968,7 +12018,7 @@ function registerMemoryImport(memory2) {
11968
12018
  // src/commands/memory-import-changelog.ts
11969
12019
  import { existsSync as existsSync57 } from "fs";
11970
12020
  import { readFile as readFile17, mkdir as mkdir15, writeFile as writeFile26 } from "fs/promises";
11971
- import path36 from "path";
12021
+ import path37 from "path";
11972
12022
  import "commander";
11973
12023
  import {
11974
12024
  buildFrontmatter as buildFrontmatter9,
@@ -12040,7 +12090,7 @@ function registerMemoryImportChangelog(memory2) {
12040
12090
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
12041
12091
  const root = findProjectRoot34(opts.dir);
12042
12092
  const paths = resolveHaivePaths31(root);
12043
- const changelogPath = path36.resolve(root, opts.fromChangelog);
12093
+ const changelogPath = path37.resolve(root, opts.fromChangelog);
12044
12094
  if (!existsSync57(changelogPath)) {
12045
12095
  ui.error(`CHANGELOG not found: ${changelogPath}`);
12046
12096
  process.exitCode = 1;
@@ -12061,9 +12111,9 @@ function registerMemoryImportChangelog(memory2) {
12061
12111
  entries = entries.filter((e) => requested.includes(e.version));
12062
12112
  }
12063
12113
  }
12064
- const pkgName = opts.package ?? path36.basename(path36.dirname(changelogPath));
12114
+ const pkgName = opts.package ?? path37.basename(path37.dirname(changelogPath));
12065
12115
  const scope = opts.scope ?? "team";
12066
- const teamDir = path36.join(paths.memoriesDir, scope);
12116
+ const teamDir = path37.join(paths.memoriesDir, scope);
12067
12117
  await mkdir15(teamDir, { recursive: true });
12068
12118
  let saved = 0;
12069
12119
  for (const entry of entries) {
@@ -12086,7 +12136,7 @@ function registerMemoryImportChangelog(memory2) {
12086
12136
  lines.push("");
12087
12137
  }
12088
12138
  lines.push(
12089
- `**Source:** \`${path36.relative(root, changelogPath)}\`
12139
+ `**Source:** \`${path37.relative(root, changelogPath)}\`
12090
12140
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
12091
12141
  );
12092
12142
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -12101,11 +12151,11 @@ function registerMemoryImportChangelog(memory2) {
12101
12151
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
12102
12152
  `v${entry.version}`
12103
12153
  ],
12104
- paths: [path36.relative(root, changelogPath)],
12154
+ paths: [path37.relative(root, changelogPath)],
12105
12155
  topic: `changelog-${pkgName}-${entry.version}`
12106
12156
  });
12107
12157
  await writeFile26(
12108
- path36.join(teamDir, `${fm.id}.md`),
12158
+ path37.join(teamDir, `${fm.id}.md`),
12109
12159
  serializeMemory24({ frontmatter: fm, body: lines.join("\n") }),
12110
12160
  "utf8"
12111
12161
  );
@@ -12130,7 +12180,7 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
12130
12180
  // src/commands/memory-digest.ts
12131
12181
  import { existsSync as existsSync58 } from "fs";
12132
12182
  import { writeFile as writeFile27 } from "fs/promises";
12133
- import path37 from "path";
12183
+ import path38 from "path";
12134
12184
  import "commander";
12135
12185
  import {
12136
12186
  deriveConfidence as deriveConfidence12,
@@ -12225,7 +12275,7 @@ function registerMemoryDigest(program2) {
12225
12275
  );
12226
12276
  const digest = lines.join("\n");
12227
12277
  if (opts.out) {
12228
- const outPath = path37.resolve(process.cwd(), opts.out);
12278
+ const outPath = path38.resolve(process.cwd(), opts.out);
12229
12279
  await writeFile27(outPath, digest, "utf8");
12230
12280
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
12231
12281
  } else {
@@ -12238,7 +12288,7 @@ function registerMemoryDigest(program2) {
12238
12288
  import { writeFile as writeFile28, mkdir as mkdir16, readFile as readFile18, rm as rm2 } from "fs/promises";
12239
12289
  import { existsSync as existsSync59 } from "fs";
12240
12290
  import { spawn as spawn4 } from "child_process";
12241
- import path38 from "path";
12291
+ import path39 from "path";
12242
12292
  import "commander";
12243
12293
  import {
12244
12294
  buildFrontmatter as buildFrontmatter10,
@@ -12253,7 +12303,7 @@ import {
12253
12303
  summarizeCaughtForYou
12254
12304
  } from "@hiveai/core";
12255
12305
  async function buildAutoRecap(paths) {
12256
- const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
12306
+ const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
12257
12307
  if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
12258
12308
  const raw = await readFile18(obsFile, "utf8").catch(() => "");
12259
12309
  if (!raw.trim()) return await buildGitAutoRecap(paths);
@@ -12422,7 +12472,7 @@ function runGit(cwd, args) {
12422
12472
  });
12423
12473
  }
12424
12474
  async function observationStart(paths) {
12425
- const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
12475
+ const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
12426
12476
  if (!existsSync59(obsFile)) return null;
12427
12477
  const raw = await readFile18(obsFile, "utf8").catch(() => "");
12428
12478
  let first = null;
@@ -12511,14 +12561,14 @@ function registerSessionEnd(session2) {
12511
12561
  });
12512
12562
  const topic = recapTopic2(scope, opts.module);
12513
12563
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
12514
- const missingPaths = filesTouched.filter((p) => !existsSync59(path38.resolve(root, p)));
12564
+ const missingPaths = filesTouched.filter((p) => !existsSync59(path39.resolve(root, p)));
12515
12565
  if (missingPaths.length > 0 && !opts.quiet) {
12516
12566
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
12517
12567
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
12518
12568
  }
12519
12569
  const cleanupObservations = async () => {
12520
12570
  if (!opts.auto) return;
12521
- const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
12571
+ const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
12522
12572
  if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
12523
12573
  });
12524
12574
  };
@@ -12543,7 +12593,7 @@ function registerSessionEnd(session2) {
12543
12593
  await cleanupObservations();
12544
12594
  if (!opts.quiet) {
12545
12595
  ui.success(`Session recap updated (revision #${revisionCount})`);
12546
- ui.info(`id=${fm.id} file=${path38.relative(root, topicMatch.filePath)}`);
12596
+ ui.info(`id=${fm.id} file=${path39.relative(root, topicMatch.filePath)}`);
12547
12597
  await printCaughtForYou(paths, caughtSince, opts.quiet);
12548
12598
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
12549
12599
  }
@@ -12561,12 +12611,12 @@ function registerSessionEnd(session2) {
12561
12611
  status: "validated"
12562
12612
  });
12563
12613
  const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
12564
- await mkdir16(path38.dirname(file), { recursive: true });
12614
+ await mkdir16(path39.dirname(file), { recursive: true });
12565
12615
  await writeFile28(file, serializeMemory25({ frontmatter, body }), "utf8");
12566
12616
  await cleanupObservations();
12567
12617
  if (!opts.quiet) {
12568
12618
  ui.success(`Session recap created`);
12569
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path38.relative(root, file)}`);
12619
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path39.relative(root, file)}`);
12570
12620
  await printCaughtForYou(paths, caughtSince, opts.quiet);
12571
12621
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
12572
12622
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
@@ -12579,8 +12629,8 @@ function parseCsv5(value) {
12579
12629
  }
12580
12630
  function normalizeAnchorPath(root, filePath) {
12581
12631
  if (!filePath) return filePath;
12582
- if (!path38.isAbsolute(filePath)) return filePath;
12583
- const rel = path38.relative(root, filePath);
12632
+ if (!path39.isAbsolute(filePath)) return filePath;
12633
+ const rel = path39.relative(root, filePath);
12584
12634
  if (rel.startsWith("..")) return filePath;
12585
12635
  return rel;
12586
12636
  }
@@ -12588,7 +12638,7 @@ function normalizeAnchorPath(root, filePath) {
12588
12638
  // src/commands/snapshot.ts
12589
12639
  import { existsSync as existsSync60 } from "fs";
12590
12640
  import { readdir as readdir4 } from "fs/promises";
12591
- import path39 from "path";
12641
+ import path40 from "path";
12592
12642
  import "commander";
12593
12643
  import {
12594
12644
  diffContract,
@@ -12627,7 +12677,7 @@ function registerSnapshot(program2) {
12627
12677
  return;
12628
12678
  }
12629
12679
  if (opts.list) {
12630
- const contractsDir = path39.join(paths.haiveDir, "contracts");
12680
+ const contractsDir = path40.join(paths.haiveDir, "contracts");
12631
12681
  if (!existsSync60(contractsDir)) {
12632
12682
  console.log(ui.dim("No contract snapshots found."));
12633
12683
  return;
@@ -12683,7 +12733,7 @@ function registerSnapshot(program2) {
12683
12733
  return;
12684
12734
  }
12685
12735
  const contractPath = opts.contract;
12686
- const name = opts.name ?? path39.basename(contractPath, path39.extname(contractPath));
12736
+ const name = opts.name ?? path40.basename(contractPath, path40.extname(contractPath));
12687
12737
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
12688
12738
  const contract = { name, path: contractPath, format };
12689
12739
  try {
@@ -12738,8 +12788,8 @@ async function runDiff(root, haiveDir, contract) {
12738
12788
  }
12739
12789
  }
12740
12790
  function detectFormat(filePath) {
12741
- const ext = path39.extname(filePath).toLowerCase();
12742
- const base = path39.basename(filePath).toLowerCase();
12791
+ const ext = path40.extname(filePath).toLowerCase();
12792
+ const base = path40.basename(filePath).toLowerCase();
12743
12793
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
12744
12794
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
12745
12795
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -12754,7 +12804,7 @@ function detectFormat(filePath) {
12754
12804
  // src/commands/hub.ts
12755
12805
  import { existsSync as existsSync61 } from "fs";
12756
12806
  import { mkdir as mkdir17, readFile as readFile19, writeFile as writeFile29, copyFile } from "fs/promises";
12757
- import path40 from "path";
12807
+ import path41 from "path";
12758
12808
  import { spawnSync as spawnSync5 } from "child_process";
12759
12809
  import "commander";
12760
12810
  import {
@@ -12773,7 +12823,7 @@ function registerHub(program2) {
12773
12823
  hub.command("init <hubPath>").description(
12774
12824
  "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"
12775
12825
  ).action(async (hubPath) => {
12776
- const absPath = path40.resolve(hubPath);
12826
+ const absPath = path41.resolve(hubPath);
12777
12827
  await mkdir17(absPath, { recursive: true });
12778
12828
  const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
12779
12829
  if (gitCheck.status !== 0) {
@@ -12784,10 +12834,10 @@ function registerHub(program2) {
12784
12834
  return;
12785
12835
  }
12786
12836
  }
12787
- const sharedDir = path40.join(absPath, ".ai", "memories", "shared");
12837
+ const sharedDir = path41.join(absPath, ".ai", "memories", "shared");
12788
12838
  await mkdir17(sharedDir, { recursive: true });
12789
12839
  await writeFile29(
12790
- path40.join(absPath, ".ai", "README.md"),
12840
+ path41.join(absPath, ".ai", "README.md"),
12791
12841
  `# hAIve Team Knowledge Hub
12792
12842
 
12793
12843
  This repo is a shared knowledge hub for hAIve.
@@ -12809,7 +12859,7 @@ haive hub pull # import into a project
12809
12859
  "utf8"
12810
12860
  );
12811
12861
  await writeFile29(
12812
- path40.join(absPath, ".gitignore"),
12862
+ path41.join(absPath, ".gitignore"),
12813
12863
  ".ai/.cache/\n.ai/memories/personal/\n",
12814
12864
  "utf8"
12815
12865
  );
@@ -12824,7 +12874,7 @@ haive hub pull # import into a project
12824
12874
  `
12825
12875
  Next steps:
12826
12876
  1. Add hubPath to your project's .ai/haive.config.json:
12827
- { "hubPath": "${path40.relative(process.cwd(), absPath)}" }
12877
+ { "hubPath": "${path41.relative(process.cwd(), absPath)}" }
12828
12878
  2. Run \`haive hub push\` to publish your shared memories
12829
12879
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
12830
12880
  `
@@ -12853,14 +12903,14 @@ Next steps:
12853
12903
  process.exitCode = 1;
12854
12904
  return;
12855
12905
  }
12856
- const hubRoot = path40.resolve(root, config.hubPath);
12906
+ const hubRoot = path41.resolve(root, config.hubPath);
12857
12907
  if (!existsSync61(hubRoot)) {
12858
12908
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
12859
12909
  process.exitCode = 1;
12860
12910
  return;
12861
12911
  }
12862
- const projectName = path40.basename(root);
12863
- const destDir = path40.join(hubRoot, ".ai", "memories", "shared", projectName);
12912
+ const projectName = path41.basename(root);
12913
+ const destDir = path41.join(hubRoot, ".ai", "memories", "shared", projectName);
12864
12914
  await mkdir17(destDir, { recursive: true });
12865
12915
  const all = await loadMemoriesFromDir30(paths.memoriesDir);
12866
12916
  const shared = all.filter(
@@ -12879,7 +12929,7 @@ Next steps:
12879
12929
  for (const { memory: memory2 } of shared) {
12880
12930
  const fm = memory2.frontmatter;
12881
12931
  const fileName = `${fm.id}.md`;
12882
- const destPath = path40.join(destDir, fileName);
12932
+ const destPath = path41.join(destDir, fileName);
12883
12933
  await writeFile29(destPath, serializeMemory26(memory2), "utf8");
12884
12934
  pushed++;
12885
12935
  }
@@ -12887,7 +12937,7 @@ Next steps:
12887
12937
  console.log(ui.dim(` Location: ${destDir}`));
12888
12938
  if (opts.commit) {
12889
12939
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
12890
- spawnSync5("git", ["add", path40.join(".ai", "memories", "shared", projectName)], {
12940
+ spawnSync5("git", ["add", path41.join(".ai", "memories", "shared", projectName)], {
12891
12941
  cwd: hubRoot
12892
12942
  });
12893
12943
  const commit = spawnSync5("git", ["commit", "-m", message], {
@@ -12922,13 +12972,13 @@ Next steps:
12922
12972
  process.exitCode = 1;
12923
12973
  return;
12924
12974
  }
12925
- const hubRoot = path40.resolve(root, config.hubPath);
12926
- const hubSharedDir = path40.join(hubRoot, ".ai", "memories", "shared");
12975
+ const hubRoot = path41.resolve(root, config.hubPath);
12976
+ const hubSharedDir = path41.join(hubRoot, ".ai", "memories", "shared");
12927
12977
  if (!existsSync61(hubSharedDir)) {
12928
12978
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
12929
12979
  return;
12930
12980
  }
12931
- const projectName = path40.basename(root);
12981
+ const projectName = path41.basename(root);
12932
12982
  const { readdir: readdir7 } = await import("fs/promises");
12933
12983
  const projectDirs = (await readdir7(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
12934
12984
  if (projectDirs.length === 0) {
@@ -12938,16 +12988,16 @@ Next steps:
12938
12988
  let totalImported = 0;
12939
12989
  let totalUpdated = 0;
12940
12990
  for (const sourceName of projectDirs) {
12941
- const sourceDir = path40.join(hubSharedDir, sourceName);
12942
- const destDir = path40.join(paths.memoriesDir, "shared", sourceName);
12991
+ const sourceDir = path41.join(hubSharedDir, sourceName);
12992
+ const destDir = path41.join(paths.memoriesDir, "shared", sourceName);
12943
12993
  await mkdir17(destDir, { recursive: true });
12944
12994
  const sourceFiles = (await readdir7(sourceDir)).filter((f) => f.endsWith(".md"));
12945
12995
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
12946
12996
  const existingInDest = await loadDir(destDir);
12947
12997
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
12948
12998
  for (const file of sourceFiles) {
12949
- const srcPath = path40.join(sourceDir, file);
12950
- const destPath = path40.join(destDir, file);
12999
+ const srcPath = path41.join(sourceDir, file);
13000
+ const destPath = path41.join(destDir, file);
12951
13001
  const fileContent = await readFile19(srcPath, "utf8");
12952
13002
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
12953
13003
  if (!alreadyTagged) {
@@ -12978,14 +13028,14 @@ Next steps:
12978
13028
  console.log(
12979
13029
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
12980
13030
  );
12981
- const sharedDir = path40.join(paths.memoriesDir, "shared");
13031
+ const sharedDir = path41.join(paths.memoriesDir, "shared");
12982
13032
  if (existsSync61(sharedDir)) {
12983
13033
  const { readdir: readdir7 } = await import("fs/promises");
12984
13034
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
12985
13035
  console.log(`
12986
13036
  Imported from ${sources.length} source(s):`);
12987
13037
  for (const src of sources) {
12988
- const files = (await readdir7(path40.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
13038
+ const files = (await readdir7(path41.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
12989
13039
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
12990
13040
  }
12991
13041
  } else {
@@ -13010,7 +13060,7 @@ Next steps:
13010
13060
  import "commander";
13011
13061
  import { existsSync as existsSync63 } from "fs";
13012
13062
  import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
13013
- import path41 from "path";
13063
+ import path43 from "path";
13014
13064
  import {
13015
13065
  aggregateUsage,
13016
13066
  findProjectRoot as findProjectRoot39,
@@ -13082,7 +13132,7 @@ function registerStats(program2) {
13082
13132
  });
13083
13133
  }
13084
13134
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
13085
- const outAbs = path41.isAbsolute(outRelative) ? path41.resolve(outRelative) : path41.resolve(root, outRelative);
13135
+ const outAbs = path43.isAbsolute(outRelative) ? path43.resolve(outRelative) : path43.resolve(root, outRelative);
13086
13136
  const size = await usageLogSize(paths);
13087
13137
  let events = await readUsageEvents2(paths);
13088
13138
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
@@ -13117,7 +13167,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
13117
13167
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
13118
13168
  events = [];
13119
13169
  }
13120
- await mkdir18(path41.dirname(outAbs), { recursive: true });
13170
+ await mkdir18(path43.dirname(outAbs), { recursive: true });
13121
13171
  const payload = {
13122
13172
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
13123
13173
  project_root: root,
@@ -13308,7 +13358,7 @@ function summarize(name, t0, payload, notes) {
13308
13358
  // src/commands/benchmark.ts
13309
13359
  import { existsSync as existsSync64 } from "fs";
13310
13360
  import { readdir as readdir5, readFile as readFile20, writeFile as writeFile31 } from "fs/promises";
13311
- import path43 from "path";
13361
+ import path44 from "path";
13312
13362
  import "commander";
13313
13363
  import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
13314
13364
  function registerBenchmark(program2) {
@@ -13323,9 +13373,9 @@ function registerBenchmark(program2) {
13323
13373
  }
13324
13374
  const markdown = renderMarkdown(root, summary, rows);
13325
13375
  if (opts.out) {
13326
- const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
13376
+ const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
13327
13377
  await writeFile31(outFile, markdown, "utf8");
13328
- ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
13378
+ ui.success(`wrote ${path44.relative(process.cwd(), outFile)}`);
13329
13379
  return;
13330
13380
  }
13331
13381
  console.log(markdown);
@@ -13349,9 +13399,9 @@ function registerBenchmark(program2) {
13349
13399
  }
13350
13400
  function resolveBenchmarkRoot(dir) {
13351
13401
  const candidate = dir ?? "benchmarks/agent-benchmark";
13352
- if (path43.isAbsolute(candidate)) return candidate;
13402
+ if (path44.isAbsolute(candidate)) return candidate;
13353
13403
  const projectRoot = findProjectRoot41(process.cwd());
13354
- return path43.join(projectRoot, candidate);
13404
+ return path44.join(projectRoot, candidate);
13355
13405
  }
13356
13406
  async function collectRows(root) {
13357
13407
  if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
@@ -13359,8 +13409,8 @@ async function collectRows(root) {
13359
13409
  const rows = [];
13360
13410
  for (const entry of entries) {
13361
13411
  if (!entry.isDirectory()) continue;
13362
- const fixtureDir = path43.join(root, entry.name);
13363
- const reportFile = path43.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
13412
+ const fixtureDir = path44.join(root, entry.name);
13413
+ const reportFile = path44.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
13364
13414
  if (!existsSync64(reportFile)) continue;
13365
13415
  const report = await readFile20(reportFile, "utf8");
13366
13416
  rows.push(parseAgentReport(entry.name, report));
@@ -13378,7 +13428,7 @@ function parseAgentReport(fixture, report) {
13378
13428
  test_iterations: countMatches(section(report, "Test Iterations"), /Iteration\s+\d+|^- /gim),
13379
13429
  terminal_failures: countMatches(section(report, "Terminal Errors"), /fail|error|not raised|exited with code 1/gi),
13380
13430
  decision_mentions: sectionBulletCount(report, "Key Decisions"),
13381
- token_proxy: estimateTokens4(report),
13431
+ report_tokens_est: estimateTokens4(report),
13382
13432
  haive_impact: /hAIve Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report)
13383
13433
  };
13384
13434
  }
@@ -13400,7 +13450,7 @@ function summarizeGroup(rows) {
13400
13450
  test_iterations: sum("test_iterations"),
13401
13451
  terminal_failures: sum("terminal_failures"),
13402
13452
  decision_mentions: sum("decision_mentions"),
13403
- token_proxy: sum("token_proxy"),
13453
+ report_tokens_est: sum("report_tokens_est"),
13404
13454
  haive_impact_count: rows.filter((r) => r.haive_impact).length
13405
13455
  };
13406
13456
  }
@@ -13412,29 +13462,31 @@ function renderMarkdown(root, summary, rows) {
13412
13462
  "",
13413
13463
  "## Summary",
13414
13464
  "",
13415
- "| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Token proxy | hAIve impact |",
13465
+ "| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Report tokens (est, report only) | hAIve impact |",
13416
13466
  "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
13417
13467
  groupLine("hAIve", summary.haive),
13418
13468
  groupLine("Plain", summary.plain),
13419
13469
  "",
13420
13470
  "## Fixtures",
13421
13471
  "",
13422
- "| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions | Token proxy | hAIve impact |",
13472
+ "| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions | Report tokens (est, report only) | hAIve impact |",
13423
13473
  "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
13424
13474
  ...rows.map(
13425
- (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"} |`
13475
+ (row) => `| \`${row.fixture}\` | ${row.group} | ${row.commands} | ${row.files_read} | ${row.files_modified} | ${row.test_iterations} | ${row.terminal_failures} | ${row.decision_mentions} | ${row.report_tokens_est} | ${row.haive_impact ? "yes" : "no"} |`
13426
13476
  ),
13427
13477
  "",
13428
13478
  "## Reading",
13429
13479
  "",
13430
- "The token proxy is estimated from the agent report size, not from private model billing data.",
13480
+ "`Report tokens (est)` estimates the size of the agent's WRITTEN REPORT only \u2014 a verbosity proxy, NOT",
13481
+ "the agent's total token consumption. For real per-agent token/latency, capture your runner's telemetry",
13482
+ "(e.g. subagent token counts) separately; this report can't see model billing.",
13431
13483
  "Use this report to compare relative effort and decision quality, then pair it with final test results and a human review of the diffs.",
13432
13484
  ""
13433
13485
  ];
13434
13486
  return lines.join("\n");
13435
13487
  }
13436
13488
  function groupLine(label, group) {
13437
- 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} |`;
13489
+ return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.report_tokens_est} | ${group.haive_impact_count} |`;
13438
13490
  }
13439
13491
  function sectionBulletCount(markdown, title) {
13440
13492
  return countMatches(section(markdown, title), /^- |^\d+\.\s/gm);
@@ -13453,7 +13505,7 @@ function escapeRegExp(value) {
13453
13505
  // src/commands/eval.ts
13454
13506
  import { mkdir as mkdir19, readFile as readFile21, writeFile as writeFile33 } from "fs/promises";
13455
13507
  import { existsSync as existsSync65 } from "fs";
13456
- import path44 from "path";
13508
+ import path45 from "path";
13457
13509
  import "commander";
13458
13510
  import {
13459
13511
  aggregateRetrieval,
@@ -13549,7 +13601,7 @@ function registerEval(program2) {
13549
13601
  });
13550
13602
  if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
13551
13603
  }
13552
- const baselineFile = opts.baselineFile ? path44.isAbsolute(opts.baselineFile) ? opts.baselineFile : path44.join(root, opts.baselineFile) : path44.join(root, ".ai", "eval", "baseline.json");
13604
+ const baselineFile = opts.baselineFile ? path45.isAbsolute(opts.baselineFile) ? opts.baselineFile : path45.join(root, opts.baselineFile) : path45.join(root, ".ai", "eval", "baseline.json");
13553
13605
  if (opts.baseline) {
13554
13606
  const snapshot = {
13555
13607
  saved_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -13558,18 +13610,18 @@ function registerEval(program2) {
13558
13610
  report,
13559
13611
  gate_precision: gatePrecision
13560
13612
  };
13561
- await mkdir19(path44.dirname(baselineFile), { recursive: true });
13613
+ await mkdir19(path45.dirname(baselineFile), { recursive: true });
13562
13614
  await writeFile33(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
13563
- if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path44.relative(root, baselineFile)}`);
13615
+ if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path45.relative(root, baselineFile)}`);
13564
13616
  }
13565
13617
  let delta = null;
13566
13618
  let gateDelta = null;
13567
13619
  if (opts.compare || opts.regressionGate) {
13568
13620
  if (!existsSync65(baselineFile)) {
13569
13621
  if (opts.regressionGate) {
13570
- if (!opts.json) ui.info(`No baseline at ${path44.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
13622
+ if (!opts.json) ui.info(`No baseline at ${path45.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
13571
13623
  } else {
13572
- ui.error(`No baseline at ${path44.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
13624
+ ui.error(`No baseline at ${path45.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
13573
13625
  process.exitCode = 1;
13574
13626
  return;
13575
13627
  }
@@ -13586,6 +13638,7 @@ function registerEval(program2) {
13586
13638
  root,
13587
13639
  k,
13588
13640
  spec_source: resolvedSpec.source,
13641
+ provenance: { synthesized: resolvedSpec.synthesized, authored: resolvedSpec.authored },
13589
13642
  report,
13590
13643
  gate_precision: gatePrecision,
13591
13644
  ...delta ? { delta } : {},
@@ -13594,17 +13647,22 @@ function registerEval(program2) {
13594
13647
  applyExitGates(opts, report, delta, gatePrecision, gateDelta);
13595
13648
  return;
13596
13649
  }
13650
+ if (resolvedSpec.authored === 0 && resolvedSpec.synthesized > 0) {
13651
+ ui.warn(
13652
+ `All ${resolvedSpec.synthesized} case(s) are self-synthesized from your own memories (self-referential). Add hand-labeled cases in .ai/eval/spec.json, or run \`haive eval --spec <file>\`, for an independent score.`
13653
+ );
13654
+ }
13597
13655
  if (delta) {
13598
13656
  console.log(renderDelta(delta));
13599
13657
  }
13600
13658
  if (gateDelta) {
13601
13659
  console.log(renderGateDelta(gateDelta));
13602
13660
  }
13603
- const md = renderMarkdown2(root, k, resolvedSpec.source, report, gatePrecision);
13661
+ const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision);
13604
13662
  if (opts.out) {
13605
- const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
13663
+ const outFile = path45.isAbsolute(opts.out) ? opts.out : path45.join(root, opts.out);
13606
13664
  await writeFile33(outFile, md, "utf8");
13607
- ui.success(`wrote ${path44.relative(process.cwd(), outFile)}`);
13665
+ ui.success(`wrote ${path45.relative(process.cwd(), outFile)}`);
13608
13666
  } else {
13609
13667
  console.log(md);
13610
13668
  }
@@ -13683,30 +13741,39 @@ function renderGateDelta(delta) {
13683
13741
  lines.push(` rejections ${delta.rejections.baseline ?? "n/a"} \u2192 ${delta.rejections.current ?? "n/a"} ${ui.dim(`(${rejectionDelta})`)}`);
13684
13742
  return lines.join("\n");
13685
13743
  }
13744
+ function countCases(spec) {
13745
+ return (spec.retrieval?.length ?? 0) + (spec.sensors?.length ?? 0);
13746
+ }
13686
13747
  async function resolveSpec(opts, root, memoriesDir) {
13687
13748
  if (opts.spec) {
13688
- const file = path44.resolve(opts.spec);
13749
+ const file = path45.resolve(opts.spec);
13689
13750
  const raw = await readFile21(file, "utf8");
13690
- return { spec: JSON.parse(raw), source: file };
13751
+ const spec = JSON.parse(raw);
13752
+ return { spec, source: file, synthesized: 0, authored: countCases(spec) };
13691
13753
  }
13692
- const defaultSpec = path44.join(root, ".ai", "eval", "spec.json");
13754
+ const defaultSpec = path45.join(root, ".ai", "eval", "spec.json");
13693
13755
  if (existsSync65(defaultSpec)) {
13694
13756
  const raw = await readFile21(defaultSpec, "utf8");
13695
13757
  const explicit = JSON.parse(raw);
13696
13758
  const memories2 = await loadMemoriesFromDir27(memoriesDir);
13697
- const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
13759
+ const synthesized2 = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
13698
13760
  return {
13699
13761
  spec: {
13700
- retrieval: [...synthesized, ...explicit.retrieval ?? []],
13762
+ retrieval: [...synthesized2, ...explicit.retrieval ?? []],
13701
13763
  sensors: explicit.sensors ?? []
13702
13764
  },
13703
- source: ".ai/eval/spec.json + synthesized anchored retrieval"
13765
+ source: ".ai/eval/spec.json + synthesized anchored retrieval",
13766
+ synthesized: synthesized2.length,
13767
+ authored: countCases(explicit)
13704
13768
  };
13705
13769
  }
13706
13770
  const memories = await loadMemoriesFromDir27(memoriesDir);
13771
+ const synthesized = synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly });
13707
13772
  return {
13708
- spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
13709
- source: "synthesized anchored retrieval"
13773
+ spec: { retrieval: synthesized },
13774
+ source: "synthesized anchored retrieval",
13775
+ synthesized: synthesized.length,
13776
+ authored: 0
13710
13777
  };
13711
13778
  }
13712
13779
  async function runRetrieval(c, k, ctx) {
@@ -13739,12 +13806,14 @@ async function runSensorCase(c, ctx) {
13739
13806
  function pct(n) {
13740
13807
  return `${Math.round(n * 100)}%`;
13741
13808
  }
13742
- function renderMarkdown2(root, k, source, report, gatePrecision) {
13809
+ function renderMarkdown2(root, k, resolved, report, gatePrecision) {
13810
+ const provenance = resolved.authored === 0 ? `${resolved.synthesized} synthesized (self-referential \u2014 sanity floor, not ground truth)` : resolved.synthesized === 0 ? `${resolved.authored} authored (independent ground truth)` : `${resolved.authored} authored (independent) + ${resolved.synthesized} synthesized (self-referential)`;
13743
13811
  const lines = [
13744
13812
  "# hAIve eval report",
13745
13813
  "",
13746
13814
  `Project: \`${root}\` \xB7 top-k: ${k}`,
13747
- `Spec: ${source}`,
13815
+ `Spec: ${resolved.source}`,
13816
+ `Cases: ${provenance}`,
13748
13817
  "",
13749
13818
  `## Overall score: ${report.score}/100`,
13750
13819
  ""
@@ -13803,7 +13872,7 @@ function renderMarkdown2(root, k, source, report, gatePrecision) {
13803
13872
  // src/commands/memory-suggest.ts
13804
13873
  import { mkdir as mkdir20, writeFile as writeFile34 } from "fs/promises";
13805
13874
  import { existsSync as existsSync66 } from "fs";
13806
- import path45 from "path";
13875
+ import path46 from "path";
13807
13876
  import "commander";
13808
13877
  import {
13809
13878
  aggregateUsage as aggregateUsage2,
@@ -13904,13 +13973,13 @@ function registerMemorySuggest(memory2) {
13904
13973
  });
13905
13974
  const body = renderTemplate(s, fm.id, status);
13906
13975
  const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
13907
- await mkdir20(path45.dirname(file), { recursive: true });
13976
+ await mkdir20(path46.dirname(file), { recursive: true });
13908
13977
  if (existsSync66(file)) {
13909
- skipped.push({ query: s.query, reason: `file already exists at ${path45.relative(root, file)}` });
13978
+ skipped.push({ query: s.query, reason: `file already exists at ${path46.relative(root, file)}` });
13910
13979
  continue;
13911
13980
  }
13912
13981
  await writeFile34(file, serializeMemory27({ frontmatter: fm, body }), "utf8");
13913
- created.push({ id: fm.id, file: path45.relative(root, file), query: s.query });
13982
+ created.push({ id: fm.id, file: path46.relative(root, file), query: s.query });
13914
13983
  }
13915
13984
  if (opts.json) {
13916
13985
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -14010,7 +14079,7 @@ function truncate2(text, max) {
14010
14079
  // src/commands/memory-archive.ts
14011
14080
  import { existsSync as existsSync67 } from "fs";
14012
14081
  import { writeFile as writeFile35 } from "fs/promises";
14013
- import path46 from "path";
14082
+ import path47 from "path";
14014
14083
  import "commander";
14015
14084
  import {
14016
14085
  findProjectRoot as findProjectRoot44,
@@ -14054,7 +14123,7 @@ function registerMemoryArchive(memory2) {
14054
14123
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
14055
14124
  const retired = retirementSignal2(fm, mem.body);
14056
14125
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
14057
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path46.join(paths.root, p)));
14126
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path47.join(paths.root, p)));
14058
14127
  const isAnchorless = !hasAnyAnchor;
14059
14128
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
14060
14129
  const u = getUsage21(usage, fm.id);
@@ -14131,7 +14200,7 @@ function parseDays(input) {
14131
14200
  // src/commands/doctor.ts
14132
14201
  import { existsSync as existsSync68, statSync as statSync2 } from "fs";
14133
14202
  import { readFile as readFile23, stat, writeFile as writeFile36 } from "fs/promises";
14134
- import path47 from "path";
14203
+ import path48 from "path";
14135
14204
  import { execFileSync, execSync as execSync3 } from "child_process";
14136
14205
  import "commander";
14137
14206
  import {
@@ -14185,8 +14254,8 @@ function registerDoctor(program2) {
14185
14254
  fix: "haive init"
14186
14255
  });
14187
14256
  } else {
14188
- const { readFile: readFile28 } = await import("fs/promises");
14189
- const content = await readFile28(paths.projectContext, "utf8");
14257
+ const { readFile: readFile29 } = await import("fs/promises");
14258
+ const content = await readFile29(paths.projectContext, "utf8");
14190
14259
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
14191
14260
  if (isTemplate) {
14192
14261
  findings.push({
@@ -14356,12 +14425,12 @@ function registerDoctor(program2) {
14356
14425
  }
14357
14426
  }
14358
14427
  if (config.enforcement?.requireBriefingFirst) {
14359
- const claudeSettings = path47.join(root, ".claude", "settings.local.json");
14428
+ const claudeSettings = path48.join(root, ".claude", "settings.local.json");
14360
14429
  let hasClaudeEnforcement = false;
14361
14430
  if (existsSync68(claudeSettings)) {
14362
14431
  try {
14363
- const { readFile: readFile28 } = await import("fs/promises");
14364
- const raw = await readFile28(claudeSettings, "utf8");
14432
+ const { readFile: readFile29 } = await import("fs/promises");
14433
+ const raw = await readFile29(claudeSettings, "utf8");
14365
14434
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
14366
14435
  } catch {
14367
14436
  hasClaudeEnforcement = false;
@@ -14384,7 +14453,7 @@ function registerDoctor(program2) {
14384
14453
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
14385
14454
  });
14386
14455
  }
14387
- findings.push(...await collectInstallFindings(root, "0.21.0"));
14456
+ findings.push(...await collectInstallFindings(root, "0.24.0"));
14388
14457
  findings.push(...await collectToolchainFindings(root));
14389
14458
  try {
14390
14459
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -14392,7 +14461,7 @@ function registerDoctor(program2) {
14392
14461
  timeout: 3e3,
14393
14462
  stdio: ["ignore", "pipe", "ignore"]
14394
14463
  }).trim();
14395
- const cliVersion = "0.21.0";
14464
+ const cliVersion = "0.24.0";
14396
14465
  if (legacyRaw && legacyRaw !== cliVersion) {
14397
14466
  findings.push({
14398
14467
  severity: "warn",
@@ -14408,9 +14477,9 @@ npm uninstall -g @hiveai/mcp`
14408
14477
  }
14409
14478
  {
14410
14479
  const configPaths = [
14411
- path47.join(root, ".mcp.json"),
14412
- path47.join(root, ".cursor", "mcp.json"),
14413
- path47.join(root, ".vscode", "mcp.json")
14480
+ path48.join(root, ".mcp.json"),
14481
+ path48.join(root, ".cursor", "mcp.json"),
14482
+ path48.join(root, ".vscode", "mcp.json")
14414
14483
  ];
14415
14484
  const staleConfigs = [];
14416
14485
  for (const cfgPath of configPaths) {
@@ -14418,7 +14487,7 @@ npm uninstall -g @hiveai/mcp`
14418
14487
  try {
14419
14488
  const raw = await readFile23(cfgPath, "utf8");
14420
14489
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
14421
- staleConfigs.push(path47.relative(root, cfgPath));
14490
+ staleConfigs.push(path48.relative(root, cfgPath));
14422
14491
  if (opts.fix && !opts.dryRun) {
14423
14492
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
14424
14493
  await writeFile36(cfgPath, updated, "utf8");
@@ -14709,7 +14778,7 @@ which -a haive`
14709
14778
  ".vscode/mcp.json"
14710
14779
  ];
14711
14780
  for (const rel of integrationFiles) {
14712
- const file = path47.join(root, rel);
14781
+ const file = path48.join(root, rel);
14713
14782
  if (!existsSync68(file)) continue;
14714
14783
  const text = await readFile23(file, "utf8").catch(() => "");
14715
14784
  for (const bin of extractAbsoluteHaiveBins(text)) {
@@ -14736,7 +14805,7 @@ which -a haive`
14736
14805
  async function collectToolchainFindings(root) {
14737
14806
  const findings = [];
14738
14807
  const pkg = await readJson(
14739
- path47.join(root, "package.json")
14808
+ path48.join(root, "package.json")
14740
14809
  );
14741
14810
  const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
14742
14811
  if (!wantsPnpm) return findings;
@@ -14755,9 +14824,9 @@ async function collectToolchainFindings(root) {
14755
14824
  }
14756
14825
  async function collectDistFreshnessFindings(root, expectedVersion) {
14757
14826
  const findings = [];
14758
- const isHaiveWorkspace = (await readJson(path47.join(root, "package.json")))?.name === "haive-monorepo";
14827
+ const isHaiveWorkspace = (await readJson(path48.join(root, "package.json")))?.name === "haive-monorepo";
14759
14828
  if (!isHaiveWorkspace) return findings;
14760
- const cliDist = path47.join(root, "packages/cli/dist/index.js");
14829
+ const cliDist = path48.join(root, "packages/cli/dist/index.js");
14761
14830
  if (!existsSync68(cliDist)) {
14762
14831
  findings.push({
14763
14832
  severity: "warn",
@@ -14782,7 +14851,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
14782
14851
  "packages/core/src/index.ts",
14783
14852
  "packages/mcp/src/server.ts",
14784
14853
  "packages/cli/src/index.ts"
14785
- ].map((rel) => path47.join(root, rel)).filter(existsSync68);
14854
+ ].map((rel) => path48.join(root, rel)).filter(existsSync68);
14786
14855
  if (sourceFiles.length > 0) {
14787
14856
  const distMtime = statSync2(cliDist).mtimeMs;
14788
14857
  const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
@@ -14800,7 +14869,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
14800
14869
  }
14801
14870
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
14802
14871
  const findings = [];
14803
- const rootPkg = await readJson(path47.join(root, "package.json"));
14872
+ const rootPkg = await readJson(path48.join(root, "package.json"));
14804
14873
  const workspacePackages = [
14805
14874
  "packages/core/package.json",
14806
14875
  "packages/embeddings/package.json",
@@ -14809,7 +14878,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
14809
14878
  ];
14810
14879
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
14811
14880
  rel,
14812
- pkg: await readJson(path47.join(root, rel))
14881
+ pkg: await readJson(path48.join(root, rel))
14813
14882
  })))).filter((item) => item.pkg);
14814
14883
  const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
14815
14884
  if (!isHaiveWorkspace) return findings;
@@ -15314,21 +15383,21 @@ function registerMemorySuggestTopic(memory2) {
15314
15383
  }
15315
15384
 
15316
15385
  // src/commands/resolve-project.ts
15317
- import path48 from "path";
15386
+ import path49 from "path";
15318
15387
  import "commander";
15319
15388
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
15320
15389
  function registerResolveProject(program2) {
15321
15390
  program2.command("resolve-project").description(
15322
15391
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
15323
15392
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
15324
- const info = resolveProjectInfo2({ cwd: path48.resolve(opts.dir) });
15393
+ const info = resolveProjectInfo2({ cwd: path49.resolve(opts.dir) });
15325
15394
  console.log(JSON.stringify({ ok: true, info }, null, 2));
15326
15395
  });
15327
15396
  }
15328
15397
 
15329
15398
  // src/commands/runtime-journal.ts
15330
15399
  import { existsSync as existsSync71 } from "fs";
15331
- import path49 from "path";
15400
+ import path50 from "path";
15332
15401
  import "commander";
15333
15402
  import {
15334
15403
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
@@ -15342,15 +15411,15 @@ function registerRuntime(program2) {
15342
15411
  );
15343
15412
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
15344
15413
  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) => {
15345
- const root = path49.resolve(opts.dir ?? process.cwd());
15414
+ const root = path50.resolve(opts.dir ?? process.cwd());
15346
15415
  const paths = resolveHaivePaths45(findProjectRoot49(root));
15347
15416
  const raw = opts.kind ?? "note";
15348
15417
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
15349
15418
  await appendRuntimeJournalEntry3(paths, { kind, message });
15350
- ui.success(`Appended to ${path49.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
15419
+ ui.success(`Appended to ${path50.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
15351
15420
  });
15352
15421
  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) => {
15353
- const root = path49.resolve(opts.dir ?? process.cwd());
15422
+ const root = path50.resolve(opts.dir ?? process.cwd());
15354
15423
  const paths = resolveHaivePaths45(findProjectRoot49(root));
15355
15424
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
15356
15425
  if (!existsSync71(paths.haiveDir)) {
@@ -15369,7 +15438,7 @@ function registerRuntime(program2) {
15369
15438
 
15370
15439
  // src/commands/memory-timeline.ts
15371
15440
  import { existsSync as existsSync73 } from "fs";
15372
- import path50 from "path";
15441
+ import path51 from "path";
15373
15442
  import "commander";
15374
15443
  import {
15375
15444
  collectTimelineEntries as collectTimelineEntries2,
@@ -15385,7 +15454,7 @@ function registerMemoryTimeline(memory2) {
15385
15454
  process.exitCode = 1;
15386
15455
  return;
15387
15456
  }
15388
- const root = path50.resolve(opts.dir ?? process.cwd());
15457
+ const root = path51.resolve(opts.dir ?? process.cwd());
15389
15458
  const paths = resolveHaivePaths46(findProjectRoot50(root));
15390
15459
  if (!existsSync73(paths.memoriesDir)) {
15391
15460
  ui.error("No memories \u2014 run `haive init`.");
@@ -15406,7 +15475,7 @@ function registerMemoryTimeline(memory2) {
15406
15475
 
15407
15476
  // src/commands/memory-conflict-candidates.ts
15408
15477
  import { existsSync as existsSync74 } from "fs";
15409
- import path51 from "path";
15478
+ import path53 from "path";
15410
15479
  import "commander";
15411
15480
  import {
15412
15481
  findLexicalConflictPairs as findLexicalConflictPairs2,
@@ -15428,7 +15497,7 @@ function registerMemoryConflictCandidates(memory2) {
15428
15497
  "decision,architecture,convention,gotcha (lexical scan)",
15429
15498
  "decision,architecture"
15430
15499
  ).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) => {
15431
- const root = path51.resolve(opts.dir ?? process.cwd());
15500
+ const root = path53.resolve(opts.dir ?? process.cwd());
15432
15501
  const paths = resolveHaivePaths47(findProjectRoot51(root));
15433
15502
  if (!existsSync74(paths.memoriesDir)) {
15434
15503
  ui.error("No memories \u2014 run `haive init`.");
@@ -15465,10 +15534,11 @@ function registerMemoryConflictCandidates(memory2) {
15465
15534
  }
15466
15535
 
15467
15536
  // src/commands/enforce.ts
15468
- import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
15537
+ import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
15469
15538
  import { existsSync as existsSync75, statSync as statSync3 } from "fs";
15470
15539
  import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
15471
- import path53 from "path";
15540
+ import path54 from "path";
15541
+ import { promisify as promisify3 } from "util";
15472
15542
  import "commander";
15473
15543
  import {
15474
15544
  antiPatternGateParams as antiPatternGateParams2,
@@ -15482,15 +15552,18 @@ import {
15482
15552
  loadMemoriesFromDir as loadMemoriesFromDir38,
15483
15553
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
15484
15554
  readRecentBriefingMarker,
15555
+ recordPreventionHits as recordPreventionHits2,
15485
15556
  resolveBriefingBudget as resolveBriefingBudget3,
15486
15557
  resolveHaivePaths as resolveHaivePaths48,
15487
15558
  runSensors as runSensors2,
15488
15559
  saveConfig as saveConfig4,
15560
+ selectCommandSensors,
15489
15561
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15490
15562
  SESSION_RECAP_TTL_MS,
15491
15563
  verifyAnchor as verifyAnchor4,
15492
15564
  writeBriefingMarker as writeBriefingMarker3
15493
15565
  } from "@hiveai/core";
15566
+ var execFileAsync2 = promisify3(execFile3);
15494
15567
  var MAX_STDIN_BYTES2 = 256 * 1024;
15495
15568
  var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
15496
15569
  function registerEnforce(program2) {
@@ -15524,7 +15597,7 @@ function registerEnforce(program2) {
15524
15597
  if (opts.claude !== false) {
15525
15598
  try {
15526
15599
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
15527
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path53.relative(root, result.settingsPath)})`);
15600
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path54.relative(root, result.settingsPath)})`);
15528
15601
  } catch (err) {
15529
15602
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
15530
15603
  }
@@ -15545,19 +15618,19 @@ function registerEnforce(program2) {
15545
15618
  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) => {
15546
15619
  const root = findProjectRoot52(opts.dir);
15547
15620
  const paths = resolveHaivePaths48(root);
15548
- const cacheDir = path53.join(paths.haiveDir, ".cache");
15621
+ const cacheDir = path54.join(paths.haiveDir, ".cache");
15549
15622
  if (existsSync75(cacheDir)) {
15550
- if (opts.dryRun) ui.info(`would clean ${path53.relative(root, cacheDir)} (preserving .gitignore)`);
15623
+ if (opts.dryRun) ui.info(`would clean ${path54.relative(root, cacheDir)} (preserving .gitignore)`);
15551
15624
  else {
15552
15625
  const removed = await cleanupCacheDir(cacheDir);
15553
- ui.success(`cleaned ${path53.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
15626
+ ui.success(`cleaned ${path54.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
15554
15627
  }
15555
15628
  }
15556
15629
  if (existsSync75(paths.runtimeDir)) {
15557
- if (opts.dryRun) ui.info(`would clean ${path53.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
15630
+ if (opts.dryRun) ui.info(`would clean ${path54.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
15558
15631
  else {
15559
15632
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
15560
- ui.success(`cleaned ${path53.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
15633
+ ui.success(`cleaned ${path54.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
15561
15634
  }
15562
15635
  }
15563
15636
  });
@@ -15910,7 +15983,7 @@ async function buildFinishReport(dir) {
15910
15983
  async function checkFailureCapture(paths, config) {
15911
15984
  const gate = config.enforcement?.failureCaptureGate ?? "warn";
15912
15985
  if (gate === "off") return [];
15913
- const obsFile = path53.join(paths.haiveDir, ".cache", "observations.jsonl");
15986
+ const obsFile = path54.join(paths.haiveDir, ".cache", "observations.jsonl");
15914
15987
  if (!existsSync75(obsFile)) return [];
15915
15988
  const failures = [];
15916
15989
  try {
@@ -15982,7 +16055,7 @@ async function runWithEnforcement(command, args, opts) {
15982
16055
  process.exit(2);
15983
16056
  }
15984
16057
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
15985
- ui.info(`Briefing written to ${path53.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
16058
+ ui.info(`Briefing written to ${path54.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
15986
16059
  const child = spawn6(command, args, {
15987
16060
  cwd: root,
15988
16061
  stdio: "inherit",
@@ -16032,9 +16105,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
16032
16105
  source: "haive-run",
16033
16106
  memoryIds: briefing.memories.map((m) => m.id)
16034
16107
  });
16035
- const dir = path53.join(paths.runtimeDir, "enforcement", "briefings");
16108
+ const dir = path54.join(paths.runtimeDir, "enforcement", "briefings");
16036
16109
  await mkdir21(dir, { recursive: true });
16037
- const file = path53.join(dir, `${sessionId}.md`);
16110
+ const file = path54.join(dir, `${sessionId}.md`);
16038
16111
  const parts = [
16039
16112
  "# hAIve Briefing",
16040
16113
  "",
@@ -16092,7 +16165,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
16092
16165
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
16093
16166
  });
16094
16167
  }
16095
- findings.push(...await inspectIntegrationVersions(root, "0.21.0"));
16168
+ findings.push(...await inspectIntegrationVersions(root, "0.24.0"));
16096
16169
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
16097
16170
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
16098
16171
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -16243,7 +16316,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
16243
16316
  const consulted = new Set(marker?.memory_ids ?? []);
16244
16317
  const missing = relevant.filter(({ memory: memory2, filePath }) => {
16245
16318
  if (consulted.has(memory2.frontmatter.id)) return false;
16246
- if (changedSet.has(path53.relative(paths.root, filePath))) return false;
16319
+ if (changedSet.has(path54.relative(paths.root, filePath))) return false;
16247
16320
  return true;
16248
16321
  }).map(({ memory: memory2 }) => memory2);
16249
16322
  if (missing.length === 0) {
@@ -16344,19 +16417,19 @@ async function runSensorGate(paths, diff) {
16344
16417
  if (!diff || !existsSync75(paths.memoriesDir)) return [];
16345
16418
  try {
16346
16419
  const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
16347
- const sensorMemories = loaded.map((l) => l.memory).filter((m) => {
16348
- const s = m.frontmatter.sensor;
16349
- return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
16350
- });
16351
- if (sensorMemories.length === 0) return [];
16420
+ const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory3(m.frontmatter, m.body));
16421
+ if (scannable.length === 0) return [];
16352
16422
  const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
16353
16423
  if (targets.length === 0) return [];
16354
- const hits = runSensors2(sensorMemories, targets);
16355
16424
  const findings = [];
16356
16425
  const seen = /* @__PURE__ */ new Set();
16426
+ const firedIds = /* @__PURE__ */ new Set();
16427
+ const regexSensorMemories = scannable.filter((m) => m.frontmatter.sensor.kind === "regex");
16428
+ const hits = regexSensorMemories.length > 0 ? runSensors2(regexSensorMemories, targets) : [];
16357
16429
  for (const hit of hits) {
16358
16430
  if (seen.has(hit.memory_id)) continue;
16359
16431
  seen.add(hit.memory_id);
16432
+ firedIds.add(hit.memory_id);
16360
16433
  const where = hit.file ? ` (${hit.file})` : "";
16361
16434
  if (hit.severity === "block") {
16362
16435
  findings.push({
@@ -16376,11 +16449,51 @@ async function runSensorGate(paths, diff) {
16376
16449
  });
16377
16450
  }
16378
16451
  }
16452
+ const config = await loadConfig14(paths).catch(() => null);
16453
+ if (config?.enforcement?.runCommandSensors === true) {
16454
+ const changedPaths = targets.map((t) => t.path).filter(Boolean);
16455
+ for (const spec of selectCommandSensors(scannable, changedPaths)) {
16456
+ if (seen.has(spec.memory_id)) continue;
16457
+ const failed = await runGateCommandSensor(spec, paths.root);
16458
+ if (!failed) continue;
16459
+ seen.add(spec.memory_id);
16460
+ firedIds.add(spec.memory_id);
16461
+ if (spec.severity === "block") {
16462
+ findings.push({
16463
+ severity: "error",
16464
+ code: "sensor-block",
16465
+ message: `Block command sensor fired \u2014 ${spec.memory_id}: ${spec.message} (command: ${spec.command})`,
16466
+ fix: "Fix the condition the command checks, or run `haive sensors check --commands` to inspect it.",
16467
+ impact: 45
16468
+ });
16469
+ } else {
16470
+ findings.push({
16471
+ severity: "warn",
16472
+ code: "sensor-warn",
16473
+ message: `Command sensor flagged ${spec.memory_id}: ${spec.message}`,
16474
+ fix: "Review the failing command; `haive sensors check --commands` re-runs it.",
16475
+ impact: 5
16476
+ });
16477
+ }
16478
+ }
16479
+ }
16480
+ if (firedIds.size > 0) {
16481
+ await recordPreventionHits2(paths, [...firedIds], "sensor").catch(() => {
16482
+ });
16483
+ }
16379
16484
  return findings;
16380
16485
  } catch {
16381
16486
  return [];
16382
16487
  }
16383
16488
  }
16489
+ async function runGateCommandSensor(spec, root) {
16490
+ try {
16491
+ await execFileAsync2("bash", ["-c", spec.command], { cwd: root, timeout: 12e4, maxBuffer: 8 * 1024 * 1024 });
16492
+ return false;
16493
+ } catch {
16494
+ return true;
16495
+ }
16496
+ }
16384
16497
  async function findGeneratedArtifacts(paths) {
16385
16498
  const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
16386
16499
  const generated = dirty.split("\n").map((line) => line.trim()).filter(Boolean).filter(
@@ -16411,16 +16524,16 @@ async function cleanupRuntimeDir(runtimeDir) {
16411
16524
  for (const entry of entries) {
16412
16525
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
16413
16526
  if (entry.name === "enforcement") {
16414
- removed += await cleanupEnforcementDir(path53.join(runtimeDir, entry.name));
16527
+ removed += await cleanupEnforcementDir(path54.join(runtimeDir, entry.name));
16415
16528
  continue;
16416
16529
  }
16417
- await rm3(path53.join(runtimeDir, entry.name), { recursive: true, force: true });
16530
+ await rm3(path54.join(runtimeDir, entry.name), { recursive: true, force: true });
16418
16531
  removed++;
16419
16532
  }
16420
- await writeFile37(path53.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
16421
- if (!existsSync75(path53.join(runtimeDir, "README.md"))) {
16533
+ await writeFile37(path54.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
16534
+ if (!existsSync75(path54.join(runtimeDir, "README.md"))) {
16422
16535
  await writeFile37(
16423
- path53.join(runtimeDir, "README.md"),
16536
+ path54.join(runtimeDir, "README.md"),
16424
16537
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
16425
16538
  "utf8"
16426
16539
  );
@@ -16433,10 +16546,10 @@ async function cleanupCacheDir(cacheDir) {
16433
16546
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
16434
16547
  for (const entry of entries) {
16435
16548
  if (entry.name === ".gitignore") continue;
16436
- await rm3(path53.join(cacheDir, entry.name), { recursive: true, force: true });
16549
+ await rm3(path54.join(cacheDir, entry.name), { recursive: true, force: true });
16437
16550
  removed++;
16438
16551
  }
16439
- await writeFile37(path53.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
16552
+ await writeFile37(path54.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
16440
16553
  return removed;
16441
16554
  }
16442
16555
  async function cleanupEnforcementDir(enforcementDir) {
@@ -16444,7 +16557,7 @@ async function cleanupEnforcementDir(enforcementDir) {
16444
16557
  const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
16445
16558
  for (const entry of entries) {
16446
16559
  if (entry.name === "briefings") continue;
16447
- await rm3(path53.join(enforcementDir, entry.name), { recursive: true, force: true });
16560
+ await rm3(path54.join(enforcementDir, entry.name), { recursive: true, force: true });
16448
16561
  removed++;
16449
16562
  }
16450
16563
  return removed;
@@ -16460,7 +16573,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
16460
16573
  ];
16461
16574
  const findings = [];
16462
16575
  for (const rel of files) {
16463
- const file = path53.join(root, rel);
16576
+ const file = path54.join(root, rel);
16464
16577
  if (!existsSync75(file)) continue;
16465
16578
  const text = await readFile24(file, "utf8").catch(() => "");
16466
16579
  for (const bin of extractAbsoluteHaiveBins2(text)) {
@@ -16676,7 +16789,7 @@ function isShippablePath(file) {
16676
16789
  }
16677
16790
  var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
16678
16791
  async function checkCommitMessageSkipCi(root, msgfile) {
16679
- const file = path53.isAbsolute(msgfile) ? msgfile : path53.join(root, msgfile);
16792
+ const file = path54.isAbsolute(msgfile) ? msgfile : path54.join(root, msgfile);
16680
16793
  const raw = await readFile24(file, "utf8").catch(() => "");
16681
16794
  const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
16682
16795
  if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
@@ -16705,7 +16818,7 @@ async function inspectReleaseVersionState(root, upstream) {
16705
16818
  }
16706
16819
  async function readPackageVersion(root, relPath) {
16707
16820
  try {
16708
- const data = JSON.parse(await readFile24(path53.join(root, relPath), "utf8"));
16821
+ const data = JSON.parse(await readFile24(path54.join(root, relPath), "utf8"));
16709
16822
  return typeof data.version === "string" ? data.version : void 0;
16710
16823
  } catch {
16711
16824
  return void 0;
@@ -16893,8 +17006,8 @@ function buildScore(findings, threshold = 80) {
16893
17006
  };
16894
17007
  }
16895
17008
  async function installGitEnforcement(root) {
16896
- const hooksDir = path53.join(root, ".git", "hooks");
16897
- if (!existsSync75(path53.join(root, ".git"))) {
17009
+ const hooksDir = path54.join(root, ".git", "hooks");
17010
+ if (!existsSync75(path54.join(root, ".git"))) {
16898
17011
  ui.warn("No .git directory found; git enforcement hooks skipped.");
16899
17012
  return;
16900
17013
  }
@@ -16923,7 +17036,7 @@ haive enforce commit-msg "$1" --dir . || exit $?
16923
17036
  }
16924
17037
  ];
16925
17038
  for (const hook of hooks) {
16926
- const file = path53.join(hooksDir, hook.name);
17039
+ const file = path54.join(hooksDir, hook.name);
16927
17040
  if (existsSync75(file)) {
16928
17041
  const current = await readFile24(file, "utf8").catch(() => "");
16929
17042
  if (current.includes(ENFORCE_HOOK_MARKER)) {
@@ -16941,8 +17054,8 @@ ${hook.body}`, "utf8");
16941
17054
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push, commit-msg");
16942
17055
  }
16943
17056
  async function installCiEnforcement(root) {
16944
- const workflowPath = path53.join(root, ".github", "workflows", "haive-enforcement.yml");
16945
- await mkdir21(path53.dirname(workflowPath), { recursive: true });
17057
+ const workflowPath = path54.join(root, ".github", "workflows", "haive-enforcement.yml");
17058
+ await mkdir21(path54.dirname(workflowPath), { recursive: true });
16946
17059
  if (existsSync75(workflowPath)) {
16947
17060
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
16948
17061
  return;
@@ -16974,7 +17087,7 @@ jobs:
16974
17087
  HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
16975
17088
  run: haive enforce ci
16976
17089
  `, "utf8");
16977
- ui.success(`Created ${path53.relative(root, workflowPath)}`);
17090
+ ui.success(`Created ${path54.relative(root, workflowPath)}`);
16978
17091
  }
16979
17092
  function printReport(report, json, explain = false) {
16980
17093
  if (json) {
@@ -17078,8 +17191,8 @@ function extractToolPaths(payload, root) {
17078
17191
  }
17079
17192
  function normalizeToolPath(file, root) {
17080
17193
  const normalized = file.replace(/\\/g, "/");
17081
- if (!path53.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
17082
- return path53.relative(root, normalized).replace(/\\/g, "/");
17194
+ if (!path54.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
17195
+ return path54.relative(root, normalized).replace(/\\/g, "/");
17083
17196
  }
17084
17197
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
17085
17198
  if (!existsSync75(paths.memoriesDir)) return [];
@@ -17127,7 +17240,7 @@ async function readStdin2(maxBytes) {
17127
17240
  }
17128
17241
  var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
17129
17242
  async function stageResyncedArtifacts(root, paths) {
17130
- const aiRel = path53.relative(root, paths.haiveDir);
17243
+ const aiRel = path54.relative(root, paths.haiveDir);
17131
17244
  const out = await runCommand4("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
17132
17245
  const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
17133
17246
  if (toStage.length === 0) return;
@@ -17166,28 +17279,25 @@ function registerRun(program2) {
17166
17279
  }
17167
17280
 
17168
17281
  // src/commands/sensors.ts
17169
- import { execFile as execFile3 } from "child_process";
17282
+ import { execFile as execFile4 } from "child_process";
17170
17283
  import { existsSync as existsSync76 } from "fs";
17171
17284
  import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
17172
- import path54 from "path";
17173
- import { promisify as promisify3 } from "util";
17285
+ import path55 from "path";
17286
+ import { promisify as promisify4 } from "util";
17174
17287
  import "commander";
17175
17288
  import {
17176
- appendPreventionEvent as appendPreventionEvent2,
17177
17289
  findProjectRoot as findProjectRoot53,
17178
17290
  isRetiredMemory as isRetiredMemory4,
17179
17291
  loadConfig as loadConfig15,
17180
17292
  loadMemoriesFromDir as loadMemoriesFromDir39,
17181
- loadUsageIndex as loadUsageIndex31,
17182
- recordPrevention as recordPrevention2,
17293
+ recordPreventionHits as recordPreventionHits3,
17183
17294
  resolveHaivePaths as resolveHaivePaths49,
17184
17295
  runSensors as runSensors3,
17185
- saveUsageIndex as saveUsageIndex8,
17186
- selectCommandSensors,
17296
+ selectCommandSensors as selectCommandSensors2,
17187
17297
  sensorTargetsFromDiff as sensorTargetsFromDiff3,
17188
17298
  serializeMemory as serializeMemory29
17189
17299
  } from "@hiveai/core";
17190
- var exec2 = promisify3(execFile3);
17300
+ var exec2 = promisify4(execFile4);
17191
17301
  function registerSensors(program2) {
17192
17302
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
17193
17303
  sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
@@ -17217,14 +17327,14 @@ function registerSensors(program2) {
17217
17327
  const root = findProjectRoot53(opts.dir);
17218
17328
  const paths = resolveHaivePaths49(root);
17219
17329
  const memories = await runnableSensorMemories(paths);
17220
- const diff = opts.diffFile ? await readFile25(path54.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
17330
+ const diff = opts.diffFile ? await readFile25(path55.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
17221
17331
  const targets = sensorTargetsFromDiff3(diff);
17222
17332
  const hits = runSensors3(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
17223
17333
  const config = await loadConfig15(paths);
17224
17334
  const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
17225
17335
  const changedPaths = targets.map((t) => t.path).filter(Boolean);
17226
17336
  const allSensorMemories = await runnableSensorMemories(paths, false);
17227
- const commandSpecs = selectCommandSensors(allSensorMemories, changedPaths);
17337
+ const commandSpecs = selectCommandSensors2(allSensorMemories, changedPaths);
17228
17338
  const commandHits = [];
17229
17339
  const commandSkipped = [];
17230
17340
  if (commandSpecs.length > 0 && runCommands) {
@@ -17243,20 +17353,7 @@ function registerSensors(program2) {
17243
17353
  for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
17244
17354
  }
17245
17355
  const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
17246
- if (firedIds.length > 0) {
17247
- const usage = await loadUsageIndex31(paths);
17248
- const recordedIds = [];
17249
- for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
17250
- if (recordedIds.length > 0) {
17251
- await saveUsageIndex8(paths, usage).catch(() => {
17252
- });
17253
- const at = (/* @__PURE__ */ new Date()).toISOString();
17254
- for (const id of recordedIds) {
17255
- await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
17256
- });
17257
- }
17258
- }
17259
- }
17356
+ await recordPreventionHits3(paths, firedIds, "sensor");
17260
17357
  const output = {
17261
17358
  scanned: memories.length,
17262
17359
  hits: hits.map((hit) => ({
@@ -17342,13 +17439,13 @@ function registerSensors(program2) {
17342
17439
  const root = findProjectRoot53(opts.dir);
17343
17440
  const paths = resolveHaivePaths49(root);
17344
17441
  const rows = await sensorRows(paths);
17345
- const outDir = path54.resolve(root, opts.outDir ?? ".ai/generated");
17442
+ const outDir = path55.resolve(root, opts.outDir ?? ".ai/generated");
17346
17443
  await mkdir23(outDir, { recursive: true });
17347
- const outPath = path54.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
17444
+ const outPath = path55.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
17348
17445
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
17349
17446
  await writeFile38(outPath, content, "utf8");
17350
17447
  if (format === "grep") await chmod3(outPath, 493);
17351
- ui.success(`Exported ${rows.length} sensor(s): ${path54.relative(root, outPath)}`);
17448
+ ui.success(`Exported ${rows.length} sensor(s): ${path55.relative(root, outPath)}`);
17352
17449
  });
17353
17450
  }
17354
17451
  async function sensorRows(paths) {
@@ -17421,7 +17518,7 @@ function shellQuote(value) {
17421
17518
  // src/commands/ingest.ts
17422
17519
  import { existsSync as existsSync77 } from "fs";
17423
17520
  import { mkdir as mkdir24, readFile as readFile26, writeFile as writeFile39 } from "fs/promises";
17424
- import path55 from "path";
17521
+ import path56 from "path";
17425
17522
  import "commander";
17426
17523
  import {
17427
17524
  draftsFromFindings as draftsFromFindings2,
@@ -17478,7 +17575,7 @@ function registerIngest(program2) {
17478
17575
  process.exitCode = 1;
17479
17576
  return;
17480
17577
  }
17481
- const reportPath = path55.resolve(root, file);
17578
+ const reportPath = path56.resolve(root, file);
17482
17579
  if (!existsSync77(reportPath)) {
17483
17580
  ui.error(`Report file not found: ${reportPath}`);
17484
17581
  process.exitCode = 1;
@@ -17567,13 +17664,13 @@ function registerIngest(program2) {
17567
17664
  await writeDraft2(paths, draft);
17568
17665
  created++;
17569
17666
  }
17570
- ui.success(`Created ${created} proposed memory(ies) under ${path55.relative(root, paths.memoriesDir)}/`);
17667
+ ui.success(`Created ${created} proposed memory(ies) under ${path56.relative(root, paths.memoriesDir)}/`);
17571
17668
  ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
17572
17669
  });
17573
17670
  }
17574
17671
  async function writeDraft2(paths, draft) {
17575
17672
  const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
17576
- await mkdir24(path55.dirname(file), { recursive: true });
17673
+ await mkdir24(path56.dirname(file), { recursive: true });
17577
17674
  await writeFile39(file, serializeMemory30({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
17578
17675
  return file;
17579
17676
  }
@@ -17623,7 +17720,7 @@ import {
17623
17720
  loadConfig as loadConfig16,
17624
17721
  loadMemoriesFromDir as loadMemoriesFromDir41,
17625
17722
  loadPreventionEvents as loadPreventionEvents4,
17626
- loadUsageIndex as loadUsageIndex33,
17723
+ loadUsageIndex as loadUsageIndex31,
17627
17724
  resolveHaivePaths as resolveHaivePaths51
17628
17725
  } from "@hiveai/core";
17629
17726
  function registerDashboard(program2) {
@@ -17638,7 +17735,7 @@ function registerDashboard(program2) {
17638
17735
  return;
17639
17736
  }
17640
17737
  const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
17641
- const usage = await loadUsageIndex33(paths);
17738
+ const usage = await loadUsageIndex31(paths);
17642
17739
  const preventionEvents = await loadPreventionEvents4(paths);
17643
17740
  const config = await loadConfig16(paths);
17644
17741
  const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
@@ -17665,6 +17762,18 @@ function renderDashboard(r) {
17665
17762
  console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
17666
17763
  console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
17667
17764
  console.log();
17765
+ console.log(ui.bold("Value") + ui.dim(" (what hAIve demonstrably earned \u2014 vs its per-task cost)"));
17766
+ const blocked = prevention.trend.last_30d;
17767
+ const demonstrated = impact.high;
17768
+ console.log(
17769
+ ` ${blocked > 0 ? ui.green(`${blocked} repeat${blocked === 1 ? "" : "s"} blocked (30d)`) : "0 repeats blocked (30d)"} \xB7 ${demonstrated} high-impact memor${demonstrated === 1 ? "y" : "ies"} (applied/prevented) \xB7 ${inv.active} active polic${inv.active === 1 ? "y" : "ies"} surfaceable`
17770
+ );
17771
+ console.log(
17772
+ ui.dim(
17773
+ " Cost is real: the briefing adds context to every task; the payoff is downstream (defects/incidents avoided), not the agent's token bill."
17774
+ )
17775
+ );
17776
+ console.log();
17668
17777
  console.log(ui.bold("Prevention") + ui.dim(" (caught-for-you outcome)"));
17669
17778
  console.log(
17670
17779
  ` ${prevention.trend.last_30d} catch${prevention.trend.last_30d === 1 ? "" : "es"} in 30d \xB7 ${prevention.recurrence.recurring_count} recurrence${prevention.recurrence.recurring_count === 1 ? "" : "s"} to review \xB7 ${prevention.trend.last_7d} in 7d`
@@ -17749,19 +17858,19 @@ function warnNum(n) {
17749
17858
  }
17750
17859
 
17751
17860
  // src/commands/dev-link.ts
17752
- import { execFile as execFile4 } from "child_process";
17861
+ import { execFile as execFile5 } from "child_process";
17753
17862
  import { cp, readFile as readFile27 } from "fs/promises";
17754
17863
  import { existsSync as existsSync79 } from "fs";
17755
- import path56 from "path";
17756
- import { promisify as promisify4 } from "util";
17864
+ import path57 from "path";
17865
+ import { promisify as promisify5 } from "util";
17757
17866
  import "commander";
17758
17867
  import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
17759
- var exec3 = promisify4(execFile4);
17868
+ var exec3 = promisify5(execFile5);
17760
17869
  function registerDevLink(program2) {
17761
17870
  const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
17762
17871
  dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
17763
17872
  const root = findProjectRoot56(opts.dir);
17764
- if (!existsSync79(path56.join(root, "packages", "cli", "dist", "index.js"))) {
17873
+ if (!existsSync79(path57.join(root, "packages", "cli", "dist", "index.js"))) {
17765
17874
  ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
17766
17875
  process.exitCode = 1;
17767
17876
  return;
@@ -17770,9 +17879,9 @@ function registerDevLink(program2) {
17770
17879
  try {
17771
17880
  globalModules = (await exec3("npm", ["root", "-g"])).stdout.trim();
17772
17881
  } catch {
17773
- globalModules = path56.join(path56.dirname(path56.dirname(process.execPath)), "lib", "node_modules");
17882
+ globalModules = path57.join(path57.dirname(path57.dirname(process.execPath)), "lib", "node_modules");
17774
17883
  }
17775
- const globalHive = path56.join(globalModules, "@hiveai");
17884
+ const globalHive = path57.join(globalModules, "@hiveai");
17776
17885
  if (!existsSync79(globalHive)) {
17777
17886
  ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
17778
17887
  process.exitCode = 1;
@@ -17780,21 +17889,21 @@ function registerDevLink(program2) {
17780
17889
  }
17781
17890
  const linked = [];
17782
17891
  const copyDist = async (fromPkg, toDistDir) => {
17783
- const from = path56.join(root, "packages", fromPkg, "dist");
17784
- if (!existsSync79(from) || !existsSync79(path56.dirname(toDistDir))) return;
17892
+ const from = path57.join(root, "packages", fromPkg, "dist");
17893
+ if (!existsSync79(from) || !existsSync79(path57.dirname(toDistDir))) return;
17785
17894
  await cp(from, toDistDir, { recursive: true });
17786
- linked.push(path56.relative(globalModules, toDistDir));
17895
+ linked.push(path57.relative(globalModules, toDistDir));
17787
17896
  };
17788
17897
  for (const pkg of ["cli", "mcp"]) {
17789
- await copyDist(pkg, path56.join(globalHive, pkg, "dist"));
17898
+ await copyDist(pkg, path57.join(globalHive, pkg, "dist"));
17790
17899
  for (const nested of ["core", "embeddings"]) {
17791
- await copyDist(nested, path56.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
17900
+ await copyDist(nested, path57.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
17792
17901
  }
17793
17902
  }
17794
- await copyDist("core", path56.join(globalHive, "core", "dist"));
17903
+ await copyDist("core", path57.join(globalHive, "core", "dist"));
17795
17904
  let version = "unknown";
17796
17905
  try {
17797
- version = JSON.parse(await readFile27(path56.join(root, "package.json"), "utf8")).version ?? "unknown";
17906
+ version = JSON.parse(await readFile27(path57.join(root, "package.json"), "utf8")).version ?? "unknown";
17798
17907
  } catch {
17799
17908
  }
17800
17909
  if (opts.json) {
@@ -17812,8 +17921,41 @@ function registerDevLink(program2) {
17812
17921
  }
17813
17922
 
17814
17923
  // src/commands/coverage.ts
17924
+ import { readFile as readFile28 } from "fs/promises";
17925
+ import { existsSync as existsSync80 } from "fs";
17926
+ import path58 from "path";
17815
17927
  import "commander";
17816
- import { findCoverageGaps, findProjectRoot as findProjectRoot57, resolveHaivePaths as resolveHaivePaths52 } from "@hiveai/core";
17928
+ import {
17929
+ findCoverageGaps,
17930
+ findProjectRoot as findProjectRoot57,
17931
+ mergeHotFiles,
17932
+ resolveHaivePaths as resolveHaivePaths52,
17933
+ tallyHotFiles
17934
+ } from "@hiveai/core";
17935
+ async function readAgentHotFiles(root, cacheFile, sinceMs) {
17936
+ if (!existsSync80(cacheFile)) return [];
17937
+ const raw = await readFile28(cacheFile, "utf8").catch(() => "");
17938
+ const files = [];
17939
+ for (const line of raw.split("\n")) {
17940
+ const trimmed = line.trim();
17941
+ if (!trimmed) continue;
17942
+ try {
17943
+ const obs = JSON.parse(trimmed);
17944
+ if (sinceMs > 0 && obs.ts) {
17945
+ const t = Date.parse(obs.ts);
17946
+ if (Number.isFinite(t) && t < sinceMs) continue;
17947
+ }
17948
+ for (const f of obs.files ?? []) {
17949
+ if (typeof f !== "string" || !f) continue;
17950
+ const rel = path58.isAbsolute(f) ? path58.relative(root, f) : f;
17951
+ if (rel.startsWith("..")) continue;
17952
+ files.push(rel);
17953
+ }
17954
+ } catch {
17955
+ }
17956
+ }
17957
+ return tallyHotFiles(files, "agent");
17958
+ }
17817
17959
  function isNoisePath(p) {
17818
17960
  if (/(^|\/)(node_modules|dist|build|coverage|\.next)\//.test(p)) return true;
17819
17961
  if (p.startsWith(".ai/")) return true;
@@ -17825,29 +17967,47 @@ function isNoisePath(p) {
17825
17967
  function registerCoverage(program2) {
17826
17968
  program2.command("coverage").description(
17827
17969
  "Coverage-gap report: frequently-edited files with no covering team memory (blind spots)."
17828
- ).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum git-churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "git-history lookback window in days", "90").option("-d, --dir <dir>", "project root").action(async (opts) => {
17970
+ ).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "lookback window in days (git history + agent edits)", "90").option("--source <which>", "heat source: git | agent | both", "both").option("-d, --dir <dir>", "project root").action(async (opts) => {
17829
17971
  const root = findProjectRoot57(opts.dir);
17830
17972
  const paths = resolveHaivePaths52(root);
17831
17973
  const minChanges = Math.max(1, parseInt(opts.minChanges ?? "3", 10) || 3);
17832
17974
  const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
17833
17975
  const days = Math.max(1, parseInt(opts.days ?? "90", 10) || 90);
17834
- const radar = await buildRadar({
17976
+ const source = (opts.source ?? "both").toLowerCase();
17977
+ if (!["git", "agent", "both"].includes(source)) {
17978
+ ui.error("--source must be one of: git | agent | both");
17979
+ process.exitCode = 1;
17980
+ return;
17981
+ }
17982
+ const useGit = source === "git" || source === "both";
17983
+ const useAgent = source === "agent" || source === "both";
17984
+ const radar = useGit ? await buildRadar({
17835
17985
  root,
17836
17986
  taskTokens: null,
17837
17987
  filePaths: [],
17838
17988
  daysBack: Math.ceil(days / 6),
17839
17989
  // getHotFiles multiplies daysBack by 6
17840
17990
  maxHotFiles: 500
17841
- });
17842
- const hotFiles = radar.hotFiles.filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes }));
17991
+ }) : null;
17992
+ const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
17993
+ const sinceMs = Date.now() - days * 864e5;
17994
+ const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path58.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
17995
+ const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
17843
17996
  const memories = await loadMemoriesFromDir27(paths.memoriesDir);
17844
17997
  const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
17845
17998
  if (opts.json) {
17846
- console.log(JSON.stringify({ root, scanned_hot_files: hotFiles.length, gaps }, null, 2));
17999
+ console.log(JSON.stringify({
18000
+ root,
18001
+ source,
18002
+ scanned_hot_files: hotFiles.length,
18003
+ git_hot_files: gitHotFiles.length,
18004
+ agent_hot_files: agentHotFiles.length,
18005
+ gaps
18006
+ }, null, 2));
17847
18007
  return;
17848
18008
  }
17849
- if (!radar.insideGitRepo) {
17850
- ui.warn("Not a git repository \u2014 coverage uses git churn to find hot files.");
18009
+ if (useGit && radar && !radar.insideGitRepo && agentHotFiles.length === 0) {
18010
+ ui.warn("Not a git repository and no agent-edit history \u2014 nothing to cross-check.");
17851
18011
  return;
17852
18012
  }
17853
18013
  if (gaps.length === 0) {
@@ -17856,7 +18016,8 @@ function registerCoverage(program2) {
17856
18016
  }
17857
18017
  console.log(ui.bold(`hAIve coverage \u2014 ${gaps.length} blind spot(s) (hot files with no covering memory)`));
17858
18018
  for (const gap of gaps) {
17859
- console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}`);
18019
+ const src = gap.source ? ui.dim(` [${gap.source}]`) : "";
18020
+ console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}${src}`);
17860
18021
  }
17861
18022
  console.log(
17862
18023
  ui.dim(
@@ -17868,8 +18029,8 @@ function registerCoverage(program2) {
17868
18029
 
17869
18030
  // src/commands/merge-driver.ts
17870
18031
  import { execFileSync as execFileSync3 } from "child_process";
17871
- import { readFileSync, writeFileSync, existsSync as existsSync80 } from "fs";
17872
- import path57 from "path";
18032
+ import { readFileSync, writeFileSync, existsSync as existsSync81 } from "fs";
18033
+ import path59 from "path";
17873
18034
  import "commander";
17874
18035
  import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
17875
18036
  var GITATTRIBUTES_MARK = "# hAIve merge driver";
@@ -17901,8 +18062,8 @@ function registerMergeDriver(program2) {
17901
18062
  process.exitCode = 1;
17902
18063
  return;
17903
18064
  }
17904
- const gaPath = path57.join(root, ".gitattributes");
17905
- let content = existsSync80(gaPath) ? readFileSync(gaPath, "utf8") : "";
18065
+ const gaPath = path59.join(root, ".gitattributes");
18066
+ let content = existsSync81(gaPath) ? readFileSync(gaPath, "utf8") : "";
17906
18067
  if (!content.includes(GITATTRIBUTES_MARK)) {
17907
18068
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
17908
18069
  content += GITATTRIBUTES_BLOCK + "\n";
@@ -17917,11 +18078,12 @@ function registerMergeDriver(program2) {
17917
18078
 
17918
18079
  // src/commands/memory-resolve-conflict.ts
17919
18080
  import { writeFile as writeFile40 } from "fs/promises";
17920
- import { existsSync as existsSync81 } from "fs";
18081
+ import { existsSync as existsSync83 } from "fs";
17921
18082
  import "commander";
17922
18083
  import {
18084
+ applyConflictResolution,
17923
18085
  findProjectRoot as findProjectRoot59,
17924
- planConflictResolution,
18086
+ planConflictResolution as planConflictResolution2,
17925
18087
  resolveHaivePaths as resolveHaivePaths53,
17926
18088
  serializeMemory as serializeMemory31
17927
18089
  } from "@hiveai/core";
@@ -17929,7 +18091,7 @@ function registerMemoryResolveConflict(memory2) {
17929
18091
  memory2.command("resolve-conflict <id_a> <id_b>").description("Resolve a contradiction: keep the stronger memory, deprecate (supersede) the other").option("--yes", "apply the resolution (without this, only previews it)", false).option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (idA, idB, opts) => {
17930
18092
  const root = findProjectRoot59(opts.dir);
17931
18093
  const paths = resolveHaivePaths53(root);
17932
- if (!existsSync81(paths.memoriesDir)) {
18094
+ if (!existsSync83(paths.memoriesDir)) {
17933
18095
  ui.error(`No .ai/memories at ${root}.`);
17934
18096
  process.exitCode = 1;
17935
18097
  return;
@@ -17942,43 +18104,53 @@ function registerMemoryResolveConflict(memory2) {
17942
18104
  process.exitCode = 1;
17943
18105
  return;
17944
18106
  }
17945
- const plan = planConflictResolution(a, b);
18107
+ const plan = planConflictResolution2(a, b);
18108
+ const winner = plan.keep_id === idA ? a : b;
17946
18109
  const loser = plan.supersede_id === idA ? a : b;
18110
+ const applied = applyConflictResolution(winner, loser, plan);
17947
18111
  if (opts.json) {
17948
- console.log(JSON.stringify({ ...plan, applied: Boolean(opts.yes) }, null, 2));
18112
+ console.log(JSON.stringify({
18113
+ ...plan,
18114
+ winner_revision_count: applied.winner.revision_count,
18115
+ topic: applied.topic,
18116
+ topic_adopted: applied.topic_adopted,
18117
+ applied: Boolean(opts.yes)
18118
+ }, null, 2));
17949
18119
  } else {
17950
18120
  console.log(ui.bold("Conflict resolution"));
17951
- console.log(` keep: ${ui.green(plan.keep_id)}`);
18121
+ console.log(` keep: ${ui.green(plan.keep_id)} ${ui.dim(`(rev ${winner.memory.frontmatter.revision_count}\u2192${applied.winner.revision_count})`)}`);
17952
18122
  console.log(` supersede: ${ui.red(plan.supersede_id)} ${ui.dim(`\u2192 deprecated`)}`);
17953
18123
  console.log(` reason: ${plan.reason}`);
18124
+ if (applied.topic) {
18125
+ console.log(` topic: ${applied.topic}${applied.topic_adopted ? ui.dim(" (adopted from superseded \u2014 future captures upsert into the winner)") : ""}`);
18126
+ }
17954
18127
  }
17955
18128
  if (!opts.yes) {
17956
18129
  if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
17957
18130
  return;
17958
18131
  }
18132
+ await writeFile40(
18133
+ winner.filePath,
18134
+ serializeMemory31({ frontmatter: applied.winner, body: winner.memory.body }),
18135
+ "utf8"
18136
+ );
17959
18137
  await writeFile40(
17960
18138
  loser.filePath,
17961
- serializeMemory31({
17962
- frontmatter: {
17963
- ...loser.memory.frontmatter,
17964
- status: "deprecated",
17965
- stale_reason: plan.stale_reason,
17966
- related_ids: [.../* @__PURE__ */ new Set([...loser.memory.frontmatter.related_ids, plan.keep_id])]
17967
- },
17968
- body: loser.memory.body
17969
- }),
18139
+ serializeMemory31({ frontmatter: applied.loser, body: loser.memory.body }),
17970
18140
  "utf8"
17971
18141
  );
17972
- if (!opts.json) ui.success(`Deprecated ${plan.supersede_id}; ${plan.keep_id} remains authoritative.`);
18142
+ if (!opts.json) {
18143
+ ui.success(`Deprecated ${plan.supersede_id}; promoted ${plan.keep_id} (rev ${applied.winner.revision_count}${applied.topic ? `, topic=${applied.topic}` : ""}).`);
18144
+ }
17973
18145
  });
17974
18146
  }
17975
18147
 
17976
18148
  // src/commands/memory-seed-git.ts
17977
- import { execFile as execFile5 } from "child_process";
18149
+ import { execFile as execFile6 } from "child_process";
17978
18150
  import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
17979
- import { existsSync as existsSync83 } from "fs";
17980
- import path58 from "path";
17981
- import { promisify as promisify5 } from "util";
18151
+ import { existsSync as existsSync84 } from "fs";
18152
+ import path60 from "path";
18153
+ import { promisify as promisify6 } from "util";
17982
18154
  import "commander";
17983
18155
  import {
17984
18156
  buildFrontmatter as buildFrontmatter12,
@@ -17988,12 +18160,12 @@ import {
17988
18160
  resolveHaivePaths as resolveHaivePaths54,
17989
18161
  serializeMemory as serializeMemory33
17990
18162
  } from "@hiveai/core";
17991
- var exec4 = promisify5(execFile5);
18163
+ var exec4 = promisify6(execFile6);
17992
18164
  function registerMemorySeedGit(memory2) {
17993
18165
  memory2.command("seed-git").description("Propose draft `attempt` seeds from revert/hotfix commits in git history (cold-start)").option("--apply", "write the proposed seeds as draft memories (default: preview only)", false).option("--limit <n>", "max seeds to propose", "20").option("--days <n>", "git-history lookback window in days", "365").option("--scope <scope>", "personal | team", "team").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
17994
18166
  const root = findProjectRoot60(opts.dir);
17995
18167
  const paths = resolveHaivePaths54(root);
17996
- if (!existsSync83(paths.haiveDir)) {
18168
+ if (!existsSync84(paths.haiveDir)) {
17997
18169
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
17998
18170
  process.exitCode = 1;
17999
18171
  return;
@@ -18038,8 +18210,8 @@ function registerMemorySeedGit(memory2) {
18038
18210
  _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
18039
18211
  `;
18040
18212
  const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
18041
- if (existsSync83(file)) continue;
18042
- await mkdir25(path58.dirname(file), { recursive: true });
18213
+ if (existsSync84(file)) continue;
18214
+ await mkdir25(path60.dirname(file), { recursive: true });
18043
18215
  await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
18044
18216
  written += 1;
18045
18217
  }
@@ -18071,8 +18243,8 @@ async function readCommits(root, days) {
18071
18243
  }
18072
18244
 
18073
18245
  // src/commands/bridges.ts
18074
- import { existsSync as existsSync84 } from "fs";
18075
- import path59 from "path";
18246
+ import { existsSync as existsSync85 } from "fs";
18247
+ import path61 from "path";
18076
18248
  import "commander";
18077
18249
  import {
18078
18250
  findProjectRoot as findProjectRoot61,
@@ -18093,7 +18265,7 @@ function registerBridges(program2) {
18093
18265
  const root = findProjectRoot61(opts.dir);
18094
18266
  const paths = resolveHaivePaths55(root);
18095
18267
  const dryRun = opts.dryRun === true;
18096
- if (!existsSync84(paths.memoriesDir)) {
18268
+ if (!existsSync85(paths.memoriesDir)) {
18097
18269
  ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
18098
18270
  process.exitCode = 1;
18099
18271
  return;
@@ -18112,7 +18284,7 @@ function registerBridges(program2) {
18112
18284
  targets = BRIDGE_TARGETS3;
18113
18285
  } else {
18114
18286
  targets = BRIDGE_TARGETS3.filter(
18115
- (t) => existsSync84(path59.join(root, BRIDGE_TARGET_PATH3[t]))
18287
+ (t) => existsSync85(path61.join(root, BRIDGE_TARGET_PATH3[t]))
18116
18288
  );
18117
18289
  if (targets.length === 0) {
18118
18290
  ui.info(
@@ -18141,7 +18313,7 @@ function registerBridges(program2) {
18141
18313
  console.log(ui.bold("hAIve bridge targets:"));
18142
18314
  for (const target of BRIDGE_TARGETS3) {
18143
18315
  const relPath = BRIDGE_TARGET_PATH3[target];
18144
- const exists = existsSync84(path59.join(root, relPath));
18316
+ const exists = existsSync85(path61.join(root, relPath));
18145
18317
  const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
18146
18318
  console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
18147
18319
  }
@@ -18152,7 +18324,7 @@ function registerBridges(program2) {
18152
18324
 
18153
18325
  // src/index.ts
18154
18326
  var program = new Command64();
18155
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.21.0").option("--advanced", "show maintenance and experimental commands in help");
18327
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.24.0").option("--advanced", "show maintenance and experimental commands in help");
18156
18328
  registerInit(program);
18157
18329
  registerWelcome(program);
18158
18330
  registerResolveProject(program);