@hiveai/cli 0.9.16 → 0.9.18

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
@@ -198,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
198
198
  if (!f) continue;
199
199
  counts.set(f, (counts.get(f) ?? 0) + 1);
200
200
  }
201
- let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
201
+ let entries = [...counts.entries()].map(([path50, changes]) => ({ path: path50, changes }));
202
202
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
203
203
  if (lowerPaths.length > 0) {
204
204
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -288,7 +288,7 @@ var TokenBudgetWriter = class {
288
288
  function registerBriefing(program2) {
289
289
  program2.command("briefing").description(
290
290
  'Print the full project briefing: last session recap + project context + relevant memories.\n Equivalent to calling get_briefing via MCP. Run before starting any task.\n\n Examples:\n haive briefing\n haive briefing --task "add Stripe payment" --files src/payments/PaymentService.ts\n haive briefing --budget quick --task "tiny fix"\n'
291
- ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option(
291
+ ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "8").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option(
292
292
  "--budget <preset>",
293
293
  "align with MCP get_briefing budget_preset: quick | balanced | deep \u2014 sets cap + truncation budget (overrides --max-memories / replaces default open-ended output)",
294
294
  void 0
@@ -325,7 +325,7 @@ function registerBriefing(program2) {
325
325
  if (b === "quick" || b === "balanced" || b === "deep") budgetPreset = b;
326
326
  else ui.warn(`Unknown --budget '${opts.budget}' \u2014 ignoring (use quick|balanced|deep).`);
327
327
  }
328
- let maxMemories = Math.max(1, Number(opts.maxMemories ?? 10));
328
+ let maxMemories = Math.max(1, Number(opts.maxMemories ?? 8));
329
329
  let budgetTokensCap = opts.maxTokens ? Math.max(100, Number(opts.maxTokens)) : null;
330
330
  if (budgetPreset !== null) {
331
331
  const presetNums = resolveBriefingBudget(budgetPreset, {
@@ -470,7 +470,22 @@ function registerBriefing(program2) {
470
470
  const usageIndex = await loadUsageIndex(paths).catch(() => null);
471
471
  out(`${ui.bold("=== Relevant Memories ===")}
472
472
  `);
473
- for (const item of top) {
473
+ const priorities = top.map(
474
+ (item) => classifyCliPriority(
475
+ item,
476
+ filePaths,
477
+ tokens,
478
+ Boolean(andTaskHits?.has(item.memory.frontmatter.id)),
479
+ Boolean(useOrFallback && tokens && literalMatchesAnyToken(item.memory, tokens))
480
+ )
481
+ );
482
+ const mustReadCount = priorities.filter((p) => p === "must_read").length;
483
+ const usefulCount = priorities.filter((p) => p === "useful").length;
484
+ const backgroundCount = priorities.filter((p) => p === "background").length;
485
+ const quality = mustReadCount > 0 || usefulCount > 0 ? backgroundCount > mustReadCount + usefulCount && backgroundCount > 2 ? "noisy" : "strong" : "thin";
486
+ out(ui.dim(`briefing_quality: ${quality} \xB7 must_read=${mustReadCount} useful=${usefulCount} background=${backgroundCount}`));
487
+ out("");
488
+ for (const [idx, item] of top.entries()) {
474
489
  if (stopped()) break;
475
490
  const fm = item.memory.frontmatter;
476
491
  const badge = ui.statusBadge(fm.status);
@@ -479,8 +494,9 @@ function registerBriefing(program2) {
479
494
  const originMarker = item.origin ? ` ${ui.yellow("[from " + item.origin + "]")}` : "";
480
495
  const reads = usageIndex?.by_id[fm.id]?.read_count ?? 0;
481
496
  const hitMarker = reads > 0 ? ` ${ui.dim("\xB7 " + reads + "\xD7 read")}` : "";
497
+ const priority = priorities[idx] ?? "background";
482
498
  out(
483
- `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
499
+ `${ui.bold(fm.id)} ${priorityBadge(priority)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
484
500
  );
485
501
  if (opts.explainSource) {
486
502
  const relPath = path.relative(root, item.filePath);
@@ -551,6 +567,20 @@ ${ui.bold("=== Symbol Locations ===")}
551
567
  }
552
568
  });
553
569
  }
570
+ function classifyCliPriority(item, filePaths, tokens, exactTaskHit, partialTaskHit) {
571
+ const fm = item.memory.frontmatter;
572
+ const anchored = filePaths.length > 0 && memoryMatchesAnchorPaths(item.memory, filePaths);
573
+ if (anchored || fm.type === "attempt" && exactTaskHit) return "must_read";
574
+ if (exactTaskHit || partialTaskHit || item.score >= 4 || tokens && fm.tags.some((tag) => tokens.includes(tag))) {
575
+ return "useful";
576
+ }
577
+ return "background";
578
+ }
579
+ function priorityBadge(priority) {
580
+ if (priority === "must_read") return ui.red("[must_read]");
581
+ if (priority === "useful") return ui.yellow("[useful]");
582
+ return ui.dim("[background]");
583
+ }
554
584
  function parseCsv(value) {
555
585
  if (!value) return [];
556
586
  return value.split(",").map((s) => s.trim()).filter(Boolean);
@@ -2432,7 +2462,7 @@ function registerInit(program2) {
2432
2462
  console.log(ui.dim(` \u2713 Stack memory packs pre-seeded (${stacksToSeed.join(", ")})`));
2433
2463
  }
2434
2464
  console.log();
2435
- if (!opts.bootstrap) {
2465
+ if (!wantBootstrap) {
2436
2466
  console.log(ui.bold("One remaining step (optional but recommended):"));
2437
2467
  console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 fill project-context.md without AI"));
2438
2468
  console.log(" " + ui.dim("Or in your AI client: invoke the MCP prompt ") + ui.bold("bootstrap_project"));
@@ -2447,7 +2477,7 @@ function registerInit(program2) {
2447
2477
  console.log(ui.dim(" haive memory import README.md \u2014 from README / docs"));
2448
2478
  } else {
2449
2479
  console.log(ui.bold("Next steps:"));
2450
- if (!opts.bootstrap) {
2480
+ if (!wantBootstrap) {
2451
2481
  console.log(ui.dim(" 1. Fill project context (pick one):"));
2452
2482
  console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 instant, no AI needed"));
2453
2483
  console.log(" or invoke the MCP prompt " + ui.bold("bootstrap_project") + ui.dim(" in your AI client"));
@@ -3054,6 +3084,7 @@ import {
3054
3084
  extractActionsBriefBody as extractActionsBriefBody2,
3055
3085
  getUsage as getUsage5,
3056
3086
  inferModulesFromPaths as inferModulesFromPaths2,
3087
+ isGlobPath,
3057
3088
  isAutoPromoteEligible,
3058
3089
  isDecaying,
3059
3090
  literalMatchesAllTokens as literalMatchesAllTokens22,
@@ -3352,7 +3383,6 @@ async function memSave(input, ctx) {
3352
3383
  const { similarityWarning: simW, body_similar: bs } = bodySimilarWarnings(/* @__PURE__ */ new Set([fm.id]));
3353
3384
  const newFrontmatter = {
3354
3385
  ...fm,
3355
- body: input.body,
3356
3386
  tags: input.tags.length ? input.tags : fm.tags,
3357
3387
  revision_count: (fm.revision_count ?? 0) + 1,
3358
3388
  anchor: {
@@ -3368,6 +3398,7 @@ async function memSave(input, ctx) {
3368
3398
  );
3369
3399
  const mergedTw = [
3370
3400
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by haive sync.` : null,
3401
+ criticalAnchorWarning(input.type, fm.status, newFrontmatter.anchor.paths, newFrontmatter.anchor.symbols),
3371
3402
  simW ?? null
3372
3403
  ].filter(Boolean).join(" \u2014 ") || void 0;
3373
3404
  return {
@@ -3423,6 +3454,7 @@ async function memSave(input, ctx) {
3423
3454
  const { similarityWarning: simWarnNew, body_similar: bsNew } = bodySimilarWarnings();
3424
3455
  const finalWarning = [
3425
3456
  invalidPaths.length > 0 ? `Anchor path(s) not found in project: ${invalidPaths.join(", ")}. They will be marked stale by \`haive sync\`.` : null,
3457
+ criticalAnchorWarning(frontmatter.type, frontmatter.status, frontmatter.anchor.paths, frontmatter.anchor.symbols),
3426
3458
  warning ?? null,
3427
3459
  simWarnNew ?? null
3428
3460
  ].filter(Boolean).join(" \u2014 ") || void 0;
@@ -3437,6 +3469,12 @@ async function memSave(input, ctx) {
3437
3469
  ...invalidPaths.length > 0 ? { invalid_paths: invalidPaths } : {}
3438
3470
  };
3439
3471
  }
3472
+ function criticalAnchorWarning(type, status, paths, symbols) {
3473
+ if (!["decision", "gotcha", "architecture"].includes(type)) return null;
3474
+ if (status !== "validated") return null;
3475
+ if (paths.length > 0 || symbols.length > 0) return null;
3476
+ return `${type} is validated without paths or symbols; add anchors so hAIve can detect drift.`;
3477
+ }
3440
3478
  var MemSearchInputSchema = {
3441
3479
  query: z5.string().describe("Substring matched against id, tags, and body"),
3442
3480
  scope: z5.enum(["personal", "team", "module"]).optional().describe("Restrict results to a single scope"),
@@ -4458,6 +4496,7 @@ async function getBriefing(input, ctx) {
4458
4496
  reasons: [reason],
4459
4497
  match_quality: matchQuality ?? "partial",
4460
4498
  ...score !== void 0 ? { semantic_score: score } : {},
4499
+ priority: "background",
4461
4500
  body: loaded.memory.body,
4462
4501
  file_path: loaded.filePath
4463
4502
  });
@@ -4473,6 +4512,13 @@ async function getBriefing(input, ctx) {
4473
4512
  if (fm.tags.some((t) => inferred.includes(t))) addOrUpdate(loaded, "module", void 0, "partial");
4474
4513
  }
4475
4514
  }
4515
+ if (input.symbols.length > 0) {
4516
+ const wanted = new Set(input.symbols.map((s) => s.toLowerCase()));
4517
+ for (const loaded of allMemories) {
4518
+ const symbols = loaded.memory.frontmatter.anchor.symbols.map((s) => s.toLowerCase());
4519
+ if (symbols.some((s) => wanted.has(s))) addOrUpdate(loaded, "symbol", void 0, "exact");
4520
+ }
4521
+ }
4476
4522
  if (input.task) {
4477
4523
  const tokens = tokenizeQuery22(input.task);
4478
4524
  const andHits = allMemories.filter((m) => literalMatchesAllTokens22(m.memory, tokens));
@@ -4498,11 +4544,12 @@ async function getBriefing(input, ctx) {
4498
4544
  }
4499
4545
  }
4500
4546
  const ranked = [...seen.values()].sort((a, b) => {
4547
+ const priorityScore = (m) => priorityRank(classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols));
4501
4548
  const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + // attempt = negative knowledge, surface first to prevent repeating mistakes
4502
- (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
4549
+ (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
4503
4550
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
4504
- const sa = reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
4505
- const sb = reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
4551
+ const sa = priorityScore(a) * 100 + reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
4552
+ const sb = priorityScore(b) * 100 + reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
4506
4553
  return sb - sa;
4507
4554
  });
4508
4555
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -4661,8 +4708,15 @@ ${m.content}`).join("\n\n---\n\n"),
4661
4708
  })) : trimmedMemories;
4662
4709
  const outputMemories = formattedMemories.map((m) => ({
4663
4710
  ...m,
4711
+ priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
4664
4712
  why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
4665
4713
  }));
4714
+ const briefingQuality = classifyBriefingQuality(outputMemories, {
4715
+ isTemplateContext,
4716
+ autoContextGenerated,
4717
+ hasLastSession: Boolean(lastSession),
4718
+ searchMode
4719
+ });
4666
4720
  let symbolLocations;
4667
4721
  const symbolsToLookup = new Set(input.symbols);
4668
4722
  for (const m of outputMemories) {
@@ -4803,6 +4857,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
4803
4857
  } : null,
4804
4858
  module_contexts: trimmedModules,
4805
4859
  memories: outputMemories,
4860
+ briefing_quality: briefingQuality,
4806
4861
  ...symbolLocations ? { symbol_locations: symbolLocations } : {},
4807
4862
  action_required: actionRequired,
4808
4863
  decay_warnings: decayWarnings,
@@ -4828,6 +4883,53 @@ function compactSummary(body) {
4828
4883
  }
4829
4884
  return body.slice(0, 120);
4830
4885
  }
4886
+ function classifyMemoryPriority(memory2, loaded, inputFiles, inputSymbols) {
4887
+ const fm = loaded?.memory.frontmatter;
4888
+ const directAnchor = Boolean(
4889
+ fm && inputFiles.length > 0 && fm.anchor.paths.some((p) => inputFiles.some((file) => pathsOverlap(p, file)))
4890
+ );
4891
+ const directSymbol = Boolean(
4892
+ fm && inputSymbols.length > 0 && fm.anchor.symbols.some(
4893
+ (sym) => inputSymbols.some((wanted) => wanted.toLowerCase() === sym.toLowerCase())
4894
+ )
4895
+ );
4896
+ const strongSemantic = (memory2.semantic_score ?? 0) >= 0.65;
4897
+ const usefulSemantic = (memory2.semantic_score ?? 0) >= 0.35;
4898
+ if (fm?.requires_human_approval || directAnchor || directSymbol || memory2.type === "attempt" && (memory2.match_quality === "exact" || strongSemantic)) {
4899
+ return "must_read";
4900
+ }
4901
+ if (memory2.reasons.includes("module") || memory2.reasons.includes("domain") || memory2.match_quality === "exact" || usefulSemantic) {
4902
+ return "useful";
4903
+ }
4904
+ return "background";
4905
+ }
4906
+ function priorityRank(priority) {
4907
+ return priority === "must_read" ? 3 : priority === "useful" ? 2 : 1;
4908
+ }
4909
+ function classifyBriefingQuality(memories, context) {
4910
+ const mustRead = memories.filter((m) => m.priority === "must_read").length;
4911
+ const useful = memories.filter((m) => m.priority === "useful").length;
4912
+ const background = memories.filter((m) => m.priority === "background").length;
4913
+ const weakSemantic = memories.filter(
4914
+ (m) => m.reasons.length === 1 && m.reasons.includes("semantic") && (m.semantic_score ?? 0) > 0 && (m.semantic_score ?? 0) < 0.35
4915
+ ).length;
4916
+ const reasons = [];
4917
+ if (memories.length === 0) reasons.push("no memories matched the task or files");
4918
+ if (context.isTemplateContext && !context.autoContextGenerated) reasons.push("project context is still a template");
4919
+ if (!context.hasLastSession) reasons.push("no previous session recap");
4920
+ if (mustRead > 0) reasons.push(`${mustRead} must_read memor${mustRead === 1 ? "y" : "ies"} matched directly`);
4921
+ if (useful > 0) reasons.push(`${useful} useful memor${useful === 1 ? "y" : "ies"} matched`);
4922
+ if (background > useful + mustRead && background > 2) reasons.push(`${background} background memories dominate the result`);
4923
+ if (weakSemantic > 0) reasons.push(`${weakSemantic} weak semantic-only match${weakSemantic === 1 ? "" : "es"}`);
4924
+ if (context.searchMode === "literal_fallback") reasons.push("semantic index unavailable or empty; literal fallback used");
4925
+ if (memories.length === 0 || mustRead === 0 && useful === 0) {
4926
+ return { level: "thin", reasons };
4927
+ }
4928
+ if (background > useful + mustRead && background > 2) {
4929
+ return { level: "noisy", reasons };
4930
+ }
4931
+ return { level: "strong", reasons };
4932
+ }
4831
4933
  function explainWhySurfaced(memory2, loaded, inputFiles, inferredModules) {
4832
4934
  const why = [];
4833
4935
  const fm = loaded?.memory.frontmatter;
@@ -4836,7 +4938,19 @@ function explainWhySurfaced(memory2, loaded, inputFiles, inferredModules) {
4836
4938
  (p) => inputFiles.length === 0 || inputFiles.some((file) => pathsOverlap(p, file))
4837
4939
  );
4838
4940
  if (matching.length > 0) {
4839
- why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
4941
+ const exact = matching.filter(
4942
+ (p) => !isGlobPath(p) && inputFiles.some((file) => p === file || pathsOverlap(p, file))
4943
+ );
4944
+ const glob = matching.filter((p) => isGlobPath(p));
4945
+ if (exact.length > 0) {
4946
+ why.push(`Exact/file anchor match: ${exact.slice(0, 4).join(", ")}`);
4947
+ }
4948
+ if (glob.length > 0) {
4949
+ why.push(`Glob anchor match: ${glob.slice(0, 4).join(", ")}`);
4950
+ }
4951
+ if (exact.length === 0 && glob.length === 0) {
4952
+ why.push(`Anchored to touched path${matching.length === 1 ? "" : "s"}: ${matching.slice(0, 4).join(", ")}`);
4953
+ }
4840
4954
  } else if (fm.anchor.paths.length > 0) {
4841
4955
  why.push(`Pulled by related anchor: ${fm.anchor.paths.slice(0, 4).join(", ")}`);
4842
4956
  }
@@ -4844,6 +4958,9 @@ function explainWhySurfaced(memory2, loaded, inputFiles, inferredModules) {
4844
4958
  why.push(`Anchor symbol${fm.anchor.symbols.length === 1 ? "" : "s"}: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
4845
4959
  }
4846
4960
  }
4961
+ if (memory2.reasons.includes("symbol") && fm) {
4962
+ why.push(`Explicit symbol match: ${fm.anchor.symbols.slice(0, 4).join(", ")}`);
4963
+ }
4847
4964
  if (memory2.reasons.includes("module")) {
4848
4965
  const moduleHints = [
4849
4966
  ...memory2.module ? [memory2.module] : [],
@@ -5068,7 +5185,8 @@ async function memRelevantTo(input, ctx) {
5068
5185
  const out = {
5069
5186
  task: input.task,
5070
5187
  search_mode: briefing.search_mode,
5071
- memories: briefing.memories
5188
+ memories: briefing.memories,
5189
+ briefing_quality: briefing.briefing_quality
5072
5190
  };
5073
5191
  if (briefing.hints && briefing.hints.length > 0) out.hints = briefing.hints;
5074
5192
  if (briefing.memories.length === 0) out.empty = true;
@@ -5750,7 +5868,7 @@ async function preCommitCheck(input, ctx) {
5750
5868
  const filesTouching = new Set(relevantMatches.map((m) => m.id));
5751
5869
  const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
5752
5870
  const blockOn = input.block_on;
5753
- const classifiedWarnings = apResult.warnings.map(classifyWarning);
5871
+ const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths));
5754
5872
  const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
5755
5873
  const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
5756
5874
  const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
@@ -5785,12 +5903,26 @@ async function preCommitCheck(input, ctx) {
5785
5903
  }))
5786
5904
  };
5787
5905
  }
5788
- function classifyWarning(warning) {
5906
+ function classifyWarning(warning, paths) {
5907
+ const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
5908
+ const repairCommand = repairCommandForWarning(warning, affectedFiles);
5909
+ const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
5910
+ if (fileDowngrade) {
5911
+ return {
5912
+ ...warning,
5913
+ level: "info",
5914
+ rationale: fileDowngrade,
5915
+ affected_files: affectedFiles,
5916
+ repair_command: repairCommand
5917
+ };
5918
+ }
5789
5919
  if (isBlockingWarning(warning)) {
5790
5920
  return {
5791
5921
  ...warning,
5792
5922
  level: "blocking",
5793
- rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)"
5923
+ rationale: "authoritative/trusted memory plus strong semantic match to the diff (score >= 0.65)",
5924
+ affected_files: affectedFiles,
5925
+ repair_command: repairCommand
5794
5926
  };
5795
5927
  }
5796
5928
  const hasSemantic = warning.reasons.includes("semantic");
@@ -5800,13 +5932,17 @@ function classifyWarning(warning) {
5800
5932
  return {
5801
5933
  ...warning,
5802
5934
  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"
5935
+ rationale: hasSemantic ? "semantic match is plausible but below blocking threshold" : "anchored high-confidence memory also matched diff tokens, but no strong semantic proof",
5936
+ affected_files: affectedFiles,
5937
+ repair_command: repairCommand
5804
5938
  };
5805
5939
  }
5806
5940
  return {
5807
5941
  ...warning,
5808
5942
  level: "info",
5809
- rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output"
5943
+ rationale: "weak signal only (literal/anchor/low semantic evidence); surfaced for audit, hidden in concise CLI output",
5944
+ affected_files: affectedFiles,
5945
+ repair_command: repairCommand
5810
5946
  };
5811
5947
  }
5812
5948
  function isBlockingWarning(warning) {
@@ -5814,6 +5950,40 @@ function isBlockingWarning(warning) {
5814
5950
  if (!highConfidence) return false;
5815
5951
  return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
5816
5952
  }
5953
+ function fileTypeDowngradeReason(warning, paths) {
5954
+ if (paths.length === 0) return null;
5955
+ if (paths.every((p) => p.startsWith(".ai/.usage/") || p === ".ai/.usage/tool-usage.jsonl")) {
5956
+ return ".ai usage logs are local telemetry and never block commits.";
5957
+ }
5958
+ const docsOnly = paths.every(isDocLikePath);
5959
+ if (docsOnly && !hasStrongSemantic(warning)) {
5960
+ return "docs/changelog-only change; anti-pattern is downgraded unless semantic evidence is strong.";
5961
+ }
5962
+ const configOnly = paths.every(isPackageOrConfigPath);
5963
+ if (configOnly && looksRuntimeSpecific(warning) && !warning.reasons.includes("anchor") && !hasStrongSemantic(warning)) {
5964
+ return "package/config-only change; runtime-specific gotcha is not anchored or semantically strong.";
5965
+ }
5966
+ return null;
5967
+ }
5968
+ function hasStrongSemantic(warning) {
5969
+ return warning.reasons.includes("semantic") && (warning.semantic_score ?? 0) >= 0.65;
5970
+ }
5971
+ function isDocLikePath(file) {
5972
+ const lower = file.toLowerCase();
5973
+ return lower.endsWith(".md") || lower.includes("changelog") || lower.startsWith("docs/") || lower.startsWith(".github/") && lower.endsWith(".md");
5974
+ }
5975
+ function isPackageOrConfigPath(file) {
5976
+ const lower = file.toLowerCase();
5977
+ return lower.endsWith("package.json") || lower.endsWith("package-lock.json") || lower.endsWith("pnpm-lock.yaml") || lower.endsWith("yarn.lock") || lower.endsWith("bun.lockb") || lower.endsWith(".config.ts") || lower.endsWith(".config.js") || lower.endsWith(".json") || lower.endsWith(".yml") || lower.endsWith(".yaml") || lower.startsWith(".github/workflows/");
5978
+ }
5979
+ function looksRuntimeSpecific(warning) {
5980
+ const text = `${warning.body_preview} ${warning.id}`.toLowerCase();
5981
+ return /\b(runtime|controller|request|response|database|transaction|auth|cache|production|service|api|endpoint)\b/.test(text);
5982
+ }
5983
+ function repairCommandForWarning(warning, paths) {
5984
+ const firstPath = paths[0];
5985
+ return firstPath ? `haive briefing --files "${firstPath}" --task "review ${warning.id}"` : `haive memory show ${warning.id}`;
5986
+ }
5817
5987
  var CONFIG_PATTERNS = [
5818
5988
  ".eslintrc",
5819
5989
  "eslint.config",
@@ -6345,7 +6515,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6345
6515
  };
6346
6516
  }
6347
6517
  var SERVER_NAME = "haive";
6348
- var SERVER_VERSION = "0.9.16";
6518
+ var SERVER_VERSION = "0.9.18";
6349
6519
  function jsonResult(data) {
6350
6520
  return {
6351
6521
  content: [
@@ -7313,161 +7483,652 @@ function registerMcp(program2) {
7313
7483
  }
7314
7484
 
7315
7485
  // src/commands/sync.ts
7316
- import { spawnSync as spawnSync3 } from "child_process";
7317
- import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
7318
- import { existsSync as existsSync29 } from "fs";
7319
- import path13 from "path";
7486
+ import { spawnSync as spawnSync4 } from "child_process";
7487
+ import { readFile as readFile9, writeFile as writeFile15, mkdir as mkdir10 } from "fs/promises";
7488
+ import { existsSync as existsSync31 } from "fs";
7489
+ import path15 from "path";
7320
7490
  import "commander";
7321
7491
  import {
7322
7492
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
7323
7493
  buildFrontmatter as buildFrontmatter6,
7324
- findProjectRoot as findProjectRoot11,
7325
- getUsage as getUsage10,
7494
+ findProjectRoot as findProjectRoot12,
7495
+ getUsage as getUsage11,
7326
7496
  isAutoPromoteEligible as isAutoPromoteEligible2,
7327
7497
  isDecaying as isDecaying2,
7328
- loadCodeMap as loadCodeMap4,
7329
- loadConfig as loadConfig4,
7330
- loadMemoriesFromDir as loadMemoriesFromDir23,
7331
- loadUsageIndex as loadUsageIndex12,
7498
+ loadCodeMap as loadCodeMap6,
7499
+ loadConfig as loadConfig5,
7500
+ loadMemoriesFromDir as loadMemoriesFromDir24,
7501
+ loadUsageIndex as loadUsageIndex13,
7332
7502
  pullCrossRepoSources,
7333
- resolveHaivePaths as resolveHaivePaths8,
7503
+ resolveHaivePaths as resolveHaivePaths9,
7334
7504
  resolveManifestFiles,
7335
- serializeMemory as serializeMemory11,
7505
+ serializeMemory as serializeMemory12,
7336
7506
  trackDependencies,
7337
7507
  verifyAnchor as verifyAnchor2,
7338
7508
  watchContracts
7339
7509
  } from "@hiveai/core";
7340
- var BRIDGE_START = "<!-- haive:memories-start -->";
7341
- var BRIDGE_END = "<!-- haive:memories-end -->";
7342
- function registerSync(program2) {
7343
- program2.command("sync").description(
7344
- "Refresh memory state after a git pull or merge.\n What it does:\n 1. Verifies anchor paths \u2014 marks stale if files/symbols moved or deleted\n 2. Re-validates previously stale memories that are now fresh\n 3. Auto-promotes proposed memories (by usage count or time delay in autopilot)\n 4. Auto-refreshes code-map if source files changed\n 5. Reports decay warnings for memories unused >90 days\n\n Install git hooks to run sync automatically: haive install-hooks\n\n Examples:\n haive sync\n haive sync --since main # also report memories changed since main\n haive sync --embed # also rebuild embeddings index\n"
7345
- ).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
7346
- "--since <ref>",
7347
- "git ref/commit to compare against; report memories added/modified/removed since"
7348
- ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
7349
- "--inject-bridge",
7350
- "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
7351
- ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
7352
- const root = findProjectRoot11(opts.dir);
7353
- const paths = resolveHaivePaths8(root);
7354
- if (!existsSync29(paths.memoriesDir)) {
7355
- if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7356
- process.exitCode = 1;
7357
- return;
7510
+
7511
+ // src/utils/autopilot.ts
7512
+ import { existsSync as existsSync30 } from "fs";
7513
+ import { readFile as readFile8, writeFile as writeFile14 } from "fs/promises";
7514
+ import path14 from "path";
7515
+ import {
7516
+ AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
7517
+ buildCodeMap as buildCodeMap3,
7518
+ loadCodeMap as loadCodeMap5,
7519
+ loadConfig as loadConfig4,
7520
+ saveCodeMap as saveCodeMap3,
7521
+ saveConfig as saveConfig2
7522
+ } from "@hiveai/core";
7523
+
7524
+ // src/commands/memory-lint.ts
7525
+ import { existsSync as existsSync29 } from "fs";
7526
+ import { writeFile as writeFile13 } from "fs/promises";
7527
+ import { spawnSync as spawnSync3 } from "child_process";
7528
+ import path13 from "path";
7529
+ import "commander";
7530
+ import {
7531
+ findProjectRoot as findProjectRoot11,
7532
+ getUsage as getUsage10,
7533
+ loadCodeMap as loadCodeMap4,
7534
+ loadMemoriesFromDir as loadMemoriesFromDir23,
7535
+ loadUsageIndex as loadUsageIndex12,
7536
+ resolveHaivePaths as resolveHaivePaths8,
7537
+ serializeMemory as serializeMemory11
7538
+ } from "@hiveai/core";
7539
+ async function lintMemoriesAsync(root, options = {}) {
7540
+ const paths = resolveHaivePaths8(root);
7541
+ const out = [];
7542
+ const fixes = [];
7543
+ if (!existsSync29(paths.memoriesDir)) return { findings: out, fixes };
7544
+ const loaded = await loadMemoriesFromDir23(paths.memoriesDir);
7545
+ const usage = await loadUsageIndex12(paths);
7546
+ const codeMap = await loadCodeMap4(paths);
7547
+ const trackedFiles = gitTrackedFiles(root);
7548
+ const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
7549
+ const actionableWords = /\b(always|never|prefer|use|run|avoid|because|instead|why|rationale|do not|must|should|require|required|requires|fix|fail|failed|fails|prevent|prevents|allow|allows|lets|ensure|ensures|catch|catches)\b/i;
7550
+ for (const { filePath, memory: memory2 } of loaded) {
7551
+ const fm = memory2.frontmatter;
7552
+ if (fm.type === "session_recap") continue;
7553
+ const body = memory2.body.trim();
7554
+ const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
7555
+ if (naked.length < 40 && fm.status !== "rejected") {
7556
+ out.push({
7557
+ file: filePath,
7558
+ id: fm.id,
7559
+ severity: "warn",
7560
+ code: "SHORT_BODY",
7561
+ message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
7562
+ });
7358
7563
  }
7359
- const log = (msg) => {
7360
- if (!opts.quiet) console.log(msg);
7361
- };
7362
- const config = await loadConfig4(paths);
7363
- const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
7364
- const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
7365
- let staleMarked = 0;
7366
- let revalidated = 0;
7367
- let promoted = 0;
7368
- let autoApproved = 0;
7369
- if (opts.verify !== false) {
7370
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
7371
- for (const { memory: memory2, filePath } of memories) {
7372
- if (memory2.frontmatter.type === "session_recap") {
7373
- if (memory2.frontmatter.status === "stale") {
7374
- await writeFile13(
7375
- filePath,
7376
- serializeMemory11({
7377
- frontmatter: {
7378
- ...memory2.frontmatter,
7379
- status: "validated",
7380
- stale_reason: null,
7381
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
7382
- },
7383
- body: memory2.body
7384
- }),
7385
- "utf8"
7386
- );
7387
- revalidated++;
7388
- }
7389
- continue;
7390
- }
7391
- const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
7392
- if (!isAnchored) continue;
7393
- const result = await verifyAnchor2(memory2, { projectRoot: root });
7394
- const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
7395
- if (result.stale) {
7396
- if (memory2.frontmatter.status !== "stale") {
7397
- await writeFile13(
7398
- filePath,
7399
- serializeMemory11({
7400
- frontmatter: {
7401
- ...memory2.frontmatter,
7402
- status: "stale",
7403
- verified_at: verifiedAt,
7404
- stale_reason: result.reason
7405
- },
7406
- body: memory2.body
7407
- }),
7408
- "utf8"
7409
- );
7410
- staleMarked++;
7411
- }
7412
- } else if (memory2.frontmatter.status === "stale") {
7413
- await writeFile13(
7414
- filePath,
7415
- serializeMemory11({
7416
- frontmatter: {
7417
- ...memory2.frontmatter,
7418
- status: "validated",
7419
- verified_at: verifiedAt,
7420
- stale_reason: null
7421
- },
7422
- body: memory2.body
7423
- }),
7424
- "utf8"
7425
- );
7426
- revalidated++;
7564
+ if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
7565
+ out.push({
7566
+ file: filePath,
7567
+ id: fm.id,
7568
+ severity: "info",
7569
+ code: "LOW_ACTIONABILITY",
7570
+ message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
7571
+ });
7572
+ }
7573
+ const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles);
7574
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
7575
+ out.push({
7576
+ file: filePath,
7577
+ id: fm.id,
7578
+ severity: "warn",
7579
+ code: "MISSING_ANCHOR",
7580
+ message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
7581
+ ...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
7582
+ });
7583
+ }
7584
+ if (fm.status === "stale" && !fm.stale_reason) {
7585
+ out.push({
7586
+ file: filePath,
7587
+ id: fm.id,
7588
+ severity: "info",
7589
+ code: "STALE_NO_REASON",
7590
+ message: "Status is stale but stale_reason is empty \u2014 document why when possible."
7591
+ });
7592
+ }
7593
+ if (fm.type === "glossary" && naked.length > 6e3) {
7594
+ out.push({
7595
+ file: filePath,
7596
+ id: fm.id,
7597
+ severity: "info",
7598
+ code: "LONG_GLOSSARY",
7599
+ message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
7600
+ });
7601
+ }
7602
+ const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
7603
+ if (!hasMarkdownHeading) {
7604
+ out.push({
7605
+ file: filePath,
7606
+ id: fm.id,
7607
+ severity: "warn",
7608
+ code: "NO_MD_HEADING",
7609
+ message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
7610
+ });
7611
+ }
7612
+ const u = getUsage10(usage, fm.id);
7613
+ if (fm.status === "validated" && u.read_count === 0) {
7614
+ out.push({
7615
+ file: filePath,
7616
+ id: fm.id,
7617
+ severity: "info",
7618
+ code: "NEVER_READ",
7619
+ message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
7620
+ });
7621
+ }
7622
+ if (options.fix) {
7623
+ const actions = [];
7624
+ let nextBody = memory2.body;
7625
+ let nextFrontmatter = memory2.frontmatter;
7626
+ if (!hasMarkdownHeading) {
7627
+ nextBody = `# ${titleFromId(fm.id)}
7628
+
7629
+ ${nextBody.trim()}`;
7630
+ actions.push("add missing Markdown heading");
7631
+ }
7632
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && suggestedAnchors.paths.length > 0) {
7633
+ nextFrontmatter = {
7634
+ ...nextFrontmatter,
7635
+ anchor: {
7636
+ ...nextFrontmatter.anchor,
7637
+ paths: [.../* @__PURE__ */ new Set([...nextFrontmatter.anchor.paths, ...suggestedAnchors.paths])],
7638
+ symbols: [
7639
+ .../* @__PURE__ */ new Set([...nextFrontmatter.anchor.symbols, ...suggestedAnchors.symbols])
7640
+ ]
7641
+ },
7642
+ tags: nextFrontmatter.tags.filter((tag) => tag !== "needs_anchor")
7643
+ };
7644
+ actions.push("add suggested tracked anchor paths");
7645
+ if (suggestedAnchors.symbols.length > 0) {
7646
+ actions.push("add suggested anchor symbols");
7427
7647
  }
7428
7648
  }
7429
- }
7430
- if (opts.promote !== false) {
7431
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
7432
- const usage = await loadUsageIndex12(paths);
7433
- const nowMs = Date.now();
7434
- for (const { memory: memory2, filePath } of memories) {
7435
- const fm = memory2.frontmatter;
7436
- if (fm.type === "session_recap") continue;
7437
- if (isAutoPromoteEligible2(fm, getUsage10(usage, fm.id), {
7438
- minReads: autoPromoteMinReads,
7439
- maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
7440
- })) {
7649
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.anchor.symbols.length === 0 && suggestedAnchors.paths.length === 0 && fm.status === "validated" && !fm.tags.includes("needs_anchor")) {
7650
+ nextFrontmatter = {
7651
+ ...nextFrontmatter,
7652
+ tags: [...nextFrontmatter.tags, "needs_anchor"]
7653
+ };
7654
+ actions.push("tag validated anchorless record with needs_anchor");
7655
+ }
7656
+ if (actions.length > 0) {
7657
+ fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
7658
+ if (options.apply) {
7441
7659
  await writeFile13(
7442
7660
  filePath,
7443
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
7661
+ serializeMemory11({ frontmatter: nextFrontmatter, body: nextBody }),
7444
7662
  "utf8"
7445
7663
  );
7446
- promoted++;
7447
- continue;
7448
- }
7449
- if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
7450
- const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
7451
- if (ageHours >= autoApproveDelayHours) {
7452
- await writeFile13(
7453
- filePath,
7454
- serializeMemory11({
7455
- frontmatter: {
7456
- ...fm,
7457
- status: "validated",
7458
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
7459
- },
7460
- body: memory2.body
7461
- }),
7462
- "utf8"
7463
- );
7464
- autoApproved++;
7465
- }
7466
7664
  }
7467
7665
  }
7468
7666
  }
7469
- const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
7470
- const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
7667
+ }
7668
+ for (const dup of nearDuplicatePairs(loaded)) {
7669
+ out.push({
7670
+ file: dup.file,
7671
+ id: dup.id,
7672
+ severity: "warn",
7673
+ code: "NEAR_DUPLICATE",
7674
+ message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
7675
+ });
7676
+ }
7677
+ return { findings: out, fixes };
7678
+ }
7679
+ function titleFromId(id) {
7680
+ const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
7681
+ return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
7682
+ }
7683
+ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
7684
+ const body = loaded.memory.body;
7685
+ const paths = /* @__PURE__ */ new Set();
7686
+ const symbols = /* @__PURE__ */ new Set();
7687
+ for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
7688
+ const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
7689
+ if (!candidate || candidate.startsWith("http")) continue;
7690
+ if (existsSync29(path13.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
7691
+ paths.add(candidate);
7692
+ }
7693
+ }
7694
+ if (codeMap) {
7695
+ const lowered = body.toLowerCase();
7696
+ for (const [file, entry] of Object.entries(codeMap.files)) {
7697
+ for (const exp of entry.exports) {
7698
+ if (!exp.name || exp.name.length < 4) continue;
7699
+ if (lowered.includes(exp.name.toLowerCase())) {
7700
+ if (isSafeAnchorPath(file, trackedFiles)) {
7701
+ paths.add(file);
7702
+ symbols.add(exp.name);
7703
+ }
7704
+ }
7705
+ if (paths.size >= 5 && symbols.size >= 5) break;
7706
+ }
7707
+ if (paths.size >= 5 && symbols.size >= 5) break;
7708
+ }
7709
+ }
7710
+ return {
7711
+ paths: [...paths].slice(0, 5),
7712
+ symbols: [...symbols].slice(0, 5)
7713
+ };
7714
+ }
7715
+ function gitTrackedFiles(root) {
7716
+ const result = spawnSync3("git", ["ls-files"], {
7717
+ cwd: root,
7718
+ encoding: "utf8",
7719
+ stdio: ["ignore", "pipe", "ignore"]
7720
+ });
7721
+ if (result.status !== 0) return null;
7722
+ const files = result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
7723
+ return new Set(files);
7724
+ }
7725
+ function isSafeAnchorPath(file, trackedFiles) {
7726
+ const normalized = file.replace(/\\/g, "/").replace(/^\.?\//, "");
7727
+ if (normalized.startsWith(".ai/.cache/") || normalized.startsWith(".ai/.runtime/")) return false;
7728
+ if (normalized.includes("/node_modules/") || normalized.startsWith("node_modules/")) return false;
7729
+ if (normalized.includes("/dist/") || normalized.startsWith("dist/")) return false;
7730
+ if (trackedFiles && !trackedFiles.has(normalized)) return false;
7731
+ return true;
7732
+ }
7733
+ function nearDuplicatePairs(loaded) {
7734
+ const out = [];
7735
+ const candidates = loaded.filter(({ memory: memory2 }) => {
7736
+ const fm = memory2.frontmatter;
7737
+ return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
7738
+ });
7739
+ for (let i = 0; i < candidates.length; i++) {
7740
+ for (let j = i + 1; j < candidates.length; j++) {
7741
+ const a = candidates[i];
7742
+ const b = candidates[j];
7743
+ if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
7744
+ if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
7745
+ const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
7746
+ if (score >= 0.72) {
7747
+ out.push({
7748
+ id: a.memory.frontmatter.id,
7749
+ otherId: b.memory.frontmatter.id,
7750
+ file: a.filePath,
7751
+ score
7752
+ });
7753
+ }
7754
+ }
7755
+ }
7756
+ return out;
7757
+ }
7758
+ function tokenSet(body) {
7759
+ return new Set(
7760
+ (body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
7761
+ );
7762
+ }
7763
+ function jaccard2(a, b) {
7764
+ if (a.size === 0 || b.size === 0) return 0;
7765
+ let inter = 0;
7766
+ for (const item of a) if (b.has(item)) inter++;
7767
+ return inter / (a.size + b.size - inter);
7768
+ }
7769
+ function registerMemoryLint(parent) {
7770
+ parent.command("lint").description(
7771
+ "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
7772
+ ).option("--json", "emit findings as JSON", false).option("--fix", "prepare simple automatic fixes (use with --dry-run or --apply)", false).option("--dry-run", "with --fix, show files that would change without writing", false).option("--apply", "with --fix, write simple fixes to disk", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
7773
+ const root = findProjectRoot11(opts.dir);
7774
+ const apply = Boolean(opts.fix && opts.apply);
7775
+ const dryRun = Boolean(opts.fix && (opts.dryRun || !opts.apply));
7776
+ const report = await lintMemoriesAsync(root, { fix: Boolean(opts.fix), apply });
7777
+ const findings = report.findings;
7778
+ if (opts.json) {
7779
+ console.log(JSON.stringify({
7780
+ findings_count: findings.length,
7781
+ findings,
7782
+ fixes_count: report.fixes.length,
7783
+ fixes: report.fixes,
7784
+ fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
7785
+ }, null, 2));
7786
+ process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
7787
+ return;
7788
+ }
7789
+ if (findings.length === 0) {
7790
+ ui.success(`memory lint OK \u2014 ${root}`);
7791
+ return;
7792
+ }
7793
+ console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
7794
+ `);
7795
+ if (opts.fix) {
7796
+ const mode = apply ? "apply" : dryRun ? "dry-run" : "dry-run";
7797
+ const verb = apply ? "changed" : "would change";
7798
+ console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
7799
+ for (const fix of report.fixes) {
7800
+ console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
7801
+ console.log(ui.dim(` \u2192 ${fix.file}`));
7802
+ }
7803
+ console.log();
7804
+ }
7805
+ const order = { error: 0, warn: 1, info: 2 };
7806
+ findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
7807
+ for (const f of findings) {
7808
+ const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
7809
+ console.log(
7810
+ `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
7811
+ );
7812
+ console.log(` ${f.message}`);
7813
+ if (f.suggested_anchors) {
7814
+ const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
7815
+ const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
7816
+ console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
7817
+ }
7818
+ console.log(ui.dim(` \u2192 ${f.file}`));
7819
+ }
7820
+ process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
7821
+ });
7822
+ }
7823
+
7824
+ // src/utils/autopilot.ts
7825
+ async function applyAutopilotRepairs(root, paths, options = {}) {
7826
+ const repairs = [];
7827
+ const config = await loadConfig4(paths);
7828
+ if (options.applyConfig) {
7829
+ const changed = await ensureAutopilotConfig(paths, config);
7830
+ if (changed) {
7831
+ repairs.push({
7832
+ code: "autopilot-config",
7833
+ message: "Enabled autopilot defaults in .ai/haive.config.json."
7834
+ });
7835
+ }
7836
+ }
7837
+ const current = await loadConfig4(paths);
7838
+ const autoRepair = current.autoRepair ?? {};
7839
+ if (options.applyContext ?? autoRepair.context ?? current.autopilot) {
7840
+ const changed = await syncProjectContextVersion(root, paths);
7841
+ if (changed) {
7842
+ repairs.push({
7843
+ code: "project-context-version",
7844
+ message: "Updated .ai/project-context.md version metadata from package.json."
7845
+ });
7846
+ }
7847
+ }
7848
+ if (options.applyCorpus ?? autoRepair.corpus ?? current.autopilot) {
7849
+ const report = await lintMemoriesAsync(root, { fix: true, apply: true });
7850
+ const applied = report.fixes.filter((fix) => fix.applied);
7851
+ if (applied.length > 0) {
7852
+ repairs.push({
7853
+ code: "memory-lint-fix",
7854
+ message: `Applied ${applied.length} safe memory lint fix${applied.length === 1 ? "" : "es"}.`
7855
+ });
7856
+ }
7857
+ }
7858
+ if (options.applyCodeMap ?? autoRepair.codeMap ?? current.autopilot) {
7859
+ const refreshed = await refreshCodeMap(root, paths, Boolean(options.forceCodeMap));
7860
+ if (refreshed) {
7861
+ repairs.push({
7862
+ code: "code-map-refresh",
7863
+ message: "Refreshed .ai/code-map.json."
7864
+ });
7865
+ }
7866
+ }
7867
+ if (options.applyCodeSearch ?? autoRepair.codeSearch ?? current.autopilot) {
7868
+ const indexed = await refreshCodeSearchIndex(paths);
7869
+ if (indexed) {
7870
+ repairs.push({
7871
+ code: "code-search-index",
7872
+ message: "Refreshed code-search embeddings index."
7873
+ });
7874
+ }
7875
+ }
7876
+ return repairs;
7877
+ }
7878
+ async function ensureAutopilotConfig(paths, currentConfig) {
7879
+ const current = currentConfig ?? await loadConfig4(paths);
7880
+ const next = {
7881
+ ...current,
7882
+ autopilot: true,
7883
+ defaultScope: "team",
7884
+ defaultStatus: "validated",
7885
+ autoApproveDelayHours: current.autoApproveDelayHours ?? AUTOPILOT_DEFAULTS2.autoApproveDelayHours,
7886
+ autoPromoteMinReads: current.autoPromoteMinReads ?? AUTOPILOT_DEFAULTS2.autoPromoteMinReads,
7887
+ autoSessionEnd: true,
7888
+ autoContext: true,
7889
+ autoRepair: {
7890
+ context: true,
7891
+ corpus: true,
7892
+ codeMap: true,
7893
+ codeSearch: current.autoRepair?.codeSearch ?? true
7894
+ },
7895
+ enforcement: {
7896
+ ...AUTOPILOT_DEFAULTS2.enforcement,
7897
+ ...current.enforcement,
7898
+ mode: "strict",
7899
+ requireBriefingFirst: true,
7900
+ requireSessionRecap: true,
7901
+ requireMemoryVerify: true,
7902
+ blockStaleDecisionChanges: true,
7903
+ requireDecisionCoverage: true,
7904
+ cleanupGeneratedArtifacts: true,
7905
+ toolProfile: current.enforcement?.toolProfile ?? "enforcement"
7906
+ }
7907
+ };
7908
+ if (JSON.stringify(current) === JSON.stringify(next)) return false;
7909
+ await saveConfig2(paths, next);
7910
+ return true;
7911
+ }
7912
+ async function syncProjectContextVersion(root, paths) {
7913
+ const status = await projectContextVersionStatus(root, paths);
7914
+ if (!status.canSync || !status.expectedVersion) return false;
7915
+ const original = await readFile8(paths.projectContext, "utf8");
7916
+ let updated = original.replace(
7917
+ /^# Project context — hAIve \(v[^)]+\)$/m,
7918
+ `# Project context \u2014 hAIve (v${status.expectedVersion})`
7919
+ ).replace(
7920
+ /> \*\*Current version\*\*: [^—\n]+—/m,
7921
+ `> **Current version**: ${status.expectedVersion} \u2014`
7922
+ );
7923
+ if (updated === original && !original.includes("Current version")) {
7924
+ updated = original.replace(
7925
+ /^(> Repo-native context enforcement[^\n]*\n)/m,
7926
+ `$1> **Current version**: ${status.expectedVersion} \u2014 @hiveai/core, cli, mcp, embeddings are versioned together.
7927
+ `
7928
+ );
7929
+ }
7930
+ if (updated === original) return false;
7931
+ await writeFile14(paths.projectContext, updated, "utf8");
7932
+ return true;
7933
+ }
7934
+ async function projectContextVersionStatus(root, paths) {
7935
+ if (!existsSync30(paths.projectContext)) {
7936
+ return { mismatch: false, canSync: false };
7937
+ }
7938
+ const packagePath = path14.join(root, "package.json");
7939
+ if (!existsSync30(packagePath)) {
7940
+ return { mismatch: false, canSync: false };
7941
+ }
7942
+ const packageJson = JSON.parse(await readFile8(packagePath, "utf8"));
7943
+ const expectedVersion = packageJson.version;
7944
+ if (!expectedVersion) {
7945
+ return { mismatch: false, canSync: false };
7946
+ }
7947
+ const content = await readFile8(paths.projectContext, "utf8");
7948
+ const headingVersion = content.match(/^# Project context — hAIve \(v([^)]+)\)$/m)?.[1];
7949
+ const currentLineVersion = content.match(/^> \*\*Current version\*\*: ([^—\n]+)—/m)?.[1]?.trim();
7950
+ const currentVersion = currentLineVersion ?? headingVersion;
7951
+ return {
7952
+ expectedVersion,
7953
+ currentVersion,
7954
+ mismatch: currentVersion !== expectedVersion,
7955
+ canSync: true
7956
+ };
7957
+ }
7958
+ async function refreshCodeMap(root, paths, force) {
7959
+ if (!force) {
7960
+ const existing = await loadCodeMap5(paths);
7961
+ if (existing) return false;
7962
+ }
7963
+ const map = await buildCodeMap3(root, {
7964
+ excludeDirs: [
7965
+ "node_modules",
7966
+ "dist",
7967
+ "build",
7968
+ "out",
7969
+ ".git",
7970
+ ".next",
7971
+ ".turbo",
7972
+ ".vitest-cache",
7973
+ "coverage"
7974
+ ]
7975
+ });
7976
+ await saveCodeMap3(paths, map);
7977
+ return true;
7978
+ }
7979
+ async function refreshCodeSearchIndex(paths) {
7980
+ try {
7981
+ const mod = await import("@hiveai/embeddings");
7982
+ const embedder = await mod.Embedder.create();
7983
+ const { report } = await mod.rebuildCodeIndex(paths, embedder);
7984
+ return report.added > 0 || report.updated > 0 || report.removed > 0;
7985
+ } catch {
7986
+ return false;
7987
+ }
7988
+ }
7989
+
7990
+ // src/commands/sync.ts
7991
+ var BRIDGE_START = "<!-- haive:memories-start -->";
7992
+ var BRIDGE_END = "<!-- haive:memories-end -->";
7993
+ function registerSync(program2) {
7994
+ program2.command("sync").description(
7995
+ "Refresh memory state after a git pull or merge.\n What it does:\n 1. Verifies anchor paths \u2014 marks stale if files/symbols moved or deleted\n 2. Re-validates previously stale memories that are now fresh\n 3. Auto-promotes proposed memories (by usage count or time delay in autopilot)\n 4. Auto-refreshes code-map if source files changed\n 5. Reports decay warnings for memories unused >90 days\n\n Install git hooks to run sync automatically: haive install-hooks\n\n Examples:\n haive sync\n haive sync --since main # also report memories changed since main\n haive sync --embed # also rebuild embeddings index\n"
7996
+ ).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
7997
+ "--since <ref>",
7998
+ "git ref/commit to compare against; report memories added/modified/removed since"
7999
+ ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
8000
+ "--inject-bridge",
8001
+ "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
8002
+ ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
8003
+ const root = findProjectRoot12(opts.dir);
8004
+ const paths = resolveHaivePaths9(root);
8005
+ if (!existsSync31(paths.memoriesDir)) {
8006
+ if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8007
+ process.exitCode = 1;
8008
+ return;
8009
+ }
8010
+ const log = (msg) => {
8011
+ if (!opts.quiet) console.log(msg);
8012
+ };
8013
+ const config = await loadConfig5(paths);
8014
+ const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
8015
+ const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
8016
+ const autoRepair = config.autoRepair ?? {};
8017
+ let staleMarked = 0;
8018
+ let revalidated = 0;
8019
+ let promoted = 0;
8020
+ let autoApproved = 0;
8021
+ if (opts.verify !== false) {
8022
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8023
+ for (const { memory: memory2, filePath } of memories) {
8024
+ if (memory2.frontmatter.type === "session_recap") {
8025
+ if (memory2.frontmatter.status === "stale") {
8026
+ await writeFile15(
8027
+ filePath,
8028
+ serializeMemory12({
8029
+ frontmatter: {
8030
+ ...memory2.frontmatter,
8031
+ status: "validated",
8032
+ stale_reason: null,
8033
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8034
+ },
8035
+ body: memory2.body
8036
+ }),
8037
+ "utf8"
8038
+ );
8039
+ revalidated++;
8040
+ }
8041
+ continue;
8042
+ }
8043
+ const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
8044
+ if (!isAnchored) continue;
8045
+ const result = await verifyAnchor2(memory2, { projectRoot: root });
8046
+ const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
8047
+ if (result.stale) {
8048
+ if (memory2.frontmatter.status !== "stale") {
8049
+ await writeFile15(
8050
+ filePath,
8051
+ serializeMemory12({
8052
+ frontmatter: {
8053
+ ...memory2.frontmatter,
8054
+ status: "stale",
8055
+ verified_at: verifiedAt,
8056
+ stale_reason: result.reason
8057
+ },
8058
+ body: memory2.body
8059
+ }),
8060
+ "utf8"
8061
+ );
8062
+ staleMarked++;
8063
+ }
8064
+ } else if (memory2.frontmatter.status === "stale") {
8065
+ await writeFile15(
8066
+ filePath,
8067
+ serializeMemory12({
8068
+ frontmatter: {
8069
+ ...memory2.frontmatter,
8070
+ status: "validated",
8071
+ verified_at: verifiedAt,
8072
+ stale_reason: null
8073
+ },
8074
+ body: memory2.body
8075
+ }),
8076
+ "utf8"
8077
+ );
8078
+ revalidated++;
8079
+ }
8080
+ }
8081
+ }
8082
+ if (opts.promote !== false) {
8083
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8084
+ const usage = await loadUsageIndex13(paths);
8085
+ const nowMs = Date.now();
8086
+ for (const { memory: memory2, filePath } of memories) {
8087
+ const fm = memory2.frontmatter;
8088
+ if (fm.type === "session_recap") continue;
8089
+ if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
8090
+ minReads: autoPromoteMinReads,
8091
+ maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8092
+ })) {
8093
+ await writeFile15(
8094
+ filePath,
8095
+ serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8096
+ "utf8"
8097
+ );
8098
+ promoted++;
8099
+ continue;
8100
+ }
8101
+ if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
8102
+ const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
8103
+ if (ageHours >= autoApproveDelayHours) {
8104
+ await writeFile15(
8105
+ filePath,
8106
+ serializeMemory12({
8107
+ frontmatter: {
8108
+ ...fm,
8109
+ status: "validated",
8110
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8111
+ },
8112
+ body: memory2.body
8113
+ }),
8114
+ "utf8"
8115
+ );
8116
+ autoApproved++;
8117
+ }
8118
+ }
8119
+ }
8120
+ }
8121
+ if (config.autopilot || autoRepair.context || autoRepair.corpus) {
8122
+ const repairs = await applyAutopilotRepairs(root, paths, {
8123
+ applyContext: autoRepair.context ?? config.autopilot,
8124
+ applyCorpus: autoRepair.corpus ?? config.autopilot,
8125
+ applyCodeMap: false,
8126
+ applyCodeSearch: false
8127
+ });
8128
+ for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8129
+ }
8130
+ const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8131
+ const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
7471
8132
  (m) => m.memory.frontmatter.status === "draft"
7472
8133
  );
7473
8134
  const draftCount = draftMemories.length;
@@ -7483,7 +8144,7 @@ function registerSync(program2) {
7483
8144
  );
7484
8145
  }
7485
8146
  if (opts.injectBridge) {
7486
- const bridgeFile = opts.bridgeFile ? path13.resolve(opts.bridgeFile) : path13.join(root, "CLAUDE.md");
8147
+ const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
7487
8148
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
7488
8149
  await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
7489
8150
  }
@@ -7502,12 +8163,12 @@ function registerSync(program2) {
7502
8163
  }
7503
8164
  }
7504
8165
  if (!opts.quiet) {
7505
- const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
7506
- const usageForDecay = await loadUsageIndex12(paths);
8166
+ const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
8167
+ const usageForDecay = await loadUsageIndex13(paths);
7507
8168
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
7508
8169
  const fm = memory2.frontmatter;
7509
8170
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
7510
- const u = getUsage10(usageForDecay, fm.id);
8171
+ const u = getUsage11(usageForDecay, fm.id);
7511
8172
  return isDecaying2(u, fm.created_at);
7512
8173
  });
7513
8174
  if (decaying.length > 0) {
@@ -7589,11 +8250,11 @@ Attends une **confirmation explicite** avant d'agir.
7589
8250
  paths: [result.file],
7590
8251
  topic: `dep-bump-${slugParts}`
7591
8252
  });
7592
- const teamDir = path13.join(paths.memoriesDir, "team");
8253
+ const teamDir = path15.join(paths.memoriesDir, "team");
7593
8254
  await mkdir10(teamDir, { recursive: true });
7594
- await writeFile13(
7595
- path13.join(teamDir, `${fm.id}.md`),
7596
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8255
+ await writeFile15(
8256
+ path15.join(teamDir, `${fm.id}.md`),
8257
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7597
8258
  "utf8"
7598
8259
  );
7599
8260
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7656,11 +8317,11 @@ Attends une **confirmation explicite** avant d'agir.
7656
8317
  paths: [diff.file],
7657
8318
  topic: `contract-breaking-${diff.contract}`
7658
8319
  });
7659
- const teamDir = path13.join(paths.memoriesDir, "team");
8320
+ const teamDir = path15.join(paths.memoriesDir, "team");
7660
8321
  await mkdir10(teamDir, { recursive: true });
7661
- await writeFile13(
7662
- path13.join(teamDir, `${fm.id}.md`),
7663
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8322
+ await writeFile15(
8323
+ path15.join(teamDir, `${fm.id}.md`),
8324
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7664
8325
  "utf8"
7665
8326
  );
7666
8327
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7670,10 +8331,19 @@ Attends une **confirmation explicite** avant d'agir.
7670
8331
  ui.warn(`contract watcher failed: ${String(err)}`);
7671
8332
  }
7672
8333
  }
7673
- const existingMap = await loadCodeMap4(paths);
7674
- if (existingMap) {
8334
+ const existingMap = await loadCodeMap6(paths);
8335
+ if (!existingMap && (config.autopilot || autoRepair.codeMap)) {
8336
+ try {
8337
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8338
+ log(ui.dim("code-map: missing \u2014 building index\u2026"));
8339
+ const newMap = await buildCodeMap4(root);
8340
+ await saveCodeMap4(paths, newMap);
8341
+ log(ui.dim(`code-map: built (${Object.keys(newMap.files).length} files)`));
8342
+ } catch {
8343
+ }
8344
+ } else if (existingMap) {
7675
8345
  const mapAge = new Date(existingMap.generated_at).getTime();
7676
- const gitResult = spawnSync3(
8346
+ const gitResult = spawnSync4(
7677
8347
  "git",
7678
8348
  [
7679
8349
  "diff",
@@ -7698,22 +8368,32 @@ Attends une **confirmation explicite** avant d'agir.
7698
8368
  const changedSourceFiles = (gitResult.stdout ?? "").trim();
7699
8369
  if (changedSourceFiles.length > 0) {
7700
8370
  try {
7701
- const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
8371
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
7702
8372
  log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
7703
- const newMap = await buildCodeMap3(root);
7704
- await saveCodeMap3(paths, newMap);
8373
+ const newMap = await buildCodeMap4(root);
8374
+ await saveCodeMap4(paths, newMap);
7705
8375
  log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
7706
8376
  } catch {
7707
8377
  }
7708
8378
  }
7709
8379
  }
7710
- if (opts.embed) {
8380
+ if (opts.embed || autoRepair.codeSearch) {
7711
8381
  try {
7712
- const { Embedder, rebuildIndex } = await import("@hiveai/embeddings");
8382
+ const { Embedder, rebuildCodeIndex, rebuildIndex } = await import("@hiveai/embeddings");
7713
8383
  log(ui.dim("embed: rebuilding index\u2026"));
7714
8384
  const embedder = await Embedder.create();
7715
8385
  const { report } = await rebuildIndex(paths, embedder);
7716
- log(ui.dim(`embed: index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`));
8386
+ const { report: codeReport } = await rebuildCodeIndex(paths, embedder);
8387
+ log(
8388
+ ui.dim(
8389
+ `embed: memory index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`
8390
+ )
8391
+ );
8392
+ log(
8393
+ ui.dim(
8394
+ `embed: code index rebuilt (${codeReport.total} symbols, ${codeReport.added} added, ${codeReport.updated} updated, ${codeReport.removed} removed)`
8395
+ )
8396
+ );
7717
8397
  } catch {
7718
8398
  ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
7719
8399
  }
@@ -7721,8 +8401,8 @@ Attends une **confirmation explicite** avant d'agir.
7721
8401
  });
7722
8402
  }
7723
8403
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
7724
- if (!existsSync29(memoriesDir)) return;
7725
- const all = await loadMemoriesFromDir23(memoriesDir);
8404
+ if (!existsSync31(memoriesDir)) return;
8405
+ const all = await loadMemoriesFromDir24(memoriesDir);
7726
8406
  const top = all.filter(({ memory: memory2 }) => {
7727
8407
  const s = memory2.frontmatter.status;
7728
8408
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -7746,17 +8426,17 @@ ${m.memory.body.trim()}`;
7746
8426
  ` + block + `
7747
8427
 
7748
8428
  ${BRIDGE_END}`;
7749
- const fileExists = existsSync29(bridgeFile);
7750
- let existing = fileExists ? await readFile8(bridgeFile, "utf8") : "";
8429
+ const fileExists = existsSync31(bridgeFile);
8430
+ let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
7751
8431
  existing = existing.replace(/\r\n/g, "\n");
7752
8432
  const startIdx = existing.indexOf(BRIDGE_START);
7753
8433
  const endIdx = existing.indexOf(BRIDGE_END);
7754
8434
  if (startIdx !== -1 && endIdx === -1) {
7755
- ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
8435
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
7756
8436
  return;
7757
8437
  }
7758
8438
  if (startIdx === -1 && endIdx !== -1) {
7759
- ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
8439
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
7760
8440
  return;
7761
8441
  }
7762
8442
  let updated;
@@ -7764,19 +8444,19 @@ ${BRIDGE_END}`;
7764
8444
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
7765
8445
  } else {
7766
8446
  if (!fileExists && !quiet) {
7767
- ui.info(`Creating ${path13.relative(root, bridgeFile)} with haive memory block.`);
8447
+ ui.info(`Creating ${path15.relative(root, bridgeFile)} with haive memory block.`);
7768
8448
  }
7769
8449
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
7770
8450
  }
7771
- await writeFile13(bridgeFile, updated, "utf8");
8451
+ await writeFile15(bridgeFile, updated, "utf8");
7772
8452
  if (!quiet) {
7773
8453
  console.log(
7774
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path13.relative(root, bridgeFile)}`)
8454
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
7775
8455
  );
7776
8456
  }
7777
8457
  }
7778
8458
  function collectSinceChanges(root, ref) {
7779
- const result = spawnSync3(
8459
+ const result = spawnSync4(
7780
8460
  "git",
7781
8461
  ["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
7782
8462
  { encoding: "utf8" }
@@ -7796,18 +8476,19 @@ function collectSinceChanges(root, ref) {
7796
8476
 
7797
8477
  // src/commands/memory-add.ts
7798
8478
  import { createHash as createHash2 } from "crypto";
7799
- import { mkdir as mkdir11, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7800
- import { existsSync as existsSync30 } from "fs";
7801
- import path14 from "path";
8479
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile16 } from "fs/promises";
8480
+ import { existsSync as existsSync33 } from "fs";
8481
+ import path16 from "path";
7802
8482
  import "commander";
7803
8483
  import {
7804
8484
  buildFrontmatter as buildFrontmatter7,
7805
- findProjectRoot as findProjectRoot12,
8485
+ findProjectRoot as findProjectRoot13,
7806
8486
  inferModulesFromPaths as inferModulesFromPaths3,
7807
- loadMemoriesFromDir as loadMemoriesFromDir24,
8487
+ loadConfig as loadConfig6,
8488
+ loadMemoriesFromDir as loadMemoriesFromDir25,
7808
8489
  memoryFilePath as memoryFilePath6,
7809
- resolveHaivePaths as resolveHaivePaths9,
7810
- serializeMemory as serializeMemory12
8490
+ resolveHaivePaths as resolveHaivePaths10,
8491
+ serializeMemory as serializeMemory13
7811
8492
  } from "@hiveai/core";
7812
8493
  function registerMemoryAdd(memory2) {
7813
8494
  memory2.command("add").description(
@@ -7833,21 +8514,22 @@ function registerMemoryAdd(memory2) {
7833
8514
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
7834
8515
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
7835
8516
  `
7836
- ).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: personal, or team in autopilot)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7837
- const root = findProjectRoot12(opts.dir);
7838
- const paths = resolveHaivePaths9(root);
7839
- if (!existsSync30(paths.haiveDir)) {
8517
+ ).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8518
+ const root = findProjectRoot13(opts.dir);
8519
+ const paths = resolveHaivePaths10(root);
8520
+ if (!existsSync33(paths.haiveDir)) {
7840
8521
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
7841
8522
  process.exitCode = 1;
7842
8523
  return;
7843
8524
  }
8525
+ const config = await loadConfig6(paths);
7844
8526
  const userTags = parseCsv2(opts.tags);
7845
8527
  const anchorPaths = parseCsv2(opts.paths);
7846
8528
  const autoTagsEnabled = opts.autoTag !== false;
7847
8529
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
7848
8530
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
7849
8531
  if (anchorPaths.length > 0) {
7850
- const missing = anchorPaths.filter((p) => !existsSync30(path14.resolve(root, p)));
8532
+ const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
7851
8533
  if (missing.length > 0) {
7852
8534
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
7853
8535
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -7859,12 +8541,12 @@ function registerMemoryAdd(memory2) {
7859
8541
  const title = opts.title ?? opts.slug;
7860
8542
  let body;
7861
8543
  if (opts.bodyFile !== void 0) {
7862
- if (!existsSync30(opts.bodyFile)) {
8544
+ if (!existsSync33(opts.bodyFile)) {
7863
8545
  ui.error(`--body-file not found: ${opts.bodyFile}`);
7864
8546
  process.exitCode = 1;
7865
8547
  return;
7866
8548
  }
7867
- const fileContent = await readFile9(opts.bodyFile, "utf8");
8549
+ const fileContent = await readFile10(opts.bodyFile, "utf8");
7868
8550
  body = opts.title ? `# ${opts.title}
7869
8551
 
7870
8552
  ${fileContent.trim()}
@@ -7879,10 +8561,10 @@ ${opts.body}` : opts.body;
7879
8561
  TODO \u2014 write the memory body.
7880
8562
  `;
7881
8563
  }
7882
- const scope = opts.scope ?? "personal";
7883
- if (existsSync30(paths.memoriesDir)) {
8564
+ const scope = opts.scope ?? config.defaultScope ?? "personal";
8565
+ if (existsSync33(paths.memoriesDir)) {
7884
8566
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
7885
- const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
8567
+ const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
7886
8568
  const hashDup = allForHash.find(
7887
8569
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
7888
8570
  );
@@ -7893,8 +8575,8 @@ TODO \u2014 write the memory body.
7893
8575
  return;
7894
8576
  }
7895
8577
  }
7896
- if (opts.topic && existsSync30(paths.memoriesDir)) {
7897
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8578
+ if (opts.topic && existsSync33(paths.memoriesDir)) {
8579
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
7898
8580
  const topicMatch = existing.find(
7899
8581
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
7900
8582
  );
@@ -7911,8 +8593,8 @@ TODO \u2014 write the memory body.
7911
8593
  symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
7912
8594
  }
7913
8595
  };
7914
- await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
7915
- ui.success(`Updated (topic upsert) ${path14.relative(root, topicMatch.filePath)}`);
8596
+ await writeFile16(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
8597
+ ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
7916
8598
  ui.info(`id=${fm.id} revision=${revisionCount}`);
7917
8599
  return;
7918
8600
  }
@@ -7928,17 +8610,18 @@ TODO \u2014 write the memory body.
7928
8610
  paths: anchorPaths,
7929
8611
  symbols: parseCsv2(opts.symbols),
7930
8612
  commit: opts.commit,
7931
- topic: opts.topic
8613
+ topic: opts.topic,
8614
+ status: config.defaultStatus === "validated" ? "validated" : void 0
7932
8615
  });
7933
8616
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
7934
- await mkdir11(path14.dirname(file), { recursive: true });
7935
- if (existsSync30(file)) {
8617
+ await mkdir11(path16.dirname(file), { recursive: true });
8618
+ if (existsSync33(file)) {
7936
8619
  ui.error(`Memory already exists at ${file}`);
7937
8620
  process.exitCode = 1;
7938
8621
  return;
7939
8622
  }
7940
- if (existsSync30(paths.memoriesDir)) {
7941
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8623
+ if (existsSync33(paths.memoriesDir)) {
8624
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
7942
8625
  const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
7943
8626
  const similar = existing.filter(({ memory: memory3 }) => {
7944
8627
  const id = memory3.frontmatter.id.toLowerCase();
@@ -7949,8 +8632,8 @@ TODO \u2014 write the memory body.
7949
8632
  ui.warn("Consider updating one of these with `haive memory update` instead.");
7950
8633
  }
7951
8634
  }
7952
- await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
7953
- ui.success(`Created ${path14.relative(root, file)}`);
8635
+ await writeFile16(file, serializeMemory13({ frontmatter, body }), "utf8");
8636
+ ui.success(`Created ${path16.relative(root, file)}`);
7954
8637
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
7955
8638
  if (inferredTags.length > 0) {
7956
8639
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
@@ -7961,7 +8644,9 @@ TODO \u2014 write the memory body.
7961
8644
  Add file anchors: haive memory update ${frontmatter.id} --paths <file1,file2>`
7962
8645
  );
7963
8646
  }
7964
- if (scope === "personal") {
8647
+ if (frontmatter.status === "validated") {
8648
+ console.log(ui.dim("\u2192 autopilot: memory is already validated and active"));
8649
+ } else if (scope === "personal") {
7965
8650
  console.log(
7966
8651
  ui.dim(
7967
8652
  `\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
@@ -7980,14 +8665,14 @@ function parseCsv2(value) {
7980
8665
  }
7981
8666
 
7982
8667
  // src/commands/memory-list.ts
7983
- import { existsSync as existsSync31 } from "fs";
7984
- import path15 from "path";
8668
+ import { existsSync as existsSync34 } from "fs";
8669
+ import path17 from "path";
7985
8670
  import "commander";
7986
- import { findProjectRoot as findProjectRoot13, resolveHaivePaths as resolveHaivePaths10 } from "@hiveai/core";
8671
+ import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
7987
8672
 
7988
8673
  // src/utils/fs.ts
7989
8674
  import {
7990
- loadMemoriesFromDir as loadMemoriesFromDir25,
8675
+ loadMemoriesFromDir as loadMemoriesFromDir26,
7991
8676
  loadMemory,
7992
8677
  listMarkdownFilesRecursive
7993
8678
  } from "@hiveai/core";
@@ -7995,14 +8680,14 @@ import {
7995
8680
  // src/commands/memory-list.ts
7996
8681
  function registerMemoryList(memory2) {
7997
8682
  memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7998
- const root = findProjectRoot13(opts.dir);
7999
- const paths = resolveHaivePaths10(root);
8000
- if (!existsSync31(paths.memoriesDir)) {
8683
+ const root = findProjectRoot14(opts.dir);
8684
+ const paths = resolveHaivePaths11(root);
8685
+ if (!existsSync34(paths.memoriesDir)) {
8001
8686
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8002
8687
  process.exitCode = 1;
8003
8688
  return;
8004
8689
  }
8005
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8690
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8006
8691
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8007
8692
  const filtered = all.filter((m) => {
8008
8693
  if (!matchesFilters(m, opts)) return false;
@@ -8029,7 +8714,7 @@ function registerMemoryList(memory2) {
8029
8714
  console.log(
8030
8715
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
8031
8716
  );
8032
- console.log(` ${ui.dim(path15.relative(root, filePath))}`);
8717
+ console.log(` ${ui.dim(path17.relative(root, filePath))}`);
8033
8718
  }
8034
8719
  console.log(ui.dim(`
8035
8720
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
@@ -8064,26 +8749,26 @@ function matchesFilters(loaded, opts) {
8064
8749
  }
8065
8750
 
8066
8751
  // src/commands/memory-promote.ts
8067
- import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
8068
- import { existsSync as existsSync33 } from "fs";
8069
- import path16 from "path";
8752
+ import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
8753
+ import { existsSync as existsSync35 } from "fs";
8754
+ import path18 from "path";
8070
8755
  import "commander";
8071
8756
  import {
8072
- findProjectRoot as findProjectRoot14,
8757
+ findProjectRoot as findProjectRoot15,
8073
8758
  memoryFilePath as memoryFilePath7,
8074
- resolveHaivePaths as resolveHaivePaths11,
8075
- serializeMemory as serializeMemory13
8759
+ resolveHaivePaths as resolveHaivePaths12,
8760
+ serializeMemory as serializeMemory14
8076
8761
  } from "@hiveai/core";
8077
8762
  function registerMemoryPromote(memory2) {
8078
8763
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8079
- const root = findProjectRoot14(opts.dir);
8080
- const paths = resolveHaivePaths11(root);
8081
- if (!existsSync33(paths.memoriesDir)) {
8764
+ const root = findProjectRoot15(opts.dir);
8765
+ const paths = resolveHaivePaths12(root);
8766
+ if (!existsSync35(paths.memoriesDir)) {
8082
8767
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8083
8768
  process.exitCode = 1;
8084
8769
  return;
8085
8770
  }
8086
- const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
8771
+ const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
8087
8772
  const alreadyShared = teamAndModule.find(
8088
8773
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
8089
8774
  );
@@ -8097,7 +8782,7 @@ function registerMemoryPromote(memory2) {
8097
8782
  }
8098
8783
  return;
8099
8784
  }
8100
- const all = await loadMemoriesFromDir25(paths.personalDir);
8785
+ const all = await loadMemoriesFromDir26(paths.personalDir);
8101
8786
  const found = all.find((m) => m.memory.frontmatter.id === id);
8102
8787
  if (!found) {
8103
8788
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -8113,35 +8798,35 @@ function registerMemoryPromote(memory2) {
8113
8798
  body: found.memory.body
8114
8799
  };
8115
8800
  const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
8116
- await mkdir12(path16.dirname(newPath), { recursive: true });
8117
- await writeFile15(newPath, serializeMemory13(updated), "utf8");
8801
+ await mkdir12(path18.dirname(newPath), { recursive: true });
8802
+ await writeFile17(newPath, serializeMemory14(updated), "utf8");
8118
8803
  await unlink2(found.filePath);
8119
8804
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
8120
- ui.info(`Now at ${path16.relative(root, newPath)}`);
8805
+ ui.info(`Now at ${path18.relative(root, newPath)}`);
8121
8806
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
8122
8807
  });
8123
8808
  }
8124
8809
 
8125
8810
  // src/commands/memory-approve.ts
8126
- import { existsSync as existsSync34 } from "fs";
8127
- import { writeFile as writeFile16 } from "fs/promises";
8128
- import path17 from "path";
8811
+ import { existsSync as existsSync36 } from "fs";
8812
+ import { writeFile as writeFile18 } from "fs/promises";
8813
+ import path19 from "path";
8129
8814
  import "commander";
8130
8815
  import {
8131
- findProjectRoot as findProjectRoot15,
8132
- resolveHaivePaths as resolveHaivePaths12,
8133
- serializeMemory as serializeMemory14
8816
+ findProjectRoot as findProjectRoot16,
8817
+ resolveHaivePaths as resolveHaivePaths13,
8818
+ serializeMemory as serializeMemory15
8134
8819
  } from "@hiveai/core";
8135
8820
  function registerMemoryApprove(memory2) {
8136
8821
  memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8137
- const root = findProjectRoot15(opts.dir);
8138
- const paths = resolveHaivePaths12(root);
8139
- if (!existsSync34(paths.memoriesDir)) {
8822
+ const root = findProjectRoot16(opts.dir);
8823
+ const paths = resolveHaivePaths13(root);
8824
+ if (!existsSync36(paths.memoriesDir)) {
8140
8825
  ui.error(`No .ai/memories at ${root}.`);
8141
8826
  process.exitCode = 1;
8142
8827
  return;
8143
8828
  }
8144
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8829
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8145
8830
  if (opts.all || opts.pending) {
8146
8831
  const candidates = all.filter((m) => {
8147
8832
  const s = m.memory.frontmatter.status;
@@ -8158,7 +8843,7 @@ function registerMemoryApprove(memory2) {
8158
8843
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
8159
8844
  body: found2.memory.body
8160
8845
  };
8161
- await writeFile16(found2.filePath, serializeMemory14(next2), "utf8");
8846
+ await writeFile18(found2.filePath, serializeMemory15(next2), "utf8");
8162
8847
  count++;
8163
8848
  }
8164
8849
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -8187,32 +8872,32 @@ function registerMemoryApprove(memory2) {
8187
8872
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
8188
8873
  body: found.memory.body
8189
8874
  };
8190
- await writeFile16(found.filePath, serializeMemory14(next), "utf8");
8875
+ await writeFile18(found.filePath, serializeMemory15(next), "utf8");
8191
8876
  ui.success(`Approved ${id} (status=validated)`);
8192
- ui.info(path17.relative(root, found.filePath));
8877
+ ui.info(path19.relative(root, found.filePath));
8193
8878
  });
8194
8879
  }
8195
8880
 
8196
8881
  // src/commands/memory-update.ts
8197
- import { writeFile as writeFile17 } from "fs/promises";
8198
- import { existsSync as existsSync35 } from "fs";
8199
- import path18 from "path";
8882
+ import { writeFile as writeFile19 } from "fs/promises";
8883
+ import { existsSync as existsSync37 } from "fs";
8884
+ import path20 from "path";
8200
8885
  import "commander";
8201
8886
  import {
8202
- findProjectRoot as findProjectRoot16,
8203
- resolveHaivePaths as resolveHaivePaths13,
8204
- serializeMemory as serializeMemory15
8887
+ findProjectRoot as findProjectRoot17,
8888
+ resolveHaivePaths as resolveHaivePaths14,
8889
+ serializeMemory as serializeMemory16
8205
8890
  } from "@hiveai/core";
8206
8891
  function registerMemoryUpdate(memory2) {
8207
8892
  memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8208
- const root = findProjectRoot16(opts.dir);
8209
- const paths = resolveHaivePaths13(root);
8210
- if (!existsSync35(paths.memoriesDir)) {
8893
+ const root = findProjectRoot17(opts.dir);
8894
+ const paths = resolveHaivePaths14(root);
8895
+ if (!existsSync37(paths.memoriesDir)) {
8211
8896
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8212
8897
  process.exitCode = 1;
8213
8898
  return;
8214
8899
  }
8215
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8900
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8216
8901
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8217
8902
  if (!loaded) {
8218
8903
  ui.error(`No memory with id "${id}".`);
@@ -8254,12 +8939,12 @@ function registerMemoryUpdate(memory2) {
8254
8939
  ui.warn("Nothing to update \u2014 provide at least one option.");
8255
8940
  return;
8256
8941
  }
8257
- await writeFile17(
8942
+ await writeFile19(
8258
8943
  loaded.filePath,
8259
- serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
8944
+ serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
8260
8945
  "utf8"
8261
8946
  );
8262
- ui.success(`Updated ${path18.relative(root, loaded.filePath)}`);
8947
+ ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
8263
8948
  ui.info(`fields: ${updated.join(", ")}`);
8264
8949
  });
8265
8950
  }
@@ -8278,18 +8963,18 @@ function parseCsv3(value) {
8278
8963
  }
8279
8964
 
8280
8965
  // src/commands/memory-auto-promote.ts
8281
- import { writeFile as writeFile18 } from "fs/promises";
8282
- import { existsSync as existsSync36 } from "fs";
8283
- import path19 from "path";
8966
+ import { writeFile as writeFile20 } from "fs/promises";
8967
+ import { existsSync as existsSync38 } from "fs";
8968
+ import path21 from "path";
8284
8969
  import "commander";
8285
8970
  import {
8286
8971
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
8287
- findProjectRoot as findProjectRoot17,
8288
- getUsage as getUsage11,
8972
+ findProjectRoot as findProjectRoot18,
8973
+ getUsage as getUsage12,
8289
8974
  isAutoPromoteEligible as isAutoPromoteEligible3,
8290
- loadUsageIndex as loadUsageIndex13,
8291
- resolveHaivePaths as resolveHaivePaths14,
8292
- serializeMemory as serializeMemory16
8975
+ loadUsageIndex as loadUsageIndex14,
8976
+ resolveHaivePaths as resolveHaivePaths15,
8977
+ serializeMemory as serializeMemory17
8293
8978
  } from "@hiveai/core";
8294
8979
  function registerMemoryAutoPromote(memory2) {
8295
8980
  memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
@@ -8297,9 +8982,9 @@ function registerMemoryAutoPromote(memory2) {
8297
8982
  "memories with more rejections than this are skipped",
8298
8983
  String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8299
8984
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8300
- const root = findProjectRoot17(opts.dir);
8301
- const paths = resolveHaivePaths14(root);
8302
- if (!existsSync36(paths.memoriesDir)) {
8985
+ const root = findProjectRoot18(opts.dir);
8986
+ const paths = resolveHaivePaths15(root);
8987
+ if (!existsSync38(paths.memoriesDir)) {
8303
8988
  ui.error(`No .ai/memories at ${root}.`);
8304
8989
  process.exitCode = 1;
8305
8990
  return;
@@ -8308,10 +8993,10 @@ function registerMemoryAutoPromote(memory2) {
8308
8993
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
8309
8994
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8310
8995
  };
8311
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8312
- const usage = await loadUsageIndex13(paths);
8996
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8997
+ const usage = await loadUsageIndex14(paths);
8313
8998
  const eligible = memories.filter(
8314
- ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
8999
+ ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
8315
9000
  );
8316
9001
  if (eligible.length === 0) {
8317
9002
  ui.info(
@@ -8321,17 +9006,17 @@ function registerMemoryAutoPromote(memory2) {
8321
9006
  }
8322
9007
  let written = 0;
8323
9008
  for (const { memory: mem, filePath } of eligible) {
8324
- const u = getUsage11(usage, mem.frontmatter.id);
9009
+ const u = getUsage12(usage, mem.frontmatter.id);
8325
9010
  console.log(
8326
9011
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8327
9012
  );
8328
- console.log(` ${ui.dim(path19.relative(root, filePath))}`);
9013
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
8329
9014
  if (opts.apply) {
8330
9015
  const next = {
8331
9016
  frontmatter: { ...mem.frontmatter, status: "validated" },
8332
9017
  body: mem.body
8333
9018
  };
8334
- await writeFile18(filePath, serializeMemory16(next), "utf8");
9019
+ await writeFile20(filePath, serializeMemory17(next), "utf8");
8335
9020
  written++;
8336
9021
  }
8337
9022
  }
@@ -8342,25 +9027,25 @@ function registerMemoryAutoPromote(memory2) {
8342
9027
 
8343
9028
  // src/commands/memory-edit.ts
8344
9029
  import { spawn as spawn3 } from "child_process";
8345
- import { existsSync as existsSync37 } from "fs";
8346
- import { readFile as readFile10 } from "fs/promises";
8347
- import path20 from "path";
9030
+ import { existsSync as existsSync39 } from "fs";
9031
+ import { readFile as readFile11 } from "fs/promises";
9032
+ import path23 from "path";
8348
9033
  import "commander";
8349
9034
  import {
8350
- findProjectRoot as findProjectRoot18,
9035
+ findProjectRoot as findProjectRoot19,
8351
9036
  parseMemory,
8352
- resolveHaivePaths as resolveHaivePaths15
9037
+ resolveHaivePaths as resolveHaivePaths16
8353
9038
  } from "@hiveai/core";
8354
9039
  function registerMemoryEdit(memory2) {
8355
9040
  memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8356
- const root = findProjectRoot18(opts.dir);
8357
- const paths = resolveHaivePaths15(root);
8358
- if (!existsSync37(paths.memoriesDir)) {
9041
+ const root = findProjectRoot19(opts.dir);
9042
+ const paths = resolveHaivePaths16(root);
9043
+ if (!existsSync39(paths.memoriesDir)) {
8359
9044
  ui.error(`No .ai/memories at ${root}.`);
8360
9045
  process.exitCode = 1;
8361
9046
  return;
8362
9047
  }
8363
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9048
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8364
9049
  const found = all.find((m) => m.memory.frontmatter.id === id);
8365
9050
  if (!found) {
8366
9051
  ui.error(`No memory with id "${id}".`);
@@ -8368,13 +9053,13 @@ function registerMemoryEdit(memory2) {
8368
9053
  return;
8369
9054
  }
8370
9055
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
8371
- ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
9056
+ ui.info(`Opening ${path23.relative(root, found.filePath)} with ${editor}\u2026`);
8372
9057
  const code = await runEditor(editor, found.filePath);
8373
9058
  if (code !== 0) {
8374
9059
  ui.warn(`Editor exited with status ${code}.`);
8375
9060
  }
8376
9061
  try {
8377
- const fresh = await readFile10(found.filePath, "utf8");
9062
+ const fresh = await readFile11(found.filePath, "utf8");
8378
9063
  parseMemory(fresh);
8379
9064
  ui.success("Memory still parses cleanly.");
8380
9065
  } catch (err) {
@@ -8395,29 +9080,29 @@ function runEditor(editor, file) {
8395
9080
  }
8396
9081
 
8397
9082
  // src/commands/memory-for-files.ts
8398
- import { existsSync as existsSync38 } from "fs";
8399
- import path21 from "path";
9083
+ import { existsSync as existsSync40 } from "fs";
9084
+ import path24 from "path";
8400
9085
  import "commander";
8401
9086
  import {
8402
9087
  deriveConfidence as deriveConfidence9,
8403
- findProjectRoot as findProjectRoot19,
8404
- getUsage as getUsage12,
9088
+ findProjectRoot as findProjectRoot20,
9089
+ getUsage as getUsage13,
8405
9090
  inferModulesFromPaths as inferModulesFromPaths4,
8406
- loadUsageIndex as loadUsageIndex14,
9091
+ loadUsageIndex as loadUsageIndex15,
8407
9092
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
8408
- resolveHaivePaths as resolveHaivePaths16
9093
+ resolveHaivePaths as resolveHaivePaths17
8409
9094
  } from "@hiveai/core";
8410
9095
  function registerMemoryForFiles(memory2) {
8411
9096
  memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
8412
- const root = findProjectRoot19(opts.dir);
8413
- const paths = resolveHaivePaths16(root);
8414
- if (!existsSync38(paths.memoriesDir)) {
9097
+ const root = findProjectRoot20(opts.dir);
9098
+ const paths = resolveHaivePaths17(root);
9099
+ if (!existsSync40(paths.memoriesDir)) {
8415
9100
  ui.error(`No .ai/memories at ${root}.`);
8416
9101
  process.exitCode = 1;
8417
9102
  return;
8418
9103
  }
8419
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8420
- const usage = await loadUsageIndex14(paths);
9104
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9105
+ const usage = await loadUsageIndex15(paths);
8421
9106
  const inferred = inferModulesFromPaths4(files);
8422
9107
  const byAnchor = [];
8423
9108
  const byModule = [];
@@ -8515,44 +9200,44 @@ function printGroup(root, label, loaded, usage) {
8515
9200
  \u2014 ${label} \u2014`));
8516
9201
  for (const { memory: mem, filePath } of loaded) {
8517
9202
  const fm = mem.frontmatter;
8518
- const u = getUsage12(usage, fm.id);
9203
+ const u = getUsage13(usage, fm.id);
8519
9204
  const conf = deriveConfidence9(fm, u);
8520
9205
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
8521
- console.log(` ${ui.dim(path21.relative(root, filePath))}`);
9206
+ console.log(` ${ui.dim(path24.relative(root, filePath))}`);
8522
9207
  }
8523
9208
  }
8524
9209
 
8525
9210
  // src/commands/memory-hot.ts
8526
- import { existsSync as existsSync39 } from "fs";
8527
- import path23 from "path";
9211
+ import { existsSync as existsSync41 } from "fs";
9212
+ import path25 from "path";
8528
9213
  import "commander";
8529
9214
  import {
8530
- findProjectRoot as findProjectRoot20,
8531
- getUsage as getUsage13,
8532
- loadUsageIndex as loadUsageIndex15,
8533
- resolveHaivePaths as resolveHaivePaths17
9215
+ findProjectRoot as findProjectRoot21,
9216
+ getUsage as getUsage14,
9217
+ loadUsageIndex as loadUsageIndex16,
9218
+ resolveHaivePaths as resolveHaivePaths18
8534
9219
  } from "@hiveai/core";
8535
9220
  function registerMemoryHot(memory2) {
8536
9221
  memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8537
- const root = findProjectRoot20(opts.dir);
8538
- const paths = resolveHaivePaths17(root);
8539
- if (!existsSync39(paths.memoriesDir)) {
9222
+ const root = findProjectRoot21(opts.dir);
9223
+ const paths = resolveHaivePaths18(root);
9224
+ if (!existsSync41(paths.memoriesDir)) {
8540
9225
  ui.error(`No .ai/memories at ${root}.`);
8541
9226
  process.exitCode = 1;
8542
9227
  return;
8543
9228
  }
8544
9229
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
8545
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8546
- const usage = await loadUsageIndex15(paths);
9230
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9231
+ const usage = await loadUsageIndex16(paths);
8547
9232
  const candidates = all.filter(({ memory: mem }) => {
8548
9233
  const fm = mem.frontmatter;
8549
9234
  if (opts.status && fm.status !== opts.status) return false;
8550
9235
  if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
8551
9236
  return false;
8552
9237
  }
8553
- return getUsage13(usage, fm.id).read_count >= threshold;
9238
+ return getUsage14(usage, fm.id).read_count >= threshold;
8554
9239
  }).sort(
8555
- (a, b) => getUsage13(usage, b.memory.frontmatter.id).read_count - getUsage13(usage, a.memory.frontmatter.id).read_count
9240
+ (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
8556
9241
  );
8557
9242
  if (candidates.length === 0) {
8558
9243
  ui.info(`No hot memories (threshold=${threshold}).`);
@@ -8560,11 +9245,11 @@ function registerMemoryHot(memory2) {
8560
9245
  }
8561
9246
  for (const { memory: mem, filePath } of candidates) {
8562
9247
  const fm = mem.frontmatter;
8563
- const u = getUsage13(usage, fm.id);
9248
+ const u = getUsage14(usage, fm.id);
8564
9249
  console.log(
8565
9250
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8566
9251
  );
8567
- console.log(` ${ui.dim(path23.relative(root, filePath))}`);
9252
+ console.log(` ${ui.dim(path25.relative(root, filePath))}`);
8568
9253
  }
8569
9254
  ui.info(
8570
9255
  `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
@@ -8573,16 +9258,16 @@ function registerMemoryHot(memory2) {
8573
9258
  }
8574
9259
 
8575
9260
  // src/commands/memory-tried.ts
8576
- import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
8577
- import { existsSync as existsSync40 } from "fs";
8578
- import path24 from "path";
9261
+ import { mkdir as mkdir13, writeFile as writeFile21 } from "fs/promises";
9262
+ import { existsSync as existsSync43 } from "fs";
9263
+ import path26 from "path";
8579
9264
  import "commander";
8580
9265
  import {
8581
9266
  buildFrontmatter as buildFrontmatter8,
8582
- findProjectRoot as findProjectRoot21,
9267
+ findProjectRoot as findProjectRoot22,
8583
9268
  memoryFilePath as memoryFilePath8,
8584
- resolveHaivePaths as resolveHaivePaths18,
8585
- serializeMemory as serializeMemory17
9269
+ resolveHaivePaths as resolveHaivePaths19,
9270
+ serializeMemory as serializeMemory18
8586
9271
  } from "@hiveai/core";
8587
9272
  function registerMemoryTried(memory2) {
8588
9273
  memory2.command("tried").description(
@@ -8601,9 +9286,9 @@ function registerMemoryTried(memory2) {
8601
9286
  --paths packages/cli/src/index.ts
8602
9287
  `
8603
9288
  ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
8604
- const root = findProjectRoot21(opts.dir);
8605
- const paths = resolveHaivePaths18(root);
8606
- if (!existsSync40(paths.haiveDir)) {
9289
+ const root = findProjectRoot22(opts.dir);
9290
+ const paths = resolveHaivePaths19(root);
9291
+ if (!existsSync43(paths.haiveDir)) {
8607
9292
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8608
9293
  process.exitCode = 1;
8609
9294
  return;
@@ -8626,14 +9311,14 @@ function registerMemoryTried(memory2) {
8626
9311
  }
8627
9312
  const body = lines.join("\n") + "\n";
8628
9313
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8629
- await mkdir13(path24.dirname(file), { recursive: true });
8630
- if (existsSync40(file)) {
9314
+ await mkdir13(path26.dirname(file), { recursive: true });
9315
+ if (existsSync43(file)) {
8631
9316
  ui.error(`Memory already exists at ${file}`);
8632
9317
  process.exitCode = 1;
8633
9318
  return;
8634
9319
  }
8635
- await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
8636
- ui.success(`Recorded: ${path24.relative(root, file)}`);
9320
+ await writeFile21(file, serializeMemory18({ frontmatter, body }), "utf8");
9321
+ ui.success(`Recorded: ${path26.relative(root, file)}`);
8637
9322
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
8638
9323
  });
8639
9324
  }
@@ -8643,26 +9328,26 @@ function parseCsv4(value) {
8643
9328
  }
8644
9329
 
8645
9330
  // src/commands/memory-pending.ts
8646
- import { existsSync as existsSync41 } from "fs";
8647
- import path25 from "path";
9331
+ import { existsSync as existsSync44 } from "fs";
9332
+ import path27 from "path";
8648
9333
  import "commander";
8649
9334
  import {
8650
- findProjectRoot as findProjectRoot22,
8651
- getUsage as getUsage14,
8652
- loadUsageIndex as loadUsageIndex16,
8653
- resolveHaivePaths as resolveHaivePaths19
9335
+ findProjectRoot as findProjectRoot23,
9336
+ getUsage as getUsage15,
9337
+ loadUsageIndex as loadUsageIndex17,
9338
+ resolveHaivePaths as resolveHaivePaths20
8654
9339
  } from "@hiveai/core";
8655
9340
  function registerMemoryPending(memory2) {
8656
9341
  memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8657
- const root = findProjectRoot22(opts.dir);
8658
- const paths = resolveHaivePaths19(root);
8659
- if (!existsSync41(paths.memoriesDir)) {
9342
+ const root = findProjectRoot23(opts.dir);
9343
+ const paths = resolveHaivePaths20(root);
9344
+ if (!existsSync44(paths.memoriesDir)) {
8660
9345
  ui.error(`No .ai/memories at ${root}.`);
8661
9346
  process.exitCode = 1;
8662
9347
  return;
8663
9348
  }
8664
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8665
- const usage = await loadUsageIndex16(paths);
9349
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9350
+ const usage = await loadUsageIndex17(paths);
8666
9351
  const proposed = all.filter(({ memory: mem }) => {
8667
9352
  if (mem.frontmatter.status !== "proposed") return false;
8668
9353
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -8673,42 +9358,42 @@ function registerMemoryPending(memory2) {
8673
9358
  return;
8674
9359
  }
8675
9360
  proposed.sort(
8676
- (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
9361
+ (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
8677
9362
  );
8678
9363
  const now = Date.now();
8679
9364
  for (const { memory: mem, filePath } of proposed) {
8680
9365
  const fm = mem.frontmatter;
8681
- const u = getUsage14(usage, fm.id);
9366
+ const u = getUsage15(usage, fm.id);
8682
9367
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
8683
9368
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
8684
9369
  console.log(
8685
9370
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
8686
9371
  );
8687
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
9372
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
8688
9373
  }
8689
9374
  ui.info(`${proposed.length} pending`);
8690
9375
  });
8691
9376
  }
8692
9377
 
8693
9378
  // src/commands/memory-query.ts
8694
- import { existsSync as existsSync43 } from "fs";
8695
- import path26 from "path";
9379
+ import { existsSync as existsSync45 } from "fs";
9380
+ import path28 from "path";
8696
9381
  import "commander";
8697
9382
  import {
8698
9383
  extractSnippet as extractSnippet2,
8699
- findProjectRoot as findProjectRoot23,
9384
+ findProjectRoot as findProjectRoot24,
8700
9385
  literalMatchesAllTokens as literalMatchesAllTokens3,
8701
9386
  literalMatchesAnyToken as literalMatchesAnyToken4,
8702
9387
  pickSnippetNeedle as pickSnippetNeedle2,
8703
- resolveHaivePaths as resolveHaivePaths20,
9388
+ resolveHaivePaths as resolveHaivePaths21,
8704
9389
  tokenizeQuery as tokenizeQuery6,
8705
9390
  trackReads as trackReads4
8706
9391
  } from "@hiveai/core";
8707
9392
  function registerMemoryQuery(memory2) {
8708
9393
  memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
8709
- const root = findProjectRoot23(opts.dir);
8710
- const paths = resolveHaivePaths20(root);
8711
- if (!existsSync43(paths.memoriesDir)) {
9394
+ const root = findProjectRoot24(opts.dir);
9395
+ const paths = resolveHaivePaths21(root);
9396
+ if (!existsSync45(paths.memoriesDir)) {
8712
9397
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8713
9398
  process.exitCode = 1;
8714
9399
  return;
@@ -8719,7 +9404,7 @@ function registerMemoryQuery(memory2) {
8719
9404
  return;
8720
9405
  }
8721
9406
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8722
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9407
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8723
9408
  const passesFilters2 = (mem) => {
8724
9409
  const fm = mem.frontmatter;
8725
9410
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -8749,7 +9434,7 @@ function registerMemoryQuery(memory2) {
8749
9434
  const fm = mem.frontmatter;
8750
9435
  const statusBadge = ui.statusBadge(fm.status);
8751
9436
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
8752
- console.log(` ${ui.dim(path26.relative(root, filePath))}`);
9437
+ console.log(` ${ui.dim(path28.relative(root, filePath))}`);
8753
9438
  const snippet = extractSnippet2(mem.body, snippetNeedle);
8754
9439
  if (snippet) console.log(` ${snippet}`);
8755
9440
  }
@@ -8766,36 +9451,36 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
8766
9451
  }
8767
9452
 
8768
9453
  // src/commands/memory-reject.ts
8769
- import { writeFile as writeFile20 } from "fs/promises";
8770
- import { existsSync as existsSync44 } from "fs";
9454
+ import { writeFile as writeFile23 } from "fs/promises";
9455
+ import { existsSync as existsSync46 } from "fs";
8771
9456
  import "commander";
8772
9457
  import {
8773
- findProjectRoot as findProjectRoot24,
8774
- loadUsageIndex as loadUsageIndex17,
9458
+ findProjectRoot as findProjectRoot25,
9459
+ loadUsageIndex as loadUsageIndex18,
8775
9460
  recordRejection as recordRejection2,
8776
- resolveHaivePaths as resolveHaivePaths21,
9461
+ resolveHaivePaths as resolveHaivePaths22,
8777
9462
  saveUsageIndex as saveUsageIndex3,
8778
- serializeMemory as serializeMemory18
9463
+ serializeMemory as serializeMemory19
8779
9464
  } from "@hiveai/core";
8780
9465
  function registerMemoryReject(memory2) {
8781
9466
  memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8782
- const root = findProjectRoot24(opts.dir);
8783
- const paths = resolveHaivePaths21(root);
8784
- if (!existsSync44(paths.memoriesDir)) {
9467
+ const root = findProjectRoot25(opts.dir);
9468
+ const paths = resolveHaivePaths22(root);
9469
+ if (!existsSync46(paths.memoriesDir)) {
8785
9470
  ui.error(`No .ai/memories at ${root}.`);
8786
9471
  process.exitCode = 1;
8787
9472
  return;
8788
9473
  }
8789
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9474
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8790
9475
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8791
9476
  if (!loaded) {
8792
9477
  ui.error(`No memory with id "${id}".`);
8793
9478
  process.exitCode = 1;
8794
9479
  return;
8795
9480
  }
8796
- await writeFile20(
9481
+ await writeFile23(
8797
9482
  loaded.filePath,
8798
- serializeMemory18({
9483
+ serializeMemory19({
8799
9484
  frontmatter: {
8800
9485
  ...loaded.memory.frontmatter,
8801
9486
  status: "rejected",
@@ -8805,7 +9490,7 @@ function registerMemoryReject(memory2) {
8805
9490
  }),
8806
9491
  "utf8"
8807
9492
  );
8808
- const idx = await loadUsageIndex17(paths);
9493
+ const idx = await loadUsageIndex18(paths);
8809
9494
  recordRejection2(idx, id, opts.reason ?? null);
8810
9495
  await saveUsageIndex3(paths, idx);
8811
9496
  const u = idx.by_id[id];
@@ -8817,34 +9502,34 @@ function registerMemoryReject(memory2) {
8817
9502
  }
8818
9503
 
8819
9504
  // src/commands/memory-rm.ts
8820
- import { existsSync as existsSync45 } from "fs";
9505
+ import { existsSync as existsSync47 } from "fs";
8821
9506
  import { unlink as unlink3 } from "fs/promises";
8822
- import path27 from "path";
9507
+ import path29 from "path";
8823
9508
  import { createInterface as createInterface2 } from "readline/promises";
8824
9509
  import "commander";
8825
9510
  import {
8826
- findProjectRoot as findProjectRoot25,
8827
- loadUsageIndex as loadUsageIndex18,
8828
- resolveHaivePaths as resolveHaivePaths22,
9511
+ findProjectRoot as findProjectRoot26,
9512
+ loadUsageIndex as loadUsageIndex19,
9513
+ resolveHaivePaths as resolveHaivePaths23,
8829
9514
  saveUsageIndex as saveUsageIndex4
8830
9515
  } from "@hiveai/core";
8831
9516
  function registerMemoryRm(memory2) {
8832
9517
  memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").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) => {
8833
- const root = findProjectRoot25(opts.dir);
8834
- const paths = resolveHaivePaths22(root);
8835
- if (!existsSync45(paths.memoriesDir)) {
9518
+ const root = findProjectRoot26(opts.dir);
9519
+ const paths = resolveHaivePaths23(root);
9520
+ if (!existsSync47(paths.memoriesDir)) {
8836
9521
  ui.error(`No .ai/memories at ${root}.`);
8837
9522
  process.exitCode = 1;
8838
9523
  return;
8839
9524
  }
8840
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9525
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8841
9526
  const found = all.find((m) => m.memory.frontmatter.id === id);
8842
9527
  if (!found) {
8843
9528
  ui.error(`No memory with id "${id}".`);
8844
9529
  process.exitCode = 1;
8845
9530
  return;
8846
9531
  }
8847
- const rel = path27.relative(root, found.filePath);
9532
+ const rel = path29.relative(root, found.filePath);
8848
9533
  if (!opts.yes) {
8849
9534
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
8850
9535
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -8857,7 +9542,7 @@ function registerMemoryRm(memory2) {
8857
9542
  await unlink3(found.filePath);
8858
9543
  ui.success(`Deleted ${rel}`);
8859
9544
  if (!opts.keepUsage) {
8860
- const idx = await loadUsageIndex18(paths);
9545
+ const idx = await loadUsageIndex19(paths);
8861
9546
  if (idx.by_id[id]) {
8862
9547
  delete idx.by_id[id];
8863
9548
  await saveUsageIndex4(paths, idx);
@@ -8868,27 +9553,27 @@ function registerMemoryRm(memory2) {
8868
9553
  }
8869
9554
 
8870
9555
  // src/commands/memory-show.ts
8871
- import { existsSync as existsSync46 } from "fs";
8872
- import { readFile as readFile11 } from "fs/promises";
8873
- import path28 from "path";
9556
+ import { existsSync as existsSync48 } from "fs";
9557
+ import { readFile as readFile12 } from "fs/promises";
9558
+ import path30 from "path";
8874
9559
  import "commander";
8875
9560
  import {
8876
9561
  deriveConfidence as deriveConfidence10,
8877
- findProjectRoot as findProjectRoot26,
8878
- getUsage as getUsage15,
8879
- loadUsageIndex as loadUsageIndex19,
8880
- resolveHaivePaths as resolveHaivePaths23
9562
+ findProjectRoot as findProjectRoot27,
9563
+ getUsage as getUsage16,
9564
+ loadUsageIndex as loadUsageIndex20,
9565
+ resolveHaivePaths as resolveHaivePaths24
8881
9566
  } from "@hiveai/core";
8882
9567
  function registerMemoryShow(memory2) {
8883
9568
  memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8884
- const root = findProjectRoot26(opts.dir);
8885
- const paths = resolveHaivePaths23(root);
8886
- if (!existsSync46(paths.memoriesDir)) {
9569
+ const root = findProjectRoot27(opts.dir);
9570
+ const paths = resolveHaivePaths24(root);
9571
+ if (!existsSync48(paths.memoriesDir)) {
8887
9572
  ui.error(`No .ai/memories at ${root}.`);
8888
9573
  process.exitCode = 1;
8889
9574
  return;
8890
9575
  }
8891
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9576
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8892
9577
  const found = all.find((m) => m.memory.frontmatter.id === id);
8893
9578
  if (!found) {
8894
9579
  ui.error(`No memory with id "${id}".`);
@@ -8896,12 +9581,12 @@ function registerMemoryShow(memory2) {
8896
9581
  return;
8897
9582
  }
8898
9583
  if (opts.raw) {
8899
- console.log(await readFile11(found.filePath, "utf8"));
9584
+ console.log(await readFile12(found.filePath, "utf8"));
8900
9585
  return;
8901
9586
  }
8902
9587
  const fm = found.memory.frontmatter;
8903
- const usage = await loadUsageIndex19(paths);
8904
- const u = getUsage15(usage, fm.id);
9588
+ const usage = await loadUsageIndex20(paths);
9589
+ const u = getUsage16(usage, fm.id);
8905
9590
  const conf = deriveConfidence10(fm, u);
8906
9591
  console.log(ui.bold(fm.id));
8907
9592
  console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
@@ -8912,7 +9597,7 @@ function registerMemoryShow(memory2) {
8912
9597
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
8913
9598
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
8914
9599
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
8915
- console.log(`${ui.dim("file:")} ${path28.relative(root, found.filePath)}`);
9600
+ console.log(`${ui.dim("file:")} ${path30.relative(root, found.filePath)}`);
8916
9601
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
8917
9602
  console.log(ui.dim("anchor:"));
8918
9603
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -8927,38 +9612,38 @@ function registerMemoryShow(memory2) {
8927
9612
  }
8928
9613
 
8929
9614
  // src/commands/memory-stats.ts
8930
- import { existsSync as existsSync47 } from "fs";
8931
- import path29 from "path";
9615
+ import { existsSync as existsSync49 } from "fs";
9616
+ import path31 from "path";
8932
9617
  import "commander";
8933
9618
  import {
8934
9619
  deriveConfidence as deriveConfidence11,
8935
- findProjectRoot as findProjectRoot27,
8936
- getUsage as getUsage16,
8937
- loadUsageIndex as loadUsageIndex20,
8938
- resolveHaivePaths as resolveHaivePaths24
9620
+ findProjectRoot as findProjectRoot28,
9621
+ getUsage as getUsage17,
9622
+ loadUsageIndex as loadUsageIndex21,
9623
+ resolveHaivePaths as resolveHaivePaths25
8939
9624
  } from "@hiveai/core";
8940
9625
  function registerMemoryStats(memory2) {
8941
9626
  memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
8942
- const root = findProjectRoot27(opts.dir);
8943
- const paths = resolveHaivePaths24(root);
8944
- if (!existsSync47(paths.memoriesDir)) {
9627
+ const root = findProjectRoot28(opts.dir);
9628
+ const paths = resolveHaivePaths25(root);
9629
+ if (!existsSync49(paths.memoriesDir)) {
8945
9630
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8946
9631
  process.exitCode = 1;
8947
9632
  return;
8948
9633
  }
8949
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8950
- const usage = await loadUsageIndex20(paths);
9634
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9635
+ const usage = await loadUsageIndex21(paths);
8951
9636
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
8952
9637
  if (target.length === 0) {
8953
9638
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
8954
9639
  return;
8955
9640
  }
8956
9641
  target.sort(
8957
- (a, b) => getUsage16(usage, b.memory.frontmatter.id).read_count - getUsage16(usage, a.memory.frontmatter.id).read_count
9642
+ (a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
8958
9643
  );
8959
9644
  for (const { memory: mem, filePath } of target) {
8960
9645
  const fm = mem.frontmatter;
8961
- const u = getUsage16(usage, fm.id);
9646
+ const u = getUsage17(usage, fm.id);
8962
9647
  const conf = deriveConfidence11(fm, u);
8963
9648
  console.log(
8964
9649
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
@@ -8966,34 +9651,34 @@ function registerMemoryStats(memory2) {
8966
9651
  console.log(
8967
9652
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
8968
9653
  );
8969
- console.log(` ${ui.dim(path29.relative(root, filePath))}`);
9654
+ console.log(` ${ui.dim(path31.relative(root, filePath))}`);
8970
9655
  }
8971
9656
  });
8972
9657
  }
8973
9658
 
8974
9659
  // src/commands/memory-verify.ts
8975
- import { writeFile as writeFile21 } from "fs/promises";
8976
- import { existsSync as existsSync48 } from "fs";
8977
- import path30 from "path";
9660
+ import { writeFile as writeFile24 } from "fs/promises";
9661
+ import { existsSync as existsSync50 } from "fs";
9662
+ import path33 from "path";
8978
9663
  import "commander";
8979
9664
  import {
8980
- findProjectRoot as findProjectRoot28,
8981
- resolveHaivePaths as resolveHaivePaths25,
8982
- serializeMemory as serializeMemory19,
9665
+ findProjectRoot as findProjectRoot29,
9666
+ resolveHaivePaths as resolveHaivePaths26,
9667
+ serializeMemory as serializeMemory20,
8983
9668
  verifyAnchor as verifyAnchor3
8984
9669
  } from "@hiveai/core";
8985
9670
  function registerMemoryVerify(memory2) {
8986
9671
  memory2.command("verify").description(
8987
9672
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
8988
9673
  ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
8989
- const root = findProjectRoot28(opts.dir);
8990
- const paths = resolveHaivePaths25(root);
8991
- if (!existsSync48(paths.memoriesDir)) {
9674
+ const root = findProjectRoot29(opts.dir);
9675
+ const paths = resolveHaivePaths26(root);
9676
+ if (!existsSync50(paths.memoriesDir)) {
8992
9677
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8993
9678
  process.exitCode = 1;
8994
9679
  return;
8995
9680
  }
8996
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9681
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8997
9682
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
8998
9683
  if (opts.id && targets.length === 0) {
8999
9684
  ui.error(`No memory with id "${opts.id}".`);
@@ -9011,7 +9696,7 @@ function registerMemoryVerify(memory2) {
9011
9696
  anchorlessIds.push(mem.frontmatter.id);
9012
9697
  continue;
9013
9698
  }
9014
- const rel = path30.relative(root, filePath);
9699
+ const rel = path33.relative(root, filePath);
9015
9700
  if (result.stale) {
9016
9701
  staleCount++;
9017
9702
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -9026,7 +9711,7 @@ function registerMemoryVerify(memory2) {
9026
9711
  }
9027
9712
  if (opts.update) {
9028
9713
  const next = applyVerification2(mem, result);
9029
- await writeFile21(filePath, serializeMemory19(next), "utf8");
9714
+ await writeFile24(filePath, serializeMemory20(next), "utf8");
9030
9715
  updated++;
9031
9716
  }
9032
9717
  }
@@ -9074,30 +9759,30 @@ function applyVerification2(mem, result) {
9074
9759
  }
9075
9760
 
9076
9761
  // src/commands/memory-import.ts
9077
- import { readFile as readFile12 } from "fs/promises";
9078
- import { existsSync as existsSync49 } from "fs";
9762
+ import { readFile as readFile13 } from "fs/promises";
9763
+ import { existsSync as existsSync51 } from "fs";
9079
9764
  import "commander";
9080
9765
  import {
9081
- findProjectRoot as findProjectRoot29,
9082
- resolveHaivePaths as resolveHaivePaths26
9766
+ findProjectRoot as findProjectRoot30,
9767
+ resolveHaivePaths as resolveHaivePaths27
9083
9768
  } from "@hiveai/core";
9084
9769
  function registerMemoryImport(memory2) {
9085
9770
  memory2.command("import").description(
9086
9771
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
9087
9772
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
9088
- const root = findProjectRoot29(opts.dir);
9089
- const paths = resolveHaivePaths26(root);
9090
- if (!existsSync49(paths.haiveDir)) {
9773
+ const root = findProjectRoot30(opts.dir);
9774
+ const paths = resolveHaivePaths27(root);
9775
+ if (!existsSync51(paths.haiveDir)) {
9091
9776
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9092
9777
  process.exitCode = 1;
9093
9778
  return;
9094
9779
  }
9095
- if (!existsSync49(opts.from)) {
9780
+ if (!existsSync51(opts.from)) {
9096
9781
  ui.error(`File not found: ${opts.from}`);
9097
9782
  process.exitCode = 1;
9098
9783
  return;
9099
9784
  }
9100
- const content = await readFile12(opts.from, "utf8");
9785
+ const content = await readFile13(opts.from, "utf8");
9101
9786
  const scope = opts.scope ?? "team";
9102
9787
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
9103
9788
  ui.info(`Content length: ${content.length} chars`);
@@ -9125,15 +9810,15 @@ function registerMemoryImport(memory2) {
9125
9810
  }
9126
9811
 
9127
9812
  // src/commands/memory-import-changelog.ts
9128
- import { existsSync as existsSync50 } from "fs";
9129
- import { readFile as readFile13, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
9130
- import path31 from "path";
9813
+ import { existsSync as existsSync53 } from "fs";
9814
+ import { readFile as readFile14, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
9815
+ import path34 from "path";
9131
9816
  import "commander";
9132
9817
  import {
9133
9818
  buildFrontmatter as buildFrontmatter9,
9134
- findProjectRoot as findProjectRoot30,
9135
- resolveHaivePaths as resolveHaivePaths27,
9136
- serializeMemory as serializeMemory20
9819
+ findProjectRoot as findProjectRoot31,
9820
+ resolveHaivePaths as resolveHaivePaths28,
9821
+ serializeMemory as serializeMemory21
9137
9822
  } from "@hiveai/core";
9138
9823
  function parseChangelog(content) {
9139
9824
  const entries = [];
@@ -9197,15 +9882,15 @@ function registerMemoryImportChangelog(memory2) {
9197
9882
  "--versions <csv>",
9198
9883
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
9199
9884
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
9200
- const root = findProjectRoot30(opts.dir);
9201
- const paths = resolveHaivePaths27(root);
9202
- const changelogPath = path31.resolve(root, opts.fromChangelog);
9203
- if (!existsSync50(changelogPath)) {
9885
+ const root = findProjectRoot31(opts.dir);
9886
+ const paths = resolveHaivePaths28(root);
9887
+ const changelogPath = path34.resolve(root, opts.fromChangelog);
9888
+ if (!existsSync53(changelogPath)) {
9204
9889
  ui.error(`CHANGELOG not found: ${changelogPath}`);
9205
9890
  process.exitCode = 1;
9206
9891
  return;
9207
9892
  }
9208
- const content = await readFile13(changelogPath, "utf8");
9893
+ const content = await readFile14(changelogPath, "utf8");
9209
9894
  let entries = parseChangelog(content);
9210
9895
  if (entries.length === 0) {
9211
9896
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -9220,9 +9905,9 @@ function registerMemoryImportChangelog(memory2) {
9220
9905
  entries = entries.filter((e) => requested.includes(e.version));
9221
9906
  }
9222
9907
  }
9223
- const pkgName = opts.package ?? path31.basename(path31.dirname(changelogPath));
9908
+ const pkgName = opts.package ?? path34.basename(path34.dirname(changelogPath));
9224
9909
  const scope = opts.scope ?? "team";
9225
- const teamDir = path31.join(paths.memoriesDir, scope);
9910
+ const teamDir = path34.join(paths.memoriesDir, scope);
9226
9911
  await mkdir14(teamDir, { recursive: true });
9227
9912
  let saved = 0;
9228
9913
  for (const entry of entries) {
@@ -9245,7 +9930,7 @@ function registerMemoryImportChangelog(memory2) {
9245
9930
  lines.push("");
9246
9931
  }
9247
9932
  lines.push(
9248
- `**Source:** \`${path31.relative(root, changelogPath)}\`
9933
+ `**Source:** \`${path34.relative(root, changelogPath)}\`
9249
9934
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
9250
9935
  );
9251
9936
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -9260,12 +9945,12 @@ function registerMemoryImportChangelog(memory2) {
9260
9945
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
9261
9946
  `v${entry.version}`
9262
9947
  ],
9263
- paths: [path31.relative(root, changelogPath)],
9948
+ paths: [path34.relative(root, changelogPath)],
9264
9949
  topic: `changelog-${pkgName}-${entry.version}`
9265
9950
  });
9266
- await writeFile23(
9267
- path31.join(teamDir, `${fm.id}.md`),
9268
- serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
9951
+ await writeFile25(
9952
+ path34.join(teamDir, `${fm.id}.md`),
9953
+ serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
9269
9954
  "utf8"
9270
9955
  );
9271
9956
  console.log(ui.green(` \u2713 ${fm.id}`));
@@ -9287,17 +9972,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
9287
9972
  }
9288
9973
 
9289
9974
  // src/commands/memory-digest.ts
9290
- import { existsSync as existsSync51 } from "fs";
9291
- import { writeFile as writeFile24 } from "fs/promises";
9292
- import path33 from "path";
9975
+ import { existsSync as existsSync54 } from "fs";
9976
+ import { writeFile as writeFile26 } from "fs/promises";
9977
+ import path35 from "path";
9293
9978
  import "commander";
9294
9979
  import {
9295
9980
  deriveConfidence as deriveConfidence12,
9296
- findProjectRoot as findProjectRoot31,
9297
- getUsage as getUsage17,
9298
- loadMemoriesFromDir as loadMemoriesFromDir26,
9299
- loadUsageIndex as loadUsageIndex21,
9300
- resolveHaivePaths as resolveHaivePaths28
9981
+ findProjectRoot as findProjectRoot32,
9982
+ getUsage as getUsage18,
9983
+ loadMemoriesFromDir as loadMemoriesFromDir27,
9984
+ loadUsageIndex as loadUsageIndex23,
9985
+ resolveHaivePaths as resolveHaivePaths29
9301
9986
  } from "@hiveai/core";
9302
9987
  var CONFIDENCE_EMOJI = {
9303
9988
  unverified: "\u2B1C",
@@ -9310,9 +9995,9 @@ function registerMemoryDigest(program2) {
9310
9995
  program2.command("digest").description(
9311
9996
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
9312
9997
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
9313
- const root = findProjectRoot31(opts.dir);
9314
- const paths = resolveHaivePaths28(root);
9315
- if (!existsSync51(paths.memoriesDir)) {
9998
+ const root = findProjectRoot32(opts.dir);
9999
+ const paths = resolveHaivePaths29(root);
10000
+ if (!existsSync54(paths.memoriesDir)) {
9316
10001
  ui.error("No .ai/memories found. Run `haive init` first.");
9317
10002
  process.exitCode = 1;
9318
10003
  return;
@@ -9320,8 +10005,8 @@ function registerMemoryDigest(program2) {
9320
10005
  const days = Math.max(1, Number(opts.days ?? 7));
9321
10006
  const scopeFilter = opts.scope ?? "team";
9322
10007
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
9323
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9324
- const usage = await loadUsageIndex21(paths);
10008
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10009
+ const usage = await loadUsageIndex23(paths);
9325
10010
  const recent = all.filter(({ memory: mem }) => {
9326
10011
  const fm = mem.frontmatter;
9327
10012
  if (fm.type === "session_recap") return false;
@@ -9352,7 +10037,7 @@ function registerMemoryDigest(program2) {
9352
10037
  lines.push(``);
9353
10038
  for (const { memory: mem } of mems) {
9354
10039
  const fm = mem.frontmatter;
9355
- const u = getUsage17(usage, fm.id);
10040
+ const u = getUsage18(usage, fm.id);
9356
10041
  const confidence = deriveConfidence12(fm, u);
9357
10042
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
9358
10043
  const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
@@ -9384,8 +10069,8 @@ function registerMemoryDigest(program2) {
9384
10069
  );
9385
10070
  const digest = lines.join("\n");
9386
10071
  if (opts.out) {
9387
- const outPath = path33.resolve(process.cwd(), opts.out);
9388
- await writeFile24(outPath, digest, "utf8");
10072
+ const outPath = path35.resolve(process.cwd(), opts.out);
10073
+ await writeFile26(outPath, digest, "utf8");
9389
10074
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
9390
10075
  } else {
9391
10076
  console.log(digest);
@@ -9394,22 +10079,22 @@ function registerMemoryDigest(program2) {
9394
10079
  }
9395
10080
 
9396
10081
  // src/commands/session-end.ts
9397
- import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile14, rm as rm2 } from "fs/promises";
9398
- import { existsSync as existsSync53 } from "fs";
9399
- import path34 from "path";
10082
+ import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile15, rm as rm2 } from "fs/promises";
10083
+ import { existsSync as existsSync55 } from "fs";
10084
+ import path36 from "path";
9400
10085
  import "commander";
9401
10086
  import {
9402
10087
  buildFrontmatter as buildFrontmatter10,
9403
- findProjectRoot as findProjectRoot32,
9404
- loadMemoriesFromDir as loadMemoriesFromDir27,
10088
+ findProjectRoot as findProjectRoot33,
10089
+ loadMemoriesFromDir as loadMemoriesFromDir28,
9405
10090
  memoryFilePath as memoryFilePath9,
9406
- resolveHaivePaths as resolveHaivePaths29,
9407
- serializeMemory as serializeMemory21
10091
+ resolveHaivePaths as resolveHaivePaths30,
10092
+ serializeMemory as serializeMemory23
9408
10093
  } from "@hiveai/core";
9409
10094
  async function buildAutoRecap(paths) {
9410
- const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9411
- if (!existsSync53(obsFile)) return null;
9412
- const raw = await readFile14(obsFile, "utf8").catch(() => "");
10095
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10096
+ if (!existsSync55(obsFile)) return null;
10097
+ const raw = await readFile15(obsFile, "utf8").catch(() => "");
9413
10098
  if (!raw.trim()) return null;
9414
10099
  const lines = raw.split("\n").filter(Boolean);
9415
10100
  const obs = [];
@@ -9487,9 +10172,9 @@ function registerSessionEnd(session2) {
9487
10172
  --next "Add integration tests for webhook signature validation"
9488
10173
  `
9489
10174
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9490
- const root = findProjectRoot32(opts.dir);
9491
- const paths = resolveHaivePaths29(root);
9492
- if (!existsSync53(paths.haiveDir)) {
10175
+ const root = findProjectRoot33(opts.dir);
10176
+ const paths = resolveHaivePaths30(root);
10177
+ if (!existsSync55(paths.haiveDir)) {
9493
10178
  if (opts.auto || opts.quiet) return;
9494
10179
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9495
10180
  process.exitCode = 1;
@@ -9521,19 +10206,19 @@ function registerSessionEnd(session2) {
9521
10206
  });
9522
10207
  const topic = recapTopic2(scope, opts.module);
9523
10208
  const filesTouched = parseCsv5(resolvedFiles);
9524
- const missingPaths = filesTouched.filter((p) => !existsSync53(path34.resolve(root, p)));
10209
+ const missingPaths = filesTouched.filter((p) => !existsSync55(path36.resolve(root, p)));
9525
10210
  if (missingPaths.length > 0 && !opts.quiet) {
9526
10211
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
9527
10212
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
9528
10213
  }
9529
10214
  const cleanupObservations = async () => {
9530
10215
  if (!opts.auto) return;
9531
- const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9532
- if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
10216
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10217
+ if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
9533
10218
  });
9534
10219
  };
9535
- if (existsSync53(paths.memoriesDir)) {
9536
- const existing = await loadMemoriesFromDir27(paths.memoriesDir);
10220
+ if (existsSync55(paths.memoriesDir)) {
10221
+ const existing = await loadMemoriesFromDir28(paths.memoriesDir);
9537
10222
  const topicMatch = existing.find(
9538
10223
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
9539
10224
  );
@@ -9549,11 +10234,11 @@ function registerSessionEnd(session2) {
9549
10234
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
9550
10235
  }
9551
10236
  };
9552
- await writeFile25(topicMatch.filePath, serializeMemory21({ frontmatter: newFrontmatter, body }), "utf8");
10237
+ await writeFile27(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
9553
10238
  await cleanupObservations();
9554
10239
  if (!opts.quiet) {
9555
10240
  ui.success(`Session recap updated (revision #${revisionCount})`);
9556
- ui.info(`id=${fm.id} file=${path34.relative(root, topicMatch.filePath)}`);
10241
+ ui.info(`id=${fm.id} file=${path36.relative(root, topicMatch.filePath)}`);
9557
10242
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
9558
10243
  }
9559
10244
  return;
@@ -9570,12 +10255,12 @@ function registerSessionEnd(session2) {
9570
10255
  status: "validated"
9571
10256
  });
9572
10257
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9573
- await mkdir15(path34.dirname(file), { recursive: true });
9574
- await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
10258
+ await mkdir15(path36.dirname(file), { recursive: true });
10259
+ await writeFile27(file, serializeMemory23({ frontmatter, body }), "utf8");
9575
10260
  await cleanupObservations();
9576
10261
  if (!opts.quiet) {
9577
10262
  ui.success(`Session recap created`);
9578
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path34.relative(root, file)}`);
10263
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path36.relative(root, file)}`);
9579
10264
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
9580
10265
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
9581
10266
  }
@@ -9587,15 +10272,15 @@ function parseCsv5(value) {
9587
10272
  }
9588
10273
 
9589
10274
  // src/commands/snapshot.ts
9590
- import { existsSync as existsSync54 } from "fs";
10275
+ import { existsSync as existsSync56 } from "fs";
9591
10276
  import { readdir as readdir4 } from "fs/promises";
9592
- import path35 from "path";
10277
+ import path37 from "path";
9593
10278
  import "commander";
9594
10279
  import {
9595
10280
  diffContract,
9596
- findProjectRoot as findProjectRoot33,
9597
- loadConfig as loadConfig5,
9598
- resolveHaivePaths as resolveHaivePaths30,
10281
+ findProjectRoot as findProjectRoot34,
10282
+ loadConfig as loadConfig7,
10283
+ resolveHaivePaths as resolveHaivePaths31,
9599
10284
  snapshotContract
9600
10285
  } from "@hiveai/core";
9601
10286
  function registerSnapshot(program2) {
@@ -9620,16 +10305,16 @@ function registerSnapshot(program2) {
9620
10305
  "--format <format>",
9621
10306
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
9622
10307
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
9623
- const root = findProjectRoot33(opts.dir);
9624
- const paths = resolveHaivePaths30(root);
9625
- if (!existsSync54(paths.haiveDir)) {
10308
+ const root = findProjectRoot34(opts.dir);
10309
+ const paths = resolveHaivePaths31(root);
10310
+ if (!existsSync56(paths.haiveDir)) {
9626
10311
  ui.error("No .ai/ found. Run `haive init` first.");
9627
10312
  process.exitCode = 1;
9628
10313
  return;
9629
10314
  }
9630
10315
  if (opts.list) {
9631
- const contractsDir = path35.join(paths.haiveDir, "contracts");
9632
- if (!existsSync54(contractsDir)) {
10316
+ const contractsDir = path37.join(paths.haiveDir, "contracts");
10317
+ if (!existsSync56(contractsDir)) {
9633
10318
  console.log(ui.dim("No contract snapshots found."));
9634
10319
  return;
9635
10320
  }
@@ -9649,7 +10334,7 @@ function registerSnapshot(program2) {
9649
10334
  }
9650
10335
  if (opts.diff) {
9651
10336
  if (!opts.name) {
9652
- const config2 = await loadConfig5(paths);
10337
+ const config2 = await loadConfig7(paths);
9653
10338
  const contracts = config2.contractFiles ?? [];
9654
10339
  if (contracts.length === 0) {
9655
10340
  ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
@@ -9661,7 +10346,7 @@ function registerSnapshot(program2) {
9661
10346
  }
9662
10347
  return;
9663
10348
  }
9664
- const config = await loadConfig5(paths);
10349
+ const config = await loadConfig7(paths);
9665
10350
  const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
9666
10351
  if (!configured && !opts.contract) {
9667
10352
  ui.error(
@@ -9684,7 +10369,7 @@ function registerSnapshot(program2) {
9684
10369
  return;
9685
10370
  }
9686
10371
  const contractPath = opts.contract;
9687
- const name = opts.name ?? path35.basename(contractPath, path35.extname(contractPath));
10372
+ const name = opts.name ?? path37.basename(contractPath, path37.extname(contractPath));
9688
10373
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
9689
10374
  const contract = { name, path: contractPath, format };
9690
10375
  try {
@@ -9739,8 +10424,8 @@ async function runDiff(root, haiveDir, contract) {
9739
10424
  }
9740
10425
  }
9741
10426
  function detectFormat(filePath) {
9742
- const ext = path35.extname(filePath).toLowerCase();
9743
- const base = path35.basename(filePath).toLowerCase();
10427
+ const ext = path37.extname(filePath).toLowerCase();
10428
+ const base = path37.basename(filePath).toLowerCase();
9744
10429
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
9745
10430
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
9746
10431
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -9753,18 +10438,18 @@ function detectFormat(filePath) {
9753
10438
  }
9754
10439
 
9755
10440
  // src/commands/hub.ts
9756
- import { existsSync as existsSync55 } from "fs";
9757
- import { mkdir as mkdir16, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9758
- import path36 from "path";
9759
- import { spawnSync as spawnSync4 } from "child_process";
10441
+ import { existsSync as existsSync57 } from "fs";
10442
+ import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile28, copyFile } from "fs/promises";
10443
+ import path38 from "path";
10444
+ import { spawnSync as spawnSync5 } from "child_process";
9760
10445
  import "commander";
9761
10446
  import {
9762
- findProjectRoot as findProjectRoot34,
9763
- loadConfig as loadConfig6,
9764
- loadMemoriesFromDir as loadMemoriesFromDir28,
9765
- resolveHaivePaths as resolveHaivePaths31,
9766
- saveConfig as saveConfig2,
9767
- serializeMemory as serializeMemory23
10447
+ findProjectRoot as findProjectRoot35,
10448
+ loadConfig as loadConfig8,
10449
+ loadMemoriesFromDir as loadMemoriesFromDir29,
10450
+ resolveHaivePaths as resolveHaivePaths32,
10451
+ saveConfig as saveConfig3,
10452
+ serializeMemory as serializeMemory24
9768
10453
  } from "@hiveai/core";
9769
10454
  function registerHub(program2) {
9770
10455
  const hub = program2.command("hub").description(
@@ -9774,21 +10459,21 @@ function registerHub(program2) {
9774
10459
  hub.command("init <hubPath>").description(
9775
10460
  "Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
9776
10461
  ).action(async (hubPath) => {
9777
- const absPath = path36.resolve(hubPath);
10462
+ const absPath = path38.resolve(hubPath);
9778
10463
  await mkdir16(absPath, { recursive: true });
9779
- const gitCheck = spawnSync4("git", ["rev-parse", "--git-dir"], { cwd: absPath });
10464
+ const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9780
10465
  if (gitCheck.status !== 0) {
9781
- const init = spawnSync4("git", ["init"], { cwd: absPath, encoding: "utf8" });
10466
+ const init = spawnSync5("git", ["init"], { cwd: absPath, encoding: "utf8" });
9782
10467
  if (init.status !== 0) {
9783
10468
  ui.error(`git init failed: ${init.stderr}`);
9784
10469
  process.exitCode = 1;
9785
10470
  return;
9786
10471
  }
9787
10472
  }
9788
- const sharedDir = path36.join(absPath, ".ai", "memories", "shared");
10473
+ const sharedDir = path38.join(absPath, ".ai", "memories", "shared");
9789
10474
  await mkdir16(sharedDir, { recursive: true });
9790
- await writeFile26(
9791
- path36.join(absPath, ".ai", "README.md"),
10475
+ await writeFile28(
10476
+ path38.join(absPath, ".ai", "README.md"),
9792
10477
  `# hAIve Team Knowledge Hub
9793
10478
 
9794
10479
  This repo is a shared knowledge hub for hAIve.
@@ -9809,13 +10494,13 @@ haive hub pull # import into a project
9809
10494
  `,
9810
10495
  "utf8"
9811
10496
  );
9812
- await writeFile26(
9813
- path36.join(absPath, ".gitignore"),
10497
+ await writeFile28(
10498
+ path38.join(absPath, ".gitignore"),
9814
10499
  ".ai/.cache/\n.ai/memories/personal/\n",
9815
10500
  "utf8"
9816
10501
  );
9817
- spawnSync4("git", ["add", "."], { cwd: absPath });
9818
- spawnSync4("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
10502
+ spawnSync5("git", ["add", "."], { cwd: absPath });
10503
+ spawnSync5("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
9819
10504
  cwd: absPath,
9820
10505
  encoding: "utf8"
9821
10506
  });
@@ -9825,7 +10510,7 @@ haive hub pull # import into a project
9825
10510
  `
9826
10511
  Next steps:
9827
10512
  1. Add hubPath to your project's .ai/haive.config.json:
9828
- { "hubPath": "${path36.relative(process.cwd(), absPath)}" }
10513
+ { "hubPath": "${path38.relative(process.cwd(), absPath)}" }
9829
10514
  2. Run \`haive hub push\` to publish your shared memories
9830
10515
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
9831
10516
  `
@@ -9844,9 +10529,9 @@ Next steps:
9844
10529
  haive hub push --commit --message "feat: add payment API contract memories"
9845
10530
  `
9846
10531
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
9847
- const root = findProjectRoot34(opts.dir);
9848
- const paths = resolveHaivePaths31(root);
9849
- const config = await loadConfig6(paths);
10532
+ const root = findProjectRoot35(opts.dir);
10533
+ const paths = resolveHaivePaths32(root);
10534
+ const config = await loadConfig8(paths);
9850
10535
  if (!config.hubPath) {
9851
10536
  ui.error(
9852
10537
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -9854,16 +10539,16 @@ Next steps:
9854
10539
  process.exitCode = 1;
9855
10540
  return;
9856
10541
  }
9857
- const hubRoot = path36.resolve(root, config.hubPath);
9858
- if (!existsSync55(hubRoot)) {
10542
+ const hubRoot = path38.resolve(root, config.hubPath);
10543
+ if (!existsSync57(hubRoot)) {
9859
10544
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
9860
10545
  process.exitCode = 1;
9861
10546
  return;
9862
10547
  }
9863
- const projectName = path36.basename(root);
9864
- const destDir = path36.join(hubRoot, ".ai", "memories", "shared", projectName);
10548
+ const projectName = path38.basename(root);
10549
+ const destDir = path38.join(hubRoot, ".ai", "memories", "shared", projectName);
9865
10550
  await mkdir16(destDir, { recursive: true });
9866
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10551
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
9867
10552
  const shared = all.filter(
9868
10553
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
9869
10554
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -9880,18 +10565,18 @@ Next steps:
9880
10565
  for (const { memory: memory2 } of shared) {
9881
10566
  const fm = memory2.frontmatter;
9882
10567
  const fileName = `${fm.id}.md`;
9883
- const destPath = path36.join(destDir, fileName);
9884
- await writeFile26(destPath, serializeMemory23(memory2), "utf8");
10568
+ const destPath = path38.join(destDir, fileName);
10569
+ await writeFile28(destPath, serializeMemory24(memory2), "utf8");
9885
10570
  pushed++;
9886
10571
  }
9887
10572
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
9888
10573
  console.log(ui.dim(` Location: ${destDir}`));
9889
10574
  if (opts.commit) {
9890
10575
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
9891
- spawnSync4("git", ["add", path36.join(".ai", "memories", "shared", projectName)], {
10576
+ spawnSync5("git", ["add", path38.join(".ai", "memories", "shared", projectName)], {
9892
10577
  cwd: hubRoot
9893
10578
  });
9894
- const commit = spawnSync4("git", ["commit", "-m", message], {
10579
+ const commit = spawnSync5("git", ["commit", "-m", message], {
9895
10580
  cwd: hubRoot,
9896
10581
  encoding: "utf8"
9897
10582
  });
@@ -9913,9 +10598,9 @@ Next steps:
9913
10598
  hub.command("pull").description(
9914
10599
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
9915
10600
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
9916
- const root = findProjectRoot34(opts.dir);
9917
- const paths = resolveHaivePaths31(root);
9918
- const config = await loadConfig6(paths);
10601
+ const root = findProjectRoot35(opts.dir);
10602
+ const paths = resolveHaivePaths32(root);
10603
+ const config = await loadConfig8(paths);
9919
10604
  if (!config.hubPath) {
9920
10605
  ui.error(
9921
10606
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -9923,13 +10608,13 @@ Next steps:
9923
10608
  process.exitCode = 1;
9924
10609
  return;
9925
10610
  }
9926
- const hubRoot = path36.resolve(root, config.hubPath);
9927
- const hubSharedDir = path36.join(hubRoot, ".ai", "memories", "shared");
9928
- if (!existsSync55(hubSharedDir)) {
10611
+ const hubRoot = path38.resolve(root, config.hubPath);
10612
+ const hubSharedDir = path38.join(hubRoot, ".ai", "memories", "shared");
10613
+ if (!existsSync57(hubSharedDir)) {
9929
10614
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
9930
10615
  return;
9931
10616
  }
9932
- const projectName = path36.basename(root);
10617
+ const projectName = path38.basename(root);
9933
10618
  const { readdir: readdir6 } = await import("fs/promises");
9934
10619
  const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
9935
10620
  if (projectDirs.length === 0) {
@@ -9939,17 +10624,17 @@ Next steps:
9939
10624
  let totalImported = 0;
9940
10625
  let totalUpdated = 0;
9941
10626
  for (const sourceName of projectDirs) {
9942
- const sourceDir = path36.join(hubSharedDir, sourceName);
9943
- const destDir = path36.join(paths.memoriesDir, "shared", sourceName);
10627
+ const sourceDir = path38.join(hubSharedDir, sourceName);
10628
+ const destDir = path38.join(paths.memoriesDir, "shared", sourceName);
9944
10629
  await mkdir16(destDir, { recursive: true });
9945
10630
  const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
9946
10631
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
9947
10632
  const existingInDest = await loadDir(destDir);
9948
10633
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
9949
10634
  for (const file of sourceFiles) {
9950
- const srcPath = path36.join(sourceDir, file);
9951
- const destPath = path36.join(destDir, file);
9952
- const fileContent = await readFile15(srcPath, "utf8");
10635
+ const srcPath = path38.join(sourceDir, file);
10636
+ const destPath = path38.join(destDir, file);
10637
+ const fileContent = await readFile16(srcPath, "utf8");
9953
10638
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
9954
10639
  if (!alreadyTagged) {
9955
10640
  await copyFile(srcPath, destPath);
@@ -9972,27 +10657,27 @@ Next steps:
9972
10657
  );
9973
10658
  });
9974
10659
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
9975
- const root = findProjectRoot34(opts.dir);
9976
- const paths = resolveHaivePaths31(root);
9977
- const config = await loadConfig6(paths);
10660
+ const root = findProjectRoot35(opts.dir);
10661
+ const paths = resolveHaivePaths32(root);
10662
+ const config = await loadConfig8(paths);
9978
10663
  console.log(ui.bold("Hub status"));
9979
10664
  console.log(
9980
10665
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
9981
10666
  );
9982
- const sharedDir = path36.join(paths.memoriesDir, "shared");
9983
- if (existsSync55(sharedDir)) {
10667
+ const sharedDir = path38.join(paths.memoriesDir, "shared");
10668
+ if (existsSync57(sharedDir)) {
9984
10669
  const { readdir: readdir6 } = await import("fs/promises");
9985
10670
  const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
9986
10671
  console.log(`
9987
10672
  Imported from ${sources.length} source(s):`);
9988
10673
  for (const src of sources) {
9989
- const files = (await readdir6(path36.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
10674
+ const files = (await readdir6(path38.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
9990
10675
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
9991
10676
  }
9992
10677
  } else {
9993
10678
  console.log(ui.dim(" No imported shared memories yet."));
9994
10679
  }
9995
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10680
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
9996
10681
  const outgoing = all.filter(
9997
10682
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
9998
10683
  );
@@ -10001,25 +10686,25 @@ Next steps:
10001
10686
  if (outgoing.length > 0) {
10002
10687
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
10003
10688
  }
10004
- void readFile15;
10005
- void writeFile26;
10006
- void saveConfig2;
10689
+ void readFile16;
10690
+ void writeFile28;
10691
+ void saveConfig3;
10007
10692
  });
10008
10693
  }
10009
10694
 
10010
10695
  // src/commands/stats.ts
10011
10696
  import "commander";
10012
- import { existsSync as existsSync56 } from "fs";
10013
- import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
10014
- import path37 from "path";
10697
+ import { existsSync as existsSync58 } from "fs";
10698
+ import { mkdir as mkdir17, writeFile as writeFile29 } from "fs/promises";
10699
+ import path39 from "path";
10015
10700
  import {
10016
10701
  aggregateUsage,
10017
- findProjectRoot as findProjectRoot35,
10018
- loadMemoriesFromDir as loadMemoriesFromDir29,
10019
- loadUsageIndex as loadUsageIndex23,
10702
+ findProjectRoot as findProjectRoot36,
10703
+ loadMemoriesFromDir as loadMemoriesFromDir30,
10704
+ loadUsageIndex as loadUsageIndex24,
10020
10705
  parseSince,
10021
10706
  readUsageEvents as readUsageEvents2,
10022
- resolveHaivePaths as resolveHaivePaths32,
10707
+ resolveHaivePaths as resolveHaivePaths33,
10023
10708
  usageLogSize
10024
10709
  } from "@hiveai/core";
10025
10710
  function registerStats(program2) {
@@ -10028,8 +10713,8 @@ function registerStats(program2) {
10028
10713
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
10029
10714
  void 0
10030
10715
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10031
- const root = findProjectRoot35(opts.dir);
10032
- const paths = resolveHaivePaths32(root);
10716
+ const root = findProjectRoot36(opts.dir);
10717
+ const paths = resolveHaivePaths33(root);
10033
10718
  if (opts.exportReport) {
10034
10719
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
10035
10720
  return;
@@ -10083,12 +10768,12 @@ function registerStats(program2) {
10083
10768
  });
10084
10769
  }
10085
10770
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10086
- const outAbs = path37.isAbsolute(outRelative) ? path37.resolve(outRelative) : path37.resolve(root, outRelative);
10771
+ const outAbs = path39.isAbsolute(outRelative) ? path39.resolve(outRelative) : path39.resolve(root, outRelative);
10087
10772
  const size = await usageLogSize(paths);
10088
10773
  let events = await readUsageEvents2(paths);
10089
10774
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
10090
- if (existsSync56(paths.memoriesDir)) {
10091
- const mems = await loadMemoriesFromDir29(paths.memoriesDir);
10775
+ if (existsSync58(paths.memoriesDir)) {
10776
+ const mems = await loadMemoriesFromDir30(paths.memoriesDir);
10092
10777
  for (const { memory: memory2 } of mems) {
10093
10778
  const fm = memory2.frontmatter;
10094
10779
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -10102,7 +10787,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10102
10787
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
10103
10788
  let memoryHitsLeader = null;
10104
10789
  try {
10105
- const usageIdx = await loadUsageIndex23(paths);
10790
+ const usageIdx = await loadUsageIndex24(paths);
10106
10791
  const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
10107
10792
  memoryHitsLeader = tops[0] ?? null;
10108
10793
  } catch {
@@ -10118,7 +10803,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10118
10803
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
10119
10804
  events = [];
10120
10805
  }
10121
- await mkdir17(path37.dirname(outAbs), { recursive: true });
10806
+ await mkdir17(path39.dirname(outAbs), { recursive: true });
10122
10807
  const payload = {
10123
10808
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
10124
10809
  project_root: root,
@@ -10130,11 +10815,11 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10130
10815
  top_memory_reads: memoryHitsLeader,
10131
10816
  roi_hints: roiHints
10132
10817
  };
10133
- await writeFile27(outAbs, JSON.stringify(payload, null, 2), "utf8");
10818
+ await writeFile29(outAbs, JSON.stringify(payload, null, 2), "utf8");
10134
10819
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
10135
10820
  }
10136
10821
  async function renderMemoryHits(paths, opts) {
10137
- const index = await loadUsageIndex23(paths);
10822
+ const index = await loadUsageIndex24(paths);
10138
10823
  const since = parseSince(opts.since ?? "30d");
10139
10824
  const sinceMs = since ? new Date(since).getTime() : null;
10140
10825
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -10182,13 +10867,13 @@ import { performance } from "perf_hooks";
10182
10867
  import "commander";
10183
10868
  import {
10184
10869
  estimateTokens as estimateTokens3,
10185
- findProjectRoot as findProjectRoot36,
10186
- resolveHaivePaths as resolveHaivePaths33
10870
+ findProjectRoot as findProjectRoot37,
10871
+ resolveHaivePaths as resolveHaivePaths34
10187
10872
  } from "@hiveai/core";
10188
10873
  function registerBench(program2) {
10189
10874
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10190
- const root = findProjectRoot36(opts.dir);
10191
- const paths = resolveHaivePaths33(root);
10875
+ const root = findProjectRoot37(opts.dir);
10876
+ const paths = resolveHaivePaths34(root);
10192
10877
  const ctx = { paths };
10193
10878
  const task = opts.task ?? "audit dependencies for security risks";
10194
10879
  const scenarios = [
@@ -10307,11 +10992,11 @@ function summarize(name, t0, payload, notes) {
10307
10992
  }
10308
10993
 
10309
10994
  // src/commands/benchmark.ts
10310
- import { existsSync as existsSync57 } from "fs";
10311
- import { readdir as readdir5, readFile as readFile16, writeFile as writeFile28 } from "fs/promises";
10312
- import path38 from "path";
10995
+ import { existsSync as existsSync59 } from "fs";
10996
+ import { readdir as readdir5, readFile as readFile17, writeFile as writeFile30 } from "fs/promises";
10997
+ import path40 from "path";
10313
10998
  import "commander";
10314
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot37 } from "@hiveai/core";
10999
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
10315
11000
  function registerBenchmark(program2) {
10316
11001
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
10317
11002
  benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
@@ -10324,9 +11009,9 @@ function registerBenchmark(program2) {
10324
11009
  }
10325
11010
  const markdown = renderMarkdown(root, summary, rows);
10326
11011
  if (opts.out) {
10327
- const outFile = path38.isAbsolute(opts.out) ? opts.out : path38.join(root, opts.out);
10328
- await writeFile28(outFile, markdown, "utf8");
10329
- ui.success(`wrote ${path38.relative(process.cwd(), outFile)}`);
11012
+ const outFile = path40.isAbsolute(opts.out) ? opts.out : path40.join(root, opts.out);
11013
+ await writeFile30(outFile, markdown, "utf8");
11014
+ ui.success(`wrote ${path40.relative(process.cwd(), outFile)}`);
10330
11015
  return;
10331
11016
  }
10332
11017
  console.log(markdown);
@@ -10350,20 +11035,20 @@ function registerBenchmark(program2) {
10350
11035
  }
10351
11036
  function resolveBenchmarkRoot(dir) {
10352
11037
  const candidate = dir ?? "benchmarks/agent-benchmark";
10353
- if (path38.isAbsolute(candidate)) return candidate;
10354
- const projectRoot = findProjectRoot37(process.cwd());
10355
- return path38.join(projectRoot, candidate);
11038
+ if (path40.isAbsolute(candidate)) return candidate;
11039
+ const projectRoot = findProjectRoot38(process.cwd());
11040
+ return path40.join(projectRoot, candidate);
10356
11041
  }
10357
11042
  async function collectRows(root) {
10358
- if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
11043
+ if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
10359
11044
  const entries = await readdir5(root, { withFileTypes: true });
10360
11045
  const rows = [];
10361
11046
  for (const entry of entries) {
10362
11047
  if (!entry.isDirectory()) continue;
10363
- const fixtureDir = path38.join(root, entry.name);
10364
- const reportFile = path38.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
10365
- if (!existsSync57(reportFile)) continue;
10366
- const report = await readFile16(reportFile, "utf8");
11048
+ const fixtureDir = path40.join(root, entry.name);
11049
+ const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11050
+ if (!existsSync59(reportFile)) continue;
11051
+ const report = await readFile17(reportFile, "utf8");
10367
11052
  rows.push(parseAgentReport(entry.name, report));
10368
11053
  }
10369
11054
  return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
@@ -10452,20 +11137,20 @@ function escapeRegExp(value) {
10452
11137
  }
10453
11138
 
10454
11139
  // src/commands/memory-suggest.ts
10455
- import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
10456
- import { existsSync as existsSync58 } from "fs";
10457
- import path39 from "path";
11140
+ import { mkdir as mkdir18, writeFile as writeFile31 } from "fs/promises";
11141
+ import { existsSync as existsSync60 } from "fs";
11142
+ import path41 from "path";
10458
11143
  import "commander";
10459
11144
  import {
10460
11145
  aggregateUsage as aggregateUsage2,
10461
11146
  buildFrontmatter as buildFrontmatter11,
10462
- findProjectRoot as findProjectRoot38,
10463
- loadMemoriesFromDir as loadMemoriesFromDir30,
11147
+ findProjectRoot as findProjectRoot39,
11148
+ loadMemoriesFromDir as loadMemoriesFromDir31,
10464
11149
  memoryFilePath as memoryFilePath10,
10465
11150
  parseSince as parseSince2,
10466
11151
  readUsageEvents as readUsageEvents3,
10467
- resolveHaivePaths as resolveHaivePaths34,
10468
- serializeMemory as serializeMemory24
11152
+ resolveHaivePaths as resolveHaivePaths35,
11153
+ serializeMemory as serializeMemory25
10469
11154
  } from "@hiveai/core";
10470
11155
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
10471
11156
  "mem_search",
@@ -10477,8 +11162,8 @@ function registerMemorySuggest(memory2) {
10477
11162
  memory2.command("suggest").description(
10478
11163
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
10479
11164
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10480
- const root = findProjectRoot38(opts.dir);
10481
- const paths = resolveHaivePaths34(root);
11165
+ const root = findProjectRoot39(opts.dir);
11166
+ const paths = resolveHaivePaths35(root);
10482
11167
  const events = await readUsageEvents3(paths);
10483
11168
  if (events.length === 0) {
10484
11169
  if (opts.json) {
@@ -10524,7 +11209,7 @@ function registerMemorySuggest(memory2) {
10524
11209
  }
10525
11210
  const created = [];
10526
11211
  const skipped = [];
10527
- const existing = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
11212
+ const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
10528
11213
  for (const s of top) {
10529
11214
  const slug = slugify(s.query);
10530
11215
  if (!slug) {
@@ -10547,13 +11232,13 @@ function registerMemorySuggest(memory2) {
10547
11232
  fm.status = "draft";
10548
11233
  const body = renderTemplate(s);
10549
11234
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
10550
- await mkdir18(path39.dirname(file), { recursive: true });
10551
- if (existsSync58(file)) {
10552
- skipped.push({ query: s.query, reason: `file already exists at ${path39.relative(root, file)}` });
11235
+ await mkdir18(path41.dirname(file), { recursive: true });
11236
+ if (existsSync60(file)) {
11237
+ skipped.push({ query: s.query, reason: `file already exists at ${path41.relative(root, file)}` });
10553
11238
  continue;
10554
11239
  }
10555
- await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
10556
- created.push({ id: fm.id, file: path39.relative(root, file), query: s.query });
11240
+ await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
11241
+ created.push({ id: fm.id, file: path41.relative(root, file), query: s.query });
10557
11242
  }
10558
11243
  if (opts.json) {
10559
11244
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -10646,26 +11331,26 @@ function truncate2(text, max) {
10646
11331
  }
10647
11332
 
10648
11333
  // src/commands/memory-archive.ts
10649
- import { existsSync as existsSync59 } from "fs";
10650
- import { writeFile as writeFile30 } from "fs/promises";
10651
- import path40 from "path";
11334
+ import { existsSync as existsSync61 } from "fs";
11335
+ import { writeFile as writeFile33 } from "fs/promises";
11336
+ import path43 from "path";
10652
11337
  import "commander";
10653
11338
  import {
10654
- findProjectRoot as findProjectRoot39,
10655
- getUsage as getUsage18,
10656
- loadMemoriesFromDir as loadMemoriesFromDir31,
10657
- loadUsageIndex as loadUsageIndex24,
10658
- resolveHaivePaths as resolveHaivePaths35,
10659
- serializeMemory as serializeMemory25
11339
+ findProjectRoot as findProjectRoot40,
11340
+ getUsage as getUsage19,
11341
+ loadMemoriesFromDir as loadMemoriesFromDir32,
11342
+ loadUsageIndex as loadUsageIndex25,
11343
+ resolveHaivePaths as resolveHaivePaths36,
11344
+ serializeMemory as serializeMemory26
10660
11345
  } from "@hiveai/core";
10661
11346
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
10662
11347
  function registerMemoryArchive(memory2) {
10663
11348
  memory2.command("archive").description(
10664
11349
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
10665
11350
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10666
- const root = findProjectRoot39(opts.dir);
10667
- const paths = resolveHaivePaths35(root);
10668
- if (!existsSync59(paths.memoriesDir)) {
11351
+ const root = findProjectRoot40(opts.dir);
11352
+ const paths = resolveHaivePaths36(root);
11353
+ if (!existsSync61(paths.memoriesDir)) {
10669
11354
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10670
11355
  process.exitCode = 1;
10671
11356
  return;
@@ -10677,8 +11362,8 @@ function registerMemoryArchive(memory2) {
10677
11362
  return;
10678
11363
  }
10679
11364
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
10680
- const all = await loadMemoriesFromDir31(paths.memoriesDir);
10681
- const usage = await loadUsageIndex24(paths);
11365
+ const all = await loadMemoriesFromDir32(paths.memoriesDir);
11366
+ const usage = await loadUsageIndex25(paths);
10682
11367
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
10683
11368
  const candidates = [];
10684
11369
  for (const { memory: mem, filePath } of all) {
@@ -10686,10 +11371,10 @@ function registerMemoryArchive(memory2) {
10686
11371
  if (typeFilter && fm.type !== typeFilter) continue;
10687
11372
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
10688
11373
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
10689
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(path40.join(paths.root, p)));
11374
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path43.join(paths.root, p)));
10690
11375
  const isAnchorless = !hasAnyAnchor;
10691
11376
  if (!isAnchorless && !allPathsGone) continue;
10692
- const u = getUsage18(usage, fm.id);
11377
+ const u = getUsage19(usage, fm.id);
10693
11378
  const lastSeen = u.last_read_at ?? fm.created_at;
10694
11379
  if (Date.parse(lastSeen) >= cutoff) continue;
10695
11380
  candidates.push({
@@ -10734,7 +11419,7 @@ function registerMemoryArchive(memory2) {
10734
11419
  if (!found) continue;
10735
11420
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
10736
11421
  try {
10737
- await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
11422
+ await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
10738
11423
  archived++;
10739
11424
  } catch (err) {
10740
11425
  if (!opts.json) {
@@ -10760,31 +11445,32 @@ function parseDays(input) {
10760
11445
  }
10761
11446
 
10762
11447
  // src/commands/doctor.ts
10763
- import { existsSync as existsSync60 } from "fs";
10764
- import { readFile as readFile17, stat } from "fs/promises";
10765
- import path41 from "path";
11448
+ import { existsSync as existsSync63 } from "fs";
11449
+ import { readFile as readFile18, stat } from "fs/promises";
11450
+ import path44 from "path";
10766
11451
  import { execFileSync, execSync as execSync3 } from "child_process";
10767
11452
  import "commander";
10768
11453
  import {
10769
11454
  codeMapPath as codeMapPath2,
10770
- findProjectRoot as findProjectRoot40,
10771
- getUsage as getUsage19,
10772
- loadCodeMap as loadCodeMap5,
10773
- loadConfig as loadConfig7,
10774
- loadMemoriesFromDir as loadMemoriesFromDir32,
10775
- loadUsageIndex as loadUsageIndex25,
11455
+ findProjectRoot as findProjectRoot41,
11456
+ getUsage as getUsage20,
11457
+ loadCodeMap as loadCodeMap7,
11458
+ loadConfig as loadConfig9,
11459
+ loadMemoriesFromDir as loadMemoriesFromDir33,
11460
+ loadUsageIndex as loadUsageIndex26,
10776
11461
  readUsageEvents as readUsageEvents4,
10777
- resolveHaivePaths as resolveHaivePaths36
11462
+ resolveHaivePaths as resolveHaivePaths37
10778
11463
  } from "@hiveai/core";
10779
11464
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
10780
11465
  function registerDoctor(program2) {
10781
11466
  program2.command("doctor").description(
10782
11467
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
10783
- ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10784
- const root = findProjectRoot40(opts.dir);
10785
- const paths = resolveHaivePaths36(root);
11468
+ ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11469
+ const root = findProjectRoot41(opts.dir);
11470
+ const paths = resolveHaivePaths37(root);
10786
11471
  const findings = [];
10787
- if (!existsSync60(paths.haiveDir)) {
11472
+ const repairs = [];
11473
+ if (!existsSync63(paths.haiveDir)) {
10788
11474
  findings.push({
10789
11475
  severity: "error",
10790
11476
  code: "not-initialized",
@@ -10793,7 +11479,18 @@ function registerDoctor(program2) {
10793
11479
  });
10794
11480
  return emit(findings, opts);
10795
11481
  }
10796
- if (!existsSync60(paths.projectContext)) {
11482
+ if (opts.fix && !opts.dryRun) {
11483
+ repairs.push(
11484
+ ...await applyAutopilotRepairs(root, paths, {
11485
+ applyConfig: true,
11486
+ applyContext: true,
11487
+ applyCorpus: true,
11488
+ applyCodeMap: true,
11489
+ applyCodeSearch: true
11490
+ })
11491
+ );
11492
+ }
11493
+ if (!existsSync63(paths.projectContext)) {
10797
11494
  findings.push({
10798
11495
  severity: "warn",
10799
11496
  code: "no-project-context",
@@ -10801,8 +11498,8 @@ function registerDoctor(program2) {
10801
11498
  fix: "haive init"
10802
11499
  });
10803
11500
  } else {
10804
- const { readFile: readFile19 } = await import("fs/promises");
10805
- const content = await readFile19(paths.projectContext, "utf8");
11501
+ const { readFile: readFile20 } = await import("fs/promises");
11502
+ const content = await readFile20(paths.projectContext, "utf8");
10806
11503
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10807
11504
  if (isTemplate) {
10808
11505
  findings.push({
@@ -10812,8 +11509,17 @@ function registerDoctor(program2) {
10812
11509
  fix: "Invoke the bootstrap_project MCP prompt from your AI client."
10813
11510
  });
10814
11511
  }
11512
+ const versionStatus = await projectContextVersionStatus(root, paths);
11513
+ if (versionStatus.mismatch) {
11514
+ findings.push({
11515
+ severity: "warn",
11516
+ code: "project-context-version-mismatch",
11517
+ message: `.ai/project-context.md version metadata (${versionStatus.currentVersion ?? "missing"}) does not match package.json (${versionStatus.expectedVersion}).`,
11518
+ fix: "haive doctor --fix"
11519
+ });
11520
+ }
10815
11521
  }
10816
- const memories = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
11522
+ const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10817
11523
  const now = Date.now();
10818
11524
  if (memories.length === 0) {
10819
11525
  findings.push({
@@ -10822,7 +11528,7 @@ function registerDoctor(program2) {
10822
11528
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
10823
11529
  });
10824
11530
  } else {
10825
- const usage = await loadUsageIndex25(paths);
11531
+ const usage = await loadUsageIndex26(paths);
10826
11532
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
10827
11533
  if (stale.length > 0) {
10828
11534
  findings.push({
@@ -10854,7 +11560,7 @@ function registerDoctor(program2) {
10854
11560
  }
10855
11561
  const decayCandidates = memories.filter((m) => {
10856
11562
  if (m.memory.frontmatter.status !== "validated") return false;
10857
- const u = getUsage19(usage, m.memory.frontmatter.id);
11563
+ const u = getUsage20(usage, m.memory.frontmatter.id);
10858
11564
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
10859
11565
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
10860
11566
  });
@@ -10867,7 +11573,7 @@ function registerDoctor(program2) {
10867
11573
  });
10868
11574
  }
10869
11575
  }
10870
- const codeMap = await loadCodeMap5(paths);
11576
+ const codeMap = await loadCodeMap7(paths);
10871
11577
  if (!codeMap) {
10872
11578
  findings.push({
10873
11579
  severity: "warn",
@@ -10922,14 +11628,14 @@ function registerDoctor(program2) {
10922
11628
  });
10923
11629
  }
10924
11630
  }
10925
- const config = await loadConfig7(paths);
11631
+ const config = await loadConfig9(paths);
10926
11632
  if (config.enforcement?.requireBriefingFirst) {
10927
- const claudeSettings = path41.join(root, ".claude", "settings.local.json");
11633
+ const claudeSettings = path44.join(root, ".claude", "settings.local.json");
10928
11634
  let hasClaudeEnforcement = false;
10929
- if (existsSync60(claudeSettings)) {
11635
+ if (existsSync63(claudeSettings)) {
10930
11636
  try {
10931
- const { readFile: readFile19 } = await import("fs/promises");
10932
- const raw = await readFile19(claudeSettings, "utf8");
11637
+ const { readFile: readFile20 } = await import("fs/promises");
11638
+ const raw = await readFile20(claudeSettings, "utf8");
10933
11639
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
10934
11640
  } catch {
10935
11641
  hasClaudeEnforcement = false;
@@ -10952,14 +11658,14 @@ function registerDoctor(program2) {
10952
11658
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
10953
11659
  });
10954
11660
  }
10955
- findings.push(...await collectInstallFindings(root, "0.9.16"));
11661
+ findings.push(...await collectInstallFindings(root, "0.9.18"));
10956
11662
  try {
10957
11663
  const legacyRaw = execSync3("haive-mcp --version", {
10958
11664
  encoding: "utf8",
10959
11665
  timeout: 3e3,
10960
11666
  stdio: ["ignore", "pipe", "ignore"]
10961
11667
  }).trim();
10962
- const cliVersion = "0.9.16";
11668
+ const cliVersion = "0.9.18";
10963
11669
  if (legacyRaw && legacyRaw !== cliVersion) {
10964
11670
  findings.push({
10965
11671
  severity: "warn",
@@ -10972,37 +11678,123 @@ npm uninstall -g @hiveai/mcp`
10972
11678
  }
10973
11679
  } catch {
10974
11680
  }
10975
- emit(findings, opts);
11681
+ if (repairs.length > 0) {
11682
+ findings.push({
11683
+ severity: "info",
11684
+ code: "autopilot-repairs-applied",
11685
+ message: repairs.map((repair) => repair.message).join(" "),
11686
+ section: "Next actions"
11687
+ });
11688
+ }
11689
+ emit(findings, opts, repairs);
10976
11690
  });
10977
11691
  }
10978
- function emit(findings, opts) {
11692
+ function emit(findings, opts, repairs = []) {
11693
+ const classified = findings.map((finding) => ({
11694
+ ...finding,
11695
+ section: finding.section ?? sectionForFinding(finding)
11696
+ }));
11697
+ const scores = computeDoctorScores(classified);
10979
11698
  if (opts.json) {
10980
- console.log(JSON.stringify({ findings }, null, 2));
11699
+ console.log(JSON.stringify({
11700
+ scores,
11701
+ findings: classified,
11702
+ sections: groupBySection(classified),
11703
+ next_actions: nextActions(classified),
11704
+ repairs,
11705
+ fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "apply" : "off"
11706
+ }, null, 2));
10981
11707
  return;
10982
11708
  }
10983
- if (findings.length === 0) {
10984
- ui.success("hAIve doctor \u2014 no issues found.");
11709
+ if (classified.length === 0) {
11710
+ ui.success(repairs.length > 0 ? "hAIve doctor \u2014 autopilot repairs applied." : "hAIve doctor \u2014 no issues found.");
10985
11711
  return;
10986
11712
  }
10987
- console.log(ui.bold(`hAIve doctor \u2014 ${findings.length} finding${findings.length === 1 ? "" : "s"}`));
11713
+ console.log(ui.bold(`hAIve doctor \u2014 ${classified.length} finding${classified.length === 1 ? "" : "s"}`));
11714
+ console.log(
11715
+ ui.dim(
11716
+ ` protection=${scores.protection_score} context=${scores.context_quality_score} corpus=${scores.corpus_quality_score}`
11717
+ )
11718
+ );
10988
11719
  console.log();
10989
- const order = ["error", "warn", "info"];
10990
- for (const sev of order) {
10991
- for (const f of findings.filter((x) => x.severity === sev)) {
10992
- const icon = sev === "error" ? ui.red("\u2717") : sev === "warn" ? ui.yellow("\u26A0") : ui.dim("\u2139");
10993
- console.log(`${icon} ${ui.bold(f.code)} ${f.message}`);
10994
- if (opts.fix && f.fix) {
10995
- for (const line of f.fix.split("\n")) {
10996
- console.log(` ${ui.dim("$")} ${line}`);
11720
+ const sectionOrder = [
11721
+ "Protection",
11722
+ "Agent coverage",
11723
+ "Context quality",
11724
+ "Corpus health",
11725
+ "Index health",
11726
+ "Next actions"
11727
+ ];
11728
+ const severityOrder = ["error", "warn", "info"];
11729
+ for (const section2 of sectionOrder) {
11730
+ const sectionFindings = classified.filter((f) => f.section === section2);
11731
+ if (sectionFindings.length === 0) continue;
11732
+ console.log(ui.bold(section2));
11733
+ for (const sev of severityOrder) {
11734
+ for (const f of sectionFindings.filter((x) => x.severity === sev)) {
11735
+ const icon = sev === "error" ? ui.red("\u2717") : sev === "warn" ? ui.yellow("\u26A0") : ui.dim("\u2139");
11736
+ console.log(`${icon} ${ui.bold(f.code)} ${f.message}`);
11737
+ if (opts.fix && f.fix) {
11738
+ for (const line of f.fix.split("\n")) {
11739
+ console.log(` ${ui.dim(opts.dryRun ? "would run:" : "$")} ${line}`);
11740
+ }
10997
11741
  }
10998
11742
  }
10999
11743
  }
11744
+ console.log();
11000
11745
  }
11001
- if (!opts.fix && findings.some((f) => f.fix)) {
11746
+ if (repairs.length > 0) {
11747
+ console.log(ui.bold("Autopilot repairs applied"));
11748
+ for (const repair of repairs) console.log(` ${ui.dim("\u2713")} ${repair.message}`);
11002
11749
  console.log();
11750
+ }
11751
+ const actions = nextActions(classified);
11752
+ if (actions.length > 0) {
11753
+ console.log(ui.bold("Next actions"));
11754
+ for (const action of actions.slice(0, 5)) console.log(` ${ui.dim("$")} ${action}`);
11755
+ } else if (!opts.fix && classified.some((f) => f.fix)) {
11003
11756
  ui.info("Re-run with --fix to see suggested commands.");
11004
11757
  }
11005
11758
  }
11759
+ function sectionForFinding(finding) {
11760
+ if (finding.code.includes("haive") || finding.code.includes("integration") || finding.code.includes("claude") || finding.code.includes("autopilot")) return "Agent coverage";
11761
+ if (finding.code.includes("context") || finding.code.includes("briefing") || finding.code.includes("search")) return "Context quality";
11762
+ if (finding.code.includes("code-map") || finding.code.includes("index")) return "Index health";
11763
+ if (finding.code.includes("memory") || finding.code.includes("anchor") || finding.code.includes("pending") || finding.code.includes("decay")) return "Corpus health";
11764
+ if (finding.severity === "error") return "Protection";
11765
+ return "Next actions";
11766
+ }
11767
+ function computeDoctorScores(findings) {
11768
+ const scoreFor = (sections) => {
11769
+ const scoped = findings.filter((f) => sections.includes(f.section ?? sectionForFinding(f)));
11770
+ const penalty = scoped.reduce((sum, f) => {
11771
+ if (f.severity === "error") return sum + 35;
11772
+ if (f.severity === "warn") return sum + 15;
11773
+ return sum + 4;
11774
+ }, 0);
11775
+ return Math.max(0, 100 - penalty);
11776
+ };
11777
+ return {
11778
+ protection_score: scoreFor(["Protection", "Agent coverage"]),
11779
+ context_quality_score: scoreFor(["Context quality", "Index health"]),
11780
+ corpus_quality_score: scoreFor(["Corpus health"])
11781
+ };
11782
+ }
11783
+ function groupBySection(findings) {
11784
+ const out = {
11785
+ "Protection": [],
11786
+ "Agent coverage": [],
11787
+ "Context quality": [],
11788
+ "Corpus health": [],
11789
+ "Index health": [],
11790
+ "Next actions": []
11791
+ };
11792
+ for (const finding of findings) out[finding.section ?? sectionForFinding(finding)].push(finding);
11793
+ return out;
11794
+ }
11795
+ function nextActions(findings) {
11796
+ return [...new Set(findings.flatMap((finding) => finding.fix ? finding.fix.split("\n") : []))].filter(Boolean);
11797
+ }
11006
11798
  function isSearchTool(name) {
11007
11799
  return ["mem_search", "code_search", "mem_relevant_to", "get_briefing"].includes(name);
11008
11800
  }
@@ -11047,9 +11839,9 @@ which -a haive`
11047
11839
  ".vscode/mcp.json"
11048
11840
  ];
11049
11841
  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(() => "");
11842
+ const file = path44.join(root, rel);
11843
+ if (!existsSync63(file)) continue;
11844
+ const text = await readFile18(file, "utf8").catch(() => "");
11053
11845
  for (const bin of extractAbsoluteHaiveBins(text)) {
11054
11846
  const version = versionForBinary(bin);
11055
11847
  if (!version) {
@@ -11105,22 +11897,22 @@ function extractAbsoluteHaiveBins(text) {
11105
11897
  }
11106
11898
 
11107
11899
  // src/commands/playback.ts
11108
- import { existsSync as existsSync61 } from "fs";
11900
+ import { existsSync as existsSync64 } from "fs";
11109
11901
  import "commander";
11110
11902
  import {
11111
- findProjectRoot as findProjectRoot41,
11112
- loadMemoriesFromDir as loadMemoriesFromDir33,
11903
+ findProjectRoot as findProjectRoot42,
11904
+ loadMemoriesFromDir as loadMemoriesFromDir34,
11113
11905
  parseSince as parseSince3,
11114
11906
  readUsageEvents as readUsageEvents5,
11115
- resolveHaivePaths as resolveHaivePaths37
11907
+ resolveHaivePaths as resolveHaivePaths38
11116
11908
  } from "@hiveai/core";
11117
11909
  var MS_PER_MINUTE = 6e4;
11118
11910
  function registerPlayback(program2) {
11119
11911
  program2.command("playback").description(
11120
11912
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
11121
11913
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11122
- const root = findProjectRoot41(opts.dir);
11123
- const paths = resolveHaivePaths37(root);
11914
+ const root = findProjectRoot42(opts.dir);
11915
+ const paths = resolveHaivePaths38(root);
11124
11916
  const events = await readUsageEvents5(paths);
11125
11917
  if (events.length === 0) {
11126
11918
  if (opts.json) {
@@ -11135,7 +11927,7 @@ function registerPlayback(program2) {
11135
11927
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
11136
11928
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
11137
11929
  const sessions = bucketSessions(filtered, gapMs);
11138
- const all = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
11930
+ const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
11139
11931
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
11140
11932
  const enriched = sessions.map((s, i) => {
11141
11933
  const startMs = Date.parse(s.start);
@@ -11225,8 +12017,8 @@ function truncate3(text, max) {
11225
12017
  import { spawn as spawn4 } from "child_process";
11226
12018
  import "commander";
11227
12019
  import {
11228
- findProjectRoot as findProjectRoot42,
11229
- resolveHaivePaths as resolveHaivePaths38
12020
+ findProjectRoot as findProjectRoot43,
12021
+ resolveHaivePaths as resolveHaivePaths39
11230
12022
  } from "@hiveai/core";
11231
12023
  function registerPrecommit(program2) {
11232
12024
  program2.command("precommit").description(
@@ -11236,8 +12028,8 @@ function registerPrecommit(program2) {
11236
12028
  "'any' | 'high-confidence' (default) | 'never' (report only)",
11237
12029
  "high-confidence"
11238
12030
  ).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
11239
- const root = findProjectRoot42(opts.dir);
11240
- const paths = resolveHaivePaths38(root);
12031
+ const root = findProjectRoot43(opts.dir);
12032
+ const paths = resolveHaivePaths39(root);
11241
12033
  const ctx = { paths };
11242
12034
  let diff = "";
11243
12035
  let touchedPaths = opts.paths ?? [];
@@ -11321,7 +12113,11 @@ function printWarnings(title, warnings, tone) {
11321
12113
  console.log(` ${ui.dim(line)}`);
11322
12114
  }
11323
12115
  console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
12116
+ if (w.affected_files && w.affected_files.length > 0) {
12117
+ console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
12118
+ }
11324
12119
  if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
12120
+ if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
11325
12121
  }
11326
12122
  console.log();
11327
12123
  }
@@ -11345,12 +12141,12 @@ function runCommand3(cmd, args, cwd) {
11345
12141
  }
11346
12142
 
11347
12143
  // src/commands/welcome.ts
11348
- import { existsSync as existsSync63 } from "fs";
12144
+ import { existsSync as existsSync65 } from "fs";
11349
12145
  import "commander";
11350
12146
  import {
11351
- findProjectRoot as findProjectRoot43,
11352
- loadMemoriesFromDir as loadMemoriesFromDir34,
11353
- resolveHaivePaths as resolveHaivePaths39
12147
+ findProjectRoot as findProjectRoot44,
12148
+ loadMemoriesFromDir as loadMemoriesFromDir35,
12149
+ resolveHaivePaths as resolveHaivePaths40
11354
12150
  } from "@hiveai/core";
11355
12151
  var TYPE_RANK = {
11356
12152
  decision: 0,
@@ -11364,14 +12160,14 @@ function registerWelcome(program2) {
11364
12160
  program2.command("welcome").description(
11365
12161
  "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
11366
12162
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
11367
- const root = findProjectRoot43(opts.dir);
11368
- const paths = resolveHaivePaths39(root);
11369
- if (!existsSync63(paths.memoriesDir)) {
12163
+ const root = findProjectRoot44(opts.dir);
12164
+ const paths = resolveHaivePaths40(root);
12165
+ if (!existsSync65(paths.memoriesDir)) {
11370
12166
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
11371
12167
  process.exitCode = 1;
11372
12168
  return;
11373
12169
  }
11374
- const all = await loadMemoriesFromDir34(paths.memoriesDir);
12170
+ const all = await loadMemoriesFromDir35(paths.memoriesDir);
11375
12171
  const team = all.filter(
11376
12172
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
11377
12173
  );
@@ -11406,173 +12202,6 @@ function registerWelcome(program2) {
11406
12202
  });
11407
12203
  }
11408
12204
 
11409
- // src/commands/memory-lint.ts
11410
- import { existsSync as existsSync64 } from "fs";
11411
- import "commander";
11412
- import {
11413
- findProjectRoot as findProjectRoot44,
11414
- getUsage as getUsage20,
11415
- loadMemoriesFromDir as loadMemoriesFromDir35,
11416
- loadUsageIndex as loadUsageIndex26,
11417
- resolveHaivePaths as resolveHaivePaths40
11418
- } from "@hiveai/core";
11419
- async function lintMemoriesAsync(root) {
11420
- const paths = resolveHaivePaths40(root);
11421
- const out = [];
11422
- if (!existsSync64(paths.memoriesDir)) return out;
11423
- const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
11424
- const usage = await loadUsageIndex26(paths);
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;
11427
- for (const { filePath, memory: memory2 } of loaded) {
11428
- const fm = memory2.frontmatter;
11429
- if (fm.type === "session_recap") continue;
11430
- const body = memory2.body.trim();
11431
- const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
11432
- if (naked.length < 40 && fm.status !== "rejected") {
11433
- out.push({
11434
- file: filePath,
11435
- id: fm.id,
11436
- severity: "warn",
11437
- code: "SHORT_BODY",
11438
- message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
11439
- });
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
- }
11450
- if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
11451
- out.push({
11452
- file: filePath,
11453
- id: fm.id,
11454
- severity: "warn",
11455
- code: "MISSING_ANCHOR",
11456
- message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`
11457
- });
11458
- }
11459
- if (fm.status === "stale" && !fm.stale_reason) {
11460
- out.push({
11461
- file: filePath,
11462
- id: fm.id,
11463
- severity: "info",
11464
- code: "STALE_NO_REASON",
11465
- message: "Status is stale but stale_reason is empty \u2014 document why when possible."
11466
- });
11467
- }
11468
- if (fm.type === "glossary" && naked.length > 6e3) {
11469
- out.push({
11470
- file: filePath,
11471
- id: fm.id,
11472
- severity: "info",
11473
- code: "LONG_GLOSSARY",
11474
- message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
11475
- });
11476
- }
11477
- const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
11478
- if (!hasMarkdownHeading) {
11479
- out.push({
11480
- file: filePath,
11481
- id: fm.id,
11482
- severity: "warn",
11483
- code: "NO_MD_HEADING",
11484
- message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
11485
- });
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
- }
11531
- }
11532
- return out;
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
- }
11545
- function registerMemoryLint(parent) {
11546
- parent.command("lint").description(
11547
- "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
11548
- ).option("--json", "emit findings as JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11549
- const root = findProjectRoot44(opts.dir);
11550
- const findings = await lintMemoriesAsync(root);
11551
- if (opts.json) {
11552
- console.log(JSON.stringify({ findings_count: findings.length, findings }, null, 2));
11553
- process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
11554
- return;
11555
- }
11556
- if (findings.length === 0) {
11557
- ui.success(`memory lint OK \u2014 ${root}`);
11558
- return;
11559
- }
11560
- console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
11561
- `);
11562
- const order = { error: 0, warn: 1, info: 2 };
11563
- findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
11564
- for (const f of findings) {
11565
- const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
11566
- console.log(
11567
- `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
11568
- );
11569
- console.log(` ${f.message}`);
11570
- console.log(ui.dim(` \u2192 ${f.file}`));
11571
- }
11572
- process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
11573
- });
11574
- }
11575
-
11576
12205
  // src/commands/memory-suggest-topic.ts
11577
12206
  import "commander";
11578
12207
  import { MemoryTypeSchema as MemoryTypeSchema2, suggestTopicKey as suggestTopicKey2 } from "@hiveai/core";
@@ -11592,21 +12221,21 @@ function registerMemorySuggestTopic(memory2) {
11592
12221
  }
11593
12222
 
11594
12223
  // src/commands/resolve-project.ts
11595
- import path43 from "path";
12224
+ import path45 from "path";
11596
12225
  import "commander";
11597
12226
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
11598
12227
  function registerResolveProject(program2) {
11599
12228
  program2.command("resolve-project").description(
11600
12229
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
11601
12230
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
11602
- const info = resolveProjectInfo2({ cwd: path43.resolve(opts.dir) });
12231
+ const info = resolveProjectInfo2({ cwd: path45.resolve(opts.dir) });
11603
12232
  console.log(JSON.stringify({ ok: true, info }, null, 2));
11604
12233
  });
11605
12234
  }
11606
12235
 
11607
12236
  // src/commands/runtime-journal.ts
11608
- import { existsSync as existsSync65 } from "fs";
11609
- import path44 from "path";
12237
+ import { existsSync as existsSync66 } from "fs";
12238
+ import path46 from "path";
11610
12239
  import "commander";
11611
12240
  import {
11612
12241
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
@@ -11620,18 +12249,18 @@ function registerRuntime(program2) {
11620
12249
  );
11621
12250
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
11622
12251
  journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
11623
- const root = path44.resolve(opts.dir ?? process.cwd());
12252
+ const root = path46.resolve(opts.dir ?? process.cwd());
11624
12253
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11625
12254
  const raw = opts.kind ?? "note";
11626
12255
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
11627
12256
  await appendRuntimeJournalEntry3(paths, { kind, message });
11628
- ui.success(`Appended to ${path44.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
12257
+ ui.success(`Appended to ${path46.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
11629
12258
  });
11630
12259
  journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
11631
- const root = path44.resolve(opts.dir ?? process.cwd());
12260
+ const root = path46.resolve(opts.dir ?? process.cwd());
11632
12261
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11633
12262
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
11634
- if (!existsSync65(paths.haiveDir)) {
12263
+ if (!existsSync66(paths.haiveDir)) {
11635
12264
  ui.error("No .ai/ \u2014 run `haive init` first.");
11636
12265
  process.exitCode = 1;
11637
12266
  return;
@@ -11646,8 +12275,8 @@ function registerRuntime(program2) {
11646
12275
  }
11647
12276
 
11648
12277
  // src/commands/memory-timeline.ts
11649
- import { existsSync as existsSync66 } from "fs";
11650
- import path45 from "path";
12278
+ import { existsSync as existsSync67 } from "fs";
12279
+ import path47 from "path";
11651
12280
  import "commander";
11652
12281
  import {
11653
12282
  collectTimelineEntries as collectTimelineEntries2,
@@ -11663,15 +12292,15 @@ function registerMemoryTimeline(memory2) {
11663
12292
  process.exitCode = 1;
11664
12293
  return;
11665
12294
  }
11666
- const root = path45.resolve(opts.dir ?? process.cwd());
12295
+ const root = path47.resolve(opts.dir ?? process.cwd());
11667
12296
  const paths = resolveHaivePaths42(findProjectRoot46(root));
11668
- if (!existsSync66(paths.memoriesDir)) {
12297
+ if (!existsSync67(paths.memoriesDir)) {
11669
12298
  ui.error("No memories \u2014 run `haive init`.");
11670
12299
  process.exitCode = 1;
11671
12300
  return;
11672
12301
  }
11673
12302
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
11674
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12303
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
11675
12304
  const { entries, notice } = collectTimelineEntries2(all, {
11676
12305
  memoryId: opts.id,
11677
12306
  topic: opts.topic,
@@ -11683,8 +12312,8 @@ function registerMemoryTimeline(memory2) {
11683
12312
  }
11684
12313
 
11685
12314
  // src/commands/memory-conflict-candidates.ts
11686
- import { existsSync as existsSync67 } from "fs";
11687
- import path46 from "path";
12315
+ import { existsSync as existsSync68 } from "fs";
12316
+ import path48 from "path";
11688
12317
  import "commander";
11689
12318
  import {
11690
12319
  findLexicalConflictPairs as findLexicalConflictPairs2,
@@ -11706,9 +12335,9 @@ function registerMemoryConflictCandidates(memory2) {
11706
12335
  "decision,architecture,convention,gotcha (lexical scan)",
11707
12336
  "decision,architecture"
11708
12337
  ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
11709
- const root = path46.resolve(opts.dir ?? process.cwd());
12338
+ const root = path48.resolve(opts.dir ?? process.cwd());
11710
12339
  const paths = resolveHaivePaths43(findProjectRoot47(root));
11711
- if (!existsSync67(paths.memoriesDir)) {
12340
+ if (!existsSync68(paths.memoriesDir)) {
11712
12341
  ui.error("No memories \u2014 run `haive init`.");
11713
12342
  process.exitCode = 1;
11714
12343
  return;
@@ -11718,7 +12347,7 @@ function registerMemoryConflictCandidates(memory2) {
11718
12347
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
11719
12348
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
11720
12349
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
11721
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12350
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
11722
12351
  const lexical = findLexicalConflictPairs2(all, {
11723
12352
  sinceDays,
11724
12353
  types: parseTypes(opts.types),
@@ -11744,21 +12373,21 @@ function registerMemoryConflictCandidates(memory2) {
11744
12373
 
11745
12374
  // src/commands/enforce.ts
11746
12375
  import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
11747
- import { existsSync as existsSync68 } from "fs";
11748
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile18, rm as rm3, writeFile as writeFile31 } from "fs/promises";
11749
- import path47 from "path";
12376
+ import { existsSync as existsSync69 } from "fs";
12377
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile19, rm as rm3, writeFile as writeFile34 } from "fs/promises";
12378
+ import path49 from "path";
11750
12379
  import "commander";
11751
12380
  import {
11752
12381
  findProjectRoot as findProjectRoot48,
11753
12382
  hasRecentBriefingMarker,
11754
12383
  isFreshIsoDate,
11755
- loadConfig as loadConfig8,
12384
+ loadConfig as loadConfig10,
11756
12385
  loadMemoriesFromDir as loadMemoriesFromDir36,
11757
12386
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
11758
12387
  readRecentBriefingMarker,
11759
12388
  resolveBriefingBudget as resolveBriefingBudget3,
11760
12389
  resolveHaivePaths as resolveHaivePaths44,
11761
- saveConfig as saveConfig3,
12390
+ saveConfig as saveConfig4,
11762
12391
  SESSION_RECAP_TTL_MS,
11763
12392
  verifyAnchor as verifyAnchor4,
11764
12393
  writeBriefingMarker as writeBriefingMarker2
@@ -11773,8 +12402,8 @@ function registerEnforce(program2) {
11773
12402
  const root = findProjectRoot48(opts.dir);
11774
12403
  const paths = resolveHaivePaths44(root);
11775
12404
  await mkdir19(paths.haiveDir, { recursive: true });
11776
- const current = await loadConfig8(paths);
11777
- await saveConfig3(paths, {
12405
+ const current = await loadConfig10(paths);
12406
+ await saveConfig4(paths, {
11778
12407
  ...current,
11779
12408
  enforcement: {
11780
12409
  ...current.enforcement,
@@ -11796,7 +12425,7 @@ function registerEnforce(program2) {
11796
12425
  if (opts.claude !== false) {
11797
12426
  try {
11798
12427
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
11799
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path47.relative(root, result.settingsPath)})`);
12428
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path49.relative(root, result.settingsPath)})`);
11800
12429
  } catch (err) {
11801
12430
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
11802
12431
  }
@@ -11804,26 +12433,26 @@ function registerEnforce(program2) {
11804
12433
  ui.info("Agent-agnostic gates are now active at workflow level: MCP, git, CI, and optional client hooks.");
11805
12434
  ui.info("Use `haive run -- <agent command>` for agents that do not expose blocking hooks.");
11806
12435
  });
11807
- enforce.command("status").description("Show whether this project has agent-agnostic hAIve enforcement installed.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
12436
+ enforce.command("status").description("Show whether this project has agent-agnostic hAIve enforcement installed.").option("-d, --dir <dir>", "project root").option("--explain", "group findings by blocking/review/info and show repair commands", false).option("--json", "emit JSON", false).action(async (opts) => {
11808
12437
  const report = await buildEnforcementReport(opts.dir, "local");
11809
- printReport(report, Boolean(opts.json));
12438
+ printReport(report, Boolean(opts.json), Boolean(opts.explain));
11810
12439
  if (report.should_block) process.exitCode = 1;
11811
12440
  });
11812
- enforce.command("check").description("Run the hAIve policy gate. Intended for pre-commit, pre-push, wrappers, and any agent client.").option("-d, --dir <dir>", "project root").option("--stage <stage>", "local | pre-commit | pre-push | ci", "local").option("--json", "emit JSON", false).action(async (opts) => {
12441
+ enforce.command("check").description("Run the hAIve policy gate. Intended for pre-commit, pre-push, wrappers, and any agent client.").option("-d, --dir <dir>", "project root").option("--stage <stage>", "local | pre-commit | pre-push | ci", "local").option("--explain", "group findings by blocking/review/info and show repair commands", false).option("--json", "emit JSON", false).action(async (opts) => {
11813
12442
  const report = await buildEnforcementReport(opts.dir, opts.stage ?? "local");
11814
- printReport(report, Boolean(opts.json));
12443
+ printReport(report, Boolean(opts.json), Boolean(opts.explain));
11815
12444
  if (report.should_block) process.exit(2);
11816
12445
  });
11817
12446
  enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
11818
12447
  const root = findProjectRoot48(opts.dir);
11819
12448
  const paths = resolveHaivePaths44(root);
11820
12449
  const targets = [
11821
- path47.join(paths.haiveDir, ".cache"),
11822
- path47.join(paths.haiveDir, ".runtime")
12450
+ path49.join(paths.haiveDir, ".cache"),
12451
+ path49.join(paths.haiveDir, ".runtime")
11823
12452
  ];
11824
12453
  for (const target of targets) {
11825
- if (!existsSync68(target)) continue;
11826
- const rel = path47.relative(root, target);
12454
+ if (!existsSync69(target)) continue;
12455
+ const rel = path49.relative(root, target);
11827
12456
  if (opts.dryRun) ui.info(`would remove ${rel}`);
11828
12457
  else {
11829
12458
  await rm3(target, { recursive: true, force: true });
@@ -11831,9 +12460,9 @@ function registerEnforce(program2) {
11831
12460
  }
11832
12461
  }
11833
12462
  });
11834
- enforce.command("ci").description("CI entrypoint: fail if the repository violates hAIve enforcement policy.").option("-d, --dir <dir>", "project root").option("--json", "emit JSON", false).action(async (opts) => {
12463
+ enforce.command("ci").description("CI entrypoint: fail if the repository violates hAIve enforcement policy.").option("-d, --dir <dir>", "project root").option("--explain", "group findings by blocking/review/info and show repair commands", false).option("--json", "emit JSON", false).action(async (opts) => {
11835
12464
  const report = await buildEnforcementReport(opts.dir, "ci");
11836
- printReport(report, Boolean(opts.json));
12465
+ printReport(report, Boolean(opts.json), Boolean(opts.explain));
11837
12466
  if (report.should_block) process.exit(2);
11838
12467
  });
11839
12468
  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) => {
@@ -11841,7 +12470,7 @@ function registerEnforce(program2) {
11841
12470
  const root = resolveRoot(opts.dir, payload);
11842
12471
  if (!root) return;
11843
12472
  const paths = resolveHaivePaths44(root);
11844
- if (!existsSync68(paths.haiveDir)) return;
12473
+ if (!existsSync69(paths.haiveDir)) return;
11845
12474
  await mkdir19(paths.runtimeDir, { recursive: true });
11846
12475
  const sessionId = opts.sessionId ?? payload.session_id;
11847
12476
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -11903,7 +12532,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
11903
12532
  const root = resolveRoot(opts.dir, payload);
11904
12533
  if (!root) return;
11905
12534
  const paths = resolveHaivePaths44(root);
11906
- if (!existsSync68(paths.haiveDir)) return;
12535
+ if (!existsSync69(paths.haiveDir)) return;
11907
12536
  if (!isWriteLikeTool(payload)) return;
11908
12537
  const ok = await hasRecentBriefingMarker(paths, payload.session_id);
11909
12538
  if (ok) return;
@@ -11927,7 +12556,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
11927
12556
  async function runWithEnforcement(command, args, opts) {
11928
12557
  const root = findProjectRoot48(opts.dir);
11929
12558
  const paths = resolveHaivePaths44(root);
11930
- if (!existsSync68(paths.haiveDir)) {
12559
+ if (!existsSync69(paths.haiveDir)) {
11931
12560
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11932
12561
  process.exit(1);
11933
12562
  }
@@ -11946,7 +12575,7 @@ async function runWithEnforcement(command, args, opts) {
11946
12575
  process.exit(2);
11947
12576
  }
11948
12577
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
11949
- ui.info(`Briefing written to ${path47.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
12578
+ ui.info(`Briefing written to ${path49.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
11950
12579
  const child = spawn5(command, args, {
11951
12580
  cwd: root,
11952
12581
  stdio: "inherit",
@@ -11995,9 +12624,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
11995
12624
  source: "haive-run",
11996
12625
  memoryIds: briefing.memories.map((m) => m.id)
11997
12626
  });
11998
- const dir = path47.join(paths.runtimeDir, "enforcement", "briefings");
12627
+ const dir = path49.join(paths.runtimeDir, "enforcement", "briefings");
11999
12628
  await mkdir19(dir, { recursive: true });
12000
- const file = path47.join(dir, `${sessionId}.md`);
12629
+ const file = path49.join(dir, `${sessionId}.md`);
12001
12630
  const parts = [
12002
12631
  "# hAIve Briefing",
12003
12632
  "",
@@ -12015,18 +12644,18 @@ async function writeWrapperBriefing(paths, sessionId, task) {
12015
12644
  if (briefing.setup_warnings.length > 0) {
12016
12645
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
12017
12646
  }
12018
- await writeFile31(file, parts.join("\n") + "\n", "utf8");
12647
+ await writeFile34(file, parts.join("\n") + "\n", "utf8");
12019
12648
  return file;
12020
12649
  }
12021
12650
  async function buildEnforcementReport(dir, stage, sessionId) {
12022
12651
  const root = findProjectRoot48(dir);
12023
12652
  const paths = resolveHaivePaths44(root);
12024
- const initialized = existsSync68(paths.haiveDir);
12025
- const config = initialized ? await loadConfig8(paths) : {};
12653
+ const initialized = existsSync69(paths.haiveDir);
12654
+ const config = initialized ? await loadConfig10(paths) : {};
12026
12655
  const mode = config.enforcement?.mode ?? "strict";
12027
12656
  const findings = [];
12028
12657
  if (!initialized) {
12029
- return {
12658
+ return withCategories({
12030
12659
  root,
12031
12660
  initialized,
12032
12661
  mode,
@@ -12039,19 +12668,19 @@ async function buildEnforcementReport(dir, stage, sessionId) {
12039
12668
  fix: "Run `haive init` or `haive enforce install`.",
12040
12669
  impact: 100
12041
12670
  }]
12042
- };
12671
+ });
12043
12672
  }
12044
12673
  if (mode === "off") {
12045
- return {
12674
+ return withCategories({
12046
12675
  root,
12047
12676
  initialized,
12048
12677
  mode,
12049
12678
  score: buildScore([], config.enforcement?.scoreThreshold),
12050
12679
  should_block: false,
12051
12680
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
12052
- };
12681
+ });
12053
12682
  }
12054
- findings.push(...await inspectIntegrationVersions(root, "0.9.16"));
12683
+ findings.push(...await inspectIntegrationVersions(root, "0.9.18"));
12055
12684
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
12056
12685
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
12057
12686
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -12101,17 +12730,27 @@ async function buildEnforcementReport(dir, stage, sessionId) {
12101
12730
  });
12102
12731
  }
12103
12732
  const hasErrors = findings.some((f) => f.severity === "error");
12104
- return {
12733
+ return withCategories({
12105
12734
  root,
12106
12735
  initialized,
12107
12736
  mode,
12108
12737
  score: buildScore(findings, config.enforcement?.scoreThreshold),
12109
12738
  should_block: mode === "strict" && hasErrors,
12110
12739
  findings
12740
+ });
12741
+ }
12742
+ function withCategories(report) {
12743
+ return {
12744
+ ...report,
12745
+ categories: {
12746
+ blocking: report.findings.filter((f) => f.severity === "error"),
12747
+ review: report.findings.filter((f) => f.severity === "warn"),
12748
+ info: report.findings.filter((f) => f.severity === "info" || f.severity === "ok")
12749
+ }
12111
12750
  };
12112
12751
  }
12113
12752
  async function hasRecentSessionRecap(paths) {
12114
- if (!existsSync68(paths.memoriesDir)) return false;
12753
+ if (!existsSync69(paths.memoriesDir)) return false;
12115
12754
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12116
12755
  return all.some(({ memory: memory2 }) => {
12117
12756
  const fm = memory2.frontmatter;
@@ -12120,7 +12759,7 @@ async function hasRecentSessionRecap(paths) {
12120
12759
  });
12121
12760
  }
12122
12761
  async function verifyMemoryPolicy(paths, config) {
12123
- if (!existsSync68(paths.memoriesDir)) return [];
12762
+ if (!existsSync69(paths.memoriesDir)) return [];
12124
12763
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12125
12764
  const findings = [];
12126
12765
  const staleImportant = [];
@@ -12158,7 +12797,7 @@ async function verifyMemoryPolicy(paths, config) {
12158
12797
  return findings;
12159
12798
  }
12160
12799
  async function verifyDecisionCoverage(paths, stage, sessionId) {
12161
- if (!existsSync68(paths.memoriesDir)) return [];
12800
+ if (!existsSync69(paths.memoriesDir)) return [];
12162
12801
  const changedFiles = await getChangedFiles(paths.root, stage);
12163
12802
  if (changedFiles.length === 0) {
12164
12803
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -12168,7 +12807,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
12168
12807
  const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
12169
12808
  const fm = memory2.frontmatter;
12170
12809
  if (!policyTypes.has(fm.type)) return false;
12171
- if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
12810
+ if (fm.status !== "validated") return false;
12172
12811
  return memoryMatchesAnchorPaths6(memory2, changedFiles);
12173
12812
  });
12174
12813
  if (relevant.length === 0) {
@@ -12251,9 +12890,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
12251
12890
  ];
12252
12891
  const findings = [];
12253
12892
  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(() => "");
12893
+ const file = path49.join(root, rel);
12894
+ if (!existsSync69(file)) continue;
12895
+ const text = await readFile19(file, "utf8").catch(() => "");
12257
12896
  for (const bin of extractAbsoluteHaiveBins2(text)) {
12258
12897
  const version = versionForBinary2(bin);
12259
12898
  if (!version) {
@@ -12318,7 +12957,9 @@ async function getChangedFiles(root, stage) {
12318
12957
  if (file) files.add(file);
12319
12958
  }
12320
12959
  }
12321
- return [...files].filter((file) => !file.startsWith(".ai/.runtime/") && !file.startsWith(".ai/.cache/"));
12960
+ return [...files].filter(
12961
+ (file) => !file.startsWith(".ai/.runtime/") && !file.startsWith(".ai/.cache/") && !file.startsWith(".ai/.usage/") && file !== ".ai/.usage/tool-usage.jsonl"
12962
+ );
12322
12963
  }
12323
12964
  function buildScore(findings, threshold = 80) {
12324
12965
  const checks = {
@@ -12339,8 +12980,8 @@ function buildScore(findings, threshold = 80) {
12339
12980
  };
12340
12981
  }
12341
12982
  async function installGitEnforcement(root) {
12342
- const hooksDir = path47.join(root, ".git", "hooks");
12343
- if (!existsSync68(path47.join(root, ".git"))) {
12983
+ const hooksDir = path49.join(root, ".git", "hooks");
12984
+ if (!existsSync69(path49.join(root, ".git"))) {
12344
12985
  ui.warn("No .git directory found; git enforcement hooks skipped.");
12345
12986
  return;
12346
12987
  }
@@ -12362,31 +13003,31 @@ haive enforce check --stage pre-push --dir . || exit $?
12362
13003
  }
12363
13004
  ];
12364
13005
  for (const hook of hooks) {
12365
- const file = path47.join(hooksDir, hook.name);
12366
- if (existsSync68(file)) {
12367
- const current = await readFile18(file, "utf8").catch(() => "");
13006
+ const file = path49.join(hooksDir, hook.name);
13007
+ if (existsSync69(file)) {
13008
+ const current = await readFile19(file, "utf8").catch(() => "");
12368
13009
  if (current.includes(ENFORCE_HOOK_MARKER)) {
12369
- await writeFile31(file, hook.body, "utf8");
13010
+ await writeFile34(file, hook.body, "utf8");
12370
13011
  } else {
12371
- await writeFile31(file, `${current.trimEnd()}
13012
+ await writeFile34(file, `${current.trimEnd()}
12372
13013
 
12373
13014
  ${hook.body}`, "utf8");
12374
13015
  }
12375
13016
  } else {
12376
- await writeFile31(file, hook.body, "utf8");
13017
+ await writeFile34(file, hook.body, "utf8");
12377
13018
  }
12378
13019
  await chmod2(file, 493);
12379
13020
  }
12380
13021
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
12381
13022
  }
12382
13023
  async function installCiEnforcement(root) {
12383
- const workflowPath = path47.join(root, ".github", "workflows", "haive-enforcement.yml");
12384
- await mkdir19(path47.dirname(workflowPath), { recursive: true });
12385
- if (existsSync68(workflowPath)) {
13024
+ const workflowPath = path49.join(root, ".github", "workflows", "haive-enforcement.yml");
13025
+ await mkdir19(path49.dirname(workflowPath), { recursive: true });
13026
+ if (existsSync69(workflowPath)) {
12386
13027
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
12387
13028
  return;
12388
13029
  }
12389
- await writeFile31(workflowPath, `name: haive-enforcement
13030
+ await writeFile34(workflowPath, `name: haive-enforcement
12390
13031
 
12391
13032
  on:
12392
13033
  pull_request:
@@ -12410,9 +13051,9 @@ jobs:
12410
13051
  - name: Enforce hAIve policy
12411
13052
  run: haive enforce ci
12412
13053
  `, "utf8");
12413
- ui.success(`Created ${path47.relative(root, workflowPath)}`);
13054
+ ui.success(`Created ${path49.relative(root, workflowPath)}`);
12414
13055
  }
12415
- function printReport(report, json) {
13056
+ function printReport(report, json, explain = false) {
12416
13057
  if (json) {
12417
13058
  console.log(JSON.stringify(report, null, 2));
12418
13059
  return;
@@ -12420,14 +13061,32 @@ function printReport(report, json) {
12420
13061
  console.log(ui.bold(`hAIve enforcement \u2014 ${report.mode}`));
12421
13062
  console.log(ui.dim(` root: ${report.root}`));
12422
13063
  console.log(ui.dim(` score: ${report.score.score}% / threshold ${report.score.threshold}%`));
12423
- for (const finding of report.findings) {
12424
- const marker = finding.severity === "error" ? ui.red("\u2717") : finding.severity === "warn" ? ui.yellow("\u26A0") : finding.severity === "ok" ? ui.green("\u2713") : ui.dim("\u2022");
12425
- console.log(`${marker} ${finding.code}: ${finding.message}`);
12426
- if (finding.fix) console.log(ui.dim(` fix: ${finding.fix}`));
13064
+ if (explain) {
13065
+ printFindingGroup("Blocking", report.categories.blocking, "error");
13066
+ printFindingGroup("Review", report.categories.review, "warn");
13067
+ printFindingGroup("Info", report.categories.info, "info");
13068
+ } else {
13069
+ for (const finding of report.findings) printFinding(finding);
12427
13070
  }
12428
13071
  if (report.should_block) ui.error("hAIve enforcement gate failed.");
12429
13072
  else ui.success("hAIve enforcement gate passed.");
12430
13073
  }
13074
+ function printFindingGroup(title, findings, tone) {
13075
+ if (findings.length === 0) return;
13076
+ console.log();
13077
+ const heading = tone === "error" ? ui.red(title) : tone === "warn" ? ui.yellow(title) : ui.bold(title);
13078
+ console.log(ui.bold(`${heading} (${findings.length})`));
13079
+ const scoreFinding = findings.find((f) => f.code === "enforcement-score-below-threshold");
13080
+ for (const finding of findings.filter((f) => f.code !== "enforcement-score-below-threshold")) {
13081
+ printFinding(finding, true);
13082
+ }
13083
+ if (scoreFinding) printFinding(scoreFinding, true);
13084
+ }
13085
+ function printFinding(finding, explain = false) {
13086
+ const marker = finding.severity === "error" ? ui.red("\u2717") : finding.severity === "warn" ? ui.yellow("\u26A0") : finding.severity === "ok" ? ui.green("\u2713") : ui.dim("\u2022");
13087
+ console.log(`${marker} ${finding.code}: ${finding.message}`);
13088
+ if (finding.fix) console.log(ui.dim(`${explain ? " repair: " : " fix: "}${finding.fix}`));
13089
+ }
12431
13090
  async function readHookPayload() {
12432
13091
  const raw = await readStdin2(MAX_STDIN_BYTES2);
12433
13092
  if (!raw.trim()) return {};
@@ -12509,7 +13168,7 @@ function registerRun(program2) {
12509
13168
 
12510
13169
  // src/index.ts
12511
13170
  var program = new Command51();
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");
13171
+ program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.18").option("--advanced", "show maintenance and experimental commands in help");
12513
13172
  registerInit(program);
12514
13173
  registerWelcome(program);
12515
13174
  registerResolveProject(program);