@hiveai/cli 0.9.14 → 0.9.16

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
@@ -3063,6 +3063,7 @@ import {
3063
3063
  loadMemoriesFromDir as loadMemoriesFromDir13,
3064
3064
  loadUsageIndex as loadUsageIndex7,
3065
3065
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
3066
+ pathsOverlap,
3066
3067
  queryCodeMap as queryCodeMap2,
3067
3068
  resolveBriefingBudget as resolveBriefingBudget2,
3068
3069
  serializeMemory as serializeMemory9,
@@ -3126,7 +3127,7 @@ import {
3126
3127
  getUsage as getUsage9,
3127
3128
  loadMemoriesFromDir as loadMemoriesFromDir20,
3128
3129
  loadUsageIndex as loadUsageIndex11,
3129
- pathsOverlap,
3130
+ pathsOverlap as pathsOverlap2,
3130
3131
  tokenizeQuery as tokenizeQuery5
3131
3132
  } from "@hiveai/core";
3132
3133
  import { z as z27 } from "zod";
@@ -4654,10 +4655,14 @@ ${m.content}`).join("\n\n---\n\n"),
4654
4655
  const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
4655
4656
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
4656
4657
  }
4657
- const outputMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({
4658
+ const formattedMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({
4658
4659
  ...m,
4659
4660
  body: extractActionsBriefBody2(m.body)
4660
4661
  })) : trimmedMemories;
4662
+ const outputMemories = formattedMemories.map((m) => ({
4663
+ ...m,
4664
+ why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
4665
+ }));
4661
4666
  let symbolLocations;
4662
4667
  const symbolsToLookup = new Set(input.symbols);
4663
4668
  for (const m of outputMemories) {
@@ -4823,6 +4828,44 @@ function compactSummary(body) {
4823
4828
  }
4824
4829
  return body.slice(0, 120);
4825
4830
  }
4831
+ function explainWhySurfaced(memory2, loaded, inputFiles, inferredModules) {
4832
+ const why = [];
4833
+ const fm = loaded?.memory.frontmatter;
4834
+ if (memory2.reasons.includes("anchor") && fm) {
4835
+ const matching = fm.anchor.paths.filter(
4836
+ (p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
4837
+ );
4838
+ if (matching.length > 0) {
4839
+ why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
4840
+ } else if (fm.anchor.paths.length > 0) {
4841
+ why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
4842
+ }
4843
+ if (fm.anchor.symbols.length > 0) {
4844
+ why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
4845
+ }
4846
+ }
4847
+ if (memory2.reasons.includes("module")) {
4848
+ const moduleHints = [
4849
+ ...memory2.module ? [memory2.module] : [],
4850
+ ...memory2.tags.filter((tag) => inferredModules.includes(tag))
4851
+ ];
4852
+ const shown = moduleHints.length > 0 ? [...new Set(moduleHints)].join(", ") : inferredModules.join(", ");
4853
+ why.push(shown ? `Matched inferred module/tag: ${shown}` : "Matched inferred module context.");
4854
+ }
4855
+ if (memory2.reasons.includes("domain")) {
4856
+ why.push("Matched inferred domain from the target file paths.");
4857
+ }
4858
+ if (memory2.reasons.includes("semantic")) {
4859
+ const score = memory2.semantic_score !== void 0 ? ` score=${Math.round(memory2.semantic_score * 100) / 100}` : "";
4860
+ why.push(`${memory2.match_quality === "exact" ? "Literal task match" : "Semantic/task relevance"}${score}.`);
4861
+ }
4862
+ why.push(`Confidence: ${memory2.confidence}; read ${memory2.read_count} time${memory2.read_count === 1 ? "" : "s"}.`);
4863
+ if (memory2.type === "attempt") why.push("Failed-approach record; read before repeating the same path.");
4864
+ if (memory2.status === "proposed" || memory2.status === "draft") {
4865
+ why.push("Unvalidated record; use cautiously or ask a human before treating it as policy.");
4866
+ }
4867
+ return why;
4868
+ }
4826
4869
  async function trySemanticHits(ctx, task, limit) {
4827
4870
  let mod;
4828
4871
  try {
@@ -5585,7 +5628,7 @@ async function memConflicts(input, ctx) {
5585
5628
  const otherText = (other.memory.body + " " + fm.tags.join(" ")).toLowerCase();
5586
5629
  const reasons = [];
5587
5630
  const sim = simScores?.get(fm.id) ?? null;
5588
- const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap(p, tp)));
5631
+ const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)));
5589
5632
  const otherTokens = new Set(tokenizeQuery5(otherText));
5590
5633
  const tokenOverlap = countIntersection(targetTokens, otherTokens);
5591
5634
  const isSemanticNeighbor = sim !== null && sim >= input.min_score;
@@ -5620,7 +5663,7 @@ async function memConflicts(input, ctx) {
5620
5663
  body_preview: other.memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
5621
5664
  similarity: sim,
5622
5665
  reasons,
5623
- shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap(p, tp)))
5666
+ shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)))
5624
5667
  });
5625
5668
  }
5626
5669
  conflicts.sort((a, b) => {
@@ -5707,10 +5750,13 @@ async function preCommitCheck(input, ctx) {
5707
5750
  const filesTouching = new Set(relevantMatches.map((m) => m.id));
5708
5751
  const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
5709
5752
  const blockOn = input.block_on;
5710
- const blockingWarnings = apResult.warnings.filter(isBlockingWarning);
5753
+ const classifiedWarnings = apResult.warnings.map(classifyWarning);
5754
+ const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
5755
+ const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
5756
+ const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
5711
5757
  let should_block = false;
5712
5758
  if (blockOn !== "never") {
5713
- if (blockOn === "any" && (apResult.warnings.length > 0 || staleHits.length > 0)) should_block = true;
5759
+ if (blockOn === "any" && (blockingWarnings.length > 0 || reviewWarnings.length > 0 || staleHits.length > 0)) should_block = true;
5714
5760
  if (blockOn === "high-confidence" && (blockingWarnings.length > 0 || staleHits.length > 0)) should_block = true;
5715
5761
  }
5716
5762
  const relevant_memories = relevantMatches.slice(0, 8).map((m) => ({
@@ -5724,10 +5770,12 @@ async function preCommitCheck(input, ctx) {
5724
5770
  summary: {
5725
5771
  anti_patterns: apResult.warnings.length,
5726
5772
  blocking_warnings: blockingWarnings.length,
5773
+ review_warnings: reviewWarnings.length,
5774
+ info_warnings: infoWarnings.length,
5727
5775
  relevant_memories: relevant_memories.length,
5728
5776
  stale_anchors: staleHits.length
5729
5777
  },
5730
- warnings: apResult.warnings,
5778
+ warnings: classifiedWarnings,
5731
5779
  relevant_memories,
5732
5780
  stale_anchors: staleHits.map((r) => ({
5733
5781
  id: r.id,
@@ -5737,6 +5785,30 @@ async function preCommitCheck(input, ctx) {
5737
5785
  }))
5738
5786
  };
5739
5787
  }
5788
+ function classifyWarning(warning) {
5789
+ if (isBlockingWarning(warning)) {
5790
+ return {
5791
+ ...warning,
5792
+ level: "blocking",
5793
+ rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)"
5794
+ };
5795
+ }
5796
+ const hasSemantic = warning.reasons.includes("semantic");
5797
+ const semanticScore = warning.semantic_score ?? 0;
5798
+ const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
5799
+ if (hasSemantic && semanticScore >= 0.45 || highConfidence && warning.reasons.includes("anchor") && warning.reasons.includes("literal")) {
5800
+ return {
5801
+ ...warning,
5802
+ level: "review",
5803
+ rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof"
5804
+ };
5805
+ }
5806
+ return {
5807
+ ...warning,
5808
+ level: "info",
5809
+ rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output"
5810
+ };
5811
+ }
5740
5812
  function isBlockingWarning(warning) {
5741
5813
  const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
5742
5814
  if (!highConfidence) return false;
@@ -6273,7 +6345,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6273
6345
  };
6274
6346
  }
6275
6347
  var SERVER_NAME = "haive";
6276
- var SERVER_VERSION = "0.9.14";
6348
+ var SERVER_VERSION = "0.9.16";
6277
6349
  function jsonResult(data) {
6278
6350
  return {
6279
6351
  content: [
@@ -6284,15 +6356,58 @@ function jsonResult(data) {
6284
6356
  ]
6285
6357
  };
6286
6358
  }
6287
- var ENFORCEMENT_PROFILE_TOOLS = /* @__PURE__ */ new Set([
6359
+ var ENFORCEMENT_PROFILE_TOOLS = [
6288
6360
  "get_briefing",
6289
6361
  "mem_save",
6362
+ "mem_tried",
6290
6363
  "mem_search",
6364
+ "mem_get",
6291
6365
  "mem_verify",
6292
6366
  "mem_relevant_to",
6367
+ "code_map",
6293
6368
  "pre_commit_check",
6294
6369
  "mem_session_end"
6295
- ]);
6370
+ ];
6371
+ var MAINTENANCE_PROFILE_TOOLS = [
6372
+ ...ENFORCEMENT_PROFILE_TOOLS,
6373
+ "mem_suggest_topic",
6374
+ "mem_for_files",
6375
+ "mem_list",
6376
+ "get_project_context",
6377
+ "bootstrap_project_save",
6378
+ "mem_resolve_project",
6379
+ "mem_update",
6380
+ "mem_approve",
6381
+ "mem_reject",
6382
+ "mem_pending",
6383
+ "mem_delete",
6384
+ "mem_diff",
6385
+ "get_recap",
6386
+ "code_search",
6387
+ "anti_patterns_check",
6388
+ "mem_distill",
6389
+ "mem_timeline",
6390
+ "mem_conflict_candidates"
6391
+ ];
6392
+ var EXPERIMENTAL_PROFILE_TOOLS = [
6393
+ ...MAINTENANCE_PROFILE_TOOLS,
6394
+ "mem_observe",
6395
+ "why_this_file",
6396
+ "why_this_decision",
6397
+ "mem_conflicts_with",
6398
+ "pattern_detect",
6399
+ "runtime_journal_append",
6400
+ "runtime_journal_tail"
6401
+ ];
6402
+ var TOOL_PROFILES = {
6403
+ enforcement: new Set(ENFORCEMENT_PROFILE_TOOLS),
6404
+ maintenance: new Set(MAINTENANCE_PROFILE_TOOLS),
6405
+ experimental: new Set(EXPERIMENTAL_PROFILE_TOOLS)
6406
+ };
6407
+ function getAllowedToolsForProfile(profile) {
6408
+ if (profile === "full") return TOOL_PROFILES.experimental;
6409
+ return TOOL_PROFILES[profile] ?? TOOL_PROFILES.enforcement;
6410
+ }
6296
6411
  var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]);
6297
6412
  var MUTATING_TOOLS = /* @__PURE__ */ new Set([
6298
6413
  "mem_save",
@@ -6319,7 +6434,8 @@ function createHaiveServer(options = {}) {
6319
6434
  { name: SERVER_NAME, version: SERVER_VERSION },
6320
6435
  { capabilities: { tools: {}, prompts: {} } }
6321
6436
  );
6322
- const shouldRegisterTool = (name) => toolProfile === "full" || ENFORCEMENT_PROFILE_TOOLS.has(name);
6437
+ const allowedTools = getAllowedToolsForProfile(toolProfile);
6438
+ const shouldRegisterTool = (name) => allowedTools.has(name);
6323
6439
  const registerTool = (name, description, schema, handler) => {
6324
6440
  if (!shouldRegisterTool(name)) return;
6325
6441
  const tool = server.tool.bind(server);
@@ -6343,7 +6459,11 @@ function createHaiveServer(options = {}) {
6343
6459
  }
6344
6460
  );
6345
6461
  };
6346
- const shouldRegisterPrompt = (name) => toolProfile === "full" || name === "bootstrap_project" || name === "post_task";
6462
+ const shouldRegisterPrompt = (name) => {
6463
+ if (name === "bootstrap_project" || name === "post_task") return true;
6464
+ if (name === "import_docs") return toolProfile !== "enforcement";
6465
+ return toolProfile === "experimental" || toolProfile === "full";
6466
+ };
6347
6467
  registerTool(
6348
6468
  "mem_save",
6349
6469
  [
@@ -10641,9 +10761,9 @@ function parseDays(input) {
10641
10761
 
10642
10762
  // src/commands/doctor.ts
10643
10763
  import { existsSync as existsSync60 } from "fs";
10644
- import { stat } from "fs/promises";
10764
+ import { readFile as readFile17, stat } from "fs/promises";
10645
10765
  import path41 from "path";
10646
- import { execSync as execSync3 } from "child_process";
10766
+ import { execFileSync, execSync as execSync3 } from "child_process";
10647
10767
  import "commander";
10648
10768
  import {
10649
10769
  codeMapPath as codeMapPath2,
@@ -10681,8 +10801,8 @@ function registerDoctor(program2) {
10681
10801
  fix: "haive init"
10682
10802
  });
10683
10803
  } else {
10684
- const { readFile: readFile18 } = await import("fs/promises");
10685
- const content = await readFile18(paths.projectContext, "utf8");
10804
+ const { readFile: readFile19 } = await import("fs/promises");
10805
+ const content = await readFile19(paths.projectContext, "utf8");
10686
10806
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10687
10807
  if (isTemplate) {
10688
10808
  findings.push({
@@ -10808,8 +10928,8 @@ function registerDoctor(program2) {
10808
10928
  let hasClaudeEnforcement = false;
10809
10929
  if (existsSync60(claudeSettings)) {
10810
10930
  try {
10811
- const { readFile: readFile18 } = await import("fs/promises");
10812
- const raw = await readFile18(claudeSettings, "utf8");
10931
+ const { readFile: readFile19 } = await import("fs/promises");
10932
+ const raw = await readFile19(claudeSettings, "utf8");
10813
10933
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
10814
10934
  } catch {
10815
10935
  hasClaudeEnforcement = false;
@@ -10832,13 +10952,14 @@ function registerDoctor(program2) {
10832
10952
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
10833
10953
  });
10834
10954
  }
10955
+ findings.push(...await collectInstallFindings(root, "0.9.16"));
10835
10956
  try {
10836
10957
  const legacyRaw = execSync3("haive-mcp --version", {
10837
10958
  encoding: "utf8",
10838
10959
  timeout: 3e3,
10839
10960
  stdio: ["ignore", "pipe", "ignore"]
10840
10961
  }).trim();
10841
- const cliVersion = "0.9.14";
10962
+ const cliVersion = "0.9.16";
10842
10963
  if (legacyRaw && legacyRaw !== cliVersion) {
10843
10964
  findings.push({
10844
10965
  severity: "warn",
@@ -10885,6 +11006,103 @@ function emit(findings, opts) {
10885
11006
  function isSearchTool(name) {
10886
11007
  return ["mem_search", "code_search", "mem_relevant_to", "get_briefing"].includes(name);
10887
11008
  }
11009
+ async function collectInstallFindings(root, expectedVersion) {
11010
+ const findings = [];
11011
+ const haiveBins = listHaiveBins();
11012
+ if (haiveBins.length === 0) {
11013
+ findings.push({
11014
+ severity: "warn",
11015
+ code: "haive-not-on-path",
11016
+ message: "No `haive` binary was found on PATH. Hooks and MCP configs that call `haive` may fail.",
11017
+ fix: `npm install -g @hiveai/cli@${expectedVersion}`
11018
+ });
11019
+ } else {
11020
+ const first = haiveBins[0];
11021
+ const firstVersion = versionForBinary(first);
11022
+ if (firstVersion && firstVersion !== expectedVersion) {
11023
+ findings.push({
11024
+ severity: "warn",
11025
+ code: "path-haive-version-mismatch",
11026
+ message: `PATH resolves haive to ${first} (${firstVersion}), but this build expects ${expectedVersion}.`,
11027
+ fix: `npm install -g @hiveai/cli@${expectedVersion}
11028
+ which -a haive`
11029
+ });
11030
+ }
11031
+ const skewed = haiveBins.map((bin) => ({ bin, version: versionForBinary(bin) })).filter((item) => item.version && item.version !== expectedVersion);
11032
+ if (skewed.length > 0) {
11033
+ findings.push({
11034
+ severity: "info",
11035
+ code: "multiple-haive-binaries",
11036
+ message: `Found ${haiveBins.length} haive binar${haiveBins.length === 1 ? "y" : "ies"} on PATH; ${skewed.length} do not match ${expectedVersion}.`,
11037
+ fix: "Remove stale global installs or ensure hooks call the intended `haive` binary."
11038
+ });
11039
+ }
11040
+ }
11041
+ const integrationFiles = [
11042
+ ".git/hooks/pre-commit",
11043
+ ".git/hooks/pre-push",
11044
+ ".claude/settings.local.json",
11045
+ ".mcp.json",
11046
+ ".cursor/mcp.json",
11047
+ ".vscode/mcp.json"
11048
+ ];
11049
+ for (const rel of integrationFiles) {
11050
+ const file = path41.join(root, rel);
11051
+ if (!existsSync60(file)) continue;
11052
+ const text = await readFile17(file, "utf8").catch(() => "");
11053
+ for (const bin of extractAbsoluteHaiveBins(text)) {
11054
+ const version = versionForBinary(bin);
11055
+ if (!version) {
11056
+ findings.push({
11057
+ severity: "warn",
11058
+ code: "integration-haive-binary-missing",
11059
+ message: `${rel} references ${bin}, but it could not be executed.`,
11060
+ fix: "Run `haive agent setup --no-global` or `haive enforce install` to rewrite project integrations."
11061
+ });
11062
+ } else if (version !== expectedVersion) {
11063
+ findings.push({
11064
+ severity: "warn",
11065
+ code: "integration-haive-version-mismatch",
11066
+ message: `${rel} references ${bin} (${version}), but current hAIve is ${expectedVersion}.`,
11067
+ fix: "Run `haive agent setup --no-global` and `haive enforce install` to refresh stale paths."
11068
+ });
11069
+ }
11070
+ }
11071
+ }
11072
+ return findings;
11073
+ }
11074
+ function listHaiveBins() {
11075
+ try {
11076
+ return execSync3("which -a haive", {
11077
+ encoding: "utf8",
11078
+ timeout: 3e3,
11079
+ stdio: ["ignore", "pipe", "ignore"]
11080
+ }).split("\n").map((line) => line.trim()).filter(Boolean);
11081
+ } catch {
11082
+ return [];
11083
+ }
11084
+ }
11085
+ function versionForBinary(bin) {
11086
+ try {
11087
+ const out = execFileSync(bin, ["--version"], {
11088
+ encoding: "utf8",
11089
+ timeout: 3e3,
11090
+ stdio: ["ignore", "pipe", "ignore"]
11091
+ }).trim();
11092
+ return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
11093
+ } catch {
11094
+ return null;
11095
+ }
11096
+ }
11097
+ function extractAbsoluteHaiveBins(text) {
11098
+ const out = /* @__PURE__ */ new Set();
11099
+ const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
11100
+ let match;
11101
+ while (match = re.exec(text)) {
11102
+ if (match[2]) out.add(match[2]);
11103
+ }
11104
+ return [...out].sort();
11105
+ }
10888
11106
 
10889
11107
  // src/commands/playback.ts
10890
11108
  import { existsSync as existsSync61 } from "fs";
@@ -11050,19 +11268,21 @@ function registerPrecommit(program2) {
11050
11268
  console.log(ui.bold(`hAIve precommit \u2014 ${touchedPaths.length} file(s)`));
11051
11269
  console.log(
11052
11270
  ui.dim(
11053
- ` anti-patterns: ${result.summary.anti_patterns} blocking: ${result.summary.blocking_warnings ?? result.summary.anti_patterns} relevant memories: ${result.summary.relevant_memories} stale anchors: ${result.summary.stale_anchors}`
11271
+ ` anti-patterns: ${result.summary.anti_patterns} blocking: ${result.summary.blocking_warnings ?? result.summary.anti_patterns} review: ${result.summary.review_warnings ?? 0} info: ${result.summary.info_warnings ?? 0} relevant memories: ${result.summary.relevant_memories} stale anchors: ${result.summary.stale_anchors}`
11054
11272
  )
11055
11273
  );
11056
11274
  console.log();
11057
- if (result.warnings.length > 0) {
11058
- console.log(ui.bold("\u26A0 Anti-patterns matched:"));
11059
- for (const w of result.warnings.slice(0, 10)) {
11060
- console.log(` ${ui.yellow("\u26A0")} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
11061
- for (const line of w.body_preview.split("\n").slice(0, 3)) {
11062
- console.log(` ${ui.dim(line)}`);
11063
- }
11064
- console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
11065
- }
11275
+ const blocking = result.warnings.filter((w) => w.level === "blocking");
11276
+ const review = result.warnings.filter((w) => w.level === "review");
11277
+ const info = result.warnings.filter((w) => w.level === "info");
11278
+ printWarnings("Blocking anti-patterns", blocking, "error");
11279
+ printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
11280
+ if (info.length > 0) {
11281
+ console.log(
11282
+ ui.dim(
11283
+ `${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
11284
+ )
11285
+ );
11066
11286
  console.log();
11067
11287
  }
11068
11288
  if (result.relevant_memories.length > 0) {
@@ -11091,6 +11311,20 @@ function registerPrecommit(program2) {
11091
11311
  }
11092
11312
  });
11093
11313
  }
11314
+ function printWarnings(title, warnings, tone) {
11315
+ if (warnings.length === 0) return;
11316
+ console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
11317
+ for (const w of warnings) {
11318
+ const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
11319
+ console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
11320
+ for (const line of w.body_preview.split("\n").slice(0, 3)) {
11321
+ console.log(` ${ui.dim(line)}`);
11322
+ }
11323
+ console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
11324
+ if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
11325
+ }
11326
+ console.log();
11327
+ }
11094
11328
  function runCommand3(cmd, args, cwd) {
11095
11329
  return new Promise((resolve, reject) => {
11096
11330
  const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -11177,7 +11411,9 @@ import { existsSync as existsSync64 } from "fs";
11177
11411
  import "commander";
11178
11412
  import {
11179
11413
  findProjectRoot as findProjectRoot44,
11414
+ getUsage as getUsage20,
11180
11415
  loadMemoriesFromDir as loadMemoriesFromDir35,
11416
+ loadUsageIndex as loadUsageIndex26,
11181
11417
  resolveHaivePaths as resolveHaivePaths40
11182
11418
  } from "@hiveai/core";
11183
11419
  async function lintMemoriesAsync(root) {
@@ -11185,7 +11421,9 @@ async function lintMemoriesAsync(root) {
11185
11421
  const out = [];
11186
11422
  if (!existsSync64(paths.memoriesDir)) return out;
11187
11423
  const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
11424
+ const usage = await loadUsageIndex26(paths);
11188
11425
  const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
11426
+ const actionableWords = /\b(always|never|prefer|use|avoid|because|instead|why|rationale|do not|must|should)\b/i;
11189
11427
  for (const { filePath, memory: memory2 } of loaded) {
11190
11428
  const fm = memory2.frontmatter;
11191
11429
  if (fm.type === "session_recap") continue;
@@ -11200,6 +11438,15 @@ async function lintMemoriesAsync(root) {
11200
11438
  message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
11201
11439
  });
11202
11440
  }
11441
+ if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
11442
+ out.push({
11443
+ file: filePath,
11444
+ id: fm.id,
11445
+ severity: "info",
11446
+ code: "LOW_ACTIONABILITY",
11447
+ message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
11448
+ });
11449
+ }
11203
11450
  if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
11204
11451
  out.push({
11205
11452
  file: filePath,
@@ -11237,9 +11484,64 @@ async function lintMemoriesAsync(root) {
11237
11484
  message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
11238
11485
  });
11239
11486
  }
11487
+ const u = getUsage20(usage, fm.id);
11488
+ if (fm.status === "validated" && u.read_count === 0) {
11489
+ out.push({
11490
+ file: filePath,
11491
+ id: fm.id,
11492
+ severity: "info",
11493
+ code: "NEVER_READ",
11494
+ message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
11495
+ });
11496
+ }
11497
+ }
11498
+ for (const dup of nearDuplicatePairs(loaded)) {
11499
+ out.push({
11500
+ file: dup.file,
11501
+ id: dup.id,
11502
+ severity: "warn",
11503
+ code: "NEAR_DUPLICATE",
11504
+ message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
11505
+ });
11506
+ }
11507
+ return out;
11508
+ }
11509
+ function nearDuplicatePairs(loaded) {
11510
+ const out = [];
11511
+ const candidates = loaded.filter(({ memory: memory2 }) => {
11512
+ const fm = memory2.frontmatter;
11513
+ return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
11514
+ });
11515
+ for (let i = 0; i < candidates.length; i++) {
11516
+ for (let j = i + 1; j < candidates.length; j++) {
11517
+ const a = candidates[i];
11518
+ const b = candidates[j];
11519
+ if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
11520
+ if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
11521
+ const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
11522
+ if (score >= 0.72) {
11523
+ out.push({
11524
+ id: a.memory.frontmatter.id,
11525
+ otherId: b.memory.frontmatter.id,
11526
+ file: a.filePath,
11527
+ score
11528
+ });
11529
+ }
11530
+ }
11240
11531
  }
11241
11532
  return out;
11242
11533
  }
11534
+ function tokenSet(body) {
11535
+ return new Set(
11536
+ (body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
11537
+ );
11538
+ }
11539
+ function jaccard2(a, b) {
11540
+ if (a.size === 0 || b.size === 0) return 0;
11541
+ let inter = 0;
11542
+ for (const item of a) if (b.has(item)) inter++;
11543
+ return inter / (a.size + b.size - inter);
11544
+ }
11243
11545
  function registerMemoryLint(parent) {
11244
11546
  parent.command("lint").description(
11245
11547
  "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
@@ -11441,9 +11743,9 @@ function registerMemoryConflictCandidates(memory2) {
11441
11743
  }
11442
11744
 
11443
11745
  // src/commands/enforce.ts
11444
- import { spawn as spawn5 } from "child_process";
11746
+ import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
11445
11747
  import { existsSync as existsSync68 } from "fs";
11446
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile17, rm as rm3, writeFile as writeFile31 } from "fs/promises";
11748
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile18, rm as rm3, writeFile as writeFile31 } from "fs/promises";
11447
11749
  import path47 from "path";
11448
11750
  import "commander";
11449
11751
  import {
@@ -11749,6 +12051,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
11749
12051
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
11750
12052
  };
11751
12053
  }
12054
+ findings.push(...await inspectIntegrationVersions(root, "0.9.16"));
11752
12055
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
11753
12056
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
11754
12057
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -11937,6 +12240,71 @@ async function findGeneratedArtifacts(paths) {
11937
12240
  impact: 10
11938
12241
  }];
11939
12242
  }
12243
+ async function inspectIntegrationVersions(root, expectedVersion) {
12244
+ const files = [
12245
+ ".git/hooks/pre-commit",
12246
+ ".git/hooks/pre-push",
12247
+ ".claude/settings.local.json",
12248
+ ".mcp.json",
12249
+ ".cursor/mcp.json",
12250
+ ".vscode/mcp.json"
12251
+ ];
12252
+ const findings = [];
12253
+ for (const rel of files) {
12254
+ const file = path47.join(root, rel);
12255
+ if (!existsSync68(file)) continue;
12256
+ const text = await readFile18(file, "utf8").catch(() => "");
12257
+ for (const bin of extractAbsoluteHaiveBins2(text)) {
12258
+ const version = versionForBinary2(bin);
12259
+ if (!version) {
12260
+ findings.push({
12261
+ severity: "warn",
12262
+ code: "integration-haive-binary-missing",
12263
+ message: `${rel} references ${bin}, but that binary could not be executed.`,
12264
+ fix: "Run `haive agent setup --no-global` or `haive enforce install` to refresh project integrations.",
12265
+ impact: 0
12266
+ });
12267
+ } else if (version !== expectedVersion) {
12268
+ findings.push({
12269
+ severity: "warn",
12270
+ code: "integration-haive-version-mismatch",
12271
+ message: `${rel} references hAIve ${version} at ${bin}; current hAIve is ${expectedVersion}.`,
12272
+ fix: "Run `haive agent setup --no-global` and `haive enforce install` to repair stale hooks/configs.",
12273
+ impact: 0
12274
+ });
12275
+ }
12276
+ }
12277
+ }
12278
+ if (findings.length === 0) {
12279
+ return [{
12280
+ severity: "ok",
12281
+ code: "integration-version-check",
12282
+ message: "No stale absolute hAIve binary paths were found in project hooks/MCP configs."
12283
+ }];
12284
+ }
12285
+ return findings;
12286
+ }
12287
+ function extractAbsoluteHaiveBins2(text) {
12288
+ const out = /* @__PURE__ */ new Set();
12289
+ const re = /(["'\s])((?:\/[^"'\s]+)*\/haive)\b/g;
12290
+ let match;
12291
+ while (match = re.exec(text)) {
12292
+ if (match[2]) out.add(match[2]);
12293
+ }
12294
+ return [...out].sort();
12295
+ }
12296
+ function versionForBinary2(bin) {
12297
+ try {
12298
+ const out = execFileSync2(bin, ["--version"], {
12299
+ encoding: "utf8",
12300
+ timeout: 3e3,
12301
+ stdio: ["ignore", "pipe", "ignore"]
12302
+ }).trim();
12303
+ return out.match(/\d+\.\d+\.\d+/)?.[0] ?? null;
12304
+ } catch {
12305
+ return null;
12306
+ }
12307
+ }
11940
12308
  async function getChangedFiles(root, stage) {
11941
12309
  const commands = stage === "pre-commit" ? [["diff", "--cached", "--name-only"]] : [
11942
12310
  ["diff", "--cached", "--name-only"],
@@ -11996,7 +12364,7 @@ haive enforce check --stage pre-push --dir . || exit $?
11996
12364
  for (const hook of hooks) {
11997
12365
  const file = path47.join(hooksDir, hook.name);
11998
12366
  if (existsSync68(file)) {
11999
- const current = await readFile17(file, "utf8").catch(() => "");
12367
+ const current = await readFile18(file, "utf8").catch(() => "");
12000
12368
  if (current.includes(ENFORCE_HOOK_MARKER)) {
12001
12369
  await writeFile31(file, hook.body, "utf8");
12002
12370
  } else {
@@ -12141,7 +12509,7 @@ function registerRun(program2) {
12141
12509
 
12142
12510
  // src/index.ts
12143
12511
  var program = new Command51();
12144
- program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.14");
12512
+ program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.16").option("--advanced", "show maintenance and experimental commands in help");
12145
12513
  registerInit(program);
12146
12514
  registerWelcome(program);
12147
12515
  registerResolveProject(program);
@@ -12194,6 +12562,32 @@ registerBenchmark(program);
12194
12562
  registerDoctor(program);
12195
12563
  registerPlayback(program);
12196
12564
  registerPrecommit(program);
12565
+ var CORE_ROOT_COMMANDS = /* @__PURE__ */ new Set([
12566
+ "init",
12567
+ "doctor",
12568
+ "agent",
12569
+ "enforce",
12570
+ "run",
12571
+ "briefing",
12572
+ "sync",
12573
+ "mcp",
12574
+ "memory",
12575
+ "session",
12576
+ "precommit",
12577
+ "welcome"
12578
+ ]);
12579
+ var CORE_MEMORY_COMMANDS = /* @__PURE__ */ new Set([
12580
+ "add",
12581
+ "list",
12582
+ "query",
12583
+ "show",
12584
+ "verify",
12585
+ "lint",
12586
+ "tried",
12587
+ "rm"
12588
+ ]);
12589
+ var CORE_SESSION_COMMANDS = /* @__PURE__ */ new Set(["end"]);
12590
+ applySurfaceVisibility(program);
12197
12591
  program.parseAsync(process.argv).catch((err) => {
12198
12592
  if (isZodError(err)) {
12199
12593
  for (const issue of err.issues) {
@@ -12205,6 +12599,44 @@ program.parseAsync(process.argv).catch((err) => {
12205
12599
  }
12206
12600
  process.exit(1);
12207
12601
  });
12602
+ function applySurfaceVisibility(root) {
12603
+ const showAdvanced = process.argv.includes("--advanced") || process.env.HAIVE_SHOW_ADVANCED === "1";
12604
+ if (!showAdvanced) hideNonCoreCommands(root);
12605
+ root.addHelpText(
12606
+ "after",
12607
+ [
12608
+ "",
12609
+ "Default help shows the core hAIve harness: init, doctor, agent setup, briefing, enforcement,",
12610
+ "sync, session recaps, and high-signal memory commands.",
12611
+ "Run `haive --advanced --help` or set HAIVE_SHOW_ADVANCED=1 to show maintenance and experimental commands."
12612
+ ].join("\n")
12613
+ );
12614
+ const memoryCommand = root.commands.find((cmd) => cmd.name() === "memory");
12615
+ memoryCommand?.addHelpText(
12616
+ "after",
12617
+ [
12618
+ "",
12619
+ "Default help shows the memory commands that support the core harness workflow.",
12620
+ "Run `haive --advanced memory --help` or set HAIVE_SHOW_ADVANCED=1 to show review, import, digest, timeline, and conflict tools."
12621
+ ].join("\n")
12622
+ );
12623
+ }
12624
+ function hideNonCoreCommands(command) {
12625
+ for (const child of command.commands) {
12626
+ if (!isCoreCommand(command, child)) {
12627
+ child._hidden = true;
12628
+ }
12629
+ hideNonCoreCommands(child);
12630
+ }
12631
+ }
12632
+ function isCoreCommand(parent, child) {
12633
+ const parentName = parent.name();
12634
+ const childName = child.name();
12635
+ if (parentName === "haive") return CORE_ROOT_COMMANDS.has(childName);
12636
+ if (parentName === "memory") return CORE_MEMORY_COMMANDS.has(childName);
12637
+ if (parentName === "session") return CORE_SESSION_COMMANDS.has(childName);
12638
+ return true;
12639
+ }
12208
12640
  function isZodError(err) {
12209
12641
  return err !== null && typeof err === "object" && "issues" in err && Array.isArray(err.issues);
12210
12642
  }