@hiveai/cli 0.9.8 → 0.9.9

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,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command48 } from "commander";
4
+ import { Command as Command49 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
8
- import { readFile } from "fs/promises";
8
+ import { mkdir, readFile } from "fs/promises";
9
9
  import path from "path";
10
10
  import "commander";
11
11
  import {
@@ -21,7 +21,8 @@ import {
21
21
  resolveBriefingBudget,
22
22
  resolveHaivePaths,
23
23
  tokenizeQuery,
24
- trackReads
24
+ trackReads,
25
+ writeBriefingMarker
25
26
  } from "@hiveai/core";
26
27
 
27
28
  // src/utils/ui.ts
@@ -197,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
197
198
  if (!f) continue;
198
199
  counts.set(f, (counts.get(f) ?? 0) + 1);
199
200
  }
200
- let entries = [...counts.entries()].map(([path45, changes]) => ({ path: path45, changes }));
201
+ let entries = [...counts.entries()].map(([path46, changes]) => ({ path: path46, changes }));
201
202
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
202
203
  if (lowerPaths.length > 0) {
203
204
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -307,6 +308,15 @@ function registerBriefing(program2) {
307
308
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
308
309
  const root = findProjectRoot(opts.dir);
309
310
  const paths = resolveHaivePaths(root);
311
+ if (existsSync(paths.haiveDir)) {
312
+ await mkdir(paths.runtimeDir, { recursive: true });
313
+ await writeBriefingMarker(paths, {
314
+ task: opts.task ?? "CLI briefing",
315
+ source: "haive-briefing-cli",
316
+ sessionId: process.env.HAIVE_SESSION_ID
317
+ }).catch(() => {
318
+ });
319
+ }
310
320
  let budgetPreset = null;
311
321
  if (opts.budget) {
312
322
  const b = opts.budget.trim().toLowerCase();
@@ -710,7 +720,7 @@ function registerIndexCode(program2) {
710
720
  }
711
721
 
712
722
  // src/commands/init.ts
713
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
723
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
714
724
  import { existsSync as existsSync6 } from "fs";
715
725
  import path7 from "path";
716
726
  import { spawnSync } from "child_process";
@@ -1052,7 +1062,7 @@ async function generateBootstrapContext(root) {
1052
1062
  }
1053
1063
 
1054
1064
  // src/commands/init-mcp-setup.ts
1055
- import { readFile as readFile3, writeFile, mkdir } from "fs/promises";
1065
+ import { readFile as readFile3, writeFile, mkdir as mkdir2 } from "fs/promises";
1056
1066
  import { existsSync as existsSync4 } from "fs";
1057
1067
  import path5 from "path";
1058
1068
  import os from "os";
@@ -1085,7 +1095,7 @@ async function configureCursor() {
1085
1095
  config.mcpServers ??= {};
1086
1096
  if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
1087
1097
  config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
1088
- await mkdir(cursorDir, { recursive: true });
1098
+ await mkdir2(cursorDir, { recursive: true });
1089
1099
  await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1090
1100
  return { client: "Cursor", status: "configured", path: mcpPath };
1091
1101
  }
@@ -1117,7 +1127,7 @@ async function configureVSCode() {
1117
1127
  config.servers ??= {};
1118
1128
  if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
1119
1129
  config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
1120
- await mkdir(path5.dirname(mcpPath), { recursive: true });
1130
+ await mkdir2(path5.dirname(mcpPath), { recursive: true });
1121
1131
  await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1122
1132
  return { client: "VS Code", status: "configured", path: mcpPath };
1123
1133
  }
@@ -1169,7 +1179,7 @@ async function configureWindsurf() {
1169
1179
  config.mcpServers ??= {};
1170
1180
  if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
1171
1181
  config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
1172
- await mkdir(path5.dirname(mcpPath), { recursive: true });
1182
+ await mkdir2(path5.dirname(mcpPath), { recursive: true });
1173
1183
  await writeFile(mcpPath, JSON.stringify(config, null, 2), "utf8");
1174
1184
  return { client: "Windsurf", status: "configured", path: mcpPath };
1175
1185
  }
@@ -1200,7 +1210,7 @@ async function configureProjectMcpClients(root) {
1200
1210
  }
1201
1211
  config.mcpServers ??= {};
1202
1212
  config.mcpServers["haive"] = entry;
1203
- await mkdir(path5.dirname(cursorPath), { recursive: true });
1213
+ await mkdir2(path5.dirname(cursorPath), { recursive: true });
1204
1214
  await writeFile(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
1205
1215
  results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
1206
1216
  } catch (err) {
@@ -1217,7 +1227,7 @@ async function configureProjectMcpClients(root) {
1217
1227
  }
1218
1228
  config.servers ??= {};
1219
1229
  config.servers["haive"] = { ...entry, type: "stdio" };
1220
- await mkdir(path5.dirname(vscodePath), { recursive: true });
1230
+ await mkdir2(path5.dirname(vscodePath), { recursive: true });
1221
1231
  await writeFile(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
1222
1232
  results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
1223
1233
  } catch (err) {
@@ -1243,7 +1253,7 @@ async function configureProjectMcpClients(root) {
1243
1253
  }
1244
1254
 
1245
1255
  // src/commands/init-stack-packs.ts
1246
- import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
1256
+ import { mkdir as mkdir3, writeFile as writeFile2 } from "fs/promises";
1247
1257
  import { existsSync as existsSync5 } from "fs";
1248
1258
  import path6 from "path";
1249
1259
  import {
@@ -1857,7 +1867,7 @@ function autoDetectStacks(deps) {
1857
1867
  async function seedStackPack(haivePaths, stack) {
1858
1868
  const memories = PACKS[stack];
1859
1869
  if (!memories) return 0;
1860
- await mkdir2(haivePaths.teamDir, { recursive: true });
1870
+ await mkdir3(haivePaths.teamDir, { recursive: true });
1861
1871
  let count = 0;
1862
1872
  for (const mem of memories) {
1863
1873
  const fm = buildFrontmatter({
@@ -1870,7 +1880,7 @@ async function seedStackPack(haivePaths, stack) {
1870
1880
  const filePath = memoryFilePath(haivePaths, "team", fm.id);
1871
1881
  if (existsSync5(filePath)) continue;
1872
1882
  const content = serializeMemory({ frontmatter: fm, body: mem.body });
1873
- await mkdir2(path6.dirname(filePath), { recursive: true });
1883
+ await mkdir3(path6.dirname(filePath), { recursive: true });
1874
1884
  await writeFile2(filePath, content, "utf8");
1875
1885
  count++;
1876
1886
  }
@@ -2094,10 +2104,10 @@ function registerInit(program2) {
2094
2104
  if (existsSync6(paths.haiveDir)) {
2095
2105
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
2096
2106
  }
2097
- await mkdir3(paths.personalDir, { recursive: true });
2098
- await mkdir3(paths.teamDir, { recursive: true });
2099
- await mkdir3(paths.moduleDir, { recursive: true });
2100
- await mkdir3(paths.modulesContextDir, { recursive: true });
2107
+ await mkdir4(paths.personalDir, { recursive: true });
2108
+ await mkdir4(paths.teamDir, { recursive: true });
2109
+ await mkdir4(paths.moduleDir, { recursive: true });
2110
+ await mkdir4(paths.modulesContextDir, { recursive: true });
2101
2111
  await ensureAiRuntimeLayout(paths.runtimeDir);
2102
2112
  if (!existsSync6(paths.projectContext)) {
2103
2113
  if (wantBootstrap) {
@@ -2154,32 +2164,22 @@ function registerInit(program2) {
2154
2164
  if (existsSync6(ciPath)) {
2155
2165
  ui.info("CI workflow already exists \u2014 skipped");
2156
2166
  } else {
2157
- await mkdir3(path7.dirname(ciPath), { recursive: true });
2167
+ await mkdir4(path7.dirname(ciPath), { recursive: true });
2158
2168
  await writeFile3(ciPath, CI_WORKFLOW, "utf8");
2159
2169
  ui.success(`Created ${path7.relative(root, ciPath)}`);
2160
2170
  }
2161
2171
  }
2162
2172
  if (autopilot) {
2163
2173
  const haiveBin = process.argv[1];
2164
- const hookResult = spawnSync(
2174
+ const enforcementResult = spawnSync(
2165
2175
  process.execPath,
2166
- [haiveBin, "install-hooks", "--dir", root],
2176
+ [haiveBin, "enforce", "install", "--dir", root],
2167
2177
  { encoding: "utf8" }
2168
2178
  );
2169
- if (hookResult.status === 0) {
2170
- ui.success("Git hooks installed (auto-sync after pull/merge)");
2179
+ if (enforcementResult.status === 0) {
2180
+ ui.success("hAIve enforcement installed (MCP, git, CI, client hooks where available)");
2171
2181
  } else {
2172
- ui.warn("Git hooks not installed (not a git repo or no .git/ found) \u2014 run `haive install-hooks` manually");
2173
- }
2174
- const claudeHookResult = spawnSync(
2175
- process.execPath,
2176
- [haiveBin, "install-hooks", "claude", "--scope", "project", "--dir", root],
2177
- { encoding: "utf8" }
2178
- );
2179
- if (claudeHookResult.status === 0) {
2180
- ui.success("Claude Code enforcement hooks installed (.claude/settings.local.json)");
2181
- } else {
2182
- ui.warn("Claude Code hooks not installed \u2014 run `haive install-hooks claude --scope project` manually");
2182
+ ui.warn("hAIve enforcement not fully installed \u2014 run `haive enforce install` manually");
2183
2183
  }
2184
2184
  try {
2185
2185
  ui.info("Building code-map\u2026");
@@ -2230,8 +2230,8 @@ function registerInit(program2) {
2230
2230
  console.log(ui.dim(" \u2713 Proposed memories auto-approve after 72h without rejection"));
2231
2231
  console.log(ui.dim(" \u2713 Session recap saved automatically when the AI session closes"));
2232
2232
  console.log(ui.dim(" \u2713 Code-map refreshes automatically after every pull"));
2233
- console.log(ui.dim(" \u2713 Git hooks installed (auto-sync after pull/merge)"));
2234
- console.log(ui.dim(" \u2713 CI workflow created (pr-stale-check + sync-on-merge)"));
2233
+ console.log(ui.dim(" \u2713 Agent-agnostic enforcement gates installed (MCP, git, CI, wrapper-ready)"));
2234
+ console.log(ui.dim(" \u2713 CI workflows created (sync + enforcement)"));
2235
2235
  if (stacksToSeed.length > 0) {
2236
2236
  console.log(ui.dim(` \u2713 Stack memory packs pre-seeded (${stacksToSeed.join(", ")})`));
2237
2237
  }
@@ -2288,7 +2288,7 @@ async function writeCursorHaiveRule(root) {
2288
2288
  ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
2289
2289
  return;
2290
2290
  }
2291
- await mkdir3(path7.dirname(target), { recursive: true });
2291
+ await mkdir4(path7.dirname(target), { recursive: true });
2292
2292
  await writeFile3(target, CURSOR_HAIVE_RULE_MDC, "utf8");
2293
2293
  ui.success(`Created Cursor rule ${relPath}`);
2294
2294
  }
@@ -2298,7 +2298,7 @@ async function writeBridge(root, relPath) {
2298
2298
  ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
2299
2299
  return;
2300
2300
  }
2301
- await mkdir3(path7.dirname(target), { recursive: true });
2301
+ await mkdir4(path7.dirname(target), { recursive: true });
2302
2302
  await writeFile3(target, BRIDGE_BODY, "utf8");
2303
2303
  ui.success(`Created bridge ${relPath}`);
2304
2304
  }
@@ -2315,7 +2315,7 @@ var RUNTIME_GITIGNORE_BODY = `*
2315
2315
  !README.md
2316
2316
  `;
2317
2317
  async function ensureAiRuntimeLayout(runtimeDir) {
2318
- await mkdir3(runtimeDir, { recursive: true });
2318
+ await mkdir4(runtimeDir, { recursive: true });
2319
2319
  const gi = path7.join(runtimeDir, ".gitignore");
2320
2320
  if (!existsSync6(gi)) {
2321
2321
  await writeFile3(gi, RUNTIME_GITIGNORE_BODY, "utf8");
@@ -2342,7 +2342,7 @@ async function ensureGitignoreEntries(root, patterns) {
2342
2342
  }
2343
2343
 
2344
2344
  // src/commands/install-hooks.ts
2345
- import { mkdir as mkdir5, writeFile as writeFile5, chmod, readFile as readFile6 } from "fs/promises";
2345
+ import { mkdir as mkdir6, writeFile as writeFile5, chmod, readFile as readFile6 } from "fs/promises";
2346
2346
  import { existsSync as existsSync8 } from "fs";
2347
2347
  import path9 from "path";
2348
2348
  import "commander";
@@ -2350,7 +2350,7 @@ import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
2350
2350
 
2351
2351
  // src/utils/claude-hooks.ts
2352
2352
  import { existsSync as existsSync7 } from "fs";
2353
- import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2353
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2354
2354
  import path8 from "path";
2355
2355
  var HAIVE_HOOK_TAG = "haive-enforcement";
2356
2356
  var POST_TOOL_USE_GROUP = {
@@ -2447,7 +2447,7 @@ async function installClaudeHooksAtPath(settingsPath) {
2447
2447
  created = true;
2448
2448
  }
2449
2449
  const patched = patchClaudeSettings(raw);
2450
- await mkdir4(path8.dirname(settingsPath), { recursive: true });
2450
+ await mkdir5(path8.dirname(settingsPath), { recursive: true });
2451
2451
  await writeFile4(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
2452
2452
  return { settingsPath, created };
2453
2453
  }
@@ -2484,9 +2484,8 @@ fi
2484
2484
  var PRE_PUSH_BODY = `#!/bin/sh
2485
2485
  ${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
2486
2486
 
2487
- # Before pushing, run haive precommit to surface known anti-patterns and stale memories.
2488
- # Exit 0 always \u2014 this is advisory only (set HAIVE_BLOCK=1 to make it blocking).
2489
- HAIVE_BLOCK=\${HAIVE_BLOCK:-0}
2487
+ # Before pushing, run the hAIve workflow policy gate. This is blocking by default:
2488
+ # initialized projects should not accept AI changes that bypass hAIve.
2490
2489
 
2491
2490
  _haive() {
2492
2491
  if command -v haive >/dev/null 2>&1; then haive "$@"
@@ -2495,15 +2494,7 @@ _haive() {
2495
2494
  fi
2496
2495
  }
2497
2496
 
2498
- # Run pre-commit check on diff between local and remote
2499
- LOCAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)
2500
- REMOTE_SHA=$(git rev-parse --verify "@{u}" 2>/dev/null || echo "")
2501
- if [ -n "$REMOTE_SHA" ]; then
2502
- DIFF=$(git diff "$REMOTE_SHA"..HEAD 2>/dev/null || "")
2503
- if [ -n "$DIFF" ]; then
2504
- _haive precommit --quiet 2>/dev/null || true
2505
- fi
2506
- fi
2497
+ _haive enforce check --stage pre-push --dir . || exit $?
2507
2498
 
2508
2499
  # Remind agent to save session recap if env var is set
2509
2500
  if [ "$HAIVE_SESSION_REMINDER" = "1" ]; then
@@ -2515,7 +2506,19 @@ exit 0
2515
2506
  var HOOKS = [
2516
2507
  { name: "post-merge", body: POST_MERGE_BODY },
2517
2508
  { name: "post-rewrite", body: POST_MERGE_BODY },
2518
- { name: "pre-push", body: PRE_PUSH_BODY }
2509
+ { name: "pre-push", body: PRE_PUSH_BODY },
2510
+ {
2511
+ name: "pre-commit",
2512
+ body: `#!/bin/sh
2513
+ ${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
2514
+
2515
+ if command -v haive >/dev/null 2>&1; then
2516
+ haive enforce check --stage pre-commit --dir . || exit $?
2517
+ elif [ -x ./node_modules/.bin/haive ]; then
2518
+ ./node_modules/.bin/haive enforce check --stage pre-commit --dir . || exit $?
2519
+ fi
2520
+ `
2521
+ }
2519
2522
  ];
2520
2523
  async function installGitHooks(opts) {
2521
2524
  const root = findProjectRoot6(opts.dir);
@@ -2526,7 +2529,7 @@ async function installGitHooks(opts) {
2526
2529
  return;
2527
2530
  }
2528
2531
  const hooksDir = path9.join(gitDir, "hooks");
2529
- await mkdir5(hooksDir, { recursive: true });
2532
+ await mkdir6(hooksDir, { recursive: true });
2530
2533
  let installed = 0;
2531
2534
  let skipped = 0;
2532
2535
  for (const { name, body } of HOOKS) {
@@ -2545,8 +2548,8 @@ async function installGitHooks(opts) {
2545
2548
  }
2546
2549
  ui.success(`Installed ${installed} git hook(s) in .git/hooks/${skipped ? `, skipped ${skipped}` : ""}`);
2547
2550
  ui.info("post-merge: haive sync runs after every pull/merge.");
2548
- ui.info("pre-push: haive precommit runs before every push (advisory, never blocks).");
2549
- ui.info(" Set HAIVE_BLOCK=1 in your shell to make pre-push blocking.");
2551
+ ui.info("pre-commit: haive enforce check blocks unsafe staged changes.");
2552
+ ui.info("pre-push: haive enforce check blocks pushes that bypass briefing/session recap policy.");
2550
2553
  }
2551
2554
  async function installClaudeHooks(opts) {
2552
2555
  const root = findProjectRoot6(opts.dir);
@@ -2594,7 +2597,7 @@ function registerInstallHooks(program2) {
2594
2597
  }
2595
2598
 
2596
2599
  // src/commands/observe.ts
2597
- import { appendFile, mkdir as mkdir6 } from "fs/promises";
2600
+ import { appendFile, mkdir as mkdir7 } from "fs/promises";
2598
2601
  import { existsSync as existsSync9 } from "fs";
2599
2602
  import path10 from "path";
2600
2603
  import "commander";
@@ -2685,7 +2688,7 @@ function registerObserve(program2) {
2685
2688
  files: extractFiles(payload)
2686
2689
  };
2687
2690
  const cacheDir = path10.join(paths.haiveDir, ".cache");
2688
- await mkdir6(cacheDir, { recursive: true });
2691
+ await mkdir7(cacheDir, { recursive: true });
2689
2692
  await appendFile(
2690
2693
  path10.join(cacheDir, "observations.jsonl"),
2691
2694
  JSON.stringify(observation) + "\n",
@@ -2704,7 +2707,7 @@ import { findProjectRoot as findProjectRoot9 } from "@hiveai/core";
2704
2707
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2705
2708
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2706
2709
  import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
2707
- import { mkdir as mkdir7, writeFile as writeFile6 } from "fs/promises";
2710
+ import { mkdir as mkdir8, writeFile as writeFile6 } from "fs/promises";
2708
2711
  import { existsSync as existsSync10 } from "fs";
2709
2712
  import path11 from "path";
2710
2713
  import { z } from "zod";
@@ -2986,7 +2989,7 @@ async function bootstrapProjectSave(input, ctx) {
2986
2989
  `${target} already exists. Pass overwrite=true to replace it.`
2987
2990
  );
2988
2991
  }
2989
- await mkdir7(path11.dirname(target), { recursive: true });
2992
+ await mkdir8(path11.dirname(target), { recursive: true });
2990
2993
  await writeFile6(target, input.content, "utf8");
2991
2994
  return {
2992
2995
  file_path: target,
@@ -6069,7 +6072,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6069
6072
  };
6070
6073
  }
6071
6074
  var SERVER_NAME = "haive";
6072
- var SERVER_VERSION = "0.9.8";
6075
+ var SERVER_VERSION = "0.9.9";
6073
6076
  function jsonResult(data) {
6074
6077
  return {
6075
6078
  content: [
@@ -6993,7 +6996,7 @@ function registerMcp(program2) {
6993
6996
 
6994
6997
  // src/commands/sync.ts
6995
6998
  import { spawnSync as spawnSync2 } from "child_process";
6996
- import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir8 } from "fs/promises";
6999
+ import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir9 } from "fs/promises";
6997
7000
  import { existsSync as existsSync29 } from "fs";
6998
7001
  import path12 from "path";
6999
7002
  import "commander";
@@ -7269,7 +7272,7 @@ Attends une **confirmation explicite** avant d'agir.
7269
7272
  topic: `dep-bump-${slugParts}`
7270
7273
  });
7271
7274
  const teamDir = path12.join(paths.memoriesDir, "team");
7272
- await mkdir8(teamDir, { recursive: true });
7275
+ await mkdir9(teamDir, { recursive: true });
7273
7276
  await writeFile13(
7274
7277
  path12.join(teamDir, `${fm.id}.md`),
7275
7278
  serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
@@ -7336,7 +7339,7 @@ Attends une **confirmation explicite** avant d'agir.
7336
7339
  topic: `contract-breaking-${diff.contract}`
7337
7340
  });
7338
7341
  const teamDir = path12.join(paths.memoriesDir, "team");
7339
- await mkdir8(teamDir, { recursive: true });
7342
+ await mkdir9(teamDir, { recursive: true });
7340
7343
  await writeFile13(
7341
7344
  path12.join(teamDir, `${fm.id}.md`),
7342
7345
  serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
@@ -7475,7 +7478,7 @@ function collectSinceChanges(root, ref) {
7475
7478
 
7476
7479
  // src/commands/memory-add.ts
7477
7480
  import { createHash as createHash2 } from "crypto";
7478
- import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7481
+ import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7479
7482
  import { existsSync as existsSync30 } from "fs";
7480
7483
  import path13 from "path";
7481
7484
  import "commander";
@@ -7610,7 +7613,7 @@ TODO \u2014 write the memory body.
7610
7613
  topic: opts.topic
7611
7614
  });
7612
7615
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
7613
- await mkdir9(path13.dirname(file), { recursive: true });
7616
+ await mkdir10(path13.dirname(file), { recursive: true });
7614
7617
  if (existsSync30(file)) {
7615
7618
  ui.error(`Memory already exists at ${file}`);
7616
7619
  process.exitCode = 1;
@@ -7743,7 +7746,7 @@ function matchesFilters(loaded, opts) {
7743
7746
  }
7744
7747
 
7745
7748
  // src/commands/memory-promote.ts
7746
- import { mkdir as mkdir10, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
7749
+ import { mkdir as mkdir11, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
7747
7750
  import { existsSync as existsSync33 } from "fs";
7748
7751
  import path15 from "path";
7749
7752
  import "commander";
@@ -7792,7 +7795,7 @@ function registerMemoryPromote(memory2) {
7792
7795
  body: found.memory.body
7793
7796
  };
7794
7797
  const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
7795
- await mkdir10(path15.dirname(newPath), { recursive: true });
7798
+ await mkdir11(path15.dirname(newPath), { recursive: true });
7796
7799
  await writeFile15(newPath, serializeMemory13(updated), "utf8");
7797
7800
  await unlink2(found.filePath);
7798
7801
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
@@ -8252,7 +8255,7 @@ function registerMemoryHot(memory2) {
8252
8255
  }
8253
8256
 
8254
8257
  // src/commands/memory-tried.ts
8255
- import { mkdir as mkdir11, writeFile as writeFile19 } from "fs/promises";
8258
+ import { mkdir as mkdir12, writeFile as writeFile19 } from "fs/promises";
8256
8259
  import { existsSync as existsSync40 } from "fs";
8257
8260
  import path23 from "path";
8258
8261
  import "commander";
@@ -8305,7 +8308,7 @@ function registerMemoryTried(memory2) {
8305
8308
  }
8306
8309
  const body = lines.join("\n") + "\n";
8307
8310
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8308
- await mkdir11(path23.dirname(file), { recursive: true });
8311
+ await mkdir12(path23.dirname(file), { recursive: true });
8309
8312
  if (existsSync40(file)) {
8310
8313
  ui.error(`Memory already exists at ${file}`);
8311
8314
  process.exitCode = 1;
@@ -8805,7 +8808,7 @@ function registerMemoryImport(memory2) {
8805
8808
 
8806
8809
  // src/commands/memory-import-changelog.ts
8807
8810
  import { existsSync as existsSync50 } from "fs";
8808
- import { readFile as readFile13, mkdir as mkdir12, writeFile as writeFile23 } from "fs/promises";
8811
+ import { readFile as readFile13, mkdir as mkdir13, writeFile as writeFile23 } from "fs/promises";
8809
8812
  import path30 from "path";
8810
8813
  import "commander";
8811
8814
  import {
@@ -8902,7 +8905,7 @@ function registerMemoryImportChangelog(memory2) {
8902
8905
  const pkgName = opts.package ?? path30.basename(path30.dirname(changelogPath));
8903
8906
  const scope = opts.scope ?? "team";
8904
8907
  const teamDir = path30.join(paths.memoriesDir, scope);
8905
- await mkdir12(teamDir, { recursive: true });
8908
+ await mkdir13(teamDir, { recursive: true });
8906
8909
  let saved = 0;
8907
8910
  for (const entry of entries) {
8908
8911
  const lines = [];
@@ -9073,7 +9076,7 @@ function registerMemoryDigest(program2) {
9073
9076
  }
9074
9077
 
9075
9078
  // src/commands/session-end.ts
9076
- import { writeFile as writeFile25, mkdir as mkdir13, readFile as readFile14, rm as rm2 } from "fs/promises";
9079
+ import { writeFile as writeFile25, mkdir as mkdir14, readFile as readFile14, rm as rm2 } from "fs/promises";
9077
9080
  import { existsSync as existsSync53 } from "fs";
9078
9081
  import path33 from "path";
9079
9082
  import "commander";
@@ -9248,7 +9251,7 @@ function registerSessionEnd(session2) {
9248
9251
  status: "validated"
9249
9252
  });
9250
9253
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9251
- await mkdir13(path33.dirname(file), { recursive: true });
9254
+ await mkdir14(path33.dirname(file), { recursive: true });
9252
9255
  await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
9253
9256
  await cleanupObservations();
9254
9257
  if (!opts.quiet) {
@@ -9432,7 +9435,7 @@ function detectFormat(filePath) {
9432
9435
 
9433
9436
  // src/commands/hub.ts
9434
9437
  import { existsSync as existsSync55 } from "fs";
9435
- import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9438
+ import { mkdir as mkdir15, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9436
9439
  import path35 from "path";
9437
9440
  import { spawnSync as spawnSync3 } from "child_process";
9438
9441
  import "commander";
@@ -9453,7 +9456,7 @@ function registerHub(program2) {
9453
9456
  "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"
9454
9457
  ).action(async (hubPath) => {
9455
9458
  const absPath = path35.resolve(hubPath);
9456
- await mkdir14(absPath, { recursive: true });
9459
+ await mkdir15(absPath, { recursive: true });
9457
9460
  const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9458
9461
  if (gitCheck.status !== 0) {
9459
9462
  const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
@@ -9464,7 +9467,7 @@ function registerHub(program2) {
9464
9467
  }
9465
9468
  }
9466
9469
  const sharedDir = path35.join(absPath, ".ai", "memories", "shared");
9467
- await mkdir14(sharedDir, { recursive: true });
9470
+ await mkdir15(sharedDir, { recursive: true });
9468
9471
  await writeFile26(
9469
9472
  path35.join(absPath, ".ai", "README.md"),
9470
9473
  `# hAIve Team Knowledge Hub
@@ -9540,7 +9543,7 @@ Next steps:
9540
9543
  }
9541
9544
  const projectName = path35.basename(root);
9542
9545
  const destDir = path35.join(hubRoot, ".ai", "memories", "shared", projectName);
9543
- await mkdir14(destDir, { recursive: true });
9546
+ await mkdir15(destDir, { recursive: true });
9544
9547
  const all = await loadMemoriesFromDir28(paths.memoriesDir);
9545
9548
  const shared = all.filter(
9546
9549
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
@@ -9619,7 +9622,7 @@ Next steps:
9619
9622
  for (const sourceName of projectDirs) {
9620
9623
  const sourceDir = path35.join(hubSharedDir, sourceName);
9621
9624
  const destDir = path35.join(paths.memoriesDir, "shared", sourceName);
9622
- await mkdir14(destDir, { recursive: true });
9625
+ await mkdir15(destDir, { recursive: true });
9623
9626
  const sourceFiles = (await readdir5(sourceDir)).filter((f) => f.endsWith(".md"));
9624
9627
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
9625
9628
  const existingInDest = await loadDir(destDir);
@@ -9688,7 +9691,7 @@ Next steps:
9688
9691
  // src/commands/stats.ts
9689
9692
  import "commander";
9690
9693
  import { existsSync as existsSync56 } from "fs";
9691
- import { mkdir as mkdir15, writeFile as writeFile27 } from "fs/promises";
9694
+ import { mkdir as mkdir16, writeFile as writeFile27 } from "fs/promises";
9692
9695
  import path36 from "path";
9693
9696
  import {
9694
9697
  aggregateUsage,
@@ -9796,7 +9799,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
9796
9799
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
9797
9800
  events = [];
9798
9801
  }
9799
- await mkdir15(path36.dirname(outAbs), { recursive: true });
9802
+ await mkdir16(path36.dirname(outAbs), { recursive: true });
9800
9803
  const payload = {
9801
9804
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
9802
9805
  project_root: root,
@@ -9985,7 +9988,7 @@ function summarize(name, t0, payload, notes) {
9985
9988
  }
9986
9989
 
9987
9990
  // src/commands/memory-suggest.ts
9988
- import { mkdir as mkdir16, writeFile as writeFile28 } from "fs/promises";
9991
+ import { mkdir as mkdir17, writeFile as writeFile28 } from "fs/promises";
9989
9992
  import { existsSync as existsSync57 } from "fs";
9990
9993
  import path37 from "path";
9991
9994
  import "commander";
@@ -10080,7 +10083,7 @@ function registerMemorySuggest(memory2) {
10080
10083
  fm.status = "draft";
10081
10084
  const body = renderTemplate(s);
10082
10085
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
10083
- await mkdir16(path37.dirname(file), { recursive: true });
10086
+ await mkdir17(path37.dirname(file), { recursive: true });
10084
10087
  if (existsSync57(file)) {
10085
10088
  skipped.push({ query: s.query, reason: `file already exists at ${path37.relative(root, file)}` });
10086
10089
  continue;
@@ -10334,8 +10337,8 @@ function registerDoctor(program2) {
10334
10337
  fix: "haive init"
10335
10338
  });
10336
10339
  } else {
10337
- const { readFile: readFile16 } = await import("fs/promises");
10338
- const content = await readFile16(paths.projectContext, "utf8");
10340
+ const { readFile: readFile17 } = await import("fs/promises");
10341
+ const content = await readFile17(paths.projectContext, "utf8");
10339
10342
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10340
10343
  if (isTemplate) {
10341
10344
  findings.push({
@@ -10461,8 +10464,8 @@ function registerDoctor(program2) {
10461
10464
  let hasClaudeEnforcement = false;
10462
10465
  if (existsSync59(claudeSettings)) {
10463
10466
  try {
10464
- const { readFile: readFile16 } = await import("fs/promises");
10465
- const raw = await readFile16(claudeSettings, "utf8");
10467
+ const { readFile: readFile17 } = await import("fs/promises");
10468
+ const raw = await readFile17(claudeSettings, "utf8");
10466
10469
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
10467
10470
  } catch {
10468
10471
  hasClaudeEnforcement = false;
@@ -10491,7 +10494,7 @@ function registerDoctor(program2) {
10491
10494
  timeout: 3e3,
10492
10495
  stdio: ["ignore", "pipe", "ignore"]
10493
10496
  }).trim();
10494
- const cliVersion = "0.9.8";
10497
+ const cliVersion = "0.9.9";
10495
10498
  if (legacyRaw && legacyRaw !== cliVersion) {
10496
10499
  findings.push({
10497
10500
  severity: "warn",
@@ -11094,29 +11097,86 @@ function registerMemoryConflictCandidates(memory2) {
11094
11097
  }
11095
11098
 
11096
11099
  // src/commands/enforce.ts
11100
+ import { spawn as spawn5 } from "child_process";
11097
11101
  import { existsSync as existsSync67 } from "fs";
11098
- import { mkdir as mkdir17 } from "fs/promises";
11102
+ import { chmod as chmod2, mkdir as mkdir18, readFile as readFile16, writeFile as writeFile30 } from "fs/promises";
11103
+ import path45 from "path";
11099
11104
  import "commander";
11100
11105
  import {
11101
11106
  findProjectRoot as findProjectRoot46,
11102
11107
  hasRecentBriefingMarker,
11108
+ isFreshIsoDate,
11109
+ loadConfig as loadConfig8,
11110
+ loadMemoriesFromDir as loadMemoriesFromDir36,
11103
11111
  resolveBriefingBudget as resolveBriefingBudget3,
11104
11112
  resolveHaivePaths as resolveHaivePaths43,
11105
- writeBriefingMarker
11113
+ saveConfig as saveConfig3,
11114
+ SESSION_RECAP_TTL_MS,
11115
+ verifyAnchor as verifyAnchor4,
11116
+ writeBriefingMarker as writeBriefingMarker2
11106
11117
  } from "@hiveai/core";
11107
11118
  var MAX_STDIN_BYTES2 = 256 * 1024;
11119
+ var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
11108
11120
  function registerEnforce(program2) {
11109
- const enforce = program2.command("enforce").description("Agent enforcement helpers used by hAIve-installed hooks.");
11121
+ const enforce = program2.command("enforce").description(
11122
+ "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
11123
+ );
11124
+ enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
11125
+ const root = findProjectRoot46(opts.dir);
11126
+ const paths = resolveHaivePaths43(root);
11127
+ await mkdir18(paths.haiveDir, { recursive: true });
11128
+ const current = await loadConfig8(paths);
11129
+ await saveConfig3(paths, {
11130
+ ...current,
11131
+ enforcement: {
11132
+ ...current.enforcement,
11133
+ mode: "strict",
11134
+ requireBriefingFirst: true,
11135
+ requireSessionRecap: true,
11136
+ requireMemoryVerify: true,
11137
+ blockStaleDecisionChanges: true,
11138
+ toolProfile: "enforcement"
11139
+ }
11140
+ });
11141
+ ui.success("hAIve strict enforcement enabled in .ai/haive.config.json");
11142
+ if (opts.git !== false) await installGitEnforcement(root);
11143
+ if (opts.ci !== false) await installCiEnforcement(root);
11144
+ if (opts.claude !== false) {
11145
+ try {
11146
+ const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
11147
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path45.relative(root, result.settingsPath)})`);
11148
+ } catch (err) {
11149
+ ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
11150
+ }
11151
+ }
11152
+ ui.info("Agent-agnostic gates are now active at workflow level: MCP, git, CI, and optional client hooks.");
11153
+ ui.info("Use `haive run -- <agent command>` for agents that do not expose blocking hooks.");
11154
+ });
11155
+ enforce.command("status").description("Show whether this project has agent-agnostic hAIve enforcement installed.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
11156
+ const report = await buildEnforcementReport(opts.dir, "local");
11157
+ printReport(report, Boolean(opts.json));
11158
+ if (report.should_block) process.exitCode = 1;
11159
+ });
11160
+ enforce.command("check").description("Run the hAIve policy gate. Intended for pre-commit, pre-push, wrappers, and any agent client.").option("-d, --dir <dir>", "project root").option("--stage <stage>", "local | pre-commit | pre-push | ci", "local").option("--json", "emit JSON", false).action(async (opts) => {
11161
+ const report = await buildEnforcementReport(opts.dir, opts.stage ?? "local");
11162
+ printReport(report, Boolean(opts.json));
11163
+ if (report.should_block) process.exit(2);
11164
+ });
11165
+ enforce.command("ci").description("CI entrypoint: fail if the repository violates hAIve enforcement policy.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
11166
+ const report = await buildEnforcementReport(opts.dir, "ci");
11167
+ printReport(report, Boolean(opts.json));
11168
+ if (report.should_block) process.exit(2);
11169
+ });
11110
11170
  enforce.command("session-start").description("Claude Code SessionStart hook: inject briefing and write a local briefing marker.").option("-d, --dir <dir>", "project root").option("--task <text>", "task text to rank memories").option("--source <name>", "marker source", "claude-session-start").option("--session-id <id>", "agent session id").action(async (opts) => {
11111
11171
  const payload = await readHookPayload();
11112
11172
  const root = resolveRoot(opts.dir, payload);
11113
11173
  if (!root) return;
11114
11174
  const paths = resolveHaivePaths43(root);
11115
11175
  if (!existsSync67(paths.haiveDir)) return;
11116
- await mkdir17(paths.runtimeDir, { recursive: true });
11176
+ await mkdir18(paths.runtimeDir, { recursive: true });
11117
11177
  const sessionId = opts.sessionId ?? payload.session_id;
11118
11178
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
11119
- await writeBriefingMarker(paths, {
11179
+ await writeBriefingMarker2(paths, {
11120
11180
  sessionId,
11121
11181
  task,
11122
11182
  source: opts.source ?? "claude-session-start"
@@ -11194,6 +11254,317 @@ ${briefing.project_context.content.slice(0, 1800)}`);
11194
11254
  process.exit(2);
11195
11255
  });
11196
11256
  }
11257
+ async function runWithEnforcement(command, args, opts) {
11258
+ const root = findProjectRoot46(opts.dir);
11259
+ const paths = resolveHaivePaths43(root);
11260
+ if (!existsSync67(paths.haiveDir)) {
11261
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11262
+ process.exit(1);
11263
+ }
11264
+ const sessionId = `haive-run-${process.pid}-${Date.now()}`;
11265
+ const task = opts.task ?? `Run agent command: ${[command, ...args].join(" ")}`;
11266
+ await writeBriefingMarker2(paths, {
11267
+ sessionId,
11268
+ task,
11269
+ source: "haive-run"
11270
+ });
11271
+ const briefingFile = await writeWrapperBriefing(paths, sessionId, task);
11272
+ const before = await buildEnforcementReport(root, "local", sessionId);
11273
+ const blocking = before.findings.filter((f) => f.severity === "error" && f.code !== "session-recap-missing");
11274
+ if (blocking.length > 0) {
11275
+ printReport({ ...before, should_block: true, findings: blocking }, false);
11276
+ process.exit(2);
11277
+ }
11278
+ ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
11279
+ ui.info(`Briefing written to ${path45.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
11280
+ const child = spawn5(command, args, {
11281
+ cwd: root,
11282
+ stdio: "inherit",
11283
+ env: {
11284
+ ...process.env,
11285
+ HAIVE_PROJECT_ROOT: root,
11286
+ HAIVE_SESSION_ID: sessionId,
11287
+ HAIVE_BRIEFING_FILE: briefingFile,
11288
+ HAIVE_ENFORCEMENT: "strict",
11289
+ HAIVE_TOOL_PROFILE: process.env.HAIVE_TOOL_PROFILE ?? "enforcement"
11290
+ }
11291
+ });
11292
+ await new Promise((resolve, reject) => {
11293
+ child.on("error", reject);
11294
+ child.on("close", (code, signal) => {
11295
+ if (signal) process.exit(128);
11296
+ process.exitCode = code ?? 0;
11297
+ resolve();
11298
+ });
11299
+ });
11300
+ }
11301
+ async function writeWrapperBriefing(paths, sessionId, task) {
11302
+ const budget = resolveBriefingBudget3("quick", {
11303
+ max_tokens: 2500,
11304
+ max_memories: 5,
11305
+ include_module_contexts: false
11306
+ });
11307
+ const briefing = await getBriefing({
11308
+ task,
11309
+ files: [],
11310
+ max_tokens: budget.max_tokens,
11311
+ max_memories: budget.max_memories,
11312
+ include_project_context: true,
11313
+ include_module_contexts: budget.include_module_contexts,
11314
+ semantic: true,
11315
+ include_stale: false,
11316
+ track: true,
11317
+ format: "actions",
11318
+ symbols: [],
11319
+ min_semantic_score: 0.25,
11320
+ budget_preset: "quick"
11321
+ }, { paths });
11322
+ const dir = path45.join(paths.runtimeDir, "enforcement", "briefings");
11323
+ await mkdir18(dir, { recursive: true });
11324
+ const file = path45.join(dir, `${sessionId}.md`);
11325
+ const parts = [
11326
+ "# hAIve Briefing",
11327
+ "",
11328
+ `Task: ${task}`,
11329
+ ""
11330
+ ];
11331
+ if (briefing.last_session) parts.push("## Last Session", briefing.last_session.body.trim(), "");
11332
+ if (briefing.project_context?.content) parts.push("## Project Context", briefing.project_context.content.trim(), "");
11333
+ if (briefing.memories.length > 0) {
11334
+ parts.push("## Relevant Memories");
11335
+ for (const memory2 of briefing.memories) {
11336
+ parts.push("", `### ${memory2.id}`, memory2.body.trim());
11337
+ }
11338
+ }
11339
+ if (briefing.setup_warnings.length > 0) {
11340
+ parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
11341
+ }
11342
+ await writeFile30(file, parts.join("\n") + "\n", "utf8");
11343
+ return file;
11344
+ }
11345
+ async function buildEnforcementReport(dir, stage, sessionId) {
11346
+ const root = findProjectRoot46(dir);
11347
+ const paths = resolveHaivePaths43(root);
11348
+ const initialized = existsSync67(paths.haiveDir);
11349
+ const config = initialized ? await loadConfig8(paths) : {};
11350
+ const mode = config.enforcement?.mode ?? "strict";
11351
+ const findings = [];
11352
+ if (!initialized) {
11353
+ return {
11354
+ root,
11355
+ initialized,
11356
+ mode,
11357
+ should_block: true,
11358
+ findings: [{
11359
+ severity: "error",
11360
+ code: "not-initialized",
11361
+ message: "This repository is not initialized with hAIve.",
11362
+ fix: "Run `haive init` or `haive enforce install`."
11363
+ }]
11364
+ };
11365
+ }
11366
+ if (mode === "off") {
11367
+ return {
11368
+ root,
11369
+ initialized,
11370
+ mode,
11371
+ should_block: false,
11372
+ findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
11373
+ };
11374
+ }
11375
+ if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
11376
+ const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
11377
+ findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
11378
+ severity: "error",
11379
+ code: "briefing-missing",
11380
+ message: "No recent hAIve briefing marker was found for this workflow.",
11381
+ fix: 'Run `haive briefing --task "..."`, `haive enforce session-start`, or wrap the agent with `haive run -- <agent>`.'
11382
+ });
11383
+ }
11384
+ if (config.enforcement?.requireSessionRecap !== false && (stage === "pre-push" || stage === "ci")) {
11385
+ const hasRecap = await hasRecentSessionRecap(paths);
11386
+ findings.push(hasRecap ? { severity: "ok", code: "session-recap-present", message: "A recent session_recap memory exists." } : {
11387
+ severity: "error",
11388
+ code: "session-recap-missing",
11389
+ message: "No recent session_recap memory was found.",
11390
+ fix: "Run `haive session end --goal ... --accomplished ...` before pushing."
11391
+ });
11392
+ }
11393
+ if (config.enforcement?.requireMemoryVerify !== false) {
11394
+ findings.push(...await verifyMemoryPolicy(paths, config));
11395
+ }
11396
+ if (stage === "pre-commit" || stage === "ci") {
11397
+ findings.push(...await runPrecommitPolicy(paths));
11398
+ }
11399
+ const hasErrors = findings.some((f) => f.severity === "error");
11400
+ return {
11401
+ root,
11402
+ initialized,
11403
+ mode,
11404
+ should_block: mode === "strict" && hasErrors,
11405
+ findings
11406
+ };
11407
+ }
11408
+ async function hasRecentSessionRecap(paths) {
11409
+ if (!existsSync67(paths.memoriesDir)) return false;
11410
+ const all = await loadMemoriesFromDir36(paths.memoriesDir);
11411
+ return all.some(
11412
+ ({ memory: memory2 }) => memory2.frontmatter.type === "session_recap" && memory2.frontmatter.status !== "rejected" && isFreshIsoDate(memory2.frontmatter.created_at, SESSION_RECAP_TTL_MS)
11413
+ );
11414
+ }
11415
+ async function verifyMemoryPolicy(paths, config) {
11416
+ if (!existsSync67(paths.memoriesDir)) return [];
11417
+ const all = await loadMemoriesFromDir36(paths.memoriesDir);
11418
+ const findings = [];
11419
+ const staleImportant = [];
11420
+ let verified = 0;
11421
+ for (const { memory: memory2 } of all) {
11422
+ const fm = memory2.frontmatter;
11423
+ const anchored = fm.anchor.paths.length > 0 || fm.anchor.symbols.length > 0;
11424
+ if (!anchored || fm.status === "rejected" || fm.status === "deprecated") continue;
11425
+ verified++;
11426
+ if (fm.status === "stale") {
11427
+ if (["decision", "gotcha", "architecture", "convention"].includes(fm.type)) {
11428
+ staleImportant.push(fm.id);
11429
+ }
11430
+ continue;
11431
+ }
11432
+ if (config.enforcement?.blockStaleDecisionChanges !== false && ["decision", "gotcha"].includes(fm.type)) {
11433
+ const result = await verifyAnchor4(memory2, { projectRoot: paths.root });
11434
+ if (result.stale) staleImportant.push(fm.id);
11435
+ }
11436
+ }
11437
+ findings.push({
11438
+ severity: "ok",
11439
+ code: "memory-verify-ran",
11440
+ message: `Checked ${verified} anchored memories for stale enforcement policy.`
11441
+ });
11442
+ if (staleImportant.length > 0) {
11443
+ findings.push({
11444
+ severity: "error",
11445
+ code: "stale-important-memories",
11446
+ message: `${staleImportant.length} important anchored memories are stale: ${staleImportant.slice(0, 8).join(", ")}`,
11447
+ fix: "Run `haive memory verify --update`, then update or delete stale decisions/gotchas before merging."
11448
+ });
11449
+ }
11450
+ return findings;
11451
+ }
11452
+ async function runPrecommitPolicy(paths) {
11453
+ const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
11454
+ const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
11455
+ if (touchedPaths.length === 0) {
11456
+ return [{ severity: "info", code: "no-staged-changes", message: "No staged changes found for pre-commit policy." }];
11457
+ }
11458
+ const diff = await runCommand4("git", ["diff", "--cached"], paths.root).catch(() => "");
11459
+ const result = await preCommitCheck({
11460
+ diff,
11461
+ paths: touchedPaths,
11462
+ block_on: "high-confidence",
11463
+ semantic: true
11464
+ }, { paths });
11465
+ if (!result.should_block) {
11466
+ return [{
11467
+ severity: "ok",
11468
+ code: "precommit-policy-pass",
11469
+ message: `Pre-commit policy passed for ${touchedPaths.length} staged file(s).`
11470
+ }];
11471
+ }
11472
+ return [{
11473
+ severity: "error",
11474
+ code: "precommit-policy-block",
11475
+ message: `Pre-commit policy matched ${result.summary.anti_patterns} anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
11476
+ fix: "Review the hAIve warnings, then update the code or the relevant memories."
11477
+ }];
11478
+ }
11479
+ async function installGitEnforcement(root) {
11480
+ const hooksDir = path45.join(root, ".git", "hooks");
11481
+ if (!existsSync67(path45.join(root, ".git"))) {
11482
+ ui.warn("No .git directory found; git enforcement hooks skipped.");
11483
+ return;
11484
+ }
11485
+ await mkdir18(hooksDir, { recursive: true });
11486
+ const hooks = [
11487
+ {
11488
+ name: "pre-commit",
11489
+ body: `#!/bin/sh
11490
+ ${ENFORCE_HOOK_MARKER}
11491
+ haive enforce check --stage pre-commit --dir . || exit $?
11492
+ `
11493
+ },
11494
+ {
11495
+ name: "pre-push",
11496
+ body: `#!/bin/sh
11497
+ ${ENFORCE_HOOK_MARKER}
11498
+ haive enforce check --stage pre-push --dir . || exit $?
11499
+ `
11500
+ }
11501
+ ];
11502
+ for (const hook of hooks) {
11503
+ const file = path45.join(hooksDir, hook.name);
11504
+ if (existsSync67(file)) {
11505
+ const current = await readFile16(file, "utf8").catch(() => "");
11506
+ if (current.includes(ENFORCE_HOOK_MARKER)) {
11507
+ await writeFile30(file, hook.body, "utf8");
11508
+ } else {
11509
+ await writeFile30(file, `${current.trimEnd()}
11510
+
11511
+ ${hook.body}`, "utf8");
11512
+ }
11513
+ } else {
11514
+ await writeFile30(file, hook.body, "utf8");
11515
+ }
11516
+ await chmod2(file, 493);
11517
+ }
11518
+ ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
11519
+ }
11520
+ async function installCiEnforcement(root) {
11521
+ const workflowPath = path45.join(root, ".github", "workflows", "haive-enforcement.yml");
11522
+ await mkdir18(path45.dirname(workflowPath), { recursive: true });
11523
+ if (existsSync67(workflowPath)) {
11524
+ ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
11525
+ return;
11526
+ }
11527
+ await writeFile30(workflowPath, `name: haive-enforcement
11528
+
11529
+ on:
11530
+ pull_request:
11531
+ push:
11532
+ branches: [main, master]
11533
+
11534
+ jobs:
11535
+ haive-enforcement:
11536
+ runs-on: ubuntu-latest
11537
+ permissions:
11538
+ contents: read
11539
+ steps:
11540
+ - uses: actions/checkout@v4
11541
+ with:
11542
+ fetch-depth: 0
11543
+ - uses: actions/setup-node@v4
11544
+ with:
11545
+ node-version: '20'
11546
+ - name: Install hAIve
11547
+ run: npm install -g @hiveai/cli
11548
+ - name: Enforce hAIve policy
11549
+ run: haive enforce ci
11550
+ `, "utf8");
11551
+ ui.success(`Created ${path45.relative(root, workflowPath)}`);
11552
+ }
11553
+ function printReport(report, json) {
11554
+ if (json) {
11555
+ console.log(JSON.stringify(report, null, 2));
11556
+ return;
11557
+ }
11558
+ console.log(ui.bold(`hAIve enforcement \u2014 ${report.mode}`));
11559
+ console.log(ui.dim(` root: ${report.root}`));
11560
+ for (const finding of report.findings) {
11561
+ const marker = finding.severity === "error" ? ui.red("\u2717") : finding.severity === "warn" ? ui.yellow("\u26A0") : finding.severity === "ok" ? ui.green("\u2713") : ui.dim("\u2022");
11562
+ console.log(`${marker} ${finding.code}: ${finding.message}`);
11563
+ if (finding.fix) console.log(ui.dim(` fix: ${finding.fix}`));
11564
+ }
11565
+ if (report.should_block) ui.error("hAIve enforcement gate failed.");
11566
+ else ui.success("hAIve enforcement gate passed.");
11567
+ }
11197
11568
  async function readHookPayload() {
11198
11569
  const raw = await readStdin2(MAX_STDIN_BYTES2);
11199
11570
  if (!raw.trim()) return {};
@@ -11242,15 +11613,46 @@ async function readStdin2(maxBytes) {
11242
11613
  setTimeout(finish, 2e3);
11243
11614
  });
11244
11615
  }
11616
+ function runCommand4(cmd, args, cwd) {
11617
+ return new Promise((resolve, reject) => {
11618
+ const proc = spawn5(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
11619
+ let stdout = "";
11620
+ let stderr = "";
11621
+ proc.stdout.on("data", (chunk) => {
11622
+ stdout += chunk.toString();
11623
+ });
11624
+ proc.stderr.on("data", (chunk) => {
11625
+ stderr += chunk.toString();
11626
+ });
11627
+ proc.on("error", reject);
11628
+ proc.on("close", (code) => {
11629
+ if (code === 0) resolve(stdout);
11630
+ else reject(new Error(stderr || `${cmd} exited with code ${code}`));
11631
+ });
11632
+ });
11633
+ }
11634
+
11635
+ // src/commands/run.ts
11636
+ import "commander";
11637
+ function registerRun(program2) {
11638
+ program2.command("run").description("Run any AI agent command inside a hAIve-enforced session.").option("-d, --dir <dir>", "project root").option("--task <text>", "task text used for the hAIve briefing marker").allowUnknownOption(true).argument("[cmd]", "agent command to run").argument("[args...]", "agent command arguments").action(async (cmd, args, opts) => {
11639
+ if (!cmd) {
11640
+ ui.error("Usage: haive run -- <agent command> [args...]");
11641
+ process.exit(1);
11642
+ }
11643
+ await runWithEnforcement(cmd, args, opts);
11644
+ });
11645
+ }
11245
11646
 
11246
11647
  // src/index.ts
11247
- var program = new Command48();
11248
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.8");
11648
+ var program = new Command49();
11649
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.9");
11249
11650
  registerInit(program);
11250
11651
  registerWelcome(program);
11251
11652
  registerResolveProject(program);
11252
11653
  registerRuntime(program);
11253
11654
  registerEnforce(program);
11655
+ registerRun(program);
11254
11656
  registerMcp(program);
11255
11657
  registerBriefing(program);
11256
11658
  registerTui(program);