@hiveai/cli 0.13.8 → 0.14.0

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 Command58 } from "commander";
4
+ import { Command as Command59 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
199
199
  if (!f) continue;
200
200
  counts.set(f, (counts.get(f) ?? 0) + 1);
201
201
  }
202
- let entries = [...counts.entries()].map(([path55, changes]) => ({ path: path55, changes }));
202
+ let entries = [...counts.entries()].map(([path56, changes]) => ({ path: path56, changes }));
203
203
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
204
204
  if (lowerPaths.length > 0) {
205
205
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -3019,7 +3019,7 @@ ${SEED_FOOTER(stack)}` });
3019
3019
  }
3020
3020
 
3021
3021
  // src/commands/init.ts
3022
- var HAIVE_GITHUB_ACTION_REF = `v${"0.13.8"}`;
3022
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.14.0"}`;
3023
3023
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3024
3024
 
3025
3025
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -4089,10 +4089,13 @@ import {
4089
4089
  literalMatchesAnyToken as literalMatchesAnyToken22,
4090
4090
  loadCodeMap as loadCodeMap5,
4091
4091
  loadConfig as loadConfig3,
4092
+ hashProjectContext,
4092
4093
  loadMemoriesFromDir as loadMemoriesFromDir15,
4093
4094
  loadUsageIndex as loadUsageIndex8,
4094
4095
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
4096
+ projectContextRecentlyEmitted,
4095
4097
  rankMemoriesLexical as rankMemoriesLexical2,
4098
+ recordProjectContextEmission,
4096
4099
  queryCodeMap as queryCodeMap2,
4097
4100
  resolveBriefingBudget as resolveBriefingBudget2,
4098
4101
  serializeMemory as serializeMemory10,
@@ -4133,6 +4136,7 @@ import { z as z25 } from "zod";
4133
4136
  import { existsSync as existsSync25 } from "fs";
4134
4137
  import {
4135
4138
  addedLinesFromDiff,
4139
+ appendPreventionEvent,
4136
4140
  buildDocFrequency,
4137
4141
  CODE_STOPWORDS,
4138
4142
  deriveConfidence as deriveConfidence6,
@@ -4143,7 +4147,9 @@ import {
4143
4147
  loadUsageIndex as loadUsageIndex10,
4144
4148
  literalMatchesAnyToken as literalMatchesAnyToken3,
4145
4149
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
4150
+ recordPrevention,
4146
4151
  runSensors,
4152
+ saveUsageIndex as saveUsageIndex4,
4147
4153
  sensorTargetsFromDiff,
4148
4154
  tokenizeQuery as tokenizeQuery3
4149
4155
  } from "@hiveai/core";
@@ -5708,6 +5714,9 @@ var GetBriefingInputSchema = {
5708
5714
  ),
5709
5715
  max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
5710
5716
  include_project_context: z19.boolean().default(true),
5717
+ dedupe_project_context: z19.boolean().optional().describe(
5718
+ "Token saver (default ON): skip re-emitting the project-context body if an identical copy was already sent within the last few minutes this session (the agent still has it). Set false to always include it."
5719
+ ),
5711
5720
  include_module_contexts: z19.boolean().default(true),
5712
5721
  semantic: z19.boolean().default(true).describe(
5713
5722
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
@@ -5915,7 +5924,17 @@ async function getBriefing(input, ctx) {
5915
5924
  }
5916
5925
  }
5917
5926
  }
5918
- const projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
5927
+ let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile52(ctx.paths.projectContext, "utf8") : "";
5928
+ let contextOmittedRecent = false;
5929
+ if (projectContextRaw && input.dedupe_project_context !== false) {
5930
+ const ctxHash = hashProjectContext(projectContextRaw);
5931
+ if (await projectContextRecentlyEmitted(ctx.paths, ctxHash)) {
5932
+ contextOmittedRecent = true;
5933
+ projectContextRaw = "";
5934
+ } else {
5935
+ await recordProjectContextEmission(ctx.paths, ctxHash);
5936
+ }
5937
+ }
5919
5938
  const isTemplateContext = projectContextRaw.includes("TODO \u2014 high-level overview") || projectContextRaw.includes("Generated by `haive init`");
5920
5939
  const setupWarnings = [];
5921
5940
  let autoContextGenerated = false;
@@ -6183,7 +6202,11 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
6183
6202
  search_mode: searchMode,
6184
6203
  inferred_modules: inferred,
6185
6204
  ...lastSession ? { last_session: lastSession } : {},
6186
- project_context: adaptiveTrim ? {
6205
+ project_context: contextOmittedRecent ? {
6206
+ content: "(project context unchanged \u2014 omitted to save tokens; it was provided earlier this session. Pass dedupe_project_context:false to force a full copy.)",
6207
+ truncated: false,
6208
+ omitted_recent: true
6209
+ } : adaptiveTrim ? {
6187
6210
  content: "(adaptive briefing: auto-generated context omitted \u2014 no team-specific policy matched, so a capable model needs nothing extra here)",
6188
6211
  truncated: false,
6189
6212
  ...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
@@ -6669,6 +6692,22 @@ async function antiPatternsCheck(input, ctx) {
6669
6692
  };
6670
6693
  return score(b) - score(a);
6671
6694
  }).slice(0, input.limit);
6695
+ const strongCatches = warnings.filter(
6696
+ (w) => w.reasons.includes("sensor") || w.distinctive_literal === true || w.reasons.includes("anchor") && w.reasons.includes("literal")
6697
+ );
6698
+ if (strongCatches.length > 0) {
6699
+ const recordedIds = [];
6700
+ for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
6701
+ if (recordedIds.length > 0) {
6702
+ await saveUsageIndex4(ctx.paths, usage).catch(() => {
6703
+ });
6704
+ const at = (/* @__PURE__ */ new Date()).toISOString();
6705
+ for (const id of recordedIds) {
6706
+ await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
6707
+ });
6708
+ }
6709
+ }
6710
+ }
6672
6711
  return {
6673
6712
  scanned: negative.length,
6674
6713
  warnings
@@ -7485,7 +7524,7 @@ async function patternDetect(input, ctx) {
7485
7524
  for (const [p, { count, tools }] of pathCounts) {
7486
7525
  if (count < HOT_FILE_MIN) continue;
7487
7526
  if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
7488
- if (CONFIG_PATTERNS.some((cp) => path122.basename(p).includes(cp))) continue;
7527
+ if (CONFIG_PATTERNS.some((cp2) => path122.basename(p).includes(cp2))) continue;
7489
7528
  const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
7490
7529
  matches.push({
7491
7530
  kind: "hot_file",
@@ -7919,7 +7958,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7919
7958
  };
7920
7959
  }
7921
7960
  var SERVER_NAME = "haive";
7922
- var SERVER_VERSION = "0.13.8";
7961
+ var SERVER_VERSION = "0.14.0";
7923
7962
  function jsonResult(data) {
7924
7963
  return {
7925
7964
  content: [
@@ -8668,6 +8707,7 @@ function createHaiveServer(options = {}) {
8668
8707
  "anti_patterns_check",
8669
8708
  [
8670
8709
  "Scan a diff (or set of paths) against documented attempt/gotcha memories.",
8710
+ "[Diff-scan layer: the MEMORY-MATCH component. `pre_commit_check` combines this with sensors + stale checks; `haive enforce check` is the gate.]",
8671
8711
  "Surfaces 'you are about to repeat a known mistake' warnings BEFORE you commit.",
8672
8712
  "",
8673
8713
  "USE BEFORE finalizing a non-trivial change. Cheap and high-signal: the only",
@@ -8811,6 +8851,7 @@ function createHaiveServer(options = {}) {
8811
8851
  "pre_commit_check",
8812
8852
  [
8813
8853
  "One-shot 'should I block this commit?' check. Combines three signals:",
8854
+ "[Diff-scan layer: the COMBINED check (sensors + anti-patterns + stale). `haive enforce check` is the gate that runs this at commit time.]",
8814
8855
  "",
8815
8856
  " 1. anti_patterns_check \u2014 known gotchas/attempts that match the diff",
8816
8857
  " 2. mem_for_files \u2014 conventions/decisions anchored to touched files",
@@ -10688,7 +10729,7 @@ import {
10688
10729
  loadUsageIndex as loadUsageIndex18,
10689
10730
  recordRejection as recordRejection3,
10690
10731
  resolveHaivePaths as resolveHaivePaths23,
10691
- saveUsageIndex as saveUsageIndex4,
10732
+ saveUsageIndex as saveUsageIndex5,
10692
10733
  serializeMemory as serializeMemory19
10693
10734
  } from "@hiveai/core";
10694
10735
  function registerMemoryReject(memory2) {
@@ -10721,7 +10762,7 @@ function registerMemoryReject(memory2) {
10721
10762
  );
10722
10763
  const idx = await loadUsageIndex18(paths);
10723
10764
  recordRejection3(idx, id, opts.reason ?? null);
10724
- await saveUsageIndex4(paths, idx);
10765
+ await saveUsageIndex5(paths, idx);
10725
10766
  const u = idx.by_id[id];
10726
10767
  ui.success(
10727
10768
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -10740,7 +10781,7 @@ import {
10740
10781
  findProjectRoot as findProjectRoot27,
10741
10782
  loadUsageIndex as loadUsageIndex19,
10742
10783
  resolveHaivePaths as resolveHaivePaths24,
10743
- saveUsageIndex as saveUsageIndex5
10784
+ saveUsageIndex as saveUsageIndex6
10744
10785
  } from "@hiveai/core";
10745
10786
  function registerMemoryRm(memory2) {
10746
10787
  memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
@@ -10774,7 +10815,7 @@ function registerMemoryRm(memory2) {
10774
10815
  const idx = await loadUsageIndex19(paths);
10775
10816
  if (idx.by_id[id]) {
10776
10817
  delete idx.by_id[id];
10777
- await saveUsageIndex5(paths, idx);
10818
+ await saveUsageIndex6(paths, idx);
10778
10819
  ui.info("Removed usage entry");
10779
10820
  }
10780
10821
  }
@@ -10984,7 +11025,7 @@ import {
10984
11025
  recordApplied as recordApplied2,
10985
11026
  recordRejection as recordRejection4,
10986
11027
  resolveHaivePaths as resolveHaivePaths28,
10987
- saveUsageIndex as saveUsageIndex6
11028
+ saveUsageIndex as saveUsageIndex7
10988
11029
  } from "@hiveai/core";
10989
11030
  function registerMemoryFeedback(memory2) {
10990
11031
  memory2.command("feedback <id>").description(
@@ -11013,7 +11054,7 @@ function registerMemoryFeedback(memory2) {
11013
11054
  const outcome = opts.applied ? "applied" : "rejected";
11014
11055
  if (opts.applied) recordApplied2(index, id);
11015
11056
  else recordRejection4(index, id, opts.reason ?? null);
11016
- await saveUsageIndex6(paths, index);
11057
+ await saveUsageIndex7(paths, index);
11017
11058
  const usage = getUsage19(index, id);
11018
11059
  const impact = computeImpact4(target.memory.frontmatter, usage);
11019
11060
  if (opts.json) {
@@ -13289,8 +13330,8 @@ function registerDoctor(program2) {
13289
13330
  fix: "haive init"
13290
13331
  });
13291
13332
  } else {
13292
- const { readFile: readFile26 } = await import("fs/promises");
13293
- const content = await readFile26(paths.projectContext, "utf8");
13333
+ const { readFile: readFile27 } = await import("fs/promises");
13334
+ const content = await readFile27(paths.projectContext, "utf8");
13294
13335
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
13295
13336
  if (isTemplate) {
13296
13337
  findings.push({
@@ -13464,8 +13505,8 @@ function registerDoctor(program2) {
13464
13505
  let hasClaudeEnforcement = false;
13465
13506
  if (existsSync68(claudeSettings)) {
13466
13507
  try {
13467
- const { readFile: readFile26 } = await import("fs/promises");
13468
- const raw = await readFile26(claudeSettings, "utf8");
13508
+ const { readFile: readFile27 } = await import("fs/promises");
13509
+ const raw = await readFile27(claudeSettings, "utf8");
13469
13510
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
13470
13511
  } catch {
13471
13512
  hasClaudeEnforcement = false;
@@ -13488,7 +13529,7 @@ function registerDoctor(program2) {
13488
13529
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
13489
13530
  });
13490
13531
  }
13491
- findings.push(...await collectInstallFindings(root, "0.13.8"));
13532
+ findings.push(...await collectInstallFindings(root, "0.14.0"));
13492
13533
  findings.push(...await collectToolchainFindings(root));
13493
13534
  try {
13494
13535
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -13496,7 +13537,7 @@ function registerDoctor(program2) {
13496
13537
  timeout: 3e3,
13497
13538
  stdio: ["ignore", "pipe", "ignore"]
13498
13539
  }).trim();
13499
- const cliVersion = "0.13.8";
13540
+ const cliVersion = "0.14.0";
13500
13541
  if (legacyRaw && legacyRaw !== cliVersion) {
13501
13542
  findings.push({
13502
13543
  severity: "warn",
@@ -14729,52 +14770,73 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14729
14770
  [setup warning] ${warning}`);
14730
14771
  }
14731
14772
  });
14732
- 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) => {
14773
+ enforce.command("pre-tool-use").description("Claude Code PreToolUse hook: surface the relevant team policy for the edited file (advise; configurable to block).").option("-d, --dir <dir>", "project root").action(async (opts) => {
14733
14774
  const payload = await readHookPayload();
14734
14775
  const root = resolveRoot(opts.dir, payload);
14735
14776
  if (!root) return;
14736
14777
  const paths = resolveHaivePaths48(root);
14737
14778
  if (!existsSync75(paths.haiveDir)) return;
14738
14779
  if (!isWriteLikeTool(payload)) return;
14739
- const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
14740
- if (ok) {
14741
- const targetFiles = extractToolPaths(payload, root);
14742
- if (targetFiles.length === 0) return;
14743
- const missing = await missingRequiredMemoriesForFiles(paths, targetFiles, payload.session_id);
14744
- if (missing.length === 0) return;
14745
- const ids = missing.slice(0, 6).map((memory2) => memory2.memory.frontmatter.id);
14780
+ const config = await loadConfig13(paths);
14781
+ if (config.enforcement?.requireBriefingFirst === false) return;
14782
+ const gate = config.enforcement?.preEditGate ?? "advise";
14783
+ const targetFiles = extractToolPaths(payload, root);
14784
+ const hasMarker = await hasRecentBriefingMarker2(paths, payload.session_id);
14785
+ const missing = targetFiles.length > 0 ? await missingRequiredMemoriesForFiles(paths, targetFiles, payload.session_id) : [];
14786
+ if (hasMarker && missing.length === 0) return;
14787
+ if (targetFiles.length > 0) {
14788
+ await recordFilesIntoBriefingMarker(paths, targetFiles, missing, payload.session_id).catch(() => {
14789
+ });
14790
+ }
14791
+ const contextText = buildPreEditContext(payload.tool_name ?? "write tool", targetFiles, missing, hasMarker);
14792
+ if (gate === "block") {
14746
14793
  console.error(
14747
- [
14748
- "hAIve enforcement blocked this action.",
14749
- `Tool: ${payload.tool_name ?? "write tool"}`,
14750
- `Files: ${targetFiles.slice(0, 6).join(", ")}`,
14751
- "",
14752
- "These files have required hAIve context that was not in the current briefing:",
14753
- ...ids.map((id) => ` - ${id}`),
14754
- "",
14755
- "Load the targeted briefing before editing:",
14756
- ` ${briefingCommandForFiles(targetFiles)}`
14757
- ].join("\n")
14794
+ contextText + '\n\nThe relevant context is now recorded \u2014 re-issue the same edit to proceed (no `haive briefing` command needed). To make this advisory instead of blocking, set `{ "enforcement": { "preEditGate": "advise" } }` in .ai/haive.config.json.'
14758
14795
  );
14759
14796
  process.exit(2);
14760
14797
  }
14761
- const tool = payload.tool_name ?? "write tool";
14762
- console.error(
14763
- [
14764
- "hAIve enforcement blocked this action.",
14765
- `Tool: ${tool}`,
14766
- "",
14767
- "This project is initialized with hAIve. Load the team briefing before editing:",
14768
- " haive enforce session-start",
14769
- "or call MCP get_briefing / mem_relevant_to from your AI client.",
14770
- "",
14771
- "If this is intentional, a human can disable enforcement in .ai/haive.config.json:",
14772
- ' { "enforcement": { "requireBriefingFirst": false } }'
14773
- ].join("\n")
14774
- );
14775
- process.exit(2);
14798
+ emitPreToolUseContext(contextText);
14799
+ });
14800
+ }
14801
+ async function recordFilesIntoBriefingMarker(paths, files, missing, sessionId) {
14802
+ const existing = await readRecentBriefingMarker(paths, sessionId);
14803
+ const ids = new Set(existing?.memory_ids ?? []);
14804
+ for (const { memory: memory2 } of missing) ids.add(memory2.frontmatter.id);
14805
+ await writeBriefingMarker3(paths, {
14806
+ sessionId,
14807
+ task: existing?.task ?? "pre-edit auto-briefing",
14808
+ source: "haive-pre-edit",
14809
+ files,
14810
+ memoryIds: [...ids]
14776
14811
  });
14777
14812
  }
14813
+ function buildPreEditContext(tool, files, missing, hasMarker) {
14814
+ const lines = ["hAIve \u2014 relevant team policy for this edit", `Tool: ${tool}`];
14815
+ if (files.length > 0) lines.push(`Files: ${files.slice(0, 6).join(", ")}`);
14816
+ if (missing.length > 0) {
14817
+ lines.push("", "Consult these before editing (anchored to the files you are touching):");
14818
+ for (const { memory: memory2 } of missing.slice(0, 5)) {
14819
+ const fm = memory2.frontmatter;
14820
+ lines.push("", `### ${fm.id} (${fm.scope}/${fm.type})`, memory2.body.trim().slice(0, 900));
14821
+ }
14822
+ } else if (!hasMarker) {
14823
+ lines.push(
14824
+ "",
14825
+ "No team briefing was loaded yet this session. Proceeding \u2014 but for substantive work call get_briefing / mem_relevant_to for richer context."
14826
+ );
14827
+ }
14828
+ return lines.join("\n");
14829
+ }
14830
+ function emitPreToolUseContext(text) {
14831
+ console.log(
14832
+ JSON.stringify({
14833
+ hookSpecificOutput: {
14834
+ hookEventName: "PreToolUse",
14835
+ additionalContext: text
14836
+ }
14837
+ })
14838
+ );
14839
+ }
14778
14840
  async function buildFinishReport(dir) {
14779
14841
  const root = findProjectRoot52(dir);
14780
14842
  const paths = resolveHaivePaths48(root);
@@ -15105,7 +15167,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
15105
15167
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
15106
15168
  });
15107
15169
  }
15108
- findings.push(...await inspectIntegrationVersions(root, "0.13.8"));
15170
+ findings.push(...await inspectIntegrationVersions(root, "0.14.0"));
15109
15171
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
15110
15172
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
15111
15173
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -15223,7 +15285,7 @@ async function verifyMemoryPolicy(paths, config) {
15223
15285
  }
15224
15286
  async function verifyDecisionCoverage(paths, stage, sessionId) {
15225
15287
  if (!existsSync75(paths.memoriesDir)) return [];
15226
- const changedFiles = await getChangedFiles(paths.root, stage);
15288
+ const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
15227
15289
  if (changedFiles.length === 0) {
15228
15290
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
15229
15291
  }
@@ -16020,8 +16082,10 @@ async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
16020
16082
  return memoryMatchesAnchorPaths6(memory2, files);
16021
16083
  }).map(({ memory: memory2, filePath }) => ({ memory: memory2, filePath }));
16022
16084
  }
16023
- function briefingCommandForFiles(files) {
16024
- return `haive briefing --files "${files.slice(0, 10).join(",")}" --task "edit ${files.slice(0, 3).join(", ")}"`;
16085
+ function isGeneratedArtifact(file) {
16086
+ if (file === ".ai/project-context.md" || file === ".ai/code-map.json") return true;
16087
+ if (file.startsWith(".ai/.cache/") || file.startsWith(".ai/.runtime/") || file.startsWith(".ai/.usage/")) return true;
16088
+ return false;
16025
16089
  }
16026
16090
  async function readStdin2(maxBytes) {
16027
16091
  if (process.stdin.isTTY) return "";
@@ -16096,14 +16160,15 @@ import path53 from "path";
16096
16160
  import { promisify as promisify2 } from "util";
16097
16161
  import "commander";
16098
16162
  import {
16163
+ appendPreventionEvent as appendPreventionEvent2,
16099
16164
  findProjectRoot as findProjectRoot53,
16100
16165
  isRetiredMemory as isRetiredMemory3,
16101
16166
  loadMemoriesFromDir as loadMemoriesFromDir39,
16102
16167
  loadUsageIndex as loadUsageIndex29,
16103
- recordPrevention,
16168
+ recordPrevention as recordPrevention2,
16104
16169
  resolveHaivePaths as resolveHaivePaths49,
16105
16170
  runSensors as runSensors2,
16106
- saveUsageIndex as saveUsageIndex7,
16171
+ saveUsageIndex as saveUsageIndex8,
16107
16172
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
16108
16173
  serializeMemory as serializeMemory27
16109
16174
  } from "@hiveai/core";
@@ -16131,7 +16196,9 @@ function registerSensors(program2) {
16131
16196
  if (row.last_fired) console.log(` ${ui.dim("last fired:")} ${row.last_fired}`);
16132
16197
  }
16133
16198
  });
16134
- sensors.command("check").description("Run regex sensors against a diff; defaults to `git diff --cached`").option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
16199
+ sensors.command("check").description(
16200
+ "Run regex sensors against a diff (the deterministic/computational layer); defaults to `git diff --cached`.\n Diff-scan layers: `sensors check` (regex) and `anti_patterns_check` (memory match) are components;\n `pre_commit_check` combines them; `haive enforce check` is THE gate that runs at commit."
16201
+ ).option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
16135
16202
  const root = findProjectRoot53(opts.dir);
16136
16203
  const paths = resolveHaivePaths49(root);
16137
16204
  const memories = await runnableSensorMemories(paths);
@@ -16141,10 +16208,17 @@ function registerSensors(program2) {
16141
16208
  const firedIds = [...new Set(hits.map((hit) => hit.memory_id))];
16142
16209
  if (firedIds.length > 0) {
16143
16210
  const usage = await loadUsageIndex29(paths);
16144
- let recorded = 0;
16145
- for (const id of firedIds) if (recordPrevention(usage, id)) recorded++;
16146
- if (recorded > 0) await saveUsageIndex7(paths, usage).catch(() => {
16147
- });
16211
+ const recordedIds = [];
16212
+ for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
16213
+ if (recordedIds.length > 0) {
16214
+ await saveUsageIndex8(paths, usage).catch(() => {
16215
+ });
16216
+ const at = (/* @__PURE__ */ new Date()).toISOString();
16217
+ for (const id of recordedIds) {
16218
+ await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
16219
+ });
16220
+ }
16221
+ }
16148
16222
  }
16149
16223
  const output = {
16150
16224
  scanned: memories.length,
@@ -16489,6 +16563,7 @@ import {
16489
16563
  buildDashboard,
16490
16564
  findProjectRoot as findProjectRoot55,
16491
16565
  loadMemoriesFromDir as loadMemoriesFromDir41,
16566
+ loadPreventionEvents,
16492
16567
  loadUsageIndex as loadUsageIndex30,
16493
16568
  resolveHaivePaths as resolveHaivePaths51
16494
16569
  } from "@hiveai/core";
@@ -16505,10 +16580,12 @@ function registerDashboard(program2) {
16505
16580
  }
16506
16581
  const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
16507
16582
  const usage = await loadUsageIndex30(paths);
16583
+ const preventionEvents = await loadPreventionEvents(paths);
16508
16584
  const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
16509
16585
  const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
16510
16586
  const report = buildDashboard(memories, usage, {
16511
16587
  top,
16588
+ preventionEvents,
16512
16589
  ...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
16513
16590
  });
16514
16591
  if (opts.json) {
@@ -16553,11 +16630,22 @@ function renderDashboard(r) {
16553
16630
  console.log(
16554
16631
  ` ${prevention.total_events > 0 ? ui.green(`${prevention.total_events} catch event(s)`) : "0 catch events"} \xB7 ${prevention.memories_with_catches} memor${prevention.memories_with_catches === 1 ? "y" : "ies"} with catches`
16555
16632
  );
16633
+ console.log(
16634
+ ` ${ui.dim("trend:")} ${prevention.trend.last_7d} in 7d \xB7 ${prevention.trend.last_30d} in 30d ${ui.dim("weekly")} [${prevention.trend.weekly.join(" ")}]`
16635
+ );
16556
16636
  for (const p of prevention.top.slice(0, 5)) {
16557
16637
  console.log(
16558
16638
  ` ${ui.green("\u2713")} ${p.prevented_count}\xD7 ${p.id}` + (p.last_prevented_at ? ui.dim(` last ${p.last_prevented_at.slice(0, 10)}`) : "")
16559
16639
  );
16560
16640
  }
16641
+ if (prevention.recurrence.recurring_count > 0) {
16642
+ console.log(
16643
+ ` ${ui.yellow("recurrence:")} ${prevention.recurrence.recurring_count} lesson(s) re-introduced after capture ` + ui.dim("(caught on \u22652 distinct days)")
16644
+ );
16645
+ for (const r2 of prevention.recurrence.top.slice(0, 5)) {
16646
+ console.log(` ${ui.yellow("\u21BB")} ${r2.distinct_days} days \xB7 ${r2.catches}\xD7 ${r2.id}`);
16647
+ }
16648
+ }
16561
16649
  console.log();
16562
16650
  console.log(ui.bold("Health"));
16563
16651
  console.log(
@@ -16593,9 +16681,72 @@ function warnNum(n) {
16593
16681
  return n > 0 ? ui.yellow(String(n)) : String(n);
16594
16682
  }
16595
16683
 
16684
+ // src/commands/dev-link.ts
16685
+ import { execFile as execFile3 } from "child_process";
16686
+ import { cp, readFile as readFile26 } from "fs/promises";
16687
+ import { existsSync as existsSync79 } from "fs";
16688
+ import path55 from "path";
16689
+ import { promisify as promisify3 } from "util";
16690
+ import "commander";
16691
+ import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
16692
+ var exec3 = promisify3(execFile3);
16693
+ function registerDevLink(program2) {
16694
+ const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
16695
+ dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
16696
+ const root = findProjectRoot56(opts.dir);
16697
+ if (!existsSync79(path55.join(root, "packages", "cli", "dist", "index.js"))) {
16698
+ ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
16699
+ process.exitCode = 1;
16700
+ return;
16701
+ }
16702
+ let globalModules;
16703
+ try {
16704
+ globalModules = (await exec3("npm", ["root", "-g"])).stdout.trim();
16705
+ } catch {
16706
+ globalModules = path55.join(path55.dirname(path55.dirname(process.execPath)), "lib", "node_modules");
16707
+ }
16708
+ const globalHive = path55.join(globalModules, "@hiveai");
16709
+ if (!existsSync79(globalHive)) {
16710
+ ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
16711
+ process.exitCode = 1;
16712
+ return;
16713
+ }
16714
+ const linked = [];
16715
+ const copyDist = async (fromPkg, toDistDir) => {
16716
+ const from = path55.join(root, "packages", fromPkg, "dist");
16717
+ if (!existsSync79(from) || !existsSync79(path55.dirname(toDistDir))) return;
16718
+ await cp(from, toDistDir, { recursive: true });
16719
+ linked.push(path55.relative(globalModules, toDistDir));
16720
+ };
16721
+ for (const pkg of ["cli", "mcp"]) {
16722
+ await copyDist(pkg, path55.join(globalHive, pkg, "dist"));
16723
+ for (const nested of ["core", "embeddings"]) {
16724
+ await copyDist(nested, path55.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
16725
+ }
16726
+ }
16727
+ await copyDist("core", path55.join(globalHive, "core", "dist"));
16728
+ let version = "unknown";
16729
+ try {
16730
+ version = JSON.parse(await readFile26(path55.join(root, "package.json"), "utf8")).version ?? "unknown";
16731
+ } catch {
16732
+ }
16733
+ if (opts.json) {
16734
+ console.log(JSON.stringify({ ok: linked.length > 0, version, global_root: globalHive, linked }, null, 2));
16735
+ return;
16736
+ }
16737
+ if (linked.length === 0) {
16738
+ ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
16739
+ return;
16740
+ }
16741
+ ui.success(`Linked local dist (v${version}) into the global @hiveai install:`);
16742
+ for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
16743
+ console.log(ui.dim("The global `haive` now runs your local build (git hooks + MCP included)."));
16744
+ });
16745
+ }
16746
+
16596
16747
  // src/index.ts
16597
- var program = new Command58();
16598
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.8").option("--advanced", "show maintenance and experimental commands in help");
16748
+ var program = new Command59();
16749
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.14.0").option("--advanced", "show maintenance and experimental commands in help");
16599
16750
  registerInit(program);
16600
16751
  registerWelcome(program);
16601
16752
  registerResolveProject(program);
@@ -16606,6 +16757,7 @@ registerAgent(program);
16606
16757
  registerSensors(program);
16607
16758
  registerIngest(program);
16608
16759
  registerDashboard(program);
16760
+ registerDevLink(program);
16609
16761
  registerMcp(program);
16610
16762
  registerBriefing(program);
16611
16763
  registerTui(program);