@hiveai/cli 0.9.29 → 0.9.31

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
@@ -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.29";
7326
+ var SERVER_VERSION = "0.9.31";
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" };
@@ -10075,44 +10209,64 @@ import {
10075
10209
  function registerMemoryVerify(memory2) {
10076
10210
  memory2.command("verify").description(
10077
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"
10078
- ).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) => {
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) => {
10079
10213
  const root = findProjectRoot30(opts.dir);
10080
10214
  const paths = resolveHaivePaths27(root);
10081
10215
  if (!existsSync49(paths.memoriesDir)) {
10082
- ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
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
+ }
10083
10221
  process.exitCode = 1;
10084
10222
  return;
10085
10223
  }
10086
10224
  const all = await loadMemoriesFromDir25(paths.memoriesDir);
10087
10225
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
10088
10226
  if (opts.id && targets.length === 0) {
10089
- 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
+ }
10090
10232
  process.exitCode = 1;
10091
10233
  return;
10092
10234
  }
10093
10235
  let staleCount = 0;
10094
10236
  let freshCount = 0;
10095
10237
  const anchorlessIds = [];
10238
+ const entries = [];
10096
10239
  let updated = 0;
10097
10240
  for (const { memory: mem, filePath } of targets) {
10098
10241
  const result = await verifyAnchor3(mem, { projectRoot: root });
10099
10242
  const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
10243
+ const rel = path34.relative(root, filePath);
10100
10244
  if (!isAnchored) {
10101
10245
  anchorlessIds.push(mem.frontmatter.id);
10246
+ entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
10102
10247
  continue;
10103
10248
  }
10104
- const rel = path34.relative(root, filePath);
10105
10249
  if (result.stale) {
10106
10250
  staleCount++;
10107
- console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
10108
- console.log(` ${ui.dim(rel)}`);
10109
- console.log(` ${result.reason}`);
10110
- if (result.possibleRenames.length > 0) {
10111
- 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
+ }
10112
10265
  }
10113
10266
  } else {
10114
10267
  freshCount++;
10115
- 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}`);
10116
10270
  }
10117
10271
  if (opts.update) {
10118
10272
  const next = applyVerification2(mem, result);
@@ -10120,6 +10274,20 @@ function registerMemoryVerify(memory2) {
10120
10274
  updated++;
10121
10275
  }
10122
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
+ }
10123
10291
  const summary = [
10124
10292
  `${freshCount} fresh`,
10125
10293
  `${staleCount} stale`,
@@ -11982,7 +12150,7 @@ function parseDays(input) {
11982
12150
  }
11983
12151
 
11984
12152
  // src/commands/doctor.ts
11985
- import { existsSync as existsSync61, statSync } from "fs";
12153
+ import { existsSync as existsSync61, statSync as statSync2 } from "fs";
11986
12154
  import { readFile as readFile20, stat, writeFile as writeFile31 } from "fs/promises";
11987
12155
  import path45 from "path";
11988
12156
  import { execFileSync, execSync as execSync3 } from "child_process";
@@ -11991,7 +12159,7 @@ import {
11991
12159
  codeMapPath as codeMapPath2,
11992
12160
  findProjectRoot as findProjectRoot42,
11993
12161
  getUsage as getUsage19,
11994
- loadCodeMap as loadCodeMap6,
12162
+ loadCodeMap as loadCodeMap7,
11995
12163
  loadConfig as loadConfig10,
11996
12164
  loadMemoriesFromDir as loadMemoriesFromDir33,
11997
12165
  loadUsageIndex as loadUsageIndex25,
@@ -12140,7 +12308,7 @@ function registerDoctor(program2) {
12140
12308
  fix: "haive memory lint --fix --apply"
12141
12309
  });
12142
12310
  }
12143
- const codeMap = await loadCodeMap6(paths);
12311
+ const codeMap = await loadCodeMap7(paths);
12144
12312
  if (!codeMap) {
12145
12313
  findings.push({
12146
12314
  severity: "warn",
@@ -12227,14 +12395,14 @@ function registerDoctor(program2) {
12227
12395
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12228
12396
  });
12229
12397
  }
12230
- findings.push(...await collectInstallFindings(root, "0.9.29"));
12398
+ findings.push(...await collectInstallFindings(root, "0.9.31"));
12231
12399
  try {
12232
12400
  const legacyRaw = execSync3("haive-mcp --version", {
12233
12401
  encoding: "utf8",
12234
12402
  timeout: 3e3,
12235
12403
  stdio: ["ignore", "pipe", "ignore"]
12236
12404
  }).trim();
12237
- const cliVersion = "0.9.29";
12405
+ const cliVersion = "0.9.31";
12238
12406
  if (legacyRaw && legacyRaw !== cliVersion) {
12239
12407
  findings.push({
12240
12408
  severity: "warn",
@@ -12685,7 +12853,7 @@ function extractAbsoluteHaiveBins(text) {
12685
12853
  const p = match[2];
12686
12854
  if (!p) continue;
12687
12855
  try {
12688
- if (statSync(p).isDirectory()) continue;
12856
+ if (statSync2(p).isDirectory()) continue;
12689
12857
  } catch {
12690
12858
  }
12691
12859
  out.add(p);
@@ -12814,7 +12982,9 @@ function truncate3(text, max) {
12814
12982
  import { spawn as spawn5 } from "child_process";
12815
12983
  import "commander";
12816
12984
  import {
12985
+ antiPatternGateParams,
12817
12986
  findProjectRoot as findProjectRoot44,
12987
+ loadConfig as loadConfig11,
12818
12988
  resolveHaivePaths as resolveHaivePaths40
12819
12989
  } from "@hiveai/core";
12820
12990
  function registerPrecommit(program2) {
@@ -12822,12 +12992,19 @@ function registerPrecommit(program2) {
12822
12992
  "Run a pre-commit safety check: scans `git diff --cached` against known anti-patterns,\n surfaces conventions/decisions anchored to touched files, and warns about stale anchored memories.\n\n Wire it into git as: `.git/hooks/pre-commit` running `haive precommit` (exit 1 = block).\n\n Examples:\n haive precommit # auto-detects staged diff\n haive precommit --block-on any # block on any warning, not just high-confidence\n haive precommit --paths src/auth.ts src/db.ts # explicit paths instead of git diff"
12823
12993
  ).option(
12824
12994
  "--block-on <mode>",
12825
- "'any' | 'high-confidence' (default) | 'never' (report only)",
12826
- "high-confidence"
12827
- ).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) => {
12995
+ "'any' | 'high-confidence' | 'never' (report only). Default: derived from enforcement.antiPatternGate."
12996
+ ).option("--no-semantic", "disable semantic search in anti-patterns matching").option(
12997
+ "--no-anchored-blocks",
12998
+ "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
12999
+ ).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) => {
12828
13000
  const root = findProjectRoot44(opts.dir);
12829
13001
  const paths = resolveHaivePaths40(root);
12830
13002
  const ctx = { paths };
13003
+ const config = await loadConfig11(paths);
13004
+ const gate = config.enforcement?.antiPatternGate ?? "anchored";
13005
+ const gateParams = antiPatternGateParams(gate);
13006
+ const blockOn = opts.blockOn ?? gateParams.block_on;
13007
+ const anchoredBlocks = opts.anchoredBlocks === false ? false : gateParams.anchored_blocks;
12831
13008
  let diff = "";
12832
13009
  let touchedPaths = opts.paths ?? [];
12833
13010
  if (touchedPaths.length === 0) {
@@ -12865,7 +13042,8 @@ function registerPrecommit(program2) {
12865
13042
  const result = await preCommitCheck({
12866
13043
  diff: diff || void 0,
12867
13044
  paths: touchedPaths,
12868
- block_on: opts.blockOn ?? "high-confidence",
13045
+ block_on: blockOn,
13046
+ anchored_blocks: anchoredBlocks,
12869
13047
  semantic: opts.noSemantic ? false : true
12870
13048
  }, ctx);
12871
13049
  if (opts.json) {
@@ -12908,7 +13086,7 @@ function registerPrecommit(program2) {
12908
13086
  console.log();
12909
13087
  }
12910
13088
  if (result.should_block) {
12911
- ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
13089
+ ui.error(`Blocking commit (gate=${gate}, block_on=${blockOn}). Address the warnings above or pass --block-on never to bypass.`);
12912
13090
  process.exit(1);
12913
13091
  }
12914
13092
  if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
@@ -13189,15 +13367,16 @@ function registerMemoryConflictCandidates(memory2) {
13189
13367
 
13190
13368
  // src/commands/enforce.ts
13191
13369
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13192
- import { existsSync as existsSync68, statSync as statSync2 } from "fs";
13370
+ import { existsSync as existsSync68, statSync as statSync3 } from "fs";
13193
13371
  import { chmod as chmod2, mkdir as mkdir19, readFile as readFile21, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
13194
13372
  import path50 from "path";
13195
13373
  import "commander";
13196
13374
  import {
13375
+ antiPatternGateParams as antiPatternGateParams2,
13197
13376
  findProjectRoot as findProjectRoot49,
13198
13377
  hasRecentBriefingMarker,
13199
13378
  isFreshIsoDate,
13200
- loadConfig as loadConfig11,
13379
+ loadConfig as loadConfig12,
13201
13380
  loadMemoriesFromDir as loadMemoriesFromDir36,
13202
13381
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13203
13382
  readRecentBriefingMarker,
@@ -13218,7 +13397,7 @@ function registerEnforce(program2) {
13218
13397
  const root = findProjectRoot49(opts.dir);
13219
13398
  const paths = resolveHaivePaths45(root);
13220
13399
  await mkdir19(paths.haiveDir, { recursive: true });
13221
- const current = await loadConfig11(paths);
13400
+ const current = await loadConfig12(paths);
13222
13401
  await saveConfig4(paths, {
13223
13402
  ...current,
13224
13403
  enforcement: {
@@ -13491,7 +13670,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13491
13670
  const root = findProjectRoot49(dir);
13492
13671
  const paths = resolveHaivePaths45(root);
13493
13672
  const initialized = existsSync68(paths.haiveDir);
13494
- const config = initialized ? await loadConfig11(paths) : {};
13673
+ const config = initialized ? await loadConfig12(paths) : {};
13495
13674
  if (initialized) await applyLightweightRepairs(root, paths);
13496
13675
  const mode = config.enforcement?.mode ?? "strict";
13497
13676
  const findings = [];
@@ -13521,7 +13700,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13521
13700
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13522
13701
  });
13523
13702
  }
13524
- findings.push(...await inspectIntegrationVersions(root, "0.9.29"));
13703
+ findings.push(...await inspectIntegrationVersions(root, "0.9.31"));
13525
13704
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13526
13705
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
13527
13706
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -13555,7 +13734,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13555
13734
  findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
13556
13735
  }
13557
13736
  if (stage === "pre-commit" || stage === "ci") {
13558
- findings.push(...await runPrecommitPolicy(paths));
13737
+ findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored"));
13559
13738
  }
13560
13739
  if (config.enforcement?.cleanupGeneratedArtifacts !== false) {
13561
13740
  findings.push(...await findGeneratedArtifacts(paths));
@@ -13679,17 +13858,22 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
13679
13858
  impact: Math.min(35, 10 + missing.length * 5)
13680
13859
  }];
13681
13860
  }
13682
- async function runPrecommitPolicy(paths) {
13861
+ async function runPrecommitPolicy(paths, gate) {
13862
+ if (gate === "off") {
13863
+ return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
13864
+ }
13683
13865
  const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
13684
13866
  const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
13685
13867
  if (touchedPaths.length === 0) {
13686
13868
  return [{ severity: "info", code: "no-staged-changes", message: "No staged changes found for pre-commit policy." }];
13687
13869
  }
13688
13870
  const diff = await runCommand4("git", ["diff", "--cached"], paths.root).catch(() => "");
13871
+ const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
13689
13872
  const result = await preCommitCheck({
13690
13873
  diff,
13691
13874
  paths: touchedPaths,
13692
- block_on: "high-confidence",
13875
+ block_on,
13876
+ anchored_blocks,
13693
13877
  semantic: true
13694
13878
  }, { paths });
13695
13879
  if (!result.should_block) {
@@ -13827,7 +14011,7 @@ function extractAbsoluteHaiveBins2(text) {
13827
14011
  const p = match[2];
13828
14012
  if (!p) continue;
13829
14013
  try {
13830
- if (statSync2(p).isDirectory()) continue;
14014
+ if (statSync3(p).isDirectory()) continue;
13831
14015
  } catch {
13832
14016
  }
13833
14017
  out.add(p);
@@ -14128,7 +14312,7 @@ function registerRun(program2) {
14128
14312
 
14129
14313
  // src/index.ts
14130
14314
  var program = new Command52();
14131
- program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.29").option("--advanced", "show maintenance and experimental commands in help");
14315
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.31").option("--advanced", "show maintenance and experimental commands in help");
14132
14316
  registerInit(program);
14133
14317
  registerWelcome(program);
14134
14318
  registerResolveProject(program);