@hiveai/cli 0.9.28 → 0.9.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command51 } from "commander";
4
+ import { Command as Command52 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
199
199
  if (!f) continue;
200
200
  counts.set(f, (counts.get(f) ?? 0) + 1);
201
201
  }
202
- let entries = [...counts.entries()].map(([path50, changes]) => ({ path: path50, changes }));
202
+ let entries = [...counts.entries()].map(([path51, changes]) => ({ path: path51, changes }));
203
203
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
204
204
  if (lowerPaths.length > 0) {
205
205
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -1213,12 +1213,14 @@ async function loadEmbeddings() {
1213
1213
  }
1214
1214
 
1215
1215
  // src/commands/index-code.ts
1216
+ import { existsSync as existsSync5, statSync } from "fs";
1216
1217
  import path5 from "path";
1217
1218
  import "commander";
1218
1219
  import {
1219
1220
  buildCodeMap as buildCodeMap2,
1220
1221
  codeMapPath,
1221
1222
  findProjectRoot as findProjectRoot5,
1223
+ loadCodeMap as loadCodeMap4,
1222
1224
  resolveHaivePaths as resolveHaivePaths4,
1223
1225
  saveCodeMap as saveCodeMap2
1224
1226
  } from "@hiveai/core";
@@ -1228,14 +1230,18 @@ function registerIndexCode(program2) {
1228
1230
  );
1229
1231
  idx.action(() => idx.help());
1230
1232
  idx.command("code").description(
1231
- "Scan source files and write .ai/code-map.json (file \u2192 exports + 1-line description).\n\n Supported languages: TypeScript, JavaScript, Java, Python, Go, Rust, C#, PHP.\n The map is used by:\n \u2022 get_briefing (symbol_locations) \u2014 look up where a class/function lives\n \u2022 code_map MCP tool \u2014 browse exports without grepping\n \u2022 haive briefing --symbols \u2014 look up symbols from the CLI\n\n Run automatically by haive init (autopilot mode) and haive sync (if source changed).\n\n Example:\n haive index code\n haive index code --exclude generated,proto\n"
1233
+ "Scan source files and write .ai/code-map.json (file \u2192 exports + 1-line description).\n\n Supported languages: TypeScript, JavaScript, Java, Python, Go, Rust, C#, PHP.\n The map is used by:\n \u2022 get_briefing (symbol_locations) \u2014 look up where a class/function lives\n \u2022 code_map MCP tool \u2014 browse exports without grepping\n \u2022 haive briefing --symbols \u2014 look up symbols from the CLI\n\n Run automatically by haive init (autopilot mode) and haive sync (if source changed).\n\n Example:\n haive index code\n haive index code --status # report freshness without rebuilding\n haive index code --exclude generated,proto\n"
1232
1234
  ).option("-d, --dir <dir>", "project root").option(
1233
1235
  "--exclude <csv>",
1234
1236
  "extra directory names to skip (comma-separated)",
1235
1237
  ""
1236
- ).action(async (opts) => {
1238
+ ).option("--status", "report code-map / code-search index freshness without rebuilding").option("--json", "with --status, emit machine-readable JSON (for CI / agents)").action(async (opts) => {
1237
1239
  const root = findProjectRoot5(opts.dir);
1238
1240
  const paths = resolveHaivePaths4(root);
1241
+ if (opts.status) {
1242
+ await reportIndexStatus(root, paths, opts.json === true);
1243
+ return;
1244
+ }
1239
1245
  const extraExcludes = (opts.exclude ?? "").split(",").map((s) => s.trim()).filter(Boolean);
1240
1246
  ui.info(`Indexing source files in ${root}\u2026`);
1241
1247
  const map = await buildCodeMap2(root, {
@@ -1288,10 +1294,49 @@ function registerIndexCode(program2) {
1288
1294
  }
1289
1295
  });
1290
1296
  }
1297
+ async function reportIndexStatus(root, paths, asJson) {
1298
+ const mapFile = codeMapPath(paths);
1299
+ const map = existsSync5(mapFile) ? await loadCodeMap4(paths) : null;
1300
+ const fileCount = map ? Object.keys(map.files).length : 0;
1301
+ const exportCount = map ? Object.values(map.files).reduce((s, f) => s + f.exports.length, 0) : 0;
1302
+ const mapMtime = existsSync5(mapFile) ? statSync(mapFile).mtime.toISOString() : null;
1303
+ const searchIndexFile = path5.join(paths.haiveDir, ".cache", "embeddings", "code-embeddings-index.json");
1304
+ const searchIndexPresent = existsSync5(searchIndexFile);
1305
+ const status = {
1306
+ code_map: {
1307
+ present: map !== null,
1308
+ path: path5.relative(root, mapFile),
1309
+ files: fileCount,
1310
+ exports: exportCount,
1311
+ generated_at: map?.generated_at ?? null,
1312
+ file_mtime: mapMtime
1313
+ },
1314
+ code_search_index: {
1315
+ present: searchIndexPresent,
1316
+ path: path5.relative(root, searchIndexFile)
1317
+ }
1318
+ };
1319
+ if (asJson) {
1320
+ console.log(JSON.stringify(status, null, 2));
1321
+ if (!status.code_map.present) process.exitCode = 1;
1322
+ return;
1323
+ }
1324
+ if (!status.code_map.present) {
1325
+ ui.warn(`No code-map at ${status.code_map.path}. Run \`haive index code\`.`);
1326
+ process.exitCode = 1;
1327
+ return;
1328
+ }
1329
+ ui.info(
1330
+ `code-map: ${fileCount} file(s), ${exportCount} export(s) \xB7 generated ${status.code_map.generated_at ?? "?"}`
1331
+ );
1332
+ ui.info(
1333
+ status.code_search_index.present ? `code-search index: present (${status.code_search_index.path})` : `code-search index: missing \u2014 run \`haive index code-search\` for semantic code lookup.`
1334
+ );
1335
+ }
1291
1336
 
1292
1337
  // src/commands/init.ts
1293
1338
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
1294
- import { existsSync as existsSync9 } from "fs";
1339
+ import { existsSync as existsSync10 } from "fs";
1295
1340
  import path10 from "path";
1296
1341
  import { spawnSync as spawnSync3 } from "child_process";
1297
1342
  import "commander";
@@ -1305,7 +1350,7 @@ import {
1305
1350
 
1306
1351
  // src/commands/agent.ts
1307
1352
  import { spawnSync as spawnSync2 } from "child_process";
1308
- import { existsSync as existsSync6 } from "fs";
1353
+ import { existsSync as existsSync7 } from "fs";
1309
1354
  import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
1310
1355
  import os2 from "os";
1311
1356
  import path7 from "path";
@@ -1315,7 +1360,7 @@ import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaiveP
1315
1360
 
1316
1361
  // src/commands/init-mcp-setup.ts
1317
1362
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
1318
- import { existsSync as existsSync5 } from "fs";
1363
+ import { existsSync as existsSync6 } from "fs";
1319
1364
  import path6 from "path";
1320
1365
  import os from "os";
1321
1366
  var HOME = os.homedir();
@@ -1336,9 +1381,9 @@ function cursorMcpPath() {
1336
1381
  async function configureCursor() {
1337
1382
  const mcpPath = cursorMcpPath();
1338
1383
  const cursorDir = path6.join(HOME, ".cursor");
1339
- if (!existsSync5(cursorDir)) return { client: "Cursor", status: "not_installed" };
1384
+ if (!existsSync6(cursorDir)) return { client: "Cursor", status: "not_installed" };
1340
1385
  let config = {};
1341
- if (existsSync5(mcpPath)) {
1386
+ if (existsSync6(mcpPath)) {
1342
1387
  try {
1343
1388
  config = JSON.parse(await readFile3(mcpPath, "utf8"));
1344
1389
  } catch {
@@ -1362,7 +1407,7 @@ function vscodeMcpPath() {
1362
1407
  path6.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
1363
1408
  ];
1364
1409
  for (const c of candidates) {
1365
- if (existsSync5(path6.dirname(c))) return c;
1410
+ if (existsSync6(path6.dirname(c))) return c;
1366
1411
  }
1367
1412
  return null;
1368
1413
  }
@@ -1370,7 +1415,7 @@ async function configureVSCode() {
1370
1415
  const mcpPath = vscodeMcpPath();
1371
1416
  if (!mcpPath) return { client: "VS Code", status: "not_installed" };
1372
1417
  let config = {};
1373
- if (existsSync5(mcpPath)) {
1418
+ if (existsSync6(mcpPath)) {
1374
1419
  try {
1375
1420
  config = JSON.parse(await readFile3(mcpPath, "utf8"));
1376
1421
  } catch {
@@ -1385,18 +1430,18 @@ async function configureVSCode() {
1385
1430
  }
1386
1431
  function claudeConfigPath() {
1387
1432
  const p = path6.join(HOME, ".claude.json");
1388
- if (existsSync5(p)) return p;
1433
+ if (existsSync6(p)) return p;
1389
1434
  const p2 = path6.join(HOME, ".config", "claude", "claude.json");
1390
- if (existsSync5(path6.dirname(p2))) return p2;
1435
+ if (existsSync6(path6.dirname(p2))) return p2;
1391
1436
  return null;
1392
1437
  }
1393
1438
  async function configureClaude() {
1394
1439
  const cfgPath = claudeConfigPath() ?? path6.join(HOME, ".claude.json");
1395
- if (!existsSync5(cfgPath) && !existsSync5(path6.join(HOME, ".claude"))) {
1440
+ if (!existsSync6(cfgPath) && !existsSync6(path6.join(HOME, ".claude"))) {
1396
1441
  return { client: "Claude Code", status: "not_installed" };
1397
1442
  }
1398
1443
  let config = {};
1399
- if (existsSync5(cfgPath)) {
1444
+ if (existsSync6(cfgPath)) {
1400
1445
  try {
1401
1446
  config = JSON.parse(await readFile3(cfgPath, "utf8"));
1402
1447
  } catch {
@@ -1414,7 +1459,7 @@ function windsurfMcpPath() {
1414
1459
  path6.join(HOME, ".windsurf", "mcp.json")
1415
1460
  ];
1416
1461
  for (const c of candidates) {
1417
- if (existsSync5(path6.dirname(c))) return c;
1462
+ if (existsSync6(path6.dirname(c))) return c;
1418
1463
  }
1419
1464
  return null;
1420
1465
  }
@@ -1422,7 +1467,7 @@ async function configureWindsurf() {
1422
1467
  const mcpPath = windsurfMcpPath();
1423
1468
  if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
1424
1469
  let config = {};
1425
- if (existsSync5(mcpPath)) {
1470
+ if (existsSync6(mcpPath)) {
1426
1471
  try {
1427
1472
  config = JSON.parse(await readFile3(mcpPath, "utf8"));
1428
1473
  } catch {
@@ -1454,7 +1499,7 @@ async function configureProjectMcpClients(root) {
1454
1499
  try {
1455
1500
  const cursorPath = path6.join(root, ".cursor", "mcp.json");
1456
1501
  let config = {};
1457
- if (existsSync5(cursorPath)) {
1502
+ if (existsSync6(cursorPath)) {
1458
1503
  try {
1459
1504
  config = JSON.parse(await readFile3(cursorPath, "utf8"));
1460
1505
  } catch {
@@ -1471,7 +1516,7 @@ async function configureProjectMcpClients(root) {
1471
1516
  try {
1472
1517
  const vscodePath = path6.join(root, ".vscode", "mcp.json");
1473
1518
  let config = {};
1474
- if (existsSync5(vscodePath)) {
1519
+ if (existsSync6(vscodePath)) {
1475
1520
  try {
1476
1521
  config = JSON.parse(await readFile3(vscodePath, "utf8"));
1477
1522
  } catch {
@@ -1488,7 +1533,7 @@ async function configureProjectMcpClients(root) {
1488
1533
  try {
1489
1534
  const mcpPath = path6.join(root, ".mcp.json");
1490
1535
  let config = {};
1491
- if (existsSync5(mcpPath)) {
1536
+ if (existsSync6(mcpPath)) {
1492
1537
  try {
1493
1538
  config = JSON.parse(await readFile3(mcpPath, "utf8"));
1494
1539
  } catch {
@@ -1562,9 +1607,9 @@ async function detectAgentMode(dir) {
1562
1607
  const root = findProjectRoot6(dir);
1563
1608
  const paths = resolveHaivePaths5(root);
1564
1609
  const projectMcp = [
1565
- { client: "Claude Code", path: path7.join(root, ".mcp.json"), present: existsSync6(path7.join(root, ".mcp.json")) },
1566
- { client: "Cursor", path: path7.join(root, ".cursor", "mcp.json"), present: existsSync6(path7.join(root, ".cursor", "mcp.json")) },
1567
- { client: "VS Code", path: path7.join(root, ".vscode", "mcp.json"), present: existsSync6(path7.join(root, ".vscode", "mcp.json")) }
1610
+ { client: "Claude Code", path: path7.join(root, ".mcp.json"), present: existsSync7(path7.join(root, ".mcp.json")) },
1611
+ { client: "Cursor", path: path7.join(root, ".cursor", "mcp.json"), present: existsSync7(path7.join(root, ".cursor", "mcp.json")) },
1612
+ { client: "VS Code", path: path7.join(root, ".vscode", "mcp.json"), present: existsSync7(path7.join(root, ".vscode", "mcp.json")) }
1568
1613
  ];
1569
1614
  const installedAgents = [
1570
1615
  { agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
@@ -1579,7 +1624,7 @@ async function detectAgentMode(dir) {
1579
1624
  const recommendedCommand = recommendedMode === "mcp" ? "Restart your AI client, then call get_briefing before editing." : recommendedMode === "wrapped" && wrapperAgent ? `haive run -- ${wrapperAgent.command}` : 'haive briefing --task "..." --files "..."';
1580
1625
  return {
1581
1626
  root,
1582
- initialized: existsSync6(paths.haiveDir),
1627
+ initialized: existsSync7(paths.haiveDir),
1583
1628
  project_mcp: projectMcp,
1584
1629
  installed_agents: installedAgents,
1585
1630
  recommended_mode: recommendedMode,
@@ -1686,7 +1731,7 @@ function printSetupResult(result) {
1686
1731
 
1687
1732
  // src/commands/init-bootstrap.ts
1688
1733
  import { readdir, readFile as readFile4 } from "fs/promises";
1689
- import { existsSync as existsSync7 } from "fs";
1734
+ import { existsSync as existsSync8, readdirSync } from "fs";
1690
1735
  import path8 from "path";
1691
1736
  var IGNORE_DIRS = /* @__PURE__ */ new Set([
1692
1737
  "node_modules",
@@ -1773,14 +1818,38 @@ function detectKeyDeps(allDeps) {
1773
1818
  return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
1774
1819
  }
1775
1820
  function detectLanguage(root) {
1776
- if (existsSync7(path8.join(root, "tsconfig.json"))) return "TypeScript";
1777
- if (existsSync7(path8.join(root, "pyproject.toml")) || existsSync7(path8.join(root, "setup.py"))) return "Python";
1778
- if (existsSync7(path8.join(root, "go.mod"))) return "Go";
1779
- if (existsSync7(path8.join(root, "pom.xml")) || existsSync7(path8.join(root, "build.gradle"))) return "Java/Kotlin";
1780
- if (existsSync7(path8.join(root, "Cargo.toml"))) return "Rust";
1781
- if (existsSync7(path8.join(root, "package.json"))) return "JavaScript";
1821
+ if (existsSync8(path8.join(root, "tsconfig.json"))) return "TypeScript";
1822
+ if (existsSync8(path8.join(root, "pyproject.toml")) || existsSync8(path8.join(root, "setup.py"))) return "Python";
1823
+ if (existsSync8(path8.join(root, "go.mod"))) return "Go";
1824
+ if (existsSync8(path8.join(root, "pom.xml")) || existsSync8(path8.join(root, "build.gradle"))) return "Java/Kotlin";
1825
+ if (existsSync8(path8.join(root, "Cargo.toml"))) return "Rust";
1826
+ if (existsSync8(path8.join(root, "package.json"))) {
1827
+ return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
1828
+ }
1782
1829
  return "Unknown";
1783
1830
  }
1831
+ function hasSourceWithExt(root, exts) {
1832
+ const matches = (name) => !name.endsWith(".d.ts") && exts.some((ext) => name.endsWith(ext));
1833
+ const scanDir = (dir, depth) => {
1834
+ let entries;
1835
+ try {
1836
+ entries = readdirSync(dir, { withFileTypes: true });
1837
+ } catch {
1838
+ return false;
1839
+ }
1840
+ for (const entry of entries) {
1841
+ if (entry.isFile() && matches(entry.name)) return true;
1842
+ }
1843
+ if (depth <= 0) return false;
1844
+ for (const entry of entries) {
1845
+ if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1846
+ if (scanDir(path8.join(dir, entry.name), depth - 1)) return true;
1847
+ }
1848
+ }
1849
+ return false;
1850
+ };
1851
+ return scanDir(root, 2);
1852
+ }
1784
1853
  function detectProjectType(frameworks, scripts, isMonorepo) {
1785
1854
  if (isMonorepo) {
1786
1855
  if (frameworks.includes("NestJS")) return "Monorepo (NestJS backend)";
@@ -1795,7 +1864,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
1795
1864
  if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
1796
1865
  if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
1797
1866
  if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
1798
- if (existsSync7("pom.xml")) return "Java backend";
1867
+ if (existsSync8("pom.xml")) return "Java backend";
1799
1868
  return "Application";
1800
1869
  }
1801
1870
  async function scanDirs(root, maxDepth = 2) {
@@ -1931,7 +2000,7 @@ function readmeExcerpt(readme) {
1931
2000
  async function generateBootstrapContext(root) {
1932
2001
  let pkg = {};
1933
2002
  const pkgPath = path8.join(root, "package.json");
1934
- if (existsSync7(pkgPath)) {
2003
+ if (existsSync8(pkgPath)) {
1935
2004
  try {
1936
2005
  pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
1937
2006
  } catch {
@@ -1948,7 +2017,7 @@ async function generateBootstrapContext(root) {
1948
2017
  let readmeSummary = "";
1949
2018
  for (const name of ["README.md", "readme.md", "README"]) {
1950
2019
  const p = path8.join(root, name);
1951
- if (existsSync7(p)) {
2020
+ if (existsSync8(p)) {
1952
2021
  try {
1953
2022
  const content = await readFile4(p, "utf8");
1954
2023
  readmeSummary = readmeExcerpt(content);
@@ -2015,7 +2084,7 @@ async function generateBootstrapContext(root) {
2015
2084
 
2016
2085
  // src/commands/init-stack-packs.ts
2017
2086
  import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
2018
- import { existsSync as existsSync8 } from "fs";
2087
+ import { existsSync as existsSync9 } from "fs";
2019
2088
  import path9 from "path";
2020
2089
  import {
2021
2090
  buildFrontmatter,
@@ -2643,7 +2712,7 @@ async function seedStackPack(haivePaths, stack) {
2643
2712
  tags: [...mem.tags, STACK_PACK_TAG]
2644
2713
  });
2645
2714
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
2646
- if (existsSync8(filePath)) continue;
2715
+ if (existsSync9(filePath)) continue;
2647
2716
  const content = serializeMemory2({ frontmatter: fm, body: `${mem.body}
2648
2717
 
2649
2718
  ${SEED_FOOTER(stack)}` });
@@ -2867,7 +2936,7 @@ function registerInit(program2) {
2867
2936
  const autopilot = opts.manual !== true;
2868
2937
  const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
2869
2938
  const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
2870
- if (existsSync9(paths.haiveDir)) {
2939
+ if (existsSync10(paths.haiveDir)) {
2871
2940
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
2872
2941
  }
2873
2942
  await mkdir5(paths.personalDir, { recursive: true });
@@ -2876,7 +2945,7 @@ function registerInit(program2) {
2876
2945
  await mkdir5(paths.modulesContextDir, { recursive: true });
2877
2946
  await ensureAiRuntimeLayout(paths.runtimeDir);
2878
2947
  await ensureAiCacheLayout(path10.join(paths.haiveDir, ".cache"));
2879
- if (!existsSync9(paths.projectContext)) {
2948
+ if (!existsSync10(paths.projectContext)) {
2880
2949
  if (wantBootstrap) {
2881
2950
  ui.info("Bootstrapping project context from local files\u2026");
2882
2951
  try {
@@ -2892,7 +2961,7 @@ function registerInit(program2) {
2892
2961
  ui.success(`Created ${path10.relative(root, paths.projectContext)}`);
2893
2962
  }
2894
2963
  }
2895
- const configExists = existsSync9(
2964
+ const configExists = existsSync10(
2896
2965
  path10.join(paths.haiveDir, "haive.config.json")
2897
2966
  );
2898
2967
  if (!configExists) {
@@ -2931,7 +3000,7 @@ function registerInit(program2) {
2931
3000
  const wantCi = opts.withCi || autopilot;
2932
3001
  if (wantCi) {
2933
3002
  const ciPath = path10.join(root, ".github", "workflows", "haive-sync.yml");
2934
- if (existsSync9(ciPath)) {
3003
+ if (existsSync10(ciPath)) {
2935
3004
  ui.info("CI workflow already exists \u2014 skipped");
2936
3005
  } else {
2937
3006
  await mkdir5(path10.dirname(ciPath), { recursive: true });
@@ -2980,8 +3049,8 @@ function registerInit(program2) {
2980
3049
  else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
2981
3050
  }
2982
3051
  for (const r of agentSetup.global_results) {
2983
- if (r.status === "configured") ui.success(`haive MCP configured in ${r.client}${r.path ? ` (${r.path})` : ""}`);
2984
- else if (r.status === "already_configured") ui.info(`haive MCP already configured in ${r.client} \u2014 skipped`);
3052
+ if (r.status === "configured") ui.success(`haive MCP configured in ${r.client} user-level config${r.path ? ` (${r.path})` : ""}`);
3053
+ else if (r.status === "already_configured") ui.info(`haive MCP already present in ${r.client} user-level config \u2014 left unchanged (this project's config was written above)`);
2985
3054
  }
2986
3055
  if (agentSetup.global_skipped_reason) ui.warn(agentSetup.global_skipped_reason);
2987
3056
  ui.info(`Recommended agent mode: ${agentSetup.detection.recommended_mode}`);
@@ -3048,7 +3117,7 @@ async function resolveStacksToSeed(root, stackOpt) {
3048
3117
  if (!stackOpt) return [];
3049
3118
  if (stackOpt === "auto") {
3050
3119
  const pkgPath = path10.join(root, "package.json");
3051
- if (!existsSync9(pkgPath)) return [];
3120
+ if (!existsSync10(pkgPath)) return [];
3052
3121
  try {
3053
3122
  const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
3054
3123
  const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
@@ -3062,7 +3131,7 @@ async function resolveStacksToSeed(root, stackOpt) {
3062
3131
  async function writeCursorHaiveRule(root) {
3063
3132
  const relPath = ".cursor/rules/haive-mcp-required.mdc";
3064
3133
  const target = path10.join(root, relPath);
3065
- if (existsSync9(target)) {
3134
+ if (existsSync10(target)) {
3066
3135
  ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
3067
3136
  return;
3068
3137
  }
@@ -3072,7 +3141,7 @@ async function writeCursorHaiveRule(root) {
3072
3141
  }
3073
3142
  async function writeBridge(root, relPath) {
3074
3143
  const target = path10.join(root, relPath);
3075
- if (existsSync9(target)) {
3144
+ if (existsSync10(target)) {
3076
3145
  ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
3077
3146
  return;
3078
3147
  }
@@ -3095,18 +3164,18 @@ var RUNTIME_GITIGNORE_BODY = `*
3095
3164
  async function ensureAiRuntimeLayout(runtimeDir) {
3096
3165
  await mkdir5(runtimeDir, { recursive: true });
3097
3166
  const gi = path10.join(runtimeDir, ".gitignore");
3098
- if (!existsSync9(gi)) {
3167
+ if (!existsSync10(gi)) {
3099
3168
  await writeFile6(gi, RUNTIME_GITIGNORE_BODY, "utf8");
3100
3169
  }
3101
3170
  const readme = path10.join(runtimeDir, "README.md");
3102
- if (!existsSync9(readme)) {
3171
+ if (!existsSync10(readme)) {
3103
3172
  await writeFile6(readme, RUNTIME_README_BODY, "utf8");
3104
3173
  }
3105
3174
  }
3106
3175
  async function ensureAiCacheLayout(cacheDir) {
3107
3176
  await mkdir5(cacheDir, { recursive: true });
3108
3177
  const gi = path10.join(cacheDir, ".gitignore");
3109
- if (!existsSync9(gi)) {
3178
+ if (!existsSync10(gi)) {
3110
3179
  await writeFile6(gi, "*\n!.gitignore\n", "utf8");
3111
3180
  }
3112
3181
  }
@@ -3114,7 +3183,7 @@ async function ensureGitignoreEntries(root, patterns) {
3114
3183
  try {
3115
3184
  const gitignorePath = path10.join(root, ".gitignore");
3116
3185
  let existing = "";
3117
- if (existsSync9(gitignorePath)) {
3186
+ if (existsSync10(gitignorePath)) {
3118
3187
  existing = await readFile5(gitignorePath, "utf8");
3119
3188
  }
3120
3189
  const lines = existing.split("\n");
@@ -3128,13 +3197,13 @@ async function ensureGitignoreEntries(root, patterns) {
3128
3197
 
3129
3198
  // src/commands/install-hooks.ts
3130
3199
  import { mkdir as mkdir7, writeFile as writeFile8, chmod, readFile as readFile7 } from "fs/promises";
3131
- import { existsSync as existsSync11 } from "fs";
3200
+ import { existsSync as existsSync12 } from "fs";
3132
3201
  import path12 from "path";
3133
3202
  import "commander";
3134
3203
  import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
3135
3204
 
3136
3205
  // src/utils/claude-hooks.ts
3137
- import { existsSync as existsSync10 } from "fs";
3206
+ import { existsSync as existsSync11 } from "fs";
3138
3207
  import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
3139
3208
  import path11 from "path";
3140
3209
  var HAIVE_HOOK_TAG = "haive-enforcement";
@@ -3222,7 +3291,7 @@ function unpatchClaudeSettings(input) {
3222
3291
  async function installClaudeHooksAtPath(settingsPath) {
3223
3292
  let raw = null;
3224
3293
  let created = false;
3225
- if (existsSync10(settingsPath)) {
3294
+ if (existsSync11(settingsPath)) {
3226
3295
  try {
3227
3296
  raw = JSON.parse(await readFile6(settingsPath, "utf8"));
3228
3297
  } catch {
@@ -3237,7 +3306,7 @@ async function installClaudeHooksAtPath(settingsPath) {
3237
3306
  return { settingsPath, created };
3238
3307
  }
3239
3308
  async function uninstallClaudeHooksAtPath(settingsPath) {
3240
- if (!existsSync10(settingsPath)) {
3309
+ if (!existsSync11(settingsPath)) {
3241
3310
  return { settingsPath, created: false };
3242
3311
  }
3243
3312
  const raw = JSON.parse(await readFile6(settingsPath, "utf8"));
@@ -3308,7 +3377,7 @@ fi
3308
3377
  async function installGitHooks(opts) {
3309
3378
  const root = findProjectRoot8(opts.dir);
3310
3379
  const gitDir = path12.join(root, ".git");
3311
- if (!existsSync11(gitDir)) {
3380
+ if (!existsSync12(gitDir)) {
3312
3381
  ui.error(`No .git directory at ${root}.`);
3313
3382
  process.exitCode = 1;
3314
3383
  return;
@@ -3319,7 +3388,7 @@ async function installGitHooks(opts) {
3319
3388
  let skipped = 0;
3320
3389
  for (const { name, body } of HOOKS) {
3321
3390
  const file = path12.join(hooksDir, name);
3322
- if (existsSync11(file) && !opts.force) {
3391
+ if (existsSync12(file) && !opts.force) {
3323
3392
  const existing = await readFile7(file, "utf8");
3324
3393
  if (!existing.includes(HOOK_MARKER)) {
3325
3394
  ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
@@ -3383,7 +3452,7 @@ function registerInstallHooks(program2) {
3383
3452
 
3384
3453
  // src/commands/observe.ts
3385
3454
  import { appendFile, mkdir as mkdir8 } from "fs/promises";
3386
- import { existsSync as existsSync12 } from "fs";
3455
+ import { existsSync as existsSync13 } from "fs";
3387
3456
  import path13 from "path";
3388
3457
  import "commander";
3389
3458
  import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
@@ -3484,7 +3553,7 @@ function registerObserve(program2) {
3484
3553
  })();
3485
3554
  if (!root) return;
3486
3555
  const paths = resolveHaivePaths7(root);
3487
- if (!existsSync12(paths.haiveDir)) return;
3556
+ if (!existsSync13(paths.haiveDir)) return;
3488
3557
  const failureHint = detectFailure(payload);
3489
3558
  const observation = {
3490
3559
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3516,7 +3585,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3516
3585
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3517
3586
  import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
3518
3587
  import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
3519
- import { existsSync as existsSync13 } from "fs";
3588
+ import { existsSync as existsSync14 } from "fs";
3520
3589
  import path14 from "path";
3521
3590
  import { z } from "zod";
3522
3591
  import { readFile as readFile8, readdir as readdir2 } from "fs/promises";
@@ -3619,7 +3688,7 @@ import {
3619
3688
  } from "@hiveai/core";
3620
3689
  import { z as z13 } from "zod";
3621
3690
  import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
3622
- import { existsSync as existsSync14 } from "fs";
3691
+ import { existsSync as existsSync142 } from "fs";
3623
3692
  import path52 from "path";
3624
3693
  import {
3625
3694
  buildFrontmatter as buildFrontmatter22,
@@ -3672,7 +3741,7 @@ import {
3672
3741
  isStackPackSeed as isStackPackSeed2,
3673
3742
  literalMatchesAllTokens as literalMatchesAllTokens22,
3674
3743
  literalMatchesAnyToken as literalMatchesAnyToken22,
3675
- loadCodeMap as loadCodeMap4,
3744
+ loadCodeMap as loadCodeMap5,
3676
3745
  loadConfig as loadConfig3,
3677
3746
  loadMemoriesFromDir as loadMemoriesFromDir13,
3678
3747
  loadUsageIndex as loadUsageIndex7,
@@ -3795,7 +3864,7 @@ var BootstrapProjectSaveInputSchema = {
3795
3864
  };
3796
3865
  async function bootstrapProjectSave(input, ctx) {
3797
3866
  const target = input.module ? path14.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
3798
- const exists = existsSync13(target);
3867
+ const exists = existsSync14(target);
3799
3868
  if (exists && !input.overwrite) {
3800
3869
  throw new Error(
3801
3870
  `${target} already exists. Pass overwrite=true to replace it.`
@@ -4675,7 +4744,7 @@ var MemTriedInputSchema = {
4675
4744
  author: z14.string().optional().describe("Author handle or email")
4676
4745
  };
4677
4746
  async function memTried(input, ctx) {
4678
- if (!existsSync14(ctx.paths.haiveDir)) {
4747
+ if (!existsSync142(ctx.paths.haiveDir)) {
4679
4748
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4680
4749
  }
4681
4750
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
@@ -4697,7 +4766,7 @@ async function memTried(input, ctx) {
4697
4766
  const body = lines.join("\n") + "\n";
4698
4767
  const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4699
4768
  await mkdir32(path52.dirname(file), { recursive: true });
4700
- if (existsSync14(file)) {
4769
+ if (existsSync142(file)) {
4701
4770
  throw new Error(`Memory already exists at ${file}`);
4702
4771
  }
4703
4772
  await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
@@ -5189,7 +5258,7 @@ async function getBriefing(input, ctx) {
5189
5258
  if ((isTemplateContext || !existsSync18(ctx.paths.projectContext)) && input.include_project_context) {
5190
5259
  const haiveConfig = await loadConfig3(ctx.paths);
5191
5260
  if (haiveConfig.autoContext) {
5192
- const codeMap = await loadCodeMap4(ctx.paths);
5261
+ const codeMap = await loadCodeMap5(ctx.paths);
5193
5262
  if (codeMap) {
5194
5263
  const totalFiles = Object.keys(codeMap.files).length;
5195
5264
  const extensions = /* @__PURE__ */ new Map();
@@ -5317,7 +5386,7 @@ ${m.content}`).join("\n\n---\n\n"),
5317
5386
  }
5318
5387
  }
5319
5388
  if (symbolsToLookup.size > 0) {
5320
- const codeMap = await loadCodeMap4(ctx.paths);
5389
+ const codeMap = await loadCodeMap5(ctx.paths);
5321
5390
  if (codeMap) {
5322
5391
  symbolLocations = [];
5323
5392
  for (const sym of symbolsToLookup) {
@@ -5944,6 +6013,58 @@ var AntiPatternsCheckInputSchema = {
5944
6013
  "When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
5945
6014
  )
5946
6015
  };
6016
+ var CODE_STOPWORDS = /* @__PURE__ */ new Set([
6017
+ "import",
6018
+ "export",
6019
+ "function",
6020
+ "return",
6021
+ "const",
6022
+ "let",
6023
+ "var",
6024
+ "class",
6025
+ "public",
6026
+ "private",
6027
+ "protected",
6028
+ "static",
6029
+ "this",
6030
+ "true",
6031
+ "false",
6032
+ "null",
6033
+ "undefined",
6034
+ "void",
6035
+ "async",
6036
+ "await",
6037
+ "from",
6038
+ "type",
6039
+ "interface",
6040
+ "extends",
6041
+ "implements",
6042
+ "number",
6043
+ "string",
6044
+ "boolean",
6045
+ "value",
6046
+ "default",
6047
+ "case",
6048
+ "break",
6049
+ "continue",
6050
+ "throw",
6051
+ "catch",
6052
+ "finally",
6053
+ "else",
6054
+ "while",
6055
+ "for",
6056
+ "new",
6057
+ "super",
6058
+ "yield",
6059
+ "module",
6060
+ "require",
6061
+ "console"
6062
+ ]);
6063
+ function tokenizeDiffForLiteral(diff) {
6064
+ const wsTokens = tokenizeQuery3(diff);
6065
+ const wordTokens = diff.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 4 && !CODE_STOPWORDS.has(t));
6066
+ return [.../* @__PURE__ */ new Set([...wsTokens, ...wordTokens])];
6067
+ }
5947
6068
  async function antiPatternsCheck(input, ctx) {
5948
6069
  if (!input.diff && input.paths.length === 0) {
5949
6070
  return {
@@ -5997,7 +6118,7 @@ async function antiPatternsCheck(input, ctx) {
5997
6118
  }
5998
6119
  }
5999
6120
  if (input.diff) {
6000
- const tokens = tokenizeQuery3(input.diff);
6121
+ const tokens = tokenizeDiffForLiteral(input.diff);
6001
6122
  if (tokens.length > 0) {
6002
6123
  for (const { memory: memory2 } of negative) {
6003
6124
  if (literalMatchesAnyToken3(memory2, tokens)) {
@@ -6450,7 +6571,10 @@ var PreCommitCheckInputSchema = {
6450
6571
  block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
6451
6572
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
6452
6573
  ),
6453
- semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index).")
6574
+ semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
6575
+ anchored_blocks: z28.boolean().default(false).describe(
6576
+ "When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
6577
+ )
6454
6578
  };
6455
6579
  async function preCommitCheck(input, ctx) {
6456
6580
  if (!input.diff && input.paths.length === 0) {
@@ -6475,7 +6599,7 @@ async function preCommitCheck(input, ctx) {
6475
6599
  const filesTouching = new Set(relevantMatches.map((m) => m.id));
6476
6600
  const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
6477
6601
  const blockOn = input.block_on;
6478
- const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths));
6602
+ const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths, input.anchored_blocks));
6479
6603
  const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
6480
6604
  const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
6481
6605
  const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
@@ -6515,7 +6639,7 @@ async function preCommitCheck(input, ctx) {
6515
6639
  })
6516
6640
  };
6517
6641
  }
6518
- function classifyWarning(warning, paths) {
6642
+ function classifyWarning(warning, paths, anchoredBlocks = false) {
6519
6643
  const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
6520
6644
  const repairCommand = repairCommandForWarning(warning, affectedFiles);
6521
6645
  const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
@@ -6540,6 +6664,15 @@ function classifyWarning(warning, paths) {
6540
6664
  const hasSemantic = warning.reasons.includes("semantic");
6541
6665
  const semanticScore = warning.semantic_score ?? 0;
6542
6666
  const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
6667
+ if (anchoredBlocks && highConfidence && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
6668
+ return {
6669
+ ...warning,
6670
+ level: "blocking",
6671
+ rationale: "high-confidence anti-pattern anchored to a touched file and corroborated by the diff (anchored gate)",
6672
+ affected_files: affectedFiles,
6673
+ repair_command: repairCommand
6674
+ };
6675
+ }
6543
6676
  if (hasSemantic && semanticScore >= 0.45 || highConfidence && warning.reasons.includes("anchor") && warning.reasons.includes("literal")) {
6544
6677
  return {
6545
6678
  ...warning,
@@ -7190,7 +7323,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7190
7323
  };
7191
7324
  }
7192
7325
  var SERVER_NAME = "haive";
7193
- var SERVER_VERSION = "0.9.28";
7326
+ var SERVER_VERSION = "0.9.30";
7194
7327
  function jsonResult(data) {
7195
7328
  return {
7196
7329
  content: [
@@ -8172,7 +8305,7 @@ import {
8172
8305
  isAutoPromoteEligible as isAutoPromoteEligible2,
8173
8306
  isDecaying as isDecaying2,
8174
8307
  isStackPackSeed as isStackPackSeed3,
8175
- loadCodeMap as loadCodeMap5,
8308
+ loadCodeMap as loadCodeMap6,
8176
8309
  loadConfig as loadConfig4,
8177
8310
  loadMemoriesFromDir as loadMemoriesFromDir23,
8178
8311
  loadUsageIndex as loadUsageIndex12,
@@ -8543,7 +8676,7 @@ Attends une **confirmation explicite** avant d'agir.
8543
8676
  ui.warn(`contract watcher failed: ${String(err)}`);
8544
8677
  }
8545
8678
  }
8546
- const existingMap = await loadCodeMap5(paths);
8679
+ const existingMap = await loadCodeMap6(paths);
8547
8680
  if (!dryRun && !existingMap && (config.autopilot || autoRepair.codeMap)) {
8548
8681
  try {
8549
8682
  const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
@@ -8737,7 +8870,7 @@ function registerMemoryAdd(memory2) {
8737
8870
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
8738
8871
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
8739
8872
  `
8740
- ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8873
+ ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8741
8874
  const root = findProjectRoot13(opts.dir);
8742
8875
  const paths = resolveHaivePaths10(root);
8743
8876
  if (!existsSync30(paths.haiveDir)) {
@@ -8747,7 +8880,7 @@ function registerMemoryAdd(memory2) {
8747
8880
  }
8748
8881
  const config = await loadConfig5(paths);
8749
8882
  const userTags = parseCsv2(opts.tags);
8750
- const anchorPaths = parseCsv2(opts.paths);
8883
+ const anchorPaths = parseCsv2(opts.paths ?? opts.files);
8751
8884
  const autoTagsEnabled = opts.autoTag !== false;
8752
8885
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
8753
8886
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
@@ -9156,7 +9289,7 @@ import {
9156
9289
  serializeMemory as serializeMemory15
9157
9290
  } from "@hiveai/core";
9158
9291
  function registerMemoryUpdate(memory2) {
9159
- memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9292
+ memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9160
9293
  const root = findProjectRoot17(opts.dir);
9161
9294
  const paths = resolveHaivePaths14(root);
9162
9295
  if (!existsSync35(paths.memoriesDir)) {
@@ -9174,8 +9307,9 @@ function registerMemoryUpdate(memory2) {
9174
9307
  const updated = [];
9175
9308
  const { frontmatter, body } = loaded.memory;
9176
9309
  const newAnchor = { ...frontmatter.anchor };
9177
- if (opts.paths !== void 0) {
9178
- newAnchor.paths = parseCsv3(opts.paths);
9310
+ const pathsOpt = opts.paths ?? opts.files;
9311
+ if (pathsOpt !== void 0) {
9312
+ newAnchor.paths = parseCsv3(pathsOpt);
9179
9313
  updated.push("anchor.paths");
9180
9314
  }
9181
9315
  if (opts.symbols !== void 0) {
@@ -9570,7 +9704,7 @@ function registerMemoryTried(memory2) {
9570
9704
  --instead "use static import in the entry file" \\\\
9571
9705
  --paths packages/cli/src/index.ts
9572
9706
  `
9573
- ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
9707
+ ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
9574
9708
  const root = findProjectRoot22(opts.dir);
9575
9709
  const paths = resolveHaivePaths19(root);
9576
9710
  if (!existsSync40(paths.haiveDir)) {
@@ -9585,7 +9719,7 @@ function registerMemoryTried(memory2) {
9585
9719
  scope: opts.scope,
9586
9720
  module: opts.module,
9587
9721
  tags: parseCsv4(opts.tags),
9588
- paths: parseCsv4(opts.paths),
9722
+ paths: parseCsv4(opts.paths ?? opts.files),
9589
9723
  author: opts.author
9590
9724
  });
9591
9725
  const frontmatter = { ...baseFm, status: "validated" };
@@ -9612,21 +9746,118 @@ function parseCsv4(value) {
9612
9746
  return value.split(",").map((s) => s.trim()).filter(Boolean);
9613
9747
  }
9614
9748
 
9615
- // src/commands/memory-pending.ts
9749
+ // src/commands/memory-seed.ts
9750
+ import { readFile as readFile13 } from "fs/promises";
9616
9751
  import { existsSync as existsSync41 } from "fs";
9617
9752
  import path27 from "path";
9618
9753
  import "commander";
9619
9754
  import {
9620
9755
  findProjectRoot as findProjectRoot23,
9756
+ loadConfig as loadConfig6,
9757
+ resolveHaivePaths as resolveHaivePaths20
9758
+ } from "@hiveai/core";
9759
+ async function readDependencyMap(root) {
9760
+ try {
9761
+ const raw = await readFile13(path27.join(root, "package.json"), "utf8");
9762
+ const pkg = JSON.parse(raw);
9763
+ return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
9764
+ } catch {
9765
+ return {};
9766
+ }
9767
+ }
9768
+ function registerMemorySeed(memory2) {
9769
+ memory2.command("seed [stack]").description(
9770
+ "Seed a stack pack of starter memories on demand.\n\n Stack packs are generic framework gotchas/conventions every team using that\n stack rediscovers. They are tagged `stack-pack` and kept at BACKGROUND priority\n in briefings until you anchor them to a real file or replace them with a\n repo-specific note \u2014 so they never crowd out your own knowledge.\n\n Examples:\n haive memory seed # auto-detect stacks from package.json and seed them\n haive memory seed nestjs # seed a specific stack\n haive memory seed --list # show supported + auto-detected stacks\n haive memory seed --list --json\n"
9771
+ ).option("--list", "list supported stacks (and which are auto-detected here) and exit").option("--json", "machine-readable output (use with --list)").option("-d, --dir <dir>", "project root").action(async (stack, opts) => {
9772
+ const root = findProjectRoot23(opts.dir);
9773
+ const paths = resolveHaivePaths20(root);
9774
+ const deps = await readDependencyMap(root);
9775
+ const detected = autoDetectStacks(deps);
9776
+ if (opts.list) {
9777
+ if (opts.json) {
9778
+ console.log(JSON.stringify({ supported: SUPPORTED_STACKS, detected }, null, 2));
9779
+ return;
9780
+ }
9781
+ ui.info("Supported stacks:");
9782
+ for (const s of SUPPORTED_STACKS) {
9783
+ const mark = detected.includes(s) ? ui.green(" \u2713 detected here") : "";
9784
+ console.log(` \u2022 ${s}${mark}`);
9785
+ }
9786
+ if (detected.length === 0) {
9787
+ ui.info("No stack auto-detected from package.json \u2014 pass a stack name explicitly.");
9788
+ }
9789
+ return;
9790
+ }
9791
+ if (!existsSync41(paths.haiveDir)) {
9792
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9793
+ process.exitCode = 1;
9794
+ return;
9795
+ }
9796
+ let stacksToSeed;
9797
+ if (stack) {
9798
+ if (!isValidStack(stack)) {
9799
+ ui.error(`Unknown stack '${stack}'. Supported: ${SUPPORTED_STACKS.join(", ")}.`);
9800
+ ui.info("Run `haive memory seed --list` to see all stacks.");
9801
+ process.exitCode = 1;
9802
+ return;
9803
+ }
9804
+ stacksToSeed = [stack];
9805
+ } else if (detected.length > 0) {
9806
+ stacksToSeed = detected;
9807
+ ui.info(`Auto-detected from package.json: ${detected.join(", ")}`);
9808
+ } else {
9809
+ ui.error("No stack auto-detected from package.json.");
9810
+ ui.info("Pass a stack name (e.g. `haive memory seed nestjs`) or run `haive memory seed --list`.");
9811
+ process.exitCode = 1;
9812
+ return;
9813
+ }
9814
+ let total = 0;
9815
+ const seededStacks = [];
9816
+ for (const s of stacksToSeed) {
9817
+ const count = await seedStackPack(paths, s);
9818
+ if (count > 0) {
9819
+ total += count;
9820
+ seededStacks.push(`${s} (${count})`);
9821
+ } else {
9822
+ ui.info(`Stack pack '${s}': all memories already exist \u2014 skipped.`);
9823
+ }
9824
+ }
9825
+ if (total === 0) {
9826
+ ui.info("Nothing new to seed \u2014 every memory in the selected pack(s) already exists.");
9827
+ return;
9828
+ }
9829
+ ui.success(`Seeded ${total} starter memor${total === 1 ? "y" : "ies"}: ${seededStacks.join(", ")}`);
9830
+ ui.info("Kept at background priority. Anchor them to a real file (or replace them) to make them high-signal:");
9831
+ ui.info(" haive memory update <id> --paths <key-file> # anchor a seed to a file");
9832
+ const config = await loadConfig6(paths);
9833
+ if (config.autopilot || config.autoRepair?.corpus === true) {
9834
+ const repairs = await applyAutopilotRepairs(root, paths, {
9835
+ applyConfig: false,
9836
+ applyContext: false,
9837
+ applyCorpus: true,
9838
+ applyCodeMap: false,
9839
+ applyCodeSearch: false
9840
+ });
9841
+ for (const repair of repairs) ui.info(repair.message);
9842
+ }
9843
+ });
9844
+ }
9845
+
9846
+ // src/commands/memory-pending.ts
9847
+ import { existsSync as existsSync43 } from "fs";
9848
+ import path28 from "path";
9849
+ import "commander";
9850
+ import {
9851
+ findProjectRoot as findProjectRoot24,
9621
9852
  getUsage as getUsage14,
9622
9853
  loadUsageIndex as loadUsageIndex16,
9623
- resolveHaivePaths as resolveHaivePaths20
9854
+ resolveHaivePaths as resolveHaivePaths21
9624
9855
  } from "@hiveai/core";
9625
9856
  function registerMemoryPending(memory2) {
9626
9857
  memory2.command("pending").description("List draft and proposed memories awaiting review (sorted by reads desc).\n\n draft = created but not yet activated \xB7 proposed = promoted, awaiting team validation").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9627
- const root = findProjectRoot23(opts.dir);
9628
- const paths = resolveHaivePaths20(root);
9629
- if (!existsSync41(paths.memoriesDir)) {
9858
+ const root = findProjectRoot24(opts.dir);
9859
+ const paths = resolveHaivePaths21(root);
9860
+ if (!existsSync43(paths.memoriesDir)) {
9630
9861
  ui.error(`No .ai/memories at ${root}.`);
9631
9862
  process.exitCode = 1;
9632
9863
  return;
@@ -9660,7 +9891,7 @@ function registerMemoryPending(memory2) {
9660
9891
  console.log(
9661
9892
  ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
9662
9893
  );
9663
- console.log(` ${ui.dim(path27.relative(root, filePath))}`);
9894
+ console.log(` ${ui.dim(path28.relative(root, filePath))}`);
9664
9895
  }
9665
9896
  if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
9666
9897
  console.log();
@@ -9675,7 +9906,7 @@ function registerMemoryPending(memory2) {
9675
9906
  console.log(
9676
9907
  ` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
9677
9908
  );
9678
- console.log(` ${ui.dim(path27.relative(root, filePath))}`);
9909
+ console.log(` ${ui.dim(path28.relative(root, filePath))}`);
9679
9910
  }
9680
9911
  console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
9681
9912
  }
@@ -9684,24 +9915,24 @@ function registerMemoryPending(memory2) {
9684
9915
  }
9685
9916
 
9686
9917
  // src/commands/memory-query.ts
9687
- import { existsSync as existsSync43 } from "fs";
9688
- import path28 from "path";
9918
+ import { existsSync as existsSync44 } from "fs";
9919
+ import path29 from "path";
9689
9920
  import "commander";
9690
9921
  import {
9691
9922
  extractSnippet as extractSnippet2,
9692
- findProjectRoot as findProjectRoot24,
9923
+ findProjectRoot as findProjectRoot25,
9693
9924
  literalMatchesAllTokens as literalMatchesAllTokens3,
9694
9925
  literalMatchesAnyToken as literalMatchesAnyToken4,
9695
9926
  pickSnippetNeedle as pickSnippetNeedle2,
9696
- resolveHaivePaths as resolveHaivePaths21,
9927
+ resolveHaivePaths as resolveHaivePaths22,
9697
9928
  tokenizeQuery as tokenizeQuery6,
9698
9929
  trackReads as trackReads4
9699
9930
  } from "@hiveai/core";
9700
9931
  function registerMemoryQuery(memory2) {
9701
9932
  memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
9702
- const root = findProjectRoot24(opts.dir);
9703
- const paths = resolveHaivePaths21(root);
9704
- if (!existsSync43(paths.memoriesDir)) {
9933
+ const root = findProjectRoot25(opts.dir);
9934
+ const paths = resolveHaivePaths22(root);
9935
+ if (!existsSync44(paths.memoriesDir)) {
9705
9936
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
9706
9937
  process.exitCode = 1;
9707
9938
  return;
@@ -9742,7 +9973,7 @@ function registerMemoryQuery(memory2) {
9742
9973
  const fm = mem.frontmatter;
9743
9974
  const statusBadge = ui.statusBadge(fm.status);
9744
9975
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
9745
- console.log(` ${ui.dim(path28.relative(root, filePath))}`);
9976
+ console.log(` ${ui.dim(path29.relative(root, filePath))}`);
9746
9977
  const snippet = extractSnippet2(mem.body, snippetNeedle);
9747
9978
  if (snippet) console.log(` ${snippet}`);
9748
9979
  }
@@ -9760,21 +9991,21 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
9760
9991
 
9761
9992
  // src/commands/memory-reject.ts
9762
9993
  import { writeFile as writeFile20 } from "fs/promises";
9763
- import { existsSync as existsSync44 } from "fs";
9994
+ import { existsSync as existsSync45 } from "fs";
9764
9995
  import "commander";
9765
9996
  import {
9766
- findProjectRoot as findProjectRoot25,
9997
+ findProjectRoot as findProjectRoot26,
9767
9998
  loadUsageIndex as loadUsageIndex17,
9768
9999
  recordRejection as recordRejection2,
9769
- resolveHaivePaths as resolveHaivePaths22,
10000
+ resolveHaivePaths as resolveHaivePaths23,
9770
10001
  saveUsageIndex as saveUsageIndex3,
9771
10002
  serializeMemory as serializeMemory18
9772
10003
  } from "@hiveai/core";
9773
10004
  function registerMemoryReject(memory2) {
9774
10005
  memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9775
- const root = findProjectRoot25(opts.dir);
9776
- const paths = resolveHaivePaths22(root);
9777
- if (!existsSync44(paths.memoriesDir)) {
10006
+ const root = findProjectRoot26(opts.dir);
10007
+ const paths = resolveHaivePaths23(root);
10008
+ if (!existsSync45(paths.memoriesDir)) {
9778
10009
  ui.error(`No .ai/memories at ${root}.`);
9779
10010
  process.exitCode = 1;
9780
10011
  return;
@@ -9810,22 +10041,22 @@ function registerMemoryReject(memory2) {
9810
10041
  }
9811
10042
 
9812
10043
  // src/commands/memory-rm.ts
9813
- import { existsSync as existsSync45 } from "fs";
10044
+ import { existsSync as existsSync46 } from "fs";
9814
10045
  import { unlink as unlink3 } from "fs/promises";
9815
- import path29 from "path";
10046
+ import path30 from "path";
9816
10047
  import { createInterface as createInterface2 } from "readline/promises";
9817
10048
  import "commander";
9818
10049
  import {
9819
- findProjectRoot as findProjectRoot26,
10050
+ findProjectRoot as findProjectRoot27,
9820
10051
  loadUsageIndex as loadUsageIndex18,
9821
- resolveHaivePaths as resolveHaivePaths23,
10052
+ resolveHaivePaths as resolveHaivePaths24,
9822
10053
  saveUsageIndex as saveUsageIndex4
9823
10054
  } from "@hiveai/core";
9824
10055
  function registerMemoryRm(memory2) {
9825
10056
  memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9826
- const root = findProjectRoot26(opts.dir);
9827
- const paths = resolveHaivePaths23(root);
9828
- if (!existsSync45(paths.memoriesDir)) {
10057
+ const root = findProjectRoot27(opts.dir);
10058
+ const paths = resolveHaivePaths24(root);
10059
+ if (!existsSync46(paths.memoriesDir)) {
9829
10060
  ui.error(`No .ai/memories at ${root}.`);
9830
10061
  process.exitCode = 1;
9831
10062
  return;
@@ -9837,7 +10068,7 @@ function registerMemoryRm(memory2) {
9837
10068
  process.exitCode = 1;
9838
10069
  return;
9839
10070
  }
9840
- const rel = path29.relative(root, found.filePath);
10071
+ const rel = path30.relative(root, found.filePath);
9841
10072
  if (!opts.yes) {
9842
10073
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
9843
10074
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -9861,22 +10092,22 @@ function registerMemoryRm(memory2) {
9861
10092
  }
9862
10093
 
9863
10094
  // src/commands/memory-show.ts
9864
- import { existsSync as existsSync46 } from "fs";
9865
- import { readFile as readFile13 } from "fs/promises";
9866
- import path30 from "path";
10095
+ import { existsSync as existsSync47 } from "fs";
10096
+ import { readFile as readFile14 } from "fs/promises";
10097
+ import path31 from "path";
9867
10098
  import "commander";
9868
10099
  import {
9869
10100
  deriveConfidence as deriveConfidence10,
9870
- findProjectRoot as findProjectRoot27,
10101
+ findProjectRoot as findProjectRoot28,
9871
10102
  getUsage as getUsage15,
9872
10103
  loadUsageIndex as loadUsageIndex19,
9873
- resolveHaivePaths as resolveHaivePaths24
10104
+ resolveHaivePaths as resolveHaivePaths25
9874
10105
  } from "@hiveai/core";
9875
10106
  function registerMemoryShow(memory2) {
9876
10107
  memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9877
- const root = findProjectRoot27(opts.dir);
9878
- const paths = resolveHaivePaths24(root);
9879
- if (!existsSync46(paths.memoriesDir)) {
10108
+ const root = findProjectRoot28(opts.dir);
10109
+ const paths = resolveHaivePaths25(root);
10110
+ if (!existsSync47(paths.memoriesDir)) {
9880
10111
  ui.error(`No .ai/memories at ${root}.`);
9881
10112
  process.exitCode = 1;
9882
10113
  return;
@@ -9889,7 +10120,7 @@ function registerMemoryShow(memory2) {
9889
10120
  return;
9890
10121
  }
9891
10122
  if (opts.raw) {
9892
- console.log(await readFile13(found.filePath, "utf8"));
10123
+ console.log(await readFile14(found.filePath, "utf8"));
9893
10124
  return;
9894
10125
  }
9895
10126
  const fm = found.memory.frontmatter;
@@ -9905,7 +10136,7 @@ function registerMemoryShow(memory2) {
9905
10136
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
9906
10137
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
9907
10138
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
9908
- console.log(`${ui.dim("file:")} ${path30.relative(root, found.filePath)}`);
10139
+ console.log(`${ui.dim("file:")} ${path31.relative(root, found.filePath)}`);
9909
10140
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
9910
10141
  console.log(ui.dim("anchor:"));
9911
10142
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -9920,21 +10151,21 @@ function registerMemoryShow(memory2) {
9920
10151
  }
9921
10152
 
9922
10153
  // src/commands/memory-stats.ts
9923
- import { existsSync as existsSync47 } from "fs";
9924
- import path31 from "path";
10154
+ import { existsSync as existsSync48 } from "fs";
10155
+ import path33 from "path";
9925
10156
  import "commander";
9926
10157
  import {
9927
10158
  deriveConfidence as deriveConfidence11,
9928
- findProjectRoot as findProjectRoot28,
10159
+ findProjectRoot as findProjectRoot29,
9929
10160
  getUsage as getUsage16,
9930
10161
  loadUsageIndex as loadUsageIndex20,
9931
- resolveHaivePaths as resolveHaivePaths25
10162
+ resolveHaivePaths as resolveHaivePaths26
9932
10163
  } from "@hiveai/core";
9933
10164
  function registerMemoryStats(memory2) {
9934
10165
  memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
9935
- const root = findProjectRoot28(opts.dir);
9936
- const paths = resolveHaivePaths25(root);
9937
- if (!existsSync47(paths.memoriesDir)) {
10166
+ const root = findProjectRoot29(opts.dir);
10167
+ const paths = resolveHaivePaths26(root);
10168
+ if (!existsSync48(paths.memoriesDir)) {
9938
10169
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9939
10170
  process.exitCode = 1;
9940
10171
  return;
@@ -9959,63 +10190,83 @@ function registerMemoryStats(memory2) {
9959
10190
  console.log(
9960
10191
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
9961
10192
  );
9962
- console.log(` ${ui.dim(path31.relative(root, filePath))}`);
10193
+ console.log(` ${ui.dim(path33.relative(root, filePath))}`);
9963
10194
  }
9964
10195
  });
9965
10196
  }
9966
10197
 
9967
10198
  // src/commands/memory-verify.ts
9968
10199
  import { writeFile as writeFile21 } from "fs/promises";
9969
- import { existsSync as existsSync48 } from "fs";
9970
- import path33 from "path";
10200
+ import { existsSync as existsSync49 } from "fs";
10201
+ import path34 from "path";
9971
10202
  import "commander";
9972
10203
  import {
9973
- findProjectRoot as findProjectRoot29,
9974
- resolveHaivePaths as resolveHaivePaths26,
10204
+ findProjectRoot as findProjectRoot30,
10205
+ resolveHaivePaths as resolveHaivePaths27,
9975
10206
  serializeMemory as serializeMemory19,
9976
10207
  verifyAnchor as verifyAnchor3
9977
10208
  } from "@hiveai/core";
9978
10209
  function registerMemoryVerify(memory2) {
9979
10210
  memory2.command("verify").description(
9980
10211
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
9981
- ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
9982
- const root = findProjectRoot29(opts.dir);
9983
- const paths = resolveHaivePaths26(root);
9984
- if (!existsSync48(paths.memoriesDir)) {
9985
- ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10212
+ ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
10213
+ const root = findProjectRoot30(opts.dir);
10214
+ const paths = resolveHaivePaths27(root);
10215
+ if (!existsSync49(paths.memoriesDir)) {
10216
+ if (opts.json) {
10217
+ console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10218
+ } else {
10219
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10220
+ }
9986
10221
  process.exitCode = 1;
9987
10222
  return;
9988
10223
  }
9989
10224
  const all = await loadMemoriesFromDir25(paths.memoriesDir);
9990
10225
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
9991
10226
  if (opts.id && targets.length === 0) {
9992
- ui.error(`No memory with id "${opts.id}".`);
10227
+ if (opts.json) {
10228
+ console.log(JSON.stringify({ error: "not-found", id: opts.id }, null, 2));
10229
+ } else {
10230
+ ui.error(`No memory with id "${opts.id}".`);
10231
+ }
9993
10232
  process.exitCode = 1;
9994
10233
  return;
9995
10234
  }
9996
10235
  let staleCount = 0;
9997
10236
  let freshCount = 0;
9998
10237
  const anchorlessIds = [];
10238
+ const entries = [];
9999
10239
  let updated = 0;
10000
10240
  for (const { memory: mem, filePath } of targets) {
10001
10241
  const result = await verifyAnchor3(mem, { projectRoot: root });
10002
10242
  const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
10243
+ const rel = path34.relative(root, filePath);
10003
10244
  if (!isAnchored) {
10004
10245
  anchorlessIds.push(mem.frontmatter.id);
10246
+ entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
10005
10247
  continue;
10006
10248
  }
10007
- const rel = path33.relative(root, filePath);
10008
10249
  if (result.stale) {
10009
10250
  staleCount++;
10010
- console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
10011
- console.log(` ${ui.dim(rel)}`);
10012
- console.log(` ${result.reason}`);
10013
- if (result.possibleRenames.length > 0) {
10014
- console.log(` ${ui.yellow("Possible renames:")} ${result.possibleRenames.join(", ")}`);
10251
+ entries.push({
10252
+ id: mem.frontmatter.id,
10253
+ status: "stale",
10254
+ path: rel,
10255
+ reason: result.reason ?? void 0,
10256
+ possible_renames: result.possibleRenames
10257
+ });
10258
+ if (!opts.json) {
10259
+ console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
10260
+ console.log(` ${ui.dim(rel)}`);
10261
+ console.log(` ${result.reason}`);
10262
+ if (result.possibleRenames.length > 0) {
10263
+ console.log(` ${ui.yellow("Possible renames:")} ${result.possibleRenames.join(", ")}`);
10264
+ }
10015
10265
  }
10016
10266
  } else {
10017
10267
  freshCount++;
10018
- console.log(`${ui.dim("fresh")} ${mem.frontmatter.id}`);
10268
+ entries.push({ id: mem.frontmatter.id, status: "fresh", path: rel });
10269
+ if (!opts.json) console.log(`${ui.dim("fresh")} ${mem.frontmatter.id}`);
10019
10270
  }
10020
10271
  if (opts.update) {
10021
10272
  const next = applyVerification2(mem, result);
@@ -10023,6 +10274,20 @@ function registerMemoryVerify(memory2) {
10023
10274
  updated++;
10024
10275
  }
10025
10276
  }
10277
+ if (opts.json) {
10278
+ console.log(JSON.stringify({
10279
+ summary: {
10280
+ checked: freshCount + staleCount,
10281
+ fresh: freshCount,
10282
+ stale: staleCount,
10283
+ anchorless: anchorlessIds.length,
10284
+ updated
10285
+ },
10286
+ results: entries
10287
+ }, null, 2));
10288
+ if (staleCount > 0) process.exitCode = 1;
10289
+ return;
10290
+ }
10026
10291
  const summary = [
10027
10292
  `${freshCount} fresh`,
10028
10293
  `${staleCount} stale`,
@@ -10067,30 +10332,30 @@ function applyVerification2(mem, result) {
10067
10332
  }
10068
10333
 
10069
10334
  // src/commands/memory-import.ts
10070
- import { readFile as readFile14 } from "fs/promises";
10071
- import { existsSync as existsSync49 } from "fs";
10335
+ import { readFile as readFile15 } from "fs/promises";
10336
+ import { existsSync as existsSync50 } from "fs";
10072
10337
  import "commander";
10073
10338
  import {
10074
- findProjectRoot as findProjectRoot30,
10075
- resolveHaivePaths as resolveHaivePaths27
10339
+ findProjectRoot as findProjectRoot31,
10340
+ resolveHaivePaths as resolveHaivePaths28
10076
10341
  } from "@hiveai/core";
10077
10342
  function registerMemoryImport(memory2) {
10078
10343
  memory2.command("import").description(
10079
10344
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
10080
10345
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
10081
- const root = findProjectRoot30(opts.dir);
10082
- const paths = resolveHaivePaths27(root);
10083
- if (!existsSync49(paths.haiveDir)) {
10346
+ const root = findProjectRoot31(opts.dir);
10347
+ const paths = resolveHaivePaths28(root);
10348
+ if (!existsSync50(paths.haiveDir)) {
10084
10349
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10085
10350
  process.exitCode = 1;
10086
10351
  return;
10087
10352
  }
10088
- if (!existsSync49(opts.from)) {
10353
+ if (!existsSync50(opts.from)) {
10089
10354
  ui.error(`File not found: ${opts.from}`);
10090
10355
  process.exitCode = 1;
10091
10356
  return;
10092
10357
  }
10093
- const content = await readFile14(opts.from, "utf8");
10358
+ const content = await readFile15(opts.from, "utf8");
10094
10359
  const scope = opts.scope ?? "team";
10095
10360
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
10096
10361
  ui.info(`Content length: ${content.length} chars`);
@@ -10118,14 +10383,14 @@ function registerMemoryImport(memory2) {
10118
10383
  }
10119
10384
 
10120
10385
  // src/commands/memory-import-changelog.ts
10121
- import { existsSync as existsSync50 } from "fs";
10122
- import { readFile as readFile15, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10123
- import path34 from "path";
10386
+ import { existsSync as existsSync51 } from "fs";
10387
+ import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10388
+ import path35 from "path";
10124
10389
  import "commander";
10125
10390
  import {
10126
10391
  buildFrontmatter as buildFrontmatter9,
10127
- findProjectRoot as findProjectRoot31,
10128
- resolveHaivePaths as resolveHaivePaths28,
10392
+ findProjectRoot as findProjectRoot32,
10393
+ resolveHaivePaths as resolveHaivePaths29,
10129
10394
  serializeMemory as serializeMemory20
10130
10395
  } from "@hiveai/core";
10131
10396
  function parseChangelog(content) {
@@ -10190,15 +10455,15 @@ function registerMemoryImportChangelog(memory2) {
10190
10455
  "--versions <csv>",
10191
10456
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
10192
10457
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10193
- const root = findProjectRoot31(opts.dir);
10194
- const paths = resolveHaivePaths28(root);
10195
- const changelogPath = path34.resolve(root, opts.fromChangelog);
10196
- if (!existsSync50(changelogPath)) {
10458
+ const root = findProjectRoot32(opts.dir);
10459
+ const paths = resolveHaivePaths29(root);
10460
+ const changelogPath = path35.resolve(root, opts.fromChangelog);
10461
+ if (!existsSync51(changelogPath)) {
10197
10462
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10198
10463
  process.exitCode = 1;
10199
10464
  return;
10200
10465
  }
10201
- const content = await readFile15(changelogPath, "utf8");
10466
+ const content = await readFile16(changelogPath, "utf8");
10202
10467
  let entries = parseChangelog(content);
10203
10468
  if (entries.length === 0) {
10204
10469
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -10213,9 +10478,9 @@ function registerMemoryImportChangelog(memory2) {
10213
10478
  entries = entries.filter((e) => requested.includes(e.version));
10214
10479
  }
10215
10480
  }
10216
- const pkgName = opts.package ?? path34.basename(path34.dirname(changelogPath));
10481
+ const pkgName = opts.package ?? path35.basename(path35.dirname(changelogPath));
10217
10482
  const scope = opts.scope ?? "team";
10218
- const teamDir = path34.join(paths.memoriesDir, scope);
10483
+ const teamDir = path35.join(paths.memoriesDir, scope);
10219
10484
  await mkdir14(teamDir, { recursive: true });
10220
10485
  let saved = 0;
10221
10486
  for (const entry of entries) {
@@ -10238,7 +10503,7 @@ function registerMemoryImportChangelog(memory2) {
10238
10503
  lines.push("");
10239
10504
  }
10240
10505
  lines.push(
10241
- `**Source:** \`${path34.relative(root, changelogPath)}\`
10506
+ `**Source:** \`${path35.relative(root, changelogPath)}\`
10242
10507
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
10243
10508
  );
10244
10509
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -10253,11 +10518,11 @@ function registerMemoryImportChangelog(memory2) {
10253
10518
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
10254
10519
  `v${entry.version}`
10255
10520
  ],
10256
- paths: [path34.relative(root, changelogPath)],
10521
+ paths: [path35.relative(root, changelogPath)],
10257
10522
  topic: `changelog-${pkgName}-${entry.version}`
10258
10523
  });
10259
10524
  await writeFile23(
10260
- path34.join(teamDir, `${fm.id}.md`),
10525
+ path35.join(teamDir, `${fm.id}.md`),
10261
10526
  serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
10262
10527
  "utf8"
10263
10528
  );
@@ -10280,17 +10545,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10280
10545
  }
10281
10546
 
10282
10547
  // src/commands/memory-digest.ts
10283
- import { existsSync as existsSync51 } from "fs";
10548
+ import { existsSync as existsSync53 } from "fs";
10284
10549
  import { writeFile as writeFile24 } from "fs/promises";
10285
- import path35 from "path";
10550
+ import path36 from "path";
10286
10551
  import "commander";
10287
10552
  import {
10288
10553
  deriveConfidence as deriveConfidence12,
10289
- findProjectRoot as findProjectRoot32,
10554
+ findProjectRoot as findProjectRoot33,
10290
10555
  getUsage as getUsage17,
10291
10556
  loadMemoriesFromDir as loadMemoriesFromDir26,
10292
10557
  loadUsageIndex as loadUsageIndex21,
10293
- resolveHaivePaths as resolveHaivePaths29
10558
+ resolveHaivePaths as resolveHaivePaths30
10294
10559
  } from "@hiveai/core";
10295
10560
  var CONFIDENCE_EMOJI = {
10296
10561
  unverified: "\u2B1C",
@@ -10303,9 +10568,9 @@ function registerMemoryDigest(program2) {
10303
10568
  program2.command("digest").description(
10304
10569
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
10305
10570
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
10306
- const root = findProjectRoot32(opts.dir);
10307
- const paths = resolveHaivePaths29(root);
10308
- if (!existsSync51(paths.memoriesDir)) {
10571
+ const root = findProjectRoot33(opts.dir);
10572
+ const paths = resolveHaivePaths30(root);
10573
+ if (!existsSync53(paths.memoriesDir)) {
10309
10574
  ui.error("No .ai/memories found. Run `haive init` first.");
10310
10575
  process.exitCode = 1;
10311
10576
  return;
@@ -10377,7 +10642,7 @@ function registerMemoryDigest(program2) {
10377
10642
  );
10378
10643
  const digest = lines.join("\n");
10379
10644
  if (opts.out) {
10380
- const outPath = path35.resolve(process.cwd(), opts.out);
10645
+ const outPath = path36.resolve(process.cwd(), opts.out);
10381
10646
  await writeFile24(outPath, digest, "utf8");
10382
10647
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
10383
10648
  } else {
@@ -10387,23 +10652,23 @@ function registerMemoryDigest(program2) {
10387
10652
  }
10388
10653
 
10389
10654
  // src/commands/session-end.ts
10390
- import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile16, rm as rm2 } from "fs/promises";
10391
- import { existsSync as existsSync53 } from "fs";
10655
+ import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
10656
+ import { existsSync as existsSync54 } from "fs";
10392
10657
  import { spawn as spawn4 } from "child_process";
10393
- import path36 from "path";
10658
+ import path37 from "path";
10394
10659
  import "commander";
10395
10660
  import {
10396
10661
  buildFrontmatter as buildFrontmatter10,
10397
- findProjectRoot as findProjectRoot33,
10662
+ findProjectRoot as findProjectRoot34,
10398
10663
  loadMemoriesFromDir as loadMemoriesFromDir27,
10399
10664
  memoryFilePath as memoryFilePath9,
10400
- resolveHaivePaths as resolveHaivePaths30,
10665
+ resolveHaivePaths as resolveHaivePaths31,
10401
10666
  serializeMemory as serializeMemory21
10402
10667
  } from "@hiveai/core";
10403
10668
  async function buildAutoRecap(paths) {
10404
- const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10405
- if (!existsSync53(obsFile)) return await buildGitAutoRecap(paths);
10406
- const raw = await readFile16(obsFile, "utf8").catch(() => "");
10669
+ const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
10670
+ if (!existsSync54(obsFile)) return await buildGitAutoRecap(paths);
10671
+ const raw = await readFile17(obsFile, "utf8").catch(() => "");
10407
10672
  if (!raw.trim()) return await buildGitAutoRecap(paths);
10408
10673
  const lines = raw.split("\n").filter(Boolean);
10409
10674
  const obs = [];
@@ -10591,9 +10856,9 @@ function registerSessionEnd(session2) {
10591
10856
  --next "Add integration tests for webhook signature validation"
10592
10857
  `
10593
10858
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
10594
- const root = findProjectRoot33(opts.dir);
10595
- const paths = resolveHaivePaths30(root);
10596
- if (!existsSync53(paths.haiveDir)) {
10859
+ const root = findProjectRoot34(opts.dir);
10860
+ const paths = resolveHaivePaths31(root);
10861
+ if (!existsSync54(paths.haiveDir)) {
10597
10862
  if (opts.auto || opts.quiet) return;
10598
10863
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10599
10864
  process.exitCode = 1;
@@ -10626,18 +10891,18 @@ function registerSessionEnd(session2) {
10626
10891
  });
10627
10892
  const topic = recapTopic2(scope, opts.module);
10628
10893
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
10629
- const missingPaths = filesTouched.filter((p) => !existsSync53(path36.resolve(root, p)));
10894
+ const missingPaths = filesTouched.filter((p) => !existsSync54(path37.resolve(root, p)));
10630
10895
  if (missingPaths.length > 0 && !opts.quiet) {
10631
10896
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
10632
10897
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
10633
10898
  }
10634
10899
  const cleanupObservations = async () => {
10635
10900
  if (!opts.auto) return;
10636
- const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10637
- if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
10901
+ const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
10902
+ if (existsSync54(obsFile)) await rm2(obsFile).catch(() => {
10638
10903
  });
10639
10904
  };
10640
- if (existsSync53(paths.memoriesDir)) {
10905
+ if (existsSync54(paths.memoriesDir)) {
10641
10906
  const existing = await loadMemoriesFromDir27(paths.memoriesDir);
10642
10907
  const topicMatch = existing.find(
10643
10908
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
@@ -10658,7 +10923,7 @@ function registerSessionEnd(session2) {
10658
10923
  await cleanupObservations();
10659
10924
  if (!opts.quiet) {
10660
10925
  ui.success(`Session recap updated (revision #${revisionCount})`);
10661
- ui.info(`id=${fm.id} file=${path36.relative(root, topicMatch.filePath)}`);
10926
+ ui.info(`id=${fm.id} file=${path37.relative(root, topicMatch.filePath)}`);
10662
10927
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
10663
10928
  }
10664
10929
  return;
@@ -10675,12 +10940,12 @@ function registerSessionEnd(session2) {
10675
10940
  status: "validated"
10676
10941
  });
10677
10942
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
10678
- await mkdir15(path36.dirname(file), { recursive: true });
10943
+ await mkdir15(path37.dirname(file), { recursive: true });
10679
10944
  await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
10680
10945
  await cleanupObservations();
10681
10946
  if (!opts.quiet) {
10682
10947
  ui.success(`Session recap created`);
10683
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path36.relative(root, file)}`);
10948
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path37.relative(root, file)}`);
10684
10949
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
10685
10950
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
10686
10951
  }
@@ -10692,22 +10957,22 @@ function parseCsv5(value) {
10692
10957
  }
10693
10958
  function normalizeAnchorPath(root, filePath) {
10694
10959
  if (!filePath) return filePath;
10695
- if (!path36.isAbsolute(filePath)) return filePath;
10696
- const rel = path36.relative(root, filePath);
10960
+ if (!path37.isAbsolute(filePath)) return filePath;
10961
+ const rel = path37.relative(root, filePath);
10697
10962
  if (rel.startsWith("..")) return filePath;
10698
10963
  return rel;
10699
10964
  }
10700
10965
 
10701
10966
  // src/commands/snapshot.ts
10702
- import { existsSync as existsSync54 } from "fs";
10967
+ import { existsSync as existsSync55 } from "fs";
10703
10968
  import { readdir as readdir4 } from "fs/promises";
10704
- import path37 from "path";
10969
+ import path38 from "path";
10705
10970
  import "commander";
10706
10971
  import {
10707
10972
  diffContract,
10708
- findProjectRoot as findProjectRoot34,
10709
- loadConfig as loadConfig6,
10710
- resolveHaivePaths as resolveHaivePaths31,
10973
+ findProjectRoot as findProjectRoot35,
10974
+ loadConfig as loadConfig7,
10975
+ resolveHaivePaths as resolveHaivePaths32,
10711
10976
  snapshotContract
10712
10977
  } from "@hiveai/core";
10713
10978
  function registerSnapshot(program2) {
@@ -10732,16 +10997,16 @@ function registerSnapshot(program2) {
10732
10997
  "--format <format>",
10733
10998
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
10734
10999
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
10735
- const root = findProjectRoot34(opts.dir);
10736
- const paths = resolveHaivePaths31(root);
10737
- if (!existsSync54(paths.haiveDir)) {
11000
+ const root = findProjectRoot35(opts.dir);
11001
+ const paths = resolveHaivePaths32(root);
11002
+ if (!existsSync55(paths.haiveDir)) {
10738
11003
  ui.error("No .ai/ found. Run `haive init` first.");
10739
11004
  process.exitCode = 1;
10740
11005
  return;
10741
11006
  }
10742
11007
  if (opts.list) {
10743
- const contractsDir = path37.join(paths.haiveDir, "contracts");
10744
- if (!existsSync54(contractsDir)) {
11008
+ const contractsDir = path38.join(paths.haiveDir, "contracts");
11009
+ if (!existsSync55(contractsDir)) {
10745
11010
  console.log(ui.dim("No contract snapshots found."));
10746
11011
  return;
10747
11012
  }
@@ -10761,7 +11026,7 @@ function registerSnapshot(program2) {
10761
11026
  }
10762
11027
  if (opts.diff) {
10763
11028
  if (!opts.name) {
10764
- const config2 = await loadConfig6(paths);
11029
+ const config2 = await loadConfig7(paths);
10765
11030
  const contracts = config2.contractFiles ?? [];
10766
11031
  if (contracts.length === 0) {
10767
11032
  ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
@@ -10773,7 +11038,7 @@ function registerSnapshot(program2) {
10773
11038
  }
10774
11039
  return;
10775
11040
  }
10776
- const config = await loadConfig6(paths);
11041
+ const config = await loadConfig7(paths);
10777
11042
  const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
10778
11043
  if (!configured && !opts.contract) {
10779
11044
  ui.error(
@@ -10796,7 +11061,7 @@ function registerSnapshot(program2) {
10796
11061
  return;
10797
11062
  }
10798
11063
  const contractPath = opts.contract;
10799
- const name = opts.name ?? path37.basename(contractPath, path37.extname(contractPath));
11064
+ const name = opts.name ?? path38.basename(contractPath, path38.extname(contractPath));
10800
11065
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
10801
11066
  const contract = { name, path: contractPath, format };
10802
11067
  try {
@@ -10851,8 +11116,8 @@ async function runDiff(root, haiveDir, contract) {
10851
11116
  }
10852
11117
  }
10853
11118
  function detectFormat(filePath) {
10854
- const ext = path37.extname(filePath).toLowerCase();
10855
- const base = path37.basename(filePath).toLowerCase();
11119
+ const ext = path38.extname(filePath).toLowerCase();
11120
+ const base = path38.basename(filePath).toLowerCase();
10856
11121
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
10857
11122
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
10858
11123
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -10865,16 +11130,16 @@ function detectFormat(filePath) {
10865
11130
  }
10866
11131
 
10867
11132
  // src/commands/hub.ts
10868
- import { existsSync as existsSync55 } from "fs";
10869
- import { mkdir as mkdir16, readFile as readFile17, writeFile as writeFile26, copyFile } from "fs/promises";
10870
- import path38 from "path";
11133
+ import { existsSync as existsSync56 } from "fs";
11134
+ import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11135
+ import path39 from "path";
10871
11136
  import { spawnSync as spawnSync5 } from "child_process";
10872
11137
  import "commander";
10873
11138
  import {
10874
- findProjectRoot as findProjectRoot35,
10875
- loadConfig as loadConfig7,
11139
+ findProjectRoot as findProjectRoot36,
11140
+ loadConfig as loadConfig8,
10876
11141
  loadMemoriesFromDir as loadMemoriesFromDir28,
10877
- resolveHaivePaths as resolveHaivePaths32,
11142
+ resolveHaivePaths as resolveHaivePaths33,
10878
11143
  saveConfig as saveConfig3,
10879
11144
  serializeMemory as serializeMemory23
10880
11145
  } from "@hiveai/core";
@@ -10886,7 +11151,7 @@ function registerHub(program2) {
10886
11151
  hub.command("init <hubPath>").description(
10887
11152
  "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"
10888
11153
  ).action(async (hubPath) => {
10889
- const absPath = path38.resolve(hubPath);
11154
+ const absPath = path39.resolve(hubPath);
10890
11155
  await mkdir16(absPath, { recursive: true });
10891
11156
  const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
10892
11157
  if (gitCheck.status !== 0) {
@@ -10897,10 +11162,10 @@ function registerHub(program2) {
10897
11162
  return;
10898
11163
  }
10899
11164
  }
10900
- const sharedDir = path38.join(absPath, ".ai", "memories", "shared");
11165
+ const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
10901
11166
  await mkdir16(sharedDir, { recursive: true });
10902
11167
  await writeFile26(
10903
- path38.join(absPath, ".ai", "README.md"),
11168
+ path39.join(absPath, ".ai", "README.md"),
10904
11169
  `# hAIve Team Knowledge Hub
10905
11170
 
10906
11171
  This repo is a shared knowledge hub for hAIve.
@@ -10922,7 +11187,7 @@ haive hub pull # import into a project
10922
11187
  "utf8"
10923
11188
  );
10924
11189
  await writeFile26(
10925
- path38.join(absPath, ".gitignore"),
11190
+ path39.join(absPath, ".gitignore"),
10926
11191
  ".ai/.cache/\n.ai/memories/personal/\n",
10927
11192
  "utf8"
10928
11193
  );
@@ -10937,7 +11202,7 @@ haive hub pull # import into a project
10937
11202
  `
10938
11203
  Next steps:
10939
11204
  1. Add hubPath to your project's .ai/haive.config.json:
10940
- { "hubPath": "${path38.relative(process.cwd(), absPath)}" }
11205
+ { "hubPath": "${path39.relative(process.cwd(), absPath)}" }
10941
11206
  2. Run \`haive hub push\` to publish your shared memories
10942
11207
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
10943
11208
  `
@@ -10956,9 +11221,9 @@ Next steps:
10956
11221
  haive hub push --commit --message "feat: add payment API contract memories"
10957
11222
  `
10958
11223
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
10959
- const root = findProjectRoot35(opts.dir);
10960
- const paths = resolveHaivePaths32(root);
10961
- const config = await loadConfig7(paths);
11224
+ const root = findProjectRoot36(opts.dir);
11225
+ const paths = resolveHaivePaths33(root);
11226
+ const config = await loadConfig8(paths);
10962
11227
  if (!config.hubPath) {
10963
11228
  ui.error(
10964
11229
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -10966,14 +11231,14 @@ Next steps:
10966
11231
  process.exitCode = 1;
10967
11232
  return;
10968
11233
  }
10969
- const hubRoot = path38.resolve(root, config.hubPath);
10970
- if (!existsSync55(hubRoot)) {
11234
+ const hubRoot = path39.resolve(root, config.hubPath);
11235
+ if (!existsSync56(hubRoot)) {
10971
11236
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
10972
11237
  process.exitCode = 1;
10973
11238
  return;
10974
11239
  }
10975
- const projectName = path38.basename(root);
10976
- const destDir = path38.join(hubRoot, ".ai", "memories", "shared", projectName);
11240
+ const projectName = path39.basename(root);
11241
+ const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
10977
11242
  await mkdir16(destDir, { recursive: true });
10978
11243
  const all = await loadMemoriesFromDir28(paths.memoriesDir);
10979
11244
  const shared = all.filter(
@@ -10992,7 +11257,7 @@ Next steps:
10992
11257
  for (const { memory: memory2 } of shared) {
10993
11258
  const fm = memory2.frontmatter;
10994
11259
  const fileName = `${fm.id}.md`;
10995
- const destPath = path38.join(destDir, fileName);
11260
+ const destPath = path39.join(destDir, fileName);
10996
11261
  await writeFile26(destPath, serializeMemory23(memory2), "utf8");
10997
11262
  pushed++;
10998
11263
  }
@@ -11000,7 +11265,7 @@ Next steps:
11000
11265
  console.log(ui.dim(` Location: ${destDir}`));
11001
11266
  if (opts.commit) {
11002
11267
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
11003
- spawnSync5("git", ["add", path38.join(".ai", "memories", "shared", projectName)], {
11268
+ spawnSync5("git", ["add", path39.join(".ai", "memories", "shared", projectName)], {
11004
11269
  cwd: hubRoot
11005
11270
  });
11006
11271
  const commit = spawnSync5("git", ["commit", "-m", message], {
@@ -11025,9 +11290,9 @@ Next steps:
11025
11290
  hub.command("pull").description(
11026
11291
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
11027
11292
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11028
- const root = findProjectRoot35(opts.dir);
11029
- const paths = resolveHaivePaths32(root);
11030
- const config = await loadConfig7(paths);
11293
+ const root = findProjectRoot36(opts.dir);
11294
+ const paths = resolveHaivePaths33(root);
11295
+ const config = await loadConfig8(paths);
11031
11296
  if (!config.hubPath) {
11032
11297
  ui.error(
11033
11298
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -11035,13 +11300,13 @@ Next steps:
11035
11300
  process.exitCode = 1;
11036
11301
  return;
11037
11302
  }
11038
- const hubRoot = path38.resolve(root, config.hubPath);
11039
- const hubSharedDir = path38.join(hubRoot, ".ai", "memories", "shared");
11040
- if (!existsSync55(hubSharedDir)) {
11303
+ const hubRoot = path39.resolve(root, config.hubPath);
11304
+ const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11305
+ if (!existsSync56(hubSharedDir)) {
11041
11306
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11042
11307
  return;
11043
11308
  }
11044
- const projectName = path38.basename(root);
11309
+ const projectName = path39.basename(root);
11045
11310
  const { readdir: readdir7 } = await import("fs/promises");
11046
11311
  const projectDirs = (await readdir7(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
11047
11312
  if (projectDirs.length === 0) {
@@ -11051,17 +11316,17 @@ Next steps:
11051
11316
  let totalImported = 0;
11052
11317
  let totalUpdated = 0;
11053
11318
  for (const sourceName of projectDirs) {
11054
- const sourceDir = path38.join(hubSharedDir, sourceName);
11055
- const destDir = path38.join(paths.memoriesDir, "shared", sourceName);
11319
+ const sourceDir = path39.join(hubSharedDir, sourceName);
11320
+ const destDir = path39.join(paths.memoriesDir, "shared", sourceName);
11056
11321
  await mkdir16(destDir, { recursive: true });
11057
11322
  const sourceFiles = (await readdir7(sourceDir)).filter((f) => f.endsWith(".md"));
11058
11323
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
11059
11324
  const existingInDest = await loadDir(destDir);
11060
11325
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
11061
11326
  for (const file of sourceFiles) {
11062
- const srcPath = path38.join(sourceDir, file);
11063
- const destPath = path38.join(destDir, file);
11064
- const fileContent = await readFile17(srcPath, "utf8");
11327
+ const srcPath = path39.join(sourceDir, file);
11328
+ const destPath = path39.join(destDir, file);
11329
+ const fileContent = await readFile18(srcPath, "utf8");
11065
11330
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
11066
11331
  if (!alreadyTagged) {
11067
11332
  await copyFile(srcPath, destPath);
@@ -11084,21 +11349,21 @@ Next steps:
11084
11349
  );
11085
11350
  });
11086
11351
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
11087
- const root = findProjectRoot35(opts.dir);
11088
- const paths = resolveHaivePaths32(root);
11089
- const config = await loadConfig7(paths);
11352
+ const root = findProjectRoot36(opts.dir);
11353
+ const paths = resolveHaivePaths33(root);
11354
+ const config = await loadConfig8(paths);
11090
11355
  console.log(ui.bold("Hub status"));
11091
11356
  console.log(
11092
11357
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11093
11358
  );
11094
- const sharedDir = path38.join(paths.memoriesDir, "shared");
11095
- if (existsSync55(sharedDir)) {
11359
+ const sharedDir = path39.join(paths.memoriesDir, "shared");
11360
+ if (existsSync56(sharedDir)) {
11096
11361
  const { readdir: readdir7 } = await import("fs/promises");
11097
11362
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11098
11363
  console.log(`
11099
11364
  Imported from ${sources.length} source(s):`);
11100
11365
  for (const src of sources) {
11101
- const files = (await readdir7(path38.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
11366
+ const files = (await readdir7(path39.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
11102
11367
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
11103
11368
  }
11104
11369
  } else {
@@ -11113,7 +11378,7 @@ Next steps:
11113
11378
  if (outgoing.length > 0) {
11114
11379
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
11115
11380
  }
11116
- void readFile17;
11381
+ void readFile18;
11117
11382
  void writeFile26;
11118
11383
  void saveConfig3;
11119
11384
  });
@@ -11121,17 +11386,17 @@ Next steps:
11121
11386
 
11122
11387
  // src/commands/stats.ts
11123
11388
  import "commander";
11124
- import { existsSync as existsSync56 } from "fs";
11389
+ import { existsSync as existsSync57 } from "fs";
11125
11390
  import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
11126
- import path39 from "path";
11391
+ import path40 from "path";
11127
11392
  import {
11128
11393
  aggregateUsage,
11129
- findProjectRoot as findProjectRoot36,
11394
+ findProjectRoot as findProjectRoot37,
11130
11395
  loadMemoriesFromDir as loadMemoriesFromDir29,
11131
11396
  loadUsageIndex as loadUsageIndex23,
11132
11397
  parseSince,
11133
11398
  readUsageEvents as readUsageEvents2,
11134
- resolveHaivePaths as resolveHaivePaths33,
11399
+ resolveHaivePaths as resolveHaivePaths34,
11135
11400
  usageLogSize
11136
11401
  } from "@hiveai/core";
11137
11402
  function registerStats(program2) {
@@ -11140,8 +11405,8 @@ function registerStats(program2) {
11140
11405
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
11141
11406
  void 0
11142
11407
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11143
- const root = findProjectRoot36(opts.dir);
11144
- const paths = resolveHaivePaths33(root);
11408
+ const root = findProjectRoot37(opts.dir);
11409
+ const paths = resolveHaivePaths34(root);
11145
11410
  if (opts.exportReport) {
11146
11411
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
11147
11412
  return;
@@ -11195,11 +11460,11 @@ function registerStats(program2) {
11195
11460
  });
11196
11461
  }
11197
11462
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11198
- const outAbs = path39.isAbsolute(outRelative) ? path39.resolve(outRelative) : path39.resolve(root, outRelative);
11463
+ const outAbs = path40.isAbsolute(outRelative) ? path40.resolve(outRelative) : path40.resolve(root, outRelative);
11199
11464
  const size = await usageLogSize(paths);
11200
11465
  let events = await readUsageEvents2(paths);
11201
11466
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11202
- if (existsSync56(paths.memoriesDir)) {
11467
+ if (existsSync57(paths.memoriesDir)) {
11203
11468
  const mems = await loadMemoriesFromDir29(paths.memoriesDir);
11204
11469
  for (const { memory: memory2 } of mems) {
11205
11470
  const fm = memory2.frontmatter;
@@ -11230,7 +11495,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11230
11495
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
11231
11496
  events = [];
11232
11497
  }
11233
- await mkdir17(path39.dirname(outAbs), { recursive: true });
11498
+ await mkdir17(path40.dirname(outAbs), { recursive: true });
11234
11499
  const payload = {
11235
11500
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
11236
11501
  project_root: root,
@@ -11294,13 +11559,13 @@ import { performance } from "perf_hooks";
11294
11559
  import "commander";
11295
11560
  import {
11296
11561
  estimateTokens as estimateTokens3,
11297
- findProjectRoot as findProjectRoot37,
11298
- resolveHaivePaths as resolveHaivePaths34
11562
+ findProjectRoot as findProjectRoot38,
11563
+ resolveHaivePaths as resolveHaivePaths35
11299
11564
  } from "@hiveai/core";
11300
11565
  function registerBench(program2) {
11301
11566
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11302
- const root = findProjectRoot37(opts.dir);
11303
- const paths = resolveHaivePaths34(root);
11567
+ const root = findProjectRoot38(opts.dir);
11568
+ const paths = resolveHaivePaths35(root);
11304
11569
  const ctx = { paths };
11305
11570
  const task = opts.task ?? "audit dependencies for security risks";
11306
11571
  const scenarios = [
@@ -11419,11 +11684,11 @@ function summarize(name, t0, payload, notes) {
11419
11684
  }
11420
11685
 
11421
11686
  // src/commands/benchmark.ts
11422
- import { existsSync as existsSync57 } from "fs";
11423
- import { readdir as readdir5, readFile as readFile18, writeFile as writeFile28 } from "fs/promises";
11424
- import path40 from "path";
11687
+ import { existsSync as existsSync58 } from "fs";
11688
+ import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
11689
+ import path41 from "path";
11425
11690
  import "commander";
11426
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
11691
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot39 } from "@hiveai/core";
11427
11692
  function registerBenchmark(program2) {
11428
11693
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
11429
11694
  benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
@@ -11436,9 +11701,9 @@ function registerBenchmark(program2) {
11436
11701
  }
11437
11702
  const markdown = renderMarkdown(root, summary, rows);
11438
11703
  if (opts.out) {
11439
- const outFile = path40.isAbsolute(opts.out) ? opts.out : path40.join(root, opts.out);
11704
+ const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
11440
11705
  await writeFile28(outFile, markdown, "utf8");
11441
- ui.success(`wrote ${path40.relative(process.cwd(), outFile)}`);
11706
+ ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
11442
11707
  return;
11443
11708
  }
11444
11709
  console.log(markdown);
@@ -11462,20 +11727,20 @@ function registerBenchmark(program2) {
11462
11727
  }
11463
11728
  function resolveBenchmarkRoot(dir) {
11464
11729
  const candidate = dir ?? "benchmarks/agent-benchmark";
11465
- if (path40.isAbsolute(candidate)) return candidate;
11466
- const projectRoot = findProjectRoot38(process.cwd());
11467
- return path40.join(projectRoot, candidate);
11730
+ if (path41.isAbsolute(candidate)) return candidate;
11731
+ const projectRoot = findProjectRoot39(process.cwd());
11732
+ return path41.join(projectRoot, candidate);
11468
11733
  }
11469
11734
  async function collectRows(root) {
11470
- if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
11735
+ if (!existsSync58(root)) throw new Error(`Benchmark directory not found: ${root}`);
11471
11736
  const entries = await readdir5(root, { withFileTypes: true });
11472
11737
  const rows = [];
11473
11738
  for (const entry of entries) {
11474
11739
  if (!entry.isDirectory()) continue;
11475
- const fixtureDir = path40.join(root, entry.name);
11476
- const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11477
- if (!existsSync57(reportFile)) continue;
11478
- const report = await readFile18(reportFile, "utf8");
11740
+ const fixtureDir = path41.join(root, entry.name);
11741
+ const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11742
+ if (!existsSync58(reportFile)) continue;
11743
+ const report = await readFile19(reportFile, "utf8");
11479
11744
  rows.push(parseAgentReport(entry.name, report));
11480
11745
  }
11481
11746
  return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
@@ -11565,19 +11830,19 @@ function escapeRegExp(value) {
11565
11830
 
11566
11831
  // src/commands/memory-suggest.ts
11567
11832
  import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
11568
- import { existsSync as existsSync58 } from "fs";
11569
- import path41 from "path";
11833
+ import { existsSync as existsSync59 } from "fs";
11834
+ import path43 from "path";
11570
11835
  import "commander";
11571
11836
  import {
11572
11837
  aggregateUsage as aggregateUsage2,
11573
11838
  buildFrontmatter as buildFrontmatter11,
11574
- findProjectRoot as findProjectRoot39,
11575
- loadConfig as loadConfig8,
11839
+ findProjectRoot as findProjectRoot40,
11840
+ loadConfig as loadConfig9,
11576
11841
  loadMemoriesFromDir as loadMemoriesFromDir30,
11577
11842
  memoryFilePath as memoryFilePath10,
11578
11843
  parseSince as parseSince2,
11579
11844
  readUsageEvents as readUsageEvents3,
11580
- resolveHaivePaths as resolveHaivePaths35,
11845
+ resolveHaivePaths as resolveHaivePaths36,
11581
11846
  serializeMemory as serializeMemory24
11582
11847
  } from "@hiveai/core";
11583
11848
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -11594,8 +11859,8 @@ function registerMemorySuggest(memory2) {
11594
11859
  memory2.command("suggest").description(
11595
11860
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
11596
11861
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11597
- const root = findProjectRoot39(opts.dir);
11598
- const paths = resolveHaivePaths35(root);
11862
+ const root = findProjectRoot40(opts.dir);
11863
+ const paths = resolveHaivePaths36(root);
11599
11864
  const events = await readUsageEvents3(paths);
11600
11865
  if (events.length === 0) {
11601
11866
  if (opts.json) {
@@ -11633,7 +11898,7 @@ function registerMemorySuggest(memory2) {
11633
11898
  inferred_type: inferType(v.tools, query)
11634
11899
  })).sort((a, b) => b.count - a.count);
11635
11900
  if (opts.autoSave) {
11636
- const config = await loadConfig8(paths);
11901
+ const config = await loadConfig9(paths);
11637
11902
  const topN = Math.max(1, parseInt(opts.topN ?? "3", 10));
11638
11903
  const scope = opts.scope === "personal" || opts.scope === "team" ? opts.scope : config.defaultScope ?? "personal";
11639
11904
  const status = config.defaultStatus === "validated" ? "validated" : "draft";
@@ -11644,7 +11909,7 @@ function registerMemorySuggest(memory2) {
11644
11909
  }
11645
11910
  const created = [];
11646
11911
  const skipped = [];
11647
- const existing = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
11912
+ const existing = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
11648
11913
  for (const s of top) {
11649
11914
  const slug = slugify2(s.query);
11650
11915
  if (!slug) {
@@ -11667,13 +11932,13 @@ function registerMemorySuggest(memory2) {
11667
11932
  });
11668
11933
  const body = renderTemplate(s, fm.id, status);
11669
11934
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
11670
- await mkdir18(path41.dirname(file), { recursive: true });
11671
- if (existsSync58(file)) {
11672
- skipped.push({ query: s.query, reason: `file already exists at ${path41.relative(root, file)}` });
11935
+ await mkdir18(path43.dirname(file), { recursive: true });
11936
+ if (existsSync59(file)) {
11937
+ skipped.push({ query: s.query, reason: `file already exists at ${path43.relative(root, file)}` });
11673
11938
  continue;
11674
11939
  }
11675
11940
  await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
11676
- created.push({ id: fm.id, file: path41.relative(root, file), query: s.query });
11941
+ created.push({ id: fm.id, file: path43.relative(root, file), query: s.query });
11677
11942
  }
11678
11943
  if (opts.json) {
11679
11944
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -11771,16 +12036,16 @@ function truncate2(text, max) {
11771
12036
  }
11772
12037
 
11773
12038
  // src/commands/memory-archive.ts
11774
- import { existsSync as existsSync59 } from "fs";
12039
+ import { existsSync as existsSync60 } from "fs";
11775
12040
  import { writeFile as writeFile30 } from "fs/promises";
11776
- import path43 from "path";
12041
+ import path44 from "path";
11777
12042
  import "commander";
11778
12043
  import {
11779
- findProjectRoot as findProjectRoot40,
12044
+ findProjectRoot as findProjectRoot41,
11780
12045
  getUsage as getUsage18,
11781
12046
  loadMemoriesFromDir as loadMemoriesFromDir31,
11782
12047
  loadUsageIndex as loadUsageIndex24,
11783
- resolveHaivePaths as resolveHaivePaths36,
12048
+ resolveHaivePaths as resolveHaivePaths37,
11784
12049
  serializeMemory as serializeMemory25
11785
12050
  } from "@hiveai/core";
11786
12051
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -11788,9 +12053,9 @@ function registerMemoryArchive(memory2) {
11788
12053
  memory2.command("archive").description(
11789
12054
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
11790
12055
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11791
- const root = findProjectRoot40(opts.dir);
11792
- const paths = resolveHaivePaths36(root);
11793
- if (!existsSync59(paths.memoriesDir)) {
12056
+ const root = findProjectRoot41(opts.dir);
12057
+ const paths = resolveHaivePaths37(root);
12058
+ if (!existsSync60(paths.memoriesDir)) {
11794
12059
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
11795
12060
  process.exitCode = 1;
11796
12061
  return;
@@ -11811,7 +12076,7 @@ function registerMemoryArchive(memory2) {
11811
12076
  if (typeFilter && fm.type !== typeFilter) continue;
11812
12077
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
11813
12078
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
11814
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(path43.join(paths.root, p)));
12079
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync60(path44.join(paths.root, p)));
11815
12080
  const isAnchorless = !hasAnyAnchor;
11816
12081
  if (!isAnchorless && !allPathsGone) continue;
11817
12082
  const u = getUsage18(usage, fm.id);
@@ -11885,33 +12150,33 @@ function parseDays(input) {
11885
12150
  }
11886
12151
 
11887
12152
  // src/commands/doctor.ts
11888
- import { existsSync as existsSync60, statSync } from "fs";
11889
- import { readFile as readFile19, stat, writeFile as writeFile31 } from "fs/promises";
11890
- import path44 from "path";
12153
+ import { existsSync as existsSync61, statSync as statSync2 } from "fs";
12154
+ import { readFile as readFile20, stat, writeFile as writeFile31 } from "fs/promises";
12155
+ import path45 from "path";
11891
12156
  import { execFileSync, execSync as execSync3 } from "child_process";
11892
12157
  import "commander";
11893
12158
  import {
11894
12159
  codeMapPath as codeMapPath2,
11895
- findProjectRoot as findProjectRoot41,
12160
+ findProjectRoot as findProjectRoot42,
11896
12161
  getUsage as getUsage19,
11897
- loadCodeMap as loadCodeMap6,
11898
- loadConfig as loadConfig9,
12162
+ loadCodeMap as loadCodeMap7,
12163
+ loadConfig as loadConfig10,
11899
12164
  loadMemoriesFromDir as loadMemoriesFromDir33,
11900
12165
  loadUsageIndex as loadUsageIndex25,
11901
12166
  readUsageEvents as readUsageEvents4,
11902
- resolveHaivePaths as resolveHaivePaths37
12167
+ resolveHaivePaths as resolveHaivePaths38
11903
12168
  } from "@hiveai/core";
11904
12169
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
11905
12170
  function registerDoctor(program2) {
11906
12171
  program2.command("doctor").description(
11907
12172
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
11908
12173
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11909
- const root = findProjectRoot41(opts.dir);
11910
- const paths = resolveHaivePaths37(root);
12174
+ const root = findProjectRoot42(opts.dir);
12175
+ const paths = resolveHaivePaths38(root);
11911
12176
  const findings = [];
11912
12177
  const repairs = [];
11913
- const config = await loadConfig9(paths);
11914
- if (!existsSync60(paths.haiveDir)) {
12178
+ const config = await loadConfig10(paths);
12179
+ if (!existsSync61(paths.haiveDir)) {
11915
12180
  findings.push({
11916
12181
  severity: "error",
11917
12182
  code: "not-initialized",
@@ -11932,7 +12197,7 @@ function registerDoctor(program2) {
11932
12197
  })
11933
12198
  );
11934
12199
  }
11935
- if (!existsSync60(paths.projectContext)) {
12200
+ if (!existsSync61(paths.projectContext)) {
11936
12201
  findings.push({
11937
12202
  severity: "warn",
11938
12203
  code: "no-project-context",
@@ -11940,8 +12205,8 @@ function registerDoctor(program2) {
11940
12205
  fix: "haive init"
11941
12206
  });
11942
12207
  } else {
11943
- const { readFile: readFile21 } = await import("fs/promises");
11944
- const content = await readFile21(paths.projectContext, "utf8");
12208
+ const { readFile: readFile23 } = await import("fs/promises");
12209
+ const content = await readFile23(paths.projectContext, "utf8");
11945
12210
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
11946
12211
  if (isTemplate) {
11947
12212
  findings.push({
@@ -11961,7 +12226,7 @@ function registerDoctor(program2) {
11961
12226
  });
11962
12227
  }
11963
12228
  }
11964
- const memories = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12229
+ const memories = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
11965
12230
  const now = Date.now();
11966
12231
  if (memories.length === 0) {
11967
12232
  findings.push({
@@ -12043,7 +12308,7 @@ function registerDoctor(program2) {
12043
12308
  fix: "haive memory lint --fix --apply"
12044
12309
  });
12045
12310
  }
12046
- const codeMap = await loadCodeMap6(paths);
12311
+ const codeMap = await loadCodeMap7(paths);
12047
12312
  if (!codeMap) {
12048
12313
  findings.push({
12049
12314
  severity: "warn",
@@ -12102,12 +12367,12 @@ function registerDoctor(program2) {
12102
12367
  }
12103
12368
  }
12104
12369
  if (config.enforcement?.requireBriefingFirst) {
12105
- const claudeSettings = path44.join(root, ".claude", "settings.local.json");
12370
+ const claudeSettings = path45.join(root, ".claude", "settings.local.json");
12106
12371
  let hasClaudeEnforcement = false;
12107
- if (existsSync60(claudeSettings)) {
12372
+ if (existsSync61(claudeSettings)) {
12108
12373
  try {
12109
- const { readFile: readFile21 } = await import("fs/promises");
12110
- const raw = await readFile21(claudeSettings, "utf8");
12374
+ const { readFile: readFile23 } = await import("fs/promises");
12375
+ const raw = await readFile23(claudeSettings, "utf8");
12111
12376
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
12112
12377
  } catch {
12113
12378
  hasClaudeEnforcement = false;
@@ -12130,14 +12395,14 @@ function registerDoctor(program2) {
12130
12395
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12131
12396
  });
12132
12397
  }
12133
- findings.push(...await collectInstallFindings(root, "0.9.28"));
12398
+ findings.push(...await collectInstallFindings(root, "0.9.30"));
12134
12399
  try {
12135
12400
  const legacyRaw = execSync3("haive-mcp --version", {
12136
12401
  encoding: "utf8",
12137
12402
  timeout: 3e3,
12138
12403
  stdio: ["ignore", "pipe", "ignore"]
12139
12404
  }).trim();
12140
- const cliVersion = "0.9.28";
12405
+ const cliVersion = "0.9.30";
12141
12406
  if (legacyRaw && legacyRaw !== cliVersion) {
12142
12407
  findings.push({
12143
12408
  severity: "warn",
@@ -12153,17 +12418,17 @@ npm uninstall -g @hiveai/mcp`
12153
12418
  }
12154
12419
  {
12155
12420
  const configPaths = [
12156
- path44.join(root, ".mcp.json"),
12157
- path44.join(root, ".cursor", "mcp.json"),
12158
- path44.join(root, ".vscode", "mcp.json")
12421
+ path45.join(root, ".mcp.json"),
12422
+ path45.join(root, ".cursor", "mcp.json"),
12423
+ path45.join(root, ".vscode", "mcp.json")
12159
12424
  ];
12160
12425
  const staleConfigs = [];
12161
12426
  for (const cfgPath of configPaths) {
12162
- if (!existsSync60(cfgPath)) continue;
12427
+ if (!existsSync61(cfgPath)) continue;
12163
12428
  try {
12164
- const raw = await readFile19(cfgPath, "utf8");
12429
+ const raw = await readFile20(cfgPath, "utf8");
12165
12430
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
12166
- staleConfigs.push(path44.relative(root, cfgPath));
12431
+ staleConfigs.push(path45.relative(root, cfgPath));
12167
12432
  if (opts.fix && !opts.dryRun) {
12168
12433
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
12169
12434
  await writeFile31(cfgPath, updated, "utf8");
@@ -12453,9 +12718,9 @@ which -a haive`
12453
12718
  ".vscode/mcp.json"
12454
12719
  ];
12455
12720
  for (const rel of integrationFiles) {
12456
- const file = path44.join(root, rel);
12457
- if (!existsSync60(file)) continue;
12458
- const text = await readFile19(file, "utf8").catch(() => "");
12721
+ const file = path45.join(root, rel);
12722
+ if (!existsSync61(file)) continue;
12723
+ const text = await readFile20(file, "utf8").catch(() => "");
12459
12724
  for (const bin of extractAbsoluteHaiveBins(text)) {
12460
12725
  const version = versionForBinary(bin);
12461
12726
  if (!version) {
@@ -12479,7 +12744,7 @@ which -a haive`
12479
12744
  }
12480
12745
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
12481
12746
  const findings = [];
12482
- const rootPkg = await readJson(path44.join(root, "package.json"));
12747
+ const rootPkg = await readJson(path45.join(root, "package.json"));
12483
12748
  const workspacePackages = [
12484
12749
  "packages/core/package.json",
12485
12750
  "packages/embeddings/package.json",
@@ -12488,7 +12753,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
12488
12753
  ];
12489
12754
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
12490
12755
  rel,
12491
- pkg: await readJson(path44.join(root, rel))
12756
+ pkg: await readJson(path45.join(root, rel))
12492
12757
  })))).filter((item) => item.pkg);
12493
12758
  const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
12494
12759
  if (!isHaiveWorkspace) return findings;
@@ -12550,9 +12815,9 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
12550
12815
  }
12551
12816
  }
12552
12817
  async function readJson(file) {
12553
- if (!existsSync60(file)) return null;
12818
+ if (!existsSync61(file)) return null;
12554
12819
  try {
12555
- return JSON.parse(await readFile19(file, "utf8"));
12820
+ return JSON.parse(await readFile20(file, "utf8"));
12556
12821
  } catch {
12557
12822
  return null;
12558
12823
  }
@@ -12588,7 +12853,7 @@ function extractAbsoluteHaiveBins(text) {
12588
12853
  const p = match[2];
12589
12854
  if (!p) continue;
12590
12855
  try {
12591
- if (statSync(p).isDirectory()) continue;
12856
+ if (statSync2(p).isDirectory()) continue;
12592
12857
  } catch {
12593
12858
  }
12594
12859
  out.add(p);
@@ -12597,22 +12862,22 @@ function extractAbsoluteHaiveBins(text) {
12597
12862
  }
12598
12863
 
12599
12864
  // src/commands/playback.ts
12600
- import { existsSync as existsSync61 } from "fs";
12865
+ import { existsSync as existsSync63 } from "fs";
12601
12866
  import "commander";
12602
12867
  import {
12603
- findProjectRoot as findProjectRoot42,
12868
+ findProjectRoot as findProjectRoot43,
12604
12869
  loadMemoriesFromDir as loadMemoriesFromDir34,
12605
12870
  parseSince as parseSince3,
12606
12871
  readUsageEvents as readUsageEvents5,
12607
- resolveHaivePaths as resolveHaivePaths38
12872
+ resolveHaivePaths as resolveHaivePaths39
12608
12873
  } from "@hiveai/core";
12609
12874
  var MS_PER_MINUTE = 6e4;
12610
12875
  function registerPlayback(program2) {
12611
12876
  program2.command("playback").description(
12612
12877
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
12613
12878
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12614
- const root = findProjectRoot42(opts.dir);
12615
- const paths = resolveHaivePaths38(root);
12879
+ const root = findProjectRoot43(opts.dir);
12880
+ const paths = resolveHaivePaths39(root);
12616
12881
  const events = await readUsageEvents5(paths);
12617
12882
  if (events.length === 0) {
12618
12883
  if (opts.json) {
@@ -12627,7 +12892,7 @@ function registerPlayback(program2) {
12627
12892
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
12628
12893
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
12629
12894
  const sessions = bucketSessions(filtered, gapMs);
12630
- const all = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12895
+ const all = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12631
12896
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
12632
12897
  const enriched = sessions.map((s, i) => {
12633
12898
  const startMs = Date.parse(s.start);
@@ -12717,8 +12982,8 @@ function truncate3(text, max) {
12717
12982
  import { spawn as spawn5 } from "child_process";
12718
12983
  import "commander";
12719
12984
  import {
12720
- findProjectRoot as findProjectRoot43,
12721
- resolveHaivePaths as resolveHaivePaths39
12985
+ findProjectRoot as findProjectRoot44,
12986
+ resolveHaivePaths as resolveHaivePaths40
12722
12987
  } from "@hiveai/core";
12723
12988
  function registerPrecommit(program2) {
12724
12989
  program2.command("precommit").description(
@@ -12727,9 +12992,12 @@ function registerPrecommit(program2) {
12727
12992
  "--block-on <mode>",
12728
12993
  "'any' | 'high-confidence' (default) | 'never' (report only)",
12729
12994
  "high-confidence"
12730
- ).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
12731
- const root = findProjectRoot43(opts.dir);
12732
- const paths = resolveHaivePaths39(root);
12995
+ ).option("--no-semantic", "disable semantic search in anti-patterns matching").option(
12996
+ "--no-anchored-blocks",
12997
+ "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
12998
+ ).option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
12999
+ const root = findProjectRoot44(opts.dir);
13000
+ const paths = resolveHaivePaths40(root);
12733
13001
  const ctx = { paths };
12734
13002
  let diff = "";
12735
13003
  let touchedPaths = opts.paths ?? [];
@@ -12769,6 +13037,7 @@ function registerPrecommit(program2) {
12769
13037
  diff: diff || void 0,
12770
13038
  paths: touchedPaths,
12771
13039
  block_on: opts.blockOn ?? "high-confidence",
13040
+ anchored_blocks: opts.anchoredBlocks !== false,
12772
13041
  semantic: opts.noSemantic ? false : true
12773
13042
  }, ctx);
12774
13043
  if (opts.json) {
@@ -12859,12 +13128,12 @@ function runCommand3(cmd, args, cwd) {
12859
13128
  }
12860
13129
 
12861
13130
  // src/commands/welcome.ts
12862
- import { existsSync as existsSync63 } from "fs";
13131
+ import { existsSync as existsSync64 } from "fs";
12863
13132
  import "commander";
12864
13133
  import {
12865
- findProjectRoot as findProjectRoot44,
13134
+ findProjectRoot as findProjectRoot45,
12866
13135
  loadMemoriesFromDir as loadMemoriesFromDir35,
12867
- resolveHaivePaths as resolveHaivePaths40
13136
+ resolveHaivePaths as resolveHaivePaths41
12868
13137
  } from "@hiveai/core";
12869
13138
  var TYPE_RANK = {
12870
13139
  skill: 0,
@@ -12879,9 +13148,9 @@ function registerWelcome(program2) {
12879
13148
  program2.command("welcome").description(
12880
13149
  "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
12881
13150
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
12882
- const root = findProjectRoot44(opts.dir);
12883
- const paths = resolveHaivePaths40(root);
12884
- if (!existsSync63(paths.memoriesDir)) {
13151
+ const root = findProjectRoot45(opts.dir);
13152
+ const paths = resolveHaivePaths41(root);
13153
+ if (!existsSync64(paths.memoriesDir)) {
12885
13154
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
12886
13155
  process.exitCode = 1;
12887
13156
  return;
@@ -12940,27 +13209,27 @@ function registerMemorySuggestTopic(memory2) {
12940
13209
  }
12941
13210
 
12942
13211
  // src/commands/resolve-project.ts
12943
- import path45 from "path";
13212
+ import path46 from "path";
12944
13213
  import "commander";
12945
13214
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
12946
13215
  function registerResolveProject(program2) {
12947
13216
  program2.command("resolve-project").description(
12948
13217
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
12949
13218
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
12950
- const info = resolveProjectInfo2({ cwd: path45.resolve(opts.dir) });
13219
+ const info = resolveProjectInfo2({ cwd: path46.resolve(opts.dir) });
12951
13220
  console.log(JSON.stringify({ ok: true, info }, null, 2));
12952
13221
  });
12953
13222
  }
12954
13223
 
12955
13224
  // src/commands/runtime-journal.ts
12956
- import { existsSync as existsSync64 } from "fs";
12957
- import path46 from "path";
13225
+ import { existsSync as existsSync65 } from "fs";
13226
+ import path47 from "path";
12958
13227
  import "commander";
12959
13228
  import {
12960
13229
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
12961
- findProjectRoot as findProjectRoot45,
13230
+ findProjectRoot as findProjectRoot46,
12962
13231
  readRuntimeJournalTail as readRuntimeJournalTail2,
12963
- resolveHaivePaths as resolveHaivePaths41
13232
+ resolveHaivePaths as resolveHaivePaths42
12964
13233
  } from "@hiveai/core";
12965
13234
  function registerRuntime(program2) {
12966
13235
  const runtime = program2.command("runtime").description(
@@ -12968,18 +13237,18 @@ function registerRuntime(program2) {
12968
13237
  );
12969
13238
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
12970
13239
  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) => {
12971
- const root = path46.resolve(opts.dir ?? process.cwd());
12972
- const paths = resolveHaivePaths41(findProjectRoot45(root));
13240
+ const root = path47.resolve(opts.dir ?? process.cwd());
13241
+ const paths = resolveHaivePaths42(findProjectRoot46(root));
12973
13242
  const raw = opts.kind ?? "note";
12974
13243
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
12975
13244
  await appendRuntimeJournalEntry3(paths, { kind, message });
12976
- ui.success(`Appended to ${path46.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
13245
+ ui.success(`Appended to ${path47.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
12977
13246
  });
12978
13247
  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) => {
12979
- const root = path46.resolve(opts.dir ?? process.cwd());
12980
- const paths = resolveHaivePaths41(findProjectRoot45(root));
13248
+ const root = path47.resolve(opts.dir ?? process.cwd());
13249
+ const paths = resolveHaivePaths42(findProjectRoot46(root));
12981
13250
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
12982
- if (!existsSync64(paths.haiveDir)) {
13251
+ if (!existsSync65(paths.haiveDir)) {
12983
13252
  ui.error("No .ai/ \u2014 run `haive init` first.");
12984
13253
  process.exitCode = 1;
12985
13254
  return;
@@ -12994,13 +13263,13 @@ function registerRuntime(program2) {
12994
13263
  }
12995
13264
 
12996
13265
  // src/commands/memory-timeline.ts
12997
- import { existsSync as existsSync65 } from "fs";
12998
- import path47 from "path";
13266
+ import { existsSync as existsSync66 } from "fs";
13267
+ import path48 from "path";
12999
13268
  import "commander";
13000
13269
  import {
13001
13270
  collectTimelineEntries as collectTimelineEntries2,
13002
- findProjectRoot as findProjectRoot46,
13003
- resolveHaivePaths as resolveHaivePaths42
13271
+ findProjectRoot as findProjectRoot47,
13272
+ resolveHaivePaths as resolveHaivePaths43
13004
13273
  } from "@hiveai/core";
13005
13274
  function registerMemoryTimeline(memory2) {
13006
13275
  memory2.command("timeline").description(
@@ -13011,9 +13280,9 @@ function registerMemoryTimeline(memory2) {
13011
13280
  process.exitCode = 1;
13012
13281
  return;
13013
13282
  }
13014
- const root = path47.resolve(opts.dir ?? process.cwd());
13015
- const paths = resolveHaivePaths42(findProjectRoot46(root));
13016
- if (!existsSync65(paths.memoriesDir)) {
13283
+ const root = path48.resolve(opts.dir ?? process.cwd());
13284
+ const paths = resolveHaivePaths43(findProjectRoot47(root));
13285
+ if (!existsSync66(paths.memoriesDir)) {
13017
13286
  ui.error("No memories \u2014 run `haive init`.");
13018
13287
  process.exitCode = 1;
13019
13288
  return;
@@ -13031,14 +13300,14 @@ function registerMemoryTimeline(memory2) {
13031
13300
  }
13032
13301
 
13033
13302
  // src/commands/memory-conflict-candidates.ts
13034
- import { existsSync as existsSync66 } from "fs";
13035
- import path48 from "path";
13303
+ import { existsSync as existsSync67 } from "fs";
13304
+ import path49 from "path";
13036
13305
  import "commander";
13037
13306
  import {
13038
13307
  findLexicalConflictPairs as findLexicalConflictPairs2,
13039
13308
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
13040
- findProjectRoot as findProjectRoot47,
13041
- resolveHaivePaths as resolveHaivePaths43
13309
+ findProjectRoot as findProjectRoot48,
13310
+ resolveHaivePaths as resolveHaivePaths44
13042
13311
  } from "@hiveai/core";
13043
13312
  function parseTypes(csv) {
13044
13313
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -13054,9 +13323,9 @@ function registerMemoryConflictCandidates(memory2) {
13054
13323
  "decision,architecture,convention,gotcha (lexical scan)",
13055
13324
  "decision,architecture"
13056
13325
  ).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) => {
13057
- const root = path48.resolve(opts.dir ?? process.cwd());
13058
- const paths = resolveHaivePaths43(findProjectRoot47(root));
13059
- if (!existsSync66(paths.memoriesDir)) {
13326
+ const root = path49.resolve(opts.dir ?? process.cwd());
13327
+ const paths = resolveHaivePaths44(findProjectRoot48(root));
13328
+ if (!existsSync67(paths.memoriesDir)) {
13060
13329
  ui.error("No memories \u2014 run `haive init`.");
13061
13330
  process.exitCode = 1;
13062
13331
  return;
@@ -13092,20 +13361,20 @@ function registerMemoryConflictCandidates(memory2) {
13092
13361
 
13093
13362
  // src/commands/enforce.ts
13094
13363
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13095
- import { existsSync as existsSync67, statSync as statSync2 } from "fs";
13096
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile20, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
13097
- import path49 from "path";
13364
+ import { existsSync as existsSync68, statSync as statSync3 } from "fs";
13365
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile21, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
13366
+ import path50 from "path";
13098
13367
  import "commander";
13099
13368
  import {
13100
- findProjectRoot as findProjectRoot48,
13369
+ findProjectRoot as findProjectRoot49,
13101
13370
  hasRecentBriefingMarker,
13102
13371
  isFreshIsoDate,
13103
- loadConfig as loadConfig10,
13372
+ loadConfig as loadConfig11,
13104
13373
  loadMemoriesFromDir as loadMemoriesFromDir36,
13105
13374
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13106
13375
  readRecentBriefingMarker,
13107
13376
  resolveBriefingBudget as resolveBriefingBudget3,
13108
- resolveHaivePaths as resolveHaivePaths44,
13377
+ resolveHaivePaths as resolveHaivePaths45,
13109
13378
  saveConfig as saveConfig4,
13110
13379
  SESSION_RECAP_TTL_MS,
13111
13380
  verifyAnchor as verifyAnchor4,
@@ -13118,10 +13387,10 @@ function registerEnforce(program2) {
13118
13387
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
13119
13388
  );
13120
13389
  enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
13121
- const root = findProjectRoot48(opts.dir);
13122
- const paths = resolveHaivePaths44(root);
13390
+ const root = findProjectRoot49(opts.dir);
13391
+ const paths = resolveHaivePaths45(root);
13123
13392
  await mkdir19(paths.haiveDir, { recursive: true });
13124
- const current = await loadConfig10(paths);
13393
+ const current = await loadConfig11(paths);
13125
13394
  await saveConfig4(paths, {
13126
13395
  ...current,
13127
13396
  enforcement: {
@@ -13144,7 +13413,7 @@ function registerEnforce(program2) {
13144
13413
  if (opts.claude !== false) {
13145
13414
  try {
13146
13415
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
13147
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path49.relative(root, result.settingsPath)})`);
13416
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path50.relative(root, result.settingsPath)})`);
13148
13417
  } catch (err) {
13149
13418
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
13150
13419
  }
@@ -13163,21 +13432,21 @@ function registerEnforce(program2) {
13163
13432
  if (report.should_block) process.exit(2);
13164
13433
  });
13165
13434
  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) => {
13166
- const root = findProjectRoot48(opts.dir);
13167
- const paths = resolveHaivePaths44(root);
13168
- const cacheDir = path49.join(paths.haiveDir, ".cache");
13169
- if (existsSync67(cacheDir)) {
13170
- if (opts.dryRun) ui.info(`would clean ${path49.relative(root, cacheDir)} (preserving .gitignore)`);
13435
+ const root = findProjectRoot49(opts.dir);
13436
+ const paths = resolveHaivePaths45(root);
13437
+ const cacheDir = path50.join(paths.haiveDir, ".cache");
13438
+ if (existsSync68(cacheDir)) {
13439
+ if (opts.dryRun) ui.info(`would clean ${path50.relative(root, cacheDir)} (preserving .gitignore)`);
13171
13440
  else {
13172
13441
  const removed = await cleanupCacheDir(cacheDir);
13173
- ui.success(`cleaned ${path49.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13442
+ ui.success(`cleaned ${path50.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13174
13443
  }
13175
13444
  }
13176
- if (existsSync67(paths.runtimeDir)) {
13177
- if (opts.dryRun) ui.info(`would clean ${path49.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13445
+ if (existsSync68(paths.runtimeDir)) {
13446
+ if (opts.dryRun) ui.info(`would clean ${path50.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13178
13447
  else {
13179
13448
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
13180
- ui.success(`cleaned ${path49.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13449
+ ui.success(`cleaned ${path50.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13181
13450
  }
13182
13451
  }
13183
13452
  });
@@ -13190,8 +13459,8 @@ function registerEnforce(program2) {
13190
13459
  const payload = await readHookPayload();
13191
13460
  const root = resolveRoot(opts.dir, payload);
13192
13461
  if (!root) return;
13193
- const paths = resolveHaivePaths44(root);
13194
- if (!existsSync67(paths.haiveDir)) return;
13462
+ const paths = resolveHaivePaths45(root);
13463
+ if (!existsSync68(paths.haiveDir)) return;
13195
13464
  await mkdir19(paths.runtimeDir, { recursive: true });
13196
13465
  const sessionId = opts.sessionId ?? payload.session_id;
13197
13466
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -13253,8 +13522,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13253
13522
  const payload = await readHookPayload();
13254
13523
  const root = resolveRoot(opts.dir, payload);
13255
13524
  if (!root) return;
13256
- const paths = resolveHaivePaths44(root);
13257
- if (!existsSync67(paths.haiveDir)) return;
13525
+ const paths = resolveHaivePaths45(root);
13526
+ if (!existsSync68(paths.haiveDir)) return;
13258
13527
  if (!isWriteLikeTool(payload)) return;
13259
13528
  const ok = await hasRecentBriefingMarker(paths, payload.session_id);
13260
13529
  if (ok) {
@@ -13296,9 +13565,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
13296
13565
  });
13297
13566
  }
13298
13567
  async function runWithEnforcement(command, args, opts) {
13299
- const root = findProjectRoot48(opts.dir);
13300
- const paths = resolveHaivePaths44(root);
13301
- if (!existsSync67(paths.haiveDir)) {
13568
+ const root = findProjectRoot49(opts.dir);
13569
+ const paths = resolveHaivePaths45(root);
13570
+ if (!existsSync68(paths.haiveDir)) {
13302
13571
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
13303
13572
  process.exit(1);
13304
13573
  }
@@ -13317,7 +13586,7 @@ async function runWithEnforcement(command, args, opts) {
13317
13586
  process.exit(2);
13318
13587
  }
13319
13588
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
13320
- ui.info(`Briefing written to ${path49.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
13589
+ ui.info(`Briefing written to ${path50.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
13321
13590
  const child = spawn6(command, args, {
13322
13591
  cwd: root,
13323
13592
  stdio: "inherit",
@@ -13367,9 +13636,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13367
13636
  source: "haive-run",
13368
13637
  memoryIds: briefing.memories.map((m) => m.id)
13369
13638
  });
13370
- const dir = path49.join(paths.runtimeDir, "enforcement", "briefings");
13639
+ const dir = path50.join(paths.runtimeDir, "enforcement", "briefings");
13371
13640
  await mkdir19(dir, { recursive: true });
13372
- const file = path49.join(dir, `${sessionId}.md`);
13641
+ const file = path50.join(dir, `${sessionId}.md`);
13373
13642
  const parts = [
13374
13643
  "# hAIve Briefing",
13375
13644
  "",
@@ -13391,10 +13660,10 @@ async function writeWrapperBriefing(paths, sessionId, task) {
13391
13660
  return file;
13392
13661
  }
13393
13662
  async function buildEnforcementReport(dir, stage, sessionId) {
13394
- const root = findProjectRoot48(dir);
13395
- const paths = resolveHaivePaths44(root);
13396
- const initialized = existsSync67(paths.haiveDir);
13397
- const config = initialized ? await loadConfig10(paths) : {};
13663
+ const root = findProjectRoot49(dir);
13664
+ const paths = resolveHaivePaths45(root);
13665
+ const initialized = existsSync68(paths.haiveDir);
13666
+ const config = initialized ? await loadConfig11(paths) : {};
13398
13667
  if (initialized) await applyLightweightRepairs(root, paths);
13399
13668
  const mode = config.enforcement?.mode ?? "strict";
13400
13669
  const findings = [];
@@ -13424,7 +13693,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13424
13693
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13425
13694
  });
13426
13695
  }
13427
- findings.push(...await inspectIntegrationVersions(root, "0.9.28"));
13696
+ findings.push(...await inspectIntegrationVersions(root, "0.9.30"));
13428
13697
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13429
13698
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
13430
13699
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -13458,7 +13727,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13458
13727
  findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
13459
13728
  }
13460
13729
  if (stage === "pre-commit" || stage === "ci") {
13461
- findings.push(...await runPrecommitPolicy(paths));
13730
+ findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored"));
13462
13731
  }
13463
13732
  if (config.enforcement?.cleanupGeneratedArtifacts !== false) {
13464
13733
  findings.push(...await findGeneratedArtifacts(paths));
@@ -13494,7 +13763,7 @@ function withCategories(report) {
13494
13763
  };
13495
13764
  }
13496
13765
  async function hasRecentSessionRecap(paths) {
13497
- if (!existsSync67(paths.memoriesDir)) return false;
13766
+ if (!existsSync68(paths.memoriesDir)) return false;
13498
13767
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
13499
13768
  return all.some(({ memory: memory2 }) => {
13500
13769
  const fm = memory2.frontmatter;
@@ -13503,7 +13772,7 @@ async function hasRecentSessionRecap(paths) {
13503
13772
  });
13504
13773
  }
13505
13774
  async function verifyMemoryPolicy(paths, config) {
13506
- if (!existsSync67(paths.memoriesDir)) return [];
13775
+ if (!existsSync68(paths.memoriesDir)) return [];
13507
13776
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
13508
13777
  const findings = [];
13509
13778
  const staleImportant = [];
@@ -13541,7 +13810,7 @@ async function verifyMemoryPolicy(paths, config) {
13541
13810
  return findings;
13542
13811
  }
13543
13812
  async function verifyDecisionCoverage(paths, stage, sessionId) {
13544
- if (!existsSync67(paths.memoriesDir)) return [];
13813
+ if (!existsSync68(paths.memoriesDir)) return [];
13545
13814
  const changedFiles = await getChangedFiles(paths.root, stage);
13546
13815
  if (changedFiles.length === 0) {
13547
13816
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -13582,7 +13851,10 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
13582
13851
  impact: Math.min(35, 10 + missing.length * 5)
13583
13852
  }];
13584
13853
  }
13585
- async function runPrecommitPolicy(paths) {
13854
+ async function runPrecommitPolicy(paths, gate) {
13855
+ if (gate === "off") {
13856
+ return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
13857
+ }
13586
13858
  const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
13587
13859
  const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
13588
13860
  if (touchedPaths.length === 0) {
@@ -13592,7 +13864,8 @@ async function runPrecommitPolicy(paths) {
13592
13864
  const result = await preCommitCheck({
13593
13865
  diff,
13594
13866
  paths: touchedPaths,
13595
- block_on: "high-confidence",
13867
+ block_on: gate === "strict" ? "any" : "high-confidence",
13868
+ anchored_blocks: gate === "anchored",
13596
13869
  semantic: true
13597
13870
  }, { paths });
13598
13871
  if (!result.should_block) {
@@ -13640,16 +13913,16 @@ async function cleanupRuntimeDir(runtimeDir) {
13640
13913
  for (const entry of entries) {
13641
13914
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
13642
13915
  if (entry.name === "enforcement") {
13643
- removed += await cleanupEnforcementDir(path49.join(runtimeDir, entry.name));
13916
+ removed += await cleanupEnforcementDir(path50.join(runtimeDir, entry.name));
13644
13917
  continue;
13645
13918
  }
13646
- await rm3(path49.join(runtimeDir, entry.name), { recursive: true, force: true });
13919
+ await rm3(path50.join(runtimeDir, entry.name), { recursive: true, force: true });
13647
13920
  removed++;
13648
13921
  }
13649
- await writeFile33(path49.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
13650
- if (!existsSync67(path49.join(runtimeDir, "README.md"))) {
13922
+ await writeFile33(path50.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
13923
+ if (!existsSync68(path50.join(runtimeDir, "README.md"))) {
13651
13924
  await writeFile33(
13652
- path49.join(runtimeDir, "README.md"),
13925
+ path50.join(runtimeDir, "README.md"),
13653
13926
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
13654
13927
  "utf8"
13655
13928
  );
@@ -13662,10 +13935,10 @@ async function cleanupCacheDir(cacheDir) {
13662
13935
  const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
13663
13936
  for (const entry of entries) {
13664
13937
  if (entry.name === ".gitignore") continue;
13665
- await rm3(path49.join(cacheDir, entry.name), { recursive: true, force: true });
13938
+ await rm3(path50.join(cacheDir, entry.name), { recursive: true, force: true });
13666
13939
  removed++;
13667
13940
  }
13668
- await writeFile33(path49.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
13941
+ await writeFile33(path50.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
13669
13942
  return removed;
13670
13943
  }
13671
13944
  async function cleanupEnforcementDir(enforcementDir) {
@@ -13673,7 +13946,7 @@ async function cleanupEnforcementDir(enforcementDir) {
13673
13946
  const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
13674
13947
  for (const entry of entries) {
13675
13948
  if (entry.name === "briefings") continue;
13676
- await rm3(path49.join(enforcementDir, entry.name), { recursive: true, force: true });
13949
+ await rm3(path50.join(enforcementDir, entry.name), { recursive: true, force: true });
13677
13950
  removed++;
13678
13951
  }
13679
13952
  return removed;
@@ -13689,9 +13962,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
13689
13962
  ];
13690
13963
  const findings = [];
13691
13964
  for (const rel of files) {
13692
- const file = path49.join(root, rel);
13693
- if (!existsSync67(file)) continue;
13694
- const text = await readFile20(file, "utf8").catch(() => "");
13965
+ const file = path50.join(root, rel);
13966
+ if (!existsSync68(file)) continue;
13967
+ const text = await readFile21(file, "utf8").catch(() => "");
13695
13968
  for (const bin of extractAbsoluteHaiveBins2(text)) {
13696
13969
  const version = versionForBinary2(bin);
13697
13970
  if (!version) {
@@ -13730,7 +14003,7 @@ function extractAbsoluteHaiveBins2(text) {
13730
14003
  const p = match[2];
13731
14004
  if (!p) continue;
13732
14005
  try {
13733
- if (statSync2(p).isDirectory()) continue;
14006
+ if (statSync3(p).isDirectory()) continue;
13734
14007
  } catch {
13735
14008
  }
13736
14009
  out.add(p);
@@ -13785,8 +14058,8 @@ function buildScore(findings, threshold = 80) {
13785
14058
  };
13786
14059
  }
13787
14060
  async function installGitEnforcement(root) {
13788
- const hooksDir = path49.join(root, ".git", "hooks");
13789
- if (!existsSync67(path49.join(root, ".git"))) {
14061
+ const hooksDir = path50.join(root, ".git", "hooks");
14062
+ if (!existsSync68(path50.join(root, ".git"))) {
13790
14063
  ui.warn("No .git directory found; git enforcement hooks skipped.");
13791
14064
  return;
13792
14065
  }
@@ -13808,9 +14081,9 @@ haive enforce check --stage pre-push --dir . || exit $?
13808
14081
  }
13809
14082
  ];
13810
14083
  for (const hook of hooks) {
13811
- const file = path49.join(hooksDir, hook.name);
13812
- if (existsSync67(file)) {
13813
- const current = await readFile20(file, "utf8").catch(() => "");
14084
+ const file = path50.join(hooksDir, hook.name);
14085
+ if (existsSync68(file)) {
14086
+ const current = await readFile21(file, "utf8").catch(() => "");
13814
14087
  if (current.includes(ENFORCE_HOOK_MARKER)) {
13815
14088
  await writeFile33(file, hook.body, "utf8");
13816
14089
  } else {
@@ -13826,9 +14099,9 @@ ${hook.body}`, "utf8");
13826
14099
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
13827
14100
  }
13828
14101
  async function installCiEnforcement(root) {
13829
- const workflowPath = path49.join(root, ".github", "workflows", "haive-enforcement.yml");
13830
- await mkdir19(path49.dirname(workflowPath), { recursive: true });
13831
- if (existsSync67(workflowPath)) {
14102
+ const workflowPath = path50.join(root, ".github", "workflows", "haive-enforcement.yml");
14103
+ await mkdir19(path50.dirname(workflowPath), { recursive: true });
14104
+ if (existsSync68(workflowPath)) {
13832
14105
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
13833
14106
  return;
13834
14107
  }
@@ -13856,7 +14129,7 @@ jobs:
13856
14129
  - name: Enforce hAIve policy
13857
14130
  run: haive enforce ci
13858
14131
  `, "utf8");
13859
- ui.success(`Created ${path49.relative(root, workflowPath)}`);
14132
+ ui.success(`Created ${path50.relative(root, workflowPath)}`);
13860
14133
  }
13861
14134
  function printReport(report, json, explain = false) {
13862
14135
  if (json) {
@@ -13916,7 +14189,7 @@ async function readHookPayload() {
13916
14189
  }
13917
14190
  function resolveRoot(dir, payload) {
13918
14191
  try {
13919
- return findProjectRoot48(dir ?? payload.cwd);
14192
+ return findProjectRoot49(dir ?? payload.cwd);
13920
14193
  } catch {
13921
14194
  return null;
13922
14195
  }
@@ -13953,11 +14226,11 @@ function extractToolPaths(payload, root) {
13953
14226
  }
13954
14227
  function normalizeToolPath(file, root) {
13955
14228
  const normalized = file.replace(/\\/g, "/");
13956
- if (!path49.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
13957
- return path49.relative(root, normalized).replace(/\\/g, "/");
14229
+ if (!path50.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
14230
+ return path50.relative(root, normalized).replace(/\\/g, "/");
13958
14231
  }
13959
14232
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
13960
- if (!existsSync67(paths.memoriesDir)) return [];
14233
+ if (!existsSync68(paths.memoriesDir)) return [];
13961
14234
  const marker = await readRecentBriefingMarker(paths, sessionId);
13962
14235
  const consulted = new Set(marker?.memory_ids ?? []);
13963
14236
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
@@ -14030,8 +14303,8 @@ function registerRun(program2) {
14030
14303
  }
14031
14304
 
14032
14305
  // src/index.ts
14033
- var program = new Command51();
14034
- program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.28").option("--advanced", "show maintenance and experimental commands in help");
14306
+ var program = new Command52();
14307
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.30").option("--advanced", "show maintenance and experimental commands in help");
14035
14308
  registerInit(program);
14036
14309
  registerWelcome(program);
14037
14310
  registerResolveProject(program);
@@ -14065,6 +14338,7 @@ registerMemoryApprove(memory);
14065
14338
  registerMemoryUpdate(memory);
14066
14339
  registerMemoryHot(memory);
14067
14340
  registerMemoryTried(memory);
14341
+ registerMemorySeed(memory);
14068
14342
  registerMemoryImport(memory);
14069
14343
  registerMemoryImportChangelog(memory);
14070
14344
  registerMemoryDigest(memory);