@hiveai/cli 0.9.7 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command47 } from "commander";
4
+ import { Command as Command48 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -2171,6 +2171,16 @@ function registerInit(program2) {
2171
2171
  } else {
2172
2172
  ui.warn("Git hooks not installed (not a git repo or no .git/ found) \u2014 run `haive install-hooks` manually");
2173
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");
2183
+ }
2174
2184
  try {
2175
2185
  ui.info("Building code-map\u2026");
2176
2186
  const map = await buildCodeMap2(root);
@@ -2342,7 +2352,7 @@ import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
2342
2352
  import { existsSync as existsSync7 } from "fs";
2343
2353
  import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2344
2354
  import path8 from "path";
2345
- var HAIVE_HOOK_TAG = "haive-passive-capture";
2355
+ var HAIVE_HOOK_TAG = "haive-enforcement";
2346
2356
  var POST_TOOL_USE_GROUP = {
2347
2357
  matcher: "Edit|Write|Bash",
2348
2358
  hooks: [
@@ -2353,6 +2363,25 @@ var POST_TOOL_USE_GROUP = {
2353
2363
  }
2354
2364
  ]
2355
2365
  };
2366
+ var PRE_TOOL_USE_GROUP = {
2367
+ matcher: "Edit|Write|MultiEdit|NotebookEdit|Bash",
2368
+ hooks: [
2369
+ {
2370
+ type: "command",
2371
+ command: "haive enforce pre-tool-use",
2372
+ haive_tag: HAIVE_HOOK_TAG
2373
+ }
2374
+ ]
2375
+ };
2376
+ var SESSION_START_GROUP = {
2377
+ hooks: [
2378
+ {
2379
+ type: "command",
2380
+ command: "haive enforce session-start",
2381
+ haive_tag: HAIVE_HOOK_TAG
2382
+ }
2383
+ ]
2384
+ };
2356
2385
  var SESSION_END_GROUP = {
2357
2386
  hooks: [
2358
2387
  {
@@ -2370,6 +2399,14 @@ function dropHaiveGroups(groups) {
2370
2399
  function patchClaudeSettings(input) {
2371
2400
  const settings = input ? { ...input } : {};
2372
2401
  const hooks = settings.hooks ? { ...settings.hooks } : {};
2402
+ hooks.SessionStart = [
2403
+ ...dropHaiveGroups(hooks.SessionStart ?? []),
2404
+ SESSION_START_GROUP
2405
+ ];
2406
+ hooks.PreToolUse = [
2407
+ ...dropHaiveGroups(hooks.PreToolUse ?? []),
2408
+ PRE_TOOL_USE_GROUP
2409
+ ];
2373
2410
  hooks.PostToolUse = [
2374
2411
  ...dropHaiveGroups(hooks.PostToolUse ?? []),
2375
2412
  POST_TOOL_USE_GROUP
@@ -2523,7 +2560,7 @@ async function installClaudeHooks(opts) {
2523
2560
  try {
2524
2561
  const result = await installClaudeHooksAtPath(settingsPath);
2525
2562
  if (result.created) {
2526
- ui.success(`Created ${result.settingsPath} with hAIve passive-capture hooks`);
2563
+ ui.success(`Created ${result.settingsPath} with hAIve enforcement hooks`);
2527
2564
  } else {
2528
2565
  ui.success(`Patched ${result.settingsPath} (existing user hooks preserved)`);
2529
2566
  }
@@ -2532,8 +2569,9 @@ async function installClaudeHooks(opts) {
2532
2569
  process.exitCode = 1;
2533
2570
  return;
2534
2571
  }
2535
- ui.info("PostToolUse hook: `haive observe` runs after every Edit/Write/Bash");
2536
- ui.info(" (appends a JSON line to .ai/.cache/observations.jsonl)");
2572
+ ui.info("SessionStart hook: `haive enforce session-start` injects briefing context");
2573
+ ui.info("PreToolUse hook: blocks Edit/Write/dangerous Bash until briefing is loaded");
2574
+ ui.info("PostToolUse hook: `haive observe` captures Edit/Write/Bash activity");
2537
2575
  ui.info("SessionEnd hook: `haive session end --auto --quiet` distills observations");
2538
2576
  ui.info(" into a session_recap memory at session close");
2539
2577
  ui.info("Restart Claude Code (or open a new conversation) for the hooks to take effect.");
@@ -2541,7 +2579,7 @@ async function installClaudeHooks(opts) {
2541
2579
  }
2542
2580
  function registerInstallHooks(program2) {
2543
2581
  program2.command("install-hooks [target]").description(
2544
- "Install hAIve hooks. Targets:\n\n git (default) post-merge / post-rewrite / pre-push for haive sync + precommit\n claude PostToolUse + SessionEnd hooks in ~/.claude/settings.json\n for passive observation capture (Claude Code only)\n\n Examples:\n haive install-hooks # git hooks (legacy default)\n haive install-hooks git\n haive install-hooks claude\n haive install-hooks claude --scope project\n haive install-hooks claude --uninstall\n"
2582
+ "Install hAIve hooks. Targets:\n\n git (default) post-merge / post-rewrite / pre-push for haive sync + precommit\n claude SessionStart + PreToolUse + PostToolUse + SessionEnd hooks\n for briefing injection, pre-edit blocking, and capture (Claude Code only)\n\n Examples:\n haive install-hooks # git hooks (legacy default)\n haive install-hooks git\n haive install-hooks claude\n haive install-hooks claude --scope project\n haive install-hooks claude --uninstall\n"
2545
2583
  ).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks (git target only)").option("--scope <scope>", "claude target: 'user' (~/.claude) or 'project' (.claude/)", "user").option("--uninstall", "remove previously installed hAIve hooks (claude target only)").option("--settings <path>", "explicit path to settings.json (claude target only)").action(async (target, opts) => {
2546
2584
  const t = (target ?? "git").toLowerCase();
2547
2585
  if (t === "git") {
@@ -2926,6 +2964,7 @@ import { z as z35 } from "zod";
2926
2964
  import { z as z36 } from "zod";
2927
2965
  import { z as z37 } from "zod";
2928
2966
  import { z as z38 } from "zod";
2967
+ import { loadConfigSync } from "@hiveai/core";
2929
2968
  function createContext(options = {}) {
2930
2969
  const env = options.env ?? process.env;
2931
2970
  const cwd = options.cwd ?? process.cwd();
@@ -6030,7 +6069,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6030
6069
  };
6031
6070
  }
6032
6071
  var SERVER_NAME = "haive";
6033
- var SERVER_VERSION = "0.9.7";
6072
+ var SERVER_VERSION = "0.9.8";
6034
6073
  function jsonResult(data) {
6035
6074
  return {
6036
6075
  content: [
@@ -6041,15 +6080,70 @@ function jsonResult(data) {
6041
6080
  ]
6042
6081
  };
6043
6082
  }
6083
+ var ENFORCEMENT_PROFILE_TOOLS = /* @__PURE__ */ new Set([
6084
+ "get_briefing",
6085
+ "mem_save",
6086
+ "mem_tried",
6087
+ "mem_search",
6088
+ "mem_get",
6089
+ "mem_update",
6090
+ "mem_verify",
6091
+ "mem_relevant_to",
6092
+ "code_map",
6093
+ "pre_commit_check"
6094
+ ]);
6095
+ var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]);
6096
+ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
6097
+ "mem_save",
6098
+ "mem_tried",
6099
+ "mem_observe",
6100
+ "mem_session_end",
6101
+ "bootstrap_project_save",
6102
+ "mem_update",
6103
+ "mem_approve",
6104
+ "mem_reject",
6105
+ "mem_delete",
6106
+ "runtime_journal_append",
6107
+ "pattern_detect"
6108
+ ]);
6044
6109
  function createHaiveServer(options = {}) {
6045
6110
  const context = createContext(options);
6111
+ const config = loadConfigSync(context.paths);
6112
+ const toolProfile = options.env?.HAIVE_TOOL_PROFILE ?? config.enforcement?.toolProfile ?? "enforcement";
6113
+ const requireBriefingFirst = options.env?.HAIVE_REQUIRE_BRIEFING_FIRST === "0" ? false : config.enforcement?.requireBriefingFirst ?? true;
6114
+ let briefingLoaded = false;
6046
6115
  const tracker = new SessionTracker(context);
6047
6116
  void tracker.init();
6048
6117
  const server = new McpServer(
6049
6118
  { name: SERVER_NAME, version: SERVER_VERSION },
6050
6119
  { capabilities: { tools: {}, prompts: {} } }
6051
6120
  );
6052
- server.tool(
6121
+ const shouldRegisterTool = (name) => toolProfile === "full" || ENFORCEMENT_PROFILE_TOOLS.has(name);
6122
+ const registerTool = (name, description, schema, handler) => {
6123
+ if (!shouldRegisterTool(name)) return;
6124
+ const tool = server.tool.bind(server);
6125
+ tool(
6126
+ name,
6127
+ description,
6128
+ schema,
6129
+ async (input) => {
6130
+ if (BRIEFING_TOOLS.has(name)) {
6131
+ briefingLoaded = true;
6132
+ return await handler(input);
6133
+ }
6134
+ if (requireBriefingFirst && MUTATING_TOOLS.has(name) && !briefingLoaded) {
6135
+ return jsonResult({
6136
+ error: "haive_briefing_required",
6137
+ message: "This hAIve project requires get_briefing or mem_relevant_to before state-changing hAIve tools. Call get_briefing({ task: '...' }) first.",
6138
+ tool: name
6139
+ });
6140
+ }
6141
+ return await handler(input);
6142
+ }
6143
+ );
6144
+ };
6145
+ const shouldRegisterPrompt = (name) => toolProfile === "full" || name === "bootstrap_project" || name === "post_task";
6146
+ registerTool(
6053
6147
  "mem_save",
6054
6148
  [
6055
6149
  "Save a piece of knowledge as a persistent memory that survives across AI sessions.",
@@ -6081,7 +6175,7 @@ function createHaiveServer(options = {}) {
6081
6175
  return jsonResult(await memSave(input, context));
6082
6176
  }
6083
6177
  );
6084
- server.tool(
6178
+ registerTool(
6085
6179
  "mem_suggest_topic",
6086
6180
  [
6087
6181
  "Propose a stable `topic` key (topic-upsert) from type + short title.",
@@ -6098,7 +6192,7 @@ function createHaiveServer(options = {}) {
6098
6192
  MemSuggestTopicInputSchema,
6099
6193
  async (input) => jsonResult(await memSuggestTopic(input, context))
6100
6194
  );
6101
- server.tool(
6195
+ registerTool(
6102
6196
  "mem_tried",
6103
6197
  [
6104
6198
  "Record a FAILED approach so future agents don't repeat the same mistake.",
@@ -6125,7 +6219,7 @@ function createHaiveServer(options = {}) {
6125
6219
  return jsonResult(await memTried(input, context));
6126
6220
  }
6127
6221
  );
6128
- server.tool(
6222
+ registerTool(
6129
6223
  "mem_observe",
6130
6224
  [
6131
6225
  "Capture a code-level discovery made WHILE READING existing code.",
@@ -6157,7 +6251,7 @@ function createHaiveServer(options = {}) {
6157
6251
  return jsonResult(await memObserve(input, context));
6158
6252
  }
6159
6253
  );
6160
- server.tool(
6254
+ registerTool(
6161
6255
  "mem_session_end",
6162
6256
  [
6163
6257
  "Save an end-of-session recap so the NEXT session starts with fresh context.",
@@ -6186,7 +6280,7 @@ function createHaiveServer(options = {}) {
6186
6280
  return jsonResult(await memSessionEnd(input, context));
6187
6281
  }
6188
6282
  );
6189
- server.tool(
6283
+ registerTool(
6190
6284
  "get_briefing",
6191
6285
  [
6192
6286
  "\u2B50 DEFAULT-FIRST for coding agents on any repo where `haive init` ran: call this BEFORE",
@@ -6237,7 +6331,7 @@ function createHaiveServer(options = {}) {
6237
6331
  return jsonResult(await getBriefing(input, context));
6238
6332
  }
6239
6333
  );
6240
- server.tool(
6334
+ registerTool(
6241
6335
  "mem_search",
6242
6336
  [
6243
6337
  "Search memories by keyword or semantic similarity.",
@@ -6269,7 +6363,7 @@ function createHaiveServer(options = {}) {
6269
6363
  return jsonResult(await memSearch(input, context));
6270
6364
  }
6271
6365
  );
6272
- server.tool(
6366
+ registerTool(
6273
6367
  "mem_timeline",
6274
6368
  [
6275
6369
  "Chronological view of related memories: by shared frontmatter.topic OR expanded from a seed id",
@@ -6285,7 +6379,7 @@ function createHaiveServer(options = {}) {
6285
6379
  MemTimelineInputSchema,
6286
6380
  async (input) => jsonResult(await memTimeline(input, context))
6287
6381
  );
6288
- server.tool(
6382
+ registerTool(
6289
6383
  "mem_for_files",
6290
6384
  [
6291
6385
  "Surface memories relevant to the files you are currently editing.",
@@ -6309,7 +6403,7 @@ function createHaiveServer(options = {}) {
6309
6403
  MemForFilesInputSchema,
6310
6404
  async (input) => jsonResult(await memForFiles(input, context))
6311
6405
  );
6312
- server.tool(
6406
+ registerTool(
6313
6407
  "mem_get",
6314
6408
  [
6315
6409
  "Fetch a single memory by its full id with all details.",
@@ -6325,7 +6419,7 @@ function createHaiveServer(options = {}) {
6325
6419
  MemGetInputSchema,
6326
6420
  async (input) => jsonResult(await memGet(input, context))
6327
6421
  );
6328
- server.tool(
6422
+ registerTool(
6329
6423
  "mem_list",
6330
6424
  [
6331
6425
  "List memories with optional filters. Use for browsing, not for task onboarding.",
@@ -6345,7 +6439,7 @@ function createHaiveServer(options = {}) {
6345
6439
  MemListInputSchema,
6346
6440
  async (input) => jsonResult(await memList(input, context))
6347
6441
  );
6348
- server.tool(
6442
+ registerTool(
6349
6443
  "get_project_context",
6350
6444
  [
6351
6445
  "Read .ai/project-context.md (and optionally a module context) directly.",
@@ -6363,7 +6457,7 @@ function createHaiveServer(options = {}) {
6363
6457
  GetProjectContextInputSchema,
6364
6458
  async (input) => jsonResult(await getProjectContext(input, context))
6365
6459
  );
6366
- server.tool(
6460
+ registerTool(
6367
6461
  "bootstrap_project_save",
6368
6462
  [
6369
6463
  "Persist the project context document (.ai/project-context.md) or a module",
@@ -6381,7 +6475,7 @@ function createHaiveServer(options = {}) {
6381
6475
  BootstrapProjectSaveInputSchema,
6382
6476
  async (input) => jsonResult(await bootstrapProjectSave(input, context))
6383
6477
  );
6384
- server.tool(
6478
+ registerTool(
6385
6479
  "code_map",
6386
6480
  [
6387
6481
  "Look up where symbols (classes, functions, interfaces) are defined in the codebase.",
@@ -6402,7 +6496,7 @@ function createHaiveServer(options = {}) {
6402
6496
  CodeMapInputSchema,
6403
6497
  async (input) => jsonResult(await codeMapTool(input, context))
6404
6498
  );
6405
- server.tool(
6499
+ registerTool(
6406
6500
  "mem_resolve_project",
6407
6501
  [
6408
6502
  "Diagnostics: resolve which project root hAIve is using (never throws).",
@@ -6418,7 +6512,7 @@ function createHaiveServer(options = {}) {
6418
6512
  MemResolveProjectInputSchema,
6419
6513
  async (input) => jsonResult(await memResolveProject(input, context))
6420
6514
  );
6421
- server.tool(
6515
+ registerTool(
6422
6516
  "mem_update",
6423
6517
  [
6424
6518
  "Update the body, tags, or anchor of an existing memory in-place.",
@@ -6441,7 +6535,7 @@ function createHaiveServer(options = {}) {
6441
6535
  MemUpdateInputSchema,
6442
6536
  async (input) => jsonResult(await memUpdate(input, context))
6443
6537
  );
6444
- server.tool(
6538
+ registerTool(
6445
6539
  "mem_verify",
6446
6540
  [
6447
6541
  "Check whether memory anchor paths and symbols still exist in the current code.",
@@ -6460,7 +6554,7 @@ function createHaiveServer(options = {}) {
6460
6554
  MemVerifyInputSchema,
6461
6555
  async (input) => jsonResult(await memVerify(input, context))
6462
6556
  );
6463
- server.tool(
6557
+ registerTool(
6464
6558
  "mem_approve",
6465
6559
  [
6466
6560
  "Mark a memory as validated (trusted, approved by a human or the team).",
@@ -6476,7 +6570,7 @@ function createHaiveServer(options = {}) {
6476
6570
  MemApproveInputSchema,
6477
6571
  async (input) => jsonResult(await memApprove(input, context))
6478
6572
  );
6479
- server.tool(
6573
+ registerTool(
6480
6574
  "mem_reject",
6481
6575
  [
6482
6576
  "Mark a memory as rejected and record a reason.",
@@ -6494,7 +6588,7 @@ function createHaiveServer(options = {}) {
6494
6588
  MemRejectInputSchema,
6495
6589
  async (input) => jsonResult(await memReject(input, context))
6496
6590
  );
6497
- server.tool(
6591
+ registerTool(
6498
6592
  "mem_pending",
6499
6593
  [
6500
6594
  "List memories in 'proposed' status awaiting review, sorted by read count.",
@@ -6510,7 +6604,7 @@ function createHaiveServer(options = {}) {
6510
6604
  MemPendingInputSchema,
6511
6605
  async (input) => jsonResult(await memPending(input, context))
6512
6606
  );
6513
- server.tool(
6607
+ registerTool(
6514
6608
  "mem_delete",
6515
6609
  [
6516
6610
  "Permanently delete a memory by id.",
@@ -6527,7 +6621,7 @@ function createHaiveServer(options = {}) {
6527
6621
  MemDeleteInputSchema,
6528
6622
  async (input) => jsonResult(await memDelete(input, context))
6529
6623
  );
6530
- server.tool(
6624
+ registerTool(
6531
6625
  "get_recap",
6532
6626
  [
6533
6627
  "Return ONLY the most recent session_recap. Cheaper than get_briefing when",
@@ -6545,7 +6639,7 @@ function createHaiveServer(options = {}) {
6545
6639
  return jsonResult(await getRecap(input, context));
6546
6640
  }
6547
6641
  );
6548
- server.tool(
6642
+ registerTool(
6549
6643
  "mem_relevant_to",
6550
6644
  [
6551
6645
  "One-shot ranked memories for a task \u2014 use instead of get_briefing when",
@@ -6571,7 +6665,7 @@ function createHaiveServer(options = {}) {
6571
6665
  return jsonResult(await memRelevantTo(input, context));
6572
6666
  }
6573
6667
  );
6574
- server.tool(
6668
+ registerTool(
6575
6669
  "code_search",
6576
6670
  [
6577
6671
  "Semantic search over the codebase \u2014 finds exported symbols (functions, classes,",
@@ -6594,7 +6688,7 @@ function createHaiveServer(options = {}) {
6594
6688
  return jsonResult(await codeSearch(input, context));
6595
6689
  }
6596
6690
  );
6597
- server.tool(
6691
+ registerTool(
6598
6692
  "why_this_file",
6599
6693
  [
6600
6694
  "One-shot file-context lookup: combines recent git history, memories anchored",
@@ -6614,7 +6708,7 @@ function createHaiveServer(options = {}) {
6614
6708
  return jsonResult(await whyThisFile(input, context));
6615
6709
  }
6616
6710
  );
6617
- server.tool(
6711
+ registerTool(
6618
6712
  "anti_patterns_check",
6619
6713
  [
6620
6714
  "Scan a diff (or set of paths) against documented attempt/gotcha memories.",
@@ -6637,7 +6731,7 @@ function createHaiveServer(options = {}) {
6637
6731
  return jsonResult(await antiPatternsCheck(input, context));
6638
6732
  }
6639
6733
  );
6640
- server.tool(
6734
+ registerTool(
6641
6735
  "mem_distill",
6642
6736
  [
6643
6737
  "Cluster recurring observations / failed attempts so a human can collapse",
@@ -6662,7 +6756,7 @@ function createHaiveServer(options = {}) {
6662
6756
  return jsonResult(await memDistill(input, context));
6663
6757
  }
6664
6758
  );
6665
- server.tool(
6759
+ registerTool(
6666
6760
  "why_this_decision",
6667
6761
  [
6668
6762
  "Trace the genealogy of a memory (especially decision/architecture):",
@@ -6684,7 +6778,7 @@ function createHaiveServer(options = {}) {
6684
6778
  return jsonResult(await whyThisDecision(input, context));
6685
6779
  }
6686
6780
  );
6687
- server.tool(
6781
+ registerTool(
6688
6782
  "mem_conflicts_with",
6689
6783
  [
6690
6784
  "Detect memories that potentially CONTRADICT a given memory.",
@@ -6710,7 +6804,7 @@ function createHaiveServer(options = {}) {
6710
6804
  return jsonResult(await memConflicts(input, context));
6711
6805
  }
6712
6806
  );
6713
- server.tool(
6807
+ registerTool(
6714
6808
  "mem_conflict_candidates",
6715
6809
  [
6716
6810
  "Bulk scan for conflict CANDIDATES (not proof):",
@@ -6731,7 +6825,7 @@ function createHaiveServer(options = {}) {
6731
6825
  return jsonResult(await memConflictCandidates(input, context));
6732
6826
  }
6733
6827
  );
6734
- server.tool(
6828
+ registerTool(
6735
6829
  "runtime_journal_append",
6736
6830
  [
6737
6831
  "Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
@@ -6745,7 +6839,7 @@ function createHaiveServer(options = {}) {
6745
6839
  RuntimeJournalAppendInputSchema,
6746
6840
  async (input) => jsonResult(await runtimeJournalAppend(input, context))
6747
6841
  );
6748
- server.tool(
6842
+ registerTool(
6749
6843
  "runtime_journal_tail",
6750
6844
  [
6751
6845
  "Read the last N entries from the runtime session journal (parsed JSON lines).",
@@ -6757,7 +6851,7 @@ function createHaiveServer(options = {}) {
6757
6851
  RuntimeJournalTailInputSchema,
6758
6852
  async (input) => jsonResult(await runtimeJournalTail(input, context))
6759
6853
  );
6760
- server.tool(
6854
+ registerTool(
6761
6855
  "pre_commit_check",
6762
6856
  [
6763
6857
  "One-shot 'should I block this commit?' check. Combines three signals:",
@@ -6782,7 +6876,7 @@ function createHaiveServer(options = {}) {
6782
6876
  return jsonResult(await preCommitCheck(input, context));
6783
6877
  }
6784
6878
  );
6785
- server.tool(
6879
+ registerTool(
6786
6880
  "pattern_detect",
6787
6881
  [
6788
6882
  "Heuristic memory detector \u2014 finds knowledge worth saving WITHOUT calling an LLM.",
@@ -6813,7 +6907,7 @@ function createHaiveServer(options = {}) {
6813
6907
  return jsonResult(await patternDetect(input, context));
6814
6908
  }
6815
6909
  );
6816
- server.tool(
6910
+ registerTool(
6817
6911
  "mem_diff",
6818
6912
  [
6819
6913
  "Compare two memories side-by-side to decide if they should be merged.",
@@ -6830,44 +6924,50 @@ function createHaiveServer(options = {}) {
6830
6924
  MemDiffInputSchema,
6831
6925
  async (input) => jsonResult(await memDiff(input, context))
6832
6926
  );
6833
- server.prompt(
6834
- "bootstrap_project",
6835
- [
6836
- "Analyze the project codebase and write .ai/project-context.md \u2014 run once after haive init.",
6837
- "The AI explores the directory structure, reads key files (package.json, README, config),",
6838
- "identifies the tech stack, architectural patterns, key modules, and conventions,",
6839
- "then persists everything via bootstrap_project_save.",
6840
- "For multi-component projects, run with module param to create .ai/modules/<name>/context.md."
6841
- ].join(" "),
6842
- BootstrapProjectArgsSchema,
6843
- (args) => bootstrapProjectPrompt(args, context)
6844
- );
6845
- server.prompt(
6846
- "post_task",
6847
- [
6848
- "\u2B50 Post-task reflection \u2014 run at the end of every session to capture what you learned:",
6849
- "failed approaches (mem_tried), new conventions/decisions/gotchas (mem_save),",
6850
- "code discoveries (mem_observe), and an end-of-session recap (mem_session_end).",
6851
- "In autopilot mode a minimal recap saves automatically; calling this produces a richer one."
6852
- ].join(" "),
6853
- PostTaskArgsSchema,
6854
- (args) => postTaskPrompt(args, context)
6855
- );
6856
- server.prompt(
6857
- "import_docs",
6858
- [
6859
- "Import knowledge from a document (README, ADR, wiki, API spec) as hAIve memories.",
6860
- "Pass the full document content; the AI extracts up to 10 actionable memories",
6861
- "(conventions, decisions, gotchas, architecture) and saves them via mem_save.",
6862
- "Good candidates: ADRs, onboarding docs, runbooks, team wikis."
6863
- ].join(" "),
6864
- ImportDocsArgsSchema,
6865
- (args) => importDocsPrompt(args, context)
6866
- );
6927
+ if (shouldRegisterPrompt("bootstrap_project")) {
6928
+ server.prompt(
6929
+ "bootstrap_project",
6930
+ [
6931
+ "Analyze the project codebase and write .ai/project-context.md \u2014 run once after haive init.",
6932
+ "The AI explores the directory structure, reads key files (package.json, README, config),",
6933
+ "identifies the tech stack, architectural patterns, key modules, and conventions,",
6934
+ "then persists everything via bootstrap_project_save.",
6935
+ "For multi-component projects, run with module param to create .ai/modules/<name>/context.md."
6936
+ ].join(" "),
6937
+ BootstrapProjectArgsSchema,
6938
+ (args) => bootstrapProjectPrompt(args, context)
6939
+ );
6940
+ }
6941
+ if (shouldRegisterPrompt("post_task")) {
6942
+ server.prompt(
6943
+ "post_task",
6944
+ [
6945
+ "\u2B50 Post-task reflection \u2014 run at the end of every session to capture what you learned:",
6946
+ "failed approaches (mem_tried), new conventions/decisions/gotchas (mem_save),",
6947
+ "code discoveries (mem_observe), and an end-of-session recap (mem_session_end).",
6948
+ "In autopilot mode a minimal recap saves automatically; calling this produces a richer one."
6949
+ ].join(" "),
6950
+ PostTaskArgsSchema,
6951
+ (args) => postTaskPrompt(args, context)
6952
+ );
6953
+ }
6954
+ if (shouldRegisterPrompt("import_docs")) {
6955
+ server.prompt(
6956
+ "import_docs",
6957
+ [
6958
+ "Import knowledge from a document (README, ADR, wiki, API spec) as hAIve memories.",
6959
+ "Pass the full document content; the AI extracts up to 10 actionable memories",
6960
+ "(conventions, decisions, gotchas, architecture) and saves them via mem_save.",
6961
+ "Good candidates: ADRs, onboarding docs, runbooks, team wikis."
6962
+ ].join(" "),
6963
+ ImportDocsArgsSchema,
6964
+ (args) => importDocsPrompt(args, context)
6965
+ );
6966
+ }
6867
6967
  return { server, context, tracker };
6868
6968
  }
6869
6969
  async function runHaiveMcpStdio(options) {
6870
- const { server, context } = createHaiveServer({ root: options.root });
6970
+ const { server, context } = createHaiveServer({ root: options.root, env: process.env });
6871
6971
  console.error(
6872
6972
  `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
6873
6973
  );
@@ -10195,7 +10295,7 @@ function parseDays(input) {
10195
10295
  // src/commands/doctor.ts
10196
10296
  import { existsSync as existsSync59 } from "fs";
10197
10297
  import { stat } from "fs/promises";
10198
- import "path";
10298
+ import path39 from "path";
10199
10299
  import { execSync as execSync3 } from "child_process";
10200
10300
  import "commander";
10201
10301
  import {
@@ -10356,6 +10456,27 @@ function registerDoctor(program2) {
10356
10456
  }
10357
10457
  }
10358
10458
  const config = await loadConfig7(paths);
10459
+ if (config.enforcement?.requireBriefingFirst) {
10460
+ const claudeSettings = path39.join(root, ".claude", "settings.local.json");
10461
+ let hasClaudeEnforcement = false;
10462
+ if (existsSync59(claudeSettings)) {
10463
+ try {
10464
+ const { readFile: readFile16 } = await import("fs/promises");
10465
+ const raw = await readFile16(claudeSettings, "utf8");
10466
+ hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
10467
+ } catch {
10468
+ hasClaudeEnforcement = false;
10469
+ }
10470
+ }
10471
+ if (!hasClaudeEnforcement) {
10472
+ findings.push({
10473
+ severity: "info",
10474
+ code: "claude-enforcement-hooks-missing",
10475
+ message: "hAIve enforcement is enabled, but project-scoped Claude Code hooks are not installed.",
10476
+ fix: "haive install-hooks claude --scope project"
10477
+ });
10478
+ }
10479
+ }
10359
10480
  if (!config.autoSessionEnd) {
10360
10481
  findings.push({
10361
10482
  severity: "info",
@@ -10370,7 +10491,7 @@ function registerDoctor(program2) {
10370
10491
  timeout: 3e3,
10371
10492
  stdio: ["ignore", "pipe", "ignore"]
10372
10493
  }).trim();
10373
- const cliVersion = "0.9.7";
10494
+ const cliVersion = "0.9.8";
10374
10495
  if (legacyRaw && legacyRaw !== cliVersion) {
10375
10496
  findings.push({
10376
10497
  severity: "warn",
@@ -10972,13 +11093,164 @@ function registerMemoryConflictCandidates(memory2) {
10972
11093
  });
10973
11094
  }
10974
11095
 
11096
+ // src/commands/enforce.ts
11097
+ import { existsSync as existsSync67 } from "fs";
11098
+ import { mkdir as mkdir17 } from "fs/promises";
11099
+ import "commander";
11100
+ import {
11101
+ findProjectRoot as findProjectRoot46,
11102
+ hasRecentBriefingMarker,
11103
+ resolveBriefingBudget as resolveBriefingBudget3,
11104
+ resolveHaivePaths as resolveHaivePaths43,
11105
+ writeBriefingMarker
11106
+ } from "@hiveai/core";
11107
+ var MAX_STDIN_BYTES2 = 256 * 1024;
11108
+ function registerEnforce(program2) {
11109
+ const enforce = program2.command("enforce").description("Agent enforcement helpers used by hAIve-installed hooks.");
11110
+ 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
+ const payload = await readHookPayload();
11112
+ const root = resolveRoot(opts.dir, payload);
11113
+ if (!root) return;
11114
+ const paths = resolveHaivePaths43(root);
11115
+ if (!existsSync67(paths.haiveDir)) return;
11116
+ await mkdir17(paths.runtimeDir, { recursive: true });
11117
+ const sessionId = opts.sessionId ?? payload.session_id;
11118
+ const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
11119
+ await writeBriefingMarker(paths, {
11120
+ sessionId,
11121
+ task,
11122
+ source: opts.source ?? "claude-session-start"
11123
+ });
11124
+ const budget = resolveBriefingBudget3("quick", {
11125
+ max_tokens: 2500,
11126
+ max_memories: 5,
11127
+ include_module_contexts: false
11128
+ });
11129
+ const briefing = await getBriefing(
11130
+ {
11131
+ task,
11132
+ files: [],
11133
+ max_tokens: budget.max_tokens,
11134
+ max_memories: budget.max_memories,
11135
+ include_project_context: true,
11136
+ include_module_contexts: budget.include_module_contexts,
11137
+ semantic: true,
11138
+ include_stale: false,
11139
+ track: true,
11140
+ format: "actions",
11141
+ symbols: [],
11142
+ min_semantic_score: 0.25,
11143
+ budget_preset: "quick"
11144
+ },
11145
+ { paths }
11146
+ );
11147
+ console.log("hAIve briefing loaded. Agents must consult this before editing.");
11148
+ if (briefing.last_session) {
11149
+ console.log(`
11150
+ ## Last session
11151
+ ${briefing.last_session.body.slice(0, 1200)}`);
11152
+ }
11153
+ if (briefing.project_context?.content) {
11154
+ console.log(`
11155
+ ## Project context
11156
+ ${briefing.project_context.content.slice(0, 1800)}`);
11157
+ }
11158
+ if (briefing.memories.length > 0) {
11159
+ console.log("\n## Relevant memories");
11160
+ for (const memory2 of briefing.memories.slice(0, 6)) {
11161
+ console.log(`
11162
+ ### ${memory2.id} (${memory2.scope}/${memory2.type}, ${memory2.confidence})`);
11163
+ console.log(memory2.body.slice(0, 1e3));
11164
+ }
11165
+ }
11166
+ for (const warning of briefing.setup_warnings) {
11167
+ console.log(`
11168
+ [setup warning] ${warning}`);
11169
+ }
11170
+ });
11171
+ enforce.command("pre-tool-use").description("Claude Code PreToolUse hook: block writes until hAIve briefing has been loaded.").option("-d, --dir <dir>", "project root").action(async (opts) => {
11172
+ const payload = await readHookPayload();
11173
+ const root = resolveRoot(opts.dir, payload);
11174
+ if (!root) return;
11175
+ const paths = resolveHaivePaths43(root);
11176
+ if (!existsSync67(paths.haiveDir)) return;
11177
+ if (!isWriteLikeTool(payload)) return;
11178
+ const ok = await hasRecentBriefingMarker(paths, payload.session_id);
11179
+ if (ok) return;
11180
+ const tool = payload.tool_name ?? "write tool";
11181
+ console.error(
11182
+ [
11183
+ "hAIve enforcement blocked this action.",
11184
+ `Tool: ${tool}`,
11185
+ "",
11186
+ "This project is initialized with hAIve. Load the team briefing before editing:",
11187
+ " haive enforce session-start",
11188
+ "or call MCP get_briefing / mem_relevant_to from your AI client.",
11189
+ "",
11190
+ "If this is intentional, a human can disable enforcement in .ai/haive.config.json:",
11191
+ ' { "enforcement": { "requireBriefingFirst": false } }'
11192
+ ].join("\n")
11193
+ );
11194
+ process.exit(2);
11195
+ });
11196
+ }
11197
+ async function readHookPayload() {
11198
+ const raw = await readStdin2(MAX_STDIN_BYTES2);
11199
+ if (!raw.trim()) return {};
11200
+ try {
11201
+ return JSON.parse(raw);
11202
+ } catch {
11203
+ return {};
11204
+ }
11205
+ }
11206
+ function resolveRoot(dir, payload) {
11207
+ try {
11208
+ return findProjectRoot46(dir ?? payload.cwd);
11209
+ } catch {
11210
+ return null;
11211
+ }
11212
+ }
11213
+ function isWriteLikeTool(payload) {
11214
+ const tool = payload.tool_name ?? "";
11215
+ if (["Edit", "Write", "MultiEdit", "NotebookEdit"].includes(tool)) return true;
11216
+ if (tool !== "Bash") return false;
11217
+ const command = String(payload.tool_input?.["command"] ?? "");
11218
+ return /\b(rm|mv|cp|mkdir|touch|tee|sed|perl|python|node|npm|pnpm|yarn|git)\b/.test(command) || />{1,2}/.test(command);
11219
+ }
11220
+ async function readStdin2(maxBytes) {
11221
+ if (process.stdin.isTTY) return "";
11222
+ return await new Promise((resolve) => {
11223
+ const chunks = [];
11224
+ let total = 0;
11225
+ let done = false;
11226
+ const finish = () => {
11227
+ if (done) return;
11228
+ done = true;
11229
+ resolve(Buffer.concat(chunks).toString("utf8"));
11230
+ };
11231
+ process.stdin.on("data", (c) => {
11232
+ total += c.length;
11233
+ if (total > maxBytes) {
11234
+ process.stdin.destroy();
11235
+ finish();
11236
+ return;
11237
+ }
11238
+ chunks.push(c);
11239
+ });
11240
+ process.stdin.on("end", finish);
11241
+ process.stdin.on("error", finish);
11242
+ setTimeout(finish, 2e3);
11243
+ });
11244
+ }
11245
+
10975
11246
  // src/index.ts
10976
- var program = new Command47();
10977
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.7");
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");
10978
11249
  registerInit(program);
10979
11250
  registerWelcome(program);
10980
11251
  registerResolveProject(program);
10981
11252
  registerRuntime(program);
11253
+ registerEnforce(program);
10982
11254
  registerMcp(program);
10983
11255
  registerBriefing(program);
10984
11256
  registerTui(program);