@hiveai/cli 0.9.17 → 0.9.19

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(([path49, changes]) => ({ path: path49, 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)));
@@ -2462,7 +2462,7 @@ function registerInit(program2) {
2462
2462
  console.log(ui.dim(` \u2713 Stack memory packs pre-seeded (${stacksToSeed.join(", ")})`));
2463
2463
  }
2464
2464
  console.log();
2465
- if (!opts.bootstrap) {
2465
+ if (!wantBootstrap) {
2466
2466
  console.log(ui.bold("One remaining step (optional but recommended):"));
2467
2467
  console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 fill project-context.md without AI"));
2468
2468
  console.log(" " + ui.dim("Or in your AI client: invoke the MCP prompt ") + ui.bold("bootstrap_project"));
@@ -2477,7 +2477,7 @@ function registerInit(program2) {
2477
2477
  console.log(ui.dim(" haive memory import README.md \u2014 from README / docs"));
2478
2478
  } else {
2479
2479
  console.log(ui.bold("Next steps:"));
2480
- if (!opts.bootstrap) {
2480
+ if (!wantBootstrap) {
2481
2481
  console.log(ui.dim(" 1. Fill project context (pick one):"));
2482
2482
  console.log(" " + ui.bold("haive init --bootstrap") + ui.dim(" \u2190 instant, no AI needed"));
2483
2483
  console.log(" or invoke the MCP prompt " + ui.bold("bootstrap_project") + ui.dim(" in your AI client"));
@@ -6515,7 +6515,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
6515
6515
  };
6516
6516
  }
6517
6517
  var SERVER_NAME = "haive";
6518
- var SERVER_VERSION = "0.9.17";
6518
+ var SERVER_VERSION = "0.9.19";
6519
6519
  function jsonResult(data) {
6520
6520
  return {
6521
6521
  content: [
@@ -6535,6 +6535,7 @@ var ENFORCEMENT_PROFILE_TOOLS = [
6535
6535
  "mem_verify",
6536
6536
  "mem_relevant_to",
6537
6537
  "code_map",
6538
+ "code_search",
6538
6539
  "pre_commit_check",
6539
6540
  "mem_session_end"
6540
6541
  ];
@@ -7483,177 +7484,668 @@ function registerMcp(program2) {
7483
7484
  }
7484
7485
 
7485
7486
  // src/commands/sync.ts
7486
- import { spawnSync as spawnSync3 } from "child_process";
7487
- import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir10 } from "fs/promises";
7488
- import { existsSync as existsSync29 } from "fs";
7489
- import path13 from "path";
7487
+ import { spawnSync as spawnSync4 } from "child_process";
7488
+ import { readFile as readFile9, writeFile as writeFile15, mkdir as mkdir10 } from "fs/promises";
7489
+ import { existsSync as existsSync31 } from "fs";
7490
+ import path15 from "path";
7490
7491
  import "commander";
7491
7492
  import {
7492
7493
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
7493
7494
  buildFrontmatter as buildFrontmatter6,
7494
- findProjectRoot as findProjectRoot11,
7495
- getUsage as getUsage10,
7495
+ findProjectRoot as findProjectRoot12,
7496
+ getUsage as getUsage11,
7496
7497
  isAutoPromoteEligible as isAutoPromoteEligible2,
7497
7498
  isDecaying as isDecaying2,
7498
- loadCodeMap as loadCodeMap4,
7499
- loadConfig as loadConfig4,
7500
- loadMemoriesFromDir as loadMemoriesFromDir23,
7501
- loadUsageIndex as loadUsageIndex12,
7499
+ loadCodeMap as loadCodeMap6,
7500
+ loadConfig as loadConfig5,
7501
+ loadMemoriesFromDir as loadMemoriesFromDir24,
7502
+ loadUsageIndex as loadUsageIndex13,
7502
7503
  pullCrossRepoSources,
7503
- resolveHaivePaths as resolveHaivePaths8,
7504
+ resolveHaivePaths as resolveHaivePaths9,
7504
7505
  resolveManifestFiles,
7505
- serializeMemory as serializeMemory11,
7506
+ serializeMemory as serializeMemory12,
7506
7507
  trackDependencies,
7507
7508
  verifyAnchor as verifyAnchor2,
7508
7509
  watchContracts
7509
7510
  } from "@hiveai/core";
7510
- var BRIDGE_START = "<!-- haive:memories-start -->";
7511
- var BRIDGE_END = "<!-- haive:memories-end -->";
7512
- function registerSync(program2) {
7513
- program2.command("sync").description(
7514
- "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"
7515
- ).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
7516
- "--since <ref>",
7517
- "git ref/commit to compare against; report memories added/modified/removed since"
7518
- ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
7519
- "--inject-bridge",
7520
- "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
7521
- ).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) => {
7522
- const root = findProjectRoot11(opts.dir);
7523
- const paths = resolveHaivePaths8(root);
7524
- if (!existsSync29(paths.memoriesDir)) {
7525
- if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7526
- process.exitCode = 1;
7527
- return;
7511
+
7512
+ // src/utils/autopilot.ts
7513
+ import { existsSync as existsSync30 } from "fs";
7514
+ import { readFile as readFile8, writeFile as writeFile14 } from "fs/promises";
7515
+ import path14 from "path";
7516
+ import {
7517
+ AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
7518
+ buildCodeMap as buildCodeMap3,
7519
+ loadCodeMap as loadCodeMap5,
7520
+ loadConfig as loadConfig4,
7521
+ saveCodeMap as saveCodeMap3,
7522
+ saveConfig as saveConfig2
7523
+ } from "@hiveai/core";
7524
+
7525
+ // src/commands/memory-lint.ts
7526
+ import { existsSync as existsSync29 } from "fs";
7527
+ import { writeFile as writeFile13 } from "fs/promises";
7528
+ import { spawnSync as spawnSync3 } from "child_process";
7529
+ import path13 from "path";
7530
+ import "commander";
7531
+ import {
7532
+ findProjectRoot as findProjectRoot11,
7533
+ getUsage as getUsage10,
7534
+ loadCodeMap as loadCodeMap4,
7535
+ loadMemoriesFromDir as loadMemoriesFromDir23,
7536
+ loadUsageIndex as loadUsageIndex12,
7537
+ resolveHaivePaths as resolveHaivePaths8,
7538
+ serializeMemory as serializeMemory11
7539
+ } from "@hiveai/core";
7540
+ async function lintMemoriesAsync(root, options = {}) {
7541
+ const paths = resolveHaivePaths8(root);
7542
+ const out = [];
7543
+ const fixes = [];
7544
+ if (!existsSync29(paths.memoriesDir)) return { findings: out, fixes };
7545
+ const loaded = await loadMemoriesFromDir23(paths.memoriesDir);
7546
+ const usage = await loadUsageIndex12(paths);
7547
+ const codeMap = await loadCodeMap4(paths);
7548
+ const trackedFiles = gitTrackedFiles(root);
7549
+ const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
7550
+ 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;
7551
+ for (const { filePath, memory: memory2 } of loaded) {
7552
+ const fm = memory2.frontmatter;
7553
+ if (fm.type === "session_recap") continue;
7554
+ const body = memory2.body.trim();
7555
+ const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
7556
+ if (naked.length < 40 && fm.status !== "rejected") {
7557
+ out.push({
7558
+ file: filePath,
7559
+ id: fm.id,
7560
+ severity: "warn",
7561
+ code: "SHORT_BODY",
7562
+ message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
7563
+ });
7528
7564
  }
7529
- const log = (msg) => {
7530
- if (!opts.quiet) console.log(msg);
7531
- };
7532
- const config = await loadConfig4(paths);
7533
- const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
7534
- const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
7535
- let staleMarked = 0;
7536
- let revalidated = 0;
7537
- let promoted = 0;
7538
- let autoApproved = 0;
7539
- if (opts.verify !== false) {
7540
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
7541
- for (const { memory: memory2, filePath } of memories) {
7542
- if (memory2.frontmatter.type === "session_recap") {
7543
- if (memory2.frontmatter.status === "stale") {
7544
- await writeFile13(
7545
- filePath,
7546
- serializeMemory11({
7547
- frontmatter: {
7548
- ...memory2.frontmatter,
7549
- status: "validated",
7550
- stale_reason: null,
7551
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
7552
- },
7553
- body: memory2.body
7554
- }),
7555
- "utf8"
7556
- );
7557
- revalidated++;
7558
- }
7559
- continue;
7560
- }
7561
- const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
7562
- if (!isAnchored) continue;
7563
- const result = await verifyAnchor2(memory2, { projectRoot: root });
7564
- const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
7565
- if (result.stale) {
7566
- if (memory2.frontmatter.status !== "stale") {
7567
- await writeFile13(
7568
- filePath,
7569
- serializeMemory11({
7570
- frontmatter: {
7571
- ...memory2.frontmatter,
7572
- status: "stale",
7573
- verified_at: verifiedAt,
7574
- stale_reason: result.reason
7575
- },
7576
- body: memory2.body
7577
- }),
7578
- "utf8"
7579
- );
7580
- staleMarked++;
7581
- }
7582
- } else if (memory2.frontmatter.status === "stale") {
7583
- await writeFile13(
7584
- filePath,
7585
- serializeMemory11({
7586
- frontmatter: {
7587
- ...memory2.frontmatter,
7588
- status: "validated",
7589
- verified_at: verifiedAt,
7590
- stale_reason: null
7591
- },
7592
- body: memory2.body
7593
- }),
7594
- "utf8"
7595
- );
7596
- revalidated++;
7565
+ if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
7566
+ out.push({
7567
+ file: filePath,
7568
+ id: fm.id,
7569
+ severity: "info",
7570
+ code: "LOW_ACTIONABILITY",
7571
+ message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
7572
+ });
7573
+ }
7574
+ const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles);
7575
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
7576
+ out.push({
7577
+ file: filePath,
7578
+ id: fm.id,
7579
+ severity: "warn",
7580
+ code: "MISSING_ANCHOR",
7581
+ message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
7582
+ ...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
7583
+ });
7584
+ }
7585
+ if (fm.status === "stale" && !fm.stale_reason) {
7586
+ out.push({
7587
+ file: filePath,
7588
+ id: fm.id,
7589
+ severity: "info",
7590
+ code: "STALE_NO_REASON",
7591
+ message: "Status is stale but stale_reason is empty \u2014 document why when possible."
7592
+ });
7593
+ }
7594
+ if (fm.type === "glossary" && naked.length > 6e3) {
7595
+ out.push({
7596
+ file: filePath,
7597
+ id: fm.id,
7598
+ severity: "info",
7599
+ code: "LONG_GLOSSARY",
7600
+ message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
7601
+ });
7602
+ }
7603
+ const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
7604
+ if (!hasMarkdownHeading) {
7605
+ out.push({
7606
+ file: filePath,
7607
+ id: fm.id,
7608
+ severity: "warn",
7609
+ code: "NO_MD_HEADING",
7610
+ message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
7611
+ });
7612
+ }
7613
+ const u = getUsage10(usage, fm.id);
7614
+ if (fm.status === "validated" && u.read_count === 0) {
7615
+ out.push({
7616
+ file: filePath,
7617
+ id: fm.id,
7618
+ severity: "info",
7619
+ code: "NEVER_READ",
7620
+ message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
7621
+ });
7622
+ }
7623
+ if (options.fix) {
7624
+ const actions = [];
7625
+ let nextBody = memory2.body;
7626
+ let nextFrontmatter = memory2.frontmatter;
7627
+ if (!hasMarkdownHeading) {
7628
+ nextBody = `# ${titleFromId(fm.id)}
7629
+
7630
+ ${nextBody.trim()}`;
7631
+ actions.push("add missing Markdown heading");
7632
+ }
7633
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && suggestedAnchors.paths.length > 0) {
7634
+ nextFrontmatter = {
7635
+ ...nextFrontmatter,
7636
+ anchor: {
7637
+ ...nextFrontmatter.anchor,
7638
+ paths: [.../* @__PURE__ */ new Set([...nextFrontmatter.anchor.paths, ...suggestedAnchors.paths])],
7639
+ symbols: [
7640
+ .../* @__PURE__ */ new Set([...nextFrontmatter.anchor.symbols, ...suggestedAnchors.symbols])
7641
+ ]
7642
+ },
7643
+ tags: nextFrontmatter.tags.filter((tag) => tag !== "needs_anchor")
7644
+ };
7645
+ actions.push("add suggested tracked anchor paths");
7646
+ if (suggestedAnchors.symbols.length > 0) {
7647
+ actions.push("add suggested anchor symbols");
7597
7648
  }
7598
7649
  }
7599
- }
7600
- if (opts.promote !== false) {
7601
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
7602
- const usage = await loadUsageIndex12(paths);
7603
- const nowMs = Date.now();
7604
- for (const { memory: memory2, filePath } of memories) {
7605
- const fm = memory2.frontmatter;
7606
- if (fm.type === "session_recap") continue;
7607
- if (isAutoPromoteEligible2(fm, getUsage10(usage, fm.id), {
7608
- minReads: autoPromoteMinReads,
7609
- maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
7610
- })) {
7650
+ 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")) {
7651
+ nextFrontmatter = {
7652
+ ...nextFrontmatter,
7653
+ tags: [...nextFrontmatter.tags, "needs_anchor"]
7654
+ };
7655
+ actions.push("tag validated anchorless record with needs_anchor");
7656
+ }
7657
+ if (actions.length > 0) {
7658
+ fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
7659
+ if (options.apply) {
7611
7660
  await writeFile13(
7612
7661
  filePath,
7613
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
7662
+ serializeMemory11({ frontmatter: nextFrontmatter, body: nextBody }),
7614
7663
  "utf8"
7615
7664
  );
7616
- promoted++;
7617
- continue;
7618
- }
7619
- if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
7620
- const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
7621
- if (ageHours >= autoApproveDelayHours) {
7622
- await writeFile13(
7623
- filePath,
7624
- serializeMemory11({
7625
- frontmatter: {
7626
- ...fm,
7627
- status: "validated",
7628
- verified_at: (/* @__PURE__ */ new Date()).toISOString()
7629
- },
7630
- body: memory2.body
7631
- }),
7632
- "utf8"
7633
- );
7634
- autoApproved++;
7635
- }
7636
7665
  }
7637
7666
  }
7638
7667
  }
7639
- const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
7640
- const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
7641
- (m) => m.memory.frontmatter.status === "draft"
7642
- );
7643
- const draftCount = draftMemories.length;
7644
- const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
7645
- log(
7646
- `${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
7647
- );
7648
- if (!opts.quiet && draftCount > 0) {
7649
- log(
7650
- ui.dim(
7651
- `\u2139 ${draftCount} memor${draftCount === 1 ? "y" : "ies"} in draft \u2014 run \`haive memory approve <id>\` to activate or \`haive memory list --status draft\` to review`
7652
- )
7668
+ }
7669
+ for (const dup of nearDuplicatePairs(loaded)) {
7670
+ out.push({
7671
+ file: dup.file,
7672
+ id: dup.id,
7673
+ severity: "warn",
7674
+ code: "NEAR_DUPLICATE",
7675
+ message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
7676
+ });
7677
+ }
7678
+ return { findings: out, fixes };
7679
+ }
7680
+ function titleFromId(id) {
7681
+ const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
7682
+ return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
7683
+ }
7684
+ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
7685
+ const body = loaded.memory.body;
7686
+ const paths = /* @__PURE__ */ new Set();
7687
+ const symbols = /* @__PURE__ */ new Set();
7688
+ for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
7689
+ const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
7690
+ if (!candidate || candidate.startsWith("http")) continue;
7691
+ if (existsSync29(path13.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
7692
+ paths.add(candidate);
7693
+ }
7694
+ }
7695
+ if (codeMap) {
7696
+ const lowered = body.toLowerCase();
7697
+ for (const [file, entry] of Object.entries(codeMap.files)) {
7698
+ for (const exp of entry.exports) {
7699
+ if (!exp.name || exp.name.length < 4) continue;
7700
+ if (lowered.includes(exp.name.toLowerCase())) {
7701
+ if (isSafeAnchorPath(file, trackedFiles)) {
7702
+ paths.add(file);
7703
+ symbols.add(exp.name);
7704
+ }
7705
+ }
7706
+ if (paths.size >= 5 && symbols.size >= 5) break;
7707
+ }
7708
+ if (paths.size >= 5 && symbols.size >= 5) break;
7709
+ }
7710
+ }
7711
+ return {
7712
+ paths: [...paths].slice(0, 5),
7713
+ symbols: [...symbols].slice(0, 5)
7714
+ };
7715
+ }
7716
+ function gitTrackedFiles(root) {
7717
+ const result = spawnSync3("git", ["ls-files"], {
7718
+ cwd: root,
7719
+ encoding: "utf8",
7720
+ stdio: ["ignore", "pipe", "ignore"]
7721
+ });
7722
+ if (result.status !== 0) return null;
7723
+ const files = result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
7724
+ return new Set(files);
7725
+ }
7726
+ function isSafeAnchorPath(file, trackedFiles) {
7727
+ const normalized = file.replace(/\\/g, "/").replace(/^\.?\//, "");
7728
+ if (normalized.startsWith(".ai/.cache/") || normalized.startsWith(".ai/.runtime/")) return false;
7729
+ if (normalized.includes("/node_modules/") || normalized.startsWith("node_modules/")) return false;
7730
+ if (normalized.includes("/dist/") || normalized.startsWith("dist/")) return false;
7731
+ if (trackedFiles && !trackedFiles.has(normalized)) return false;
7732
+ return true;
7733
+ }
7734
+ function nearDuplicatePairs(loaded) {
7735
+ const out = [];
7736
+ const candidates = loaded.filter(({ memory: memory2 }) => {
7737
+ const fm = memory2.frontmatter;
7738
+ return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
7739
+ });
7740
+ for (let i = 0; i < candidates.length; i++) {
7741
+ for (let j = i + 1; j < candidates.length; j++) {
7742
+ const a = candidates[i];
7743
+ const b = candidates[j];
7744
+ if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
7745
+ if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
7746
+ const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
7747
+ if (score >= 0.72) {
7748
+ out.push({
7749
+ id: a.memory.frontmatter.id,
7750
+ otherId: b.memory.frontmatter.id,
7751
+ file: a.filePath,
7752
+ score
7753
+ });
7754
+ }
7755
+ }
7756
+ }
7757
+ return out;
7758
+ }
7759
+ function tokenSet(body) {
7760
+ return new Set(
7761
+ (body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
7762
+ );
7763
+ }
7764
+ function jaccard2(a, b) {
7765
+ if (a.size === 0 || b.size === 0) return 0;
7766
+ let inter = 0;
7767
+ for (const item of a) if (b.has(item)) inter++;
7768
+ return inter / (a.size + b.size - inter);
7769
+ }
7770
+ function registerMemoryLint(parent) {
7771
+ parent.command("lint").description(
7772
+ "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
7773
+ ).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) => {
7774
+ const root = findProjectRoot11(opts.dir);
7775
+ const apply = Boolean(opts.fix && opts.apply);
7776
+ const dryRun = Boolean(opts.fix && (opts.dryRun || !opts.apply));
7777
+ const report = await lintMemoriesAsync(root, { fix: Boolean(opts.fix), apply });
7778
+ const findings = report.findings;
7779
+ if (opts.json) {
7780
+ console.log(JSON.stringify({
7781
+ findings_count: findings.length,
7782
+ findings,
7783
+ fixes_count: report.fixes.length,
7784
+ fixes: report.fixes,
7785
+ fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
7786
+ }, null, 2));
7787
+ process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
7788
+ return;
7789
+ }
7790
+ if (findings.length === 0) {
7791
+ ui.success(`memory lint OK \u2014 ${root}`);
7792
+ return;
7793
+ }
7794
+ console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
7795
+ `);
7796
+ if (opts.fix) {
7797
+ const mode = apply ? "apply" : dryRun ? "dry-run" : "dry-run";
7798
+ const verb = apply ? "changed" : "would change";
7799
+ console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
7800
+ for (const fix of report.fixes) {
7801
+ console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
7802
+ console.log(ui.dim(` \u2192 ${fix.file}`));
7803
+ }
7804
+ console.log();
7805
+ }
7806
+ const order = { error: 0, warn: 1, info: 2 };
7807
+ findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
7808
+ for (const f of findings) {
7809
+ const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
7810
+ console.log(
7811
+ `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
7812
+ );
7813
+ console.log(` ${f.message}`);
7814
+ if (f.suggested_anchors) {
7815
+ const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
7816
+ const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
7817
+ console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
7818
+ }
7819
+ console.log(ui.dim(` \u2192 ${f.file}`));
7820
+ }
7821
+ process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
7822
+ });
7823
+ }
7824
+
7825
+ // src/utils/autopilot.ts
7826
+ async function applyAutopilotRepairs(root, paths, options = {}) {
7827
+ const repairs = [];
7828
+ const config = await loadConfig4(paths);
7829
+ if (options.applyConfig) {
7830
+ const changed = await ensureAutopilotConfig(paths, config);
7831
+ if (changed) {
7832
+ repairs.push({
7833
+ code: "autopilot-config",
7834
+ message: "Enabled autopilot defaults in .ai/haive.config.json."
7835
+ });
7836
+ }
7837
+ }
7838
+ const current = await loadConfig4(paths);
7839
+ const autoRepair = current.autoRepair ?? {};
7840
+ if (options.applyContext ?? autoRepair.context ?? current.autopilot) {
7841
+ const changed = await syncProjectContextVersion(root, paths);
7842
+ if (changed) {
7843
+ repairs.push({
7844
+ code: "project-context-version",
7845
+ message: "Updated .ai/project-context.md version metadata from package.json."
7846
+ });
7847
+ }
7848
+ }
7849
+ if (options.applyCorpus ?? autoRepair.corpus ?? current.autopilot) {
7850
+ const report = await lintMemoriesAsync(root, { fix: true, apply: true });
7851
+ const applied = report.fixes.filter((fix) => fix.applied);
7852
+ if (applied.length > 0) {
7853
+ repairs.push({
7854
+ code: "memory-lint-fix",
7855
+ message: `Applied ${applied.length} safe memory lint fix${applied.length === 1 ? "" : "es"}.`
7856
+ });
7857
+ }
7858
+ }
7859
+ if (options.applyCodeMap ?? autoRepair.codeMap ?? current.autopilot) {
7860
+ const refreshed = await refreshCodeMap(root, paths, Boolean(options.forceCodeMap));
7861
+ if (refreshed) {
7862
+ repairs.push({
7863
+ code: "code-map-refresh",
7864
+ message: "Refreshed .ai/code-map.json."
7865
+ });
7866
+ }
7867
+ }
7868
+ if (options.applyCodeSearch ?? autoRepair.codeSearch ?? current.autopilot) {
7869
+ const indexed = await refreshCodeSearchIndex(paths);
7870
+ if (indexed) {
7871
+ repairs.push({
7872
+ code: "code-search-index",
7873
+ message: "Refreshed code-search embeddings index."
7874
+ });
7875
+ }
7876
+ }
7877
+ return repairs;
7878
+ }
7879
+ async function ensureAutopilotConfig(paths, currentConfig) {
7880
+ const current = currentConfig ?? await loadConfig4(paths);
7881
+ const next = {
7882
+ ...current,
7883
+ autopilot: true,
7884
+ defaultScope: "team",
7885
+ defaultStatus: "validated",
7886
+ autoApproveDelayHours: current.autoApproveDelayHours ?? AUTOPILOT_DEFAULTS2.autoApproveDelayHours,
7887
+ autoPromoteMinReads: current.autoPromoteMinReads ?? AUTOPILOT_DEFAULTS2.autoPromoteMinReads,
7888
+ autoSessionEnd: true,
7889
+ autoContext: true,
7890
+ autoRepair: {
7891
+ context: true,
7892
+ corpus: true,
7893
+ codeMap: true,
7894
+ codeSearch: current.autoRepair?.codeSearch ?? true
7895
+ },
7896
+ enforcement: {
7897
+ ...AUTOPILOT_DEFAULTS2.enforcement,
7898
+ ...current.enforcement,
7899
+ mode: "strict",
7900
+ requireBriefingFirst: true,
7901
+ requireSessionRecap: true,
7902
+ requireMemoryVerify: true,
7903
+ blockStaleDecisionChanges: true,
7904
+ requireDecisionCoverage: true,
7905
+ cleanupGeneratedArtifacts: true,
7906
+ toolProfile: current.enforcement?.toolProfile ?? "enforcement"
7907
+ }
7908
+ };
7909
+ if (JSON.stringify(current) === JSON.stringify(next)) return false;
7910
+ await saveConfig2(paths, next);
7911
+ return true;
7912
+ }
7913
+ async function syncProjectContextVersion(root, paths) {
7914
+ const status = await projectContextVersionStatus(root, paths);
7915
+ if (!status.canSync || !status.expectedVersion) return false;
7916
+ const original = await readFile8(paths.projectContext, "utf8");
7917
+ let updated = original.replace(
7918
+ /^# Project context — hAIve \(v[^)]+\)$/m,
7919
+ `# Project context \u2014 hAIve (v${status.expectedVersion})`
7920
+ ).replace(
7921
+ /> \*\*Current version\*\*: [^—\n]+—/m,
7922
+ `> **Current version**: ${status.expectedVersion} \u2014`
7923
+ );
7924
+ if (updated === original && !original.includes("Current version")) {
7925
+ updated = original.replace(
7926
+ /^(> Repo-native context enforcement[^\n]*\n)/m,
7927
+ `$1> **Current version**: ${status.expectedVersion} \u2014 @hiveai/core, cli, mcp, embeddings are versioned together.
7928
+ `
7929
+ );
7930
+ }
7931
+ if (updated === original) return false;
7932
+ await writeFile14(paths.projectContext, updated, "utf8");
7933
+ return true;
7934
+ }
7935
+ async function projectContextVersionStatus(root, paths) {
7936
+ if (!existsSync30(paths.projectContext)) {
7937
+ return { mismatch: false, canSync: false };
7938
+ }
7939
+ const packagePath = path14.join(root, "package.json");
7940
+ if (!existsSync30(packagePath)) {
7941
+ return { mismatch: false, canSync: false };
7942
+ }
7943
+ const packageJson = JSON.parse(await readFile8(packagePath, "utf8"));
7944
+ const expectedVersion = packageJson.version;
7945
+ if (!expectedVersion) {
7946
+ return { mismatch: false, canSync: false };
7947
+ }
7948
+ const content = await readFile8(paths.projectContext, "utf8");
7949
+ const headingVersion = content.match(/^# Project context — hAIve \(v([^)]+)\)$/m)?.[1];
7950
+ const currentLineVersion = content.match(/^> \*\*Current version\*\*: ([^—\n]+)—/m)?.[1]?.trim();
7951
+ const currentVersion = currentLineVersion ?? headingVersion;
7952
+ return {
7953
+ expectedVersion,
7954
+ currentVersion,
7955
+ mismatch: currentVersion !== expectedVersion,
7956
+ canSync: true
7957
+ };
7958
+ }
7959
+ async function refreshCodeMap(root, paths, force) {
7960
+ if (!force) {
7961
+ const existing = await loadCodeMap5(paths);
7962
+ if (existing) return false;
7963
+ }
7964
+ const map = await buildCodeMap3(root, {
7965
+ excludeDirs: [
7966
+ "node_modules",
7967
+ "dist",
7968
+ "build",
7969
+ "out",
7970
+ ".git",
7971
+ ".next",
7972
+ ".turbo",
7973
+ ".vitest-cache",
7974
+ "coverage"
7975
+ ]
7976
+ });
7977
+ await saveCodeMap3(paths, map);
7978
+ return true;
7979
+ }
7980
+ async function refreshCodeSearchIndex(paths) {
7981
+ try {
7982
+ const mod = await import("@hiveai/embeddings");
7983
+ const embedder = await mod.Embedder.create();
7984
+ const { report } = await mod.rebuildCodeIndex(paths, embedder);
7985
+ return report.added > 0 || report.updated > 0 || report.removed > 0;
7986
+ } catch {
7987
+ return false;
7988
+ }
7989
+ }
7990
+
7991
+ // src/commands/sync.ts
7992
+ var BRIDGE_START = "<!-- haive:memories-start -->";
7993
+ var BRIDGE_END = "<!-- haive:memories-end -->";
7994
+ function registerSync(program2) {
7995
+ program2.command("sync").description(
7996
+ "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"
7997
+ ).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
7998
+ "--since <ref>",
7999
+ "git ref/commit to compare against; report memories added/modified/removed since"
8000
+ ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
8001
+ "--inject-bridge",
8002
+ "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
8003
+ ).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) => {
8004
+ const root = findProjectRoot12(opts.dir);
8005
+ const paths = resolveHaivePaths9(root);
8006
+ if (!existsSync31(paths.memoriesDir)) {
8007
+ if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8008
+ process.exitCode = 1;
8009
+ return;
8010
+ }
8011
+ const log = (msg) => {
8012
+ if (!opts.quiet) console.log(msg);
8013
+ };
8014
+ const config = await loadConfig5(paths);
8015
+ const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
8016
+ const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
8017
+ const autoRepair = config.autoRepair ?? {};
8018
+ let staleMarked = 0;
8019
+ let revalidated = 0;
8020
+ let promoted = 0;
8021
+ let autoApproved = 0;
8022
+ if (opts.verify !== false) {
8023
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8024
+ for (const { memory: memory2, filePath } of memories) {
8025
+ if (memory2.frontmatter.type === "session_recap") {
8026
+ if (memory2.frontmatter.status === "stale") {
8027
+ await writeFile15(
8028
+ filePath,
8029
+ serializeMemory12({
8030
+ frontmatter: {
8031
+ ...memory2.frontmatter,
8032
+ status: "validated",
8033
+ stale_reason: null,
8034
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8035
+ },
8036
+ body: memory2.body
8037
+ }),
8038
+ "utf8"
8039
+ );
8040
+ revalidated++;
8041
+ }
8042
+ continue;
8043
+ }
8044
+ const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
8045
+ if (!isAnchored) continue;
8046
+ const result = await verifyAnchor2(memory2, { projectRoot: root });
8047
+ const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
8048
+ if (result.stale) {
8049
+ if (memory2.frontmatter.status !== "stale") {
8050
+ await writeFile15(
8051
+ filePath,
8052
+ serializeMemory12({
8053
+ frontmatter: {
8054
+ ...memory2.frontmatter,
8055
+ status: "stale",
8056
+ verified_at: verifiedAt,
8057
+ stale_reason: result.reason
8058
+ },
8059
+ body: memory2.body
8060
+ }),
8061
+ "utf8"
8062
+ );
8063
+ staleMarked++;
8064
+ }
8065
+ } else if (memory2.frontmatter.status === "stale") {
8066
+ await writeFile15(
8067
+ filePath,
8068
+ serializeMemory12({
8069
+ frontmatter: {
8070
+ ...memory2.frontmatter,
8071
+ status: "validated",
8072
+ verified_at: verifiedAt,
8073
+ stale_reason: null
8074
+ },
8075
+ body: memory2.body
8076
+ }),
8077
+ "utf8"
8078
+ );
8079
+ revalidated++;
8080
+ }
8081
+ }
8082
+ }
8083
+ if (opts.promote !== false) {
8084
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8085
+ const usage = await loadUsageIndex13(paths);
8086
+ const nowMs = Date.now();
8087
+ for (const { memory: memory2, filePath } of memories) {
8088
+ const fm = memory2.frontmatter;
8089
+ if (fm.type === "session_recap") continue;
8090
+ if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
8091
+ minReads: autoPromoteMinReads,
8092
+ maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
8093
+ })) {
8094
+ await writeFile15(
8095
+ filePath,
8096
+ serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8097
+ "utf8"
8098
+ );
8099
+ promoted++;
8100
+ continue;
8101
+ }
8102
+ if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
8103
+ const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
8104
+ if (ageHours >= autoApproveDelayHours) {
8105
+ await writeFile15(
8106
+ filePath,
8107
+ serializeMemory12({
8108
+ frontmatter: {
8109
+ ...fm,
8110
+ status: "validated",
8111
+ verified_at: (/* @__PURE__ */ new Date()).toISOString()
8112
+ },
8113
+ body: memory2.body
8114
+ }),
8115
+ "utf8"
8116
+ );
8117
+ autoApproved++;
8118
+ }
8119
+ }
8120
+ }
8121
+ }
8122
+ if (config.autopilot || autoRepair.context || autoRepair.corpus) {
8123
+ const repairs = await applyAutopilotRepairs(root, paths, {
8124
+ applyContext: autoRepair.context ?? config.autopilot,
8125
+ applyCorpus: autoRepair.corpus ?? config.autopilot,
8126
+ applyCodeMap: false,
8127
+ applyCodeSearch: false
8128
+ });
8129
+ for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8130
+ }
8131
+ const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
8132
+ const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
8133
+ (m) => m.memory.frontmatter.status === "draft"
8134
+ );
8135
+ const draftCount = draftMemories.length;
8136
+ const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
8137
+ log(
8138
+ `${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
8139
+ );
8140
+ if (!opts.quiet && draftCount > 0) {
8141
+ log(
8142
+ ui.dim(
8143
+ `\u2139 ${draftCount} memor${draftCount === 1 ? "y" : "ies"} in draft \u2014 run \`haive memory approve <id>\` to activate or \`haive memory list --status draft\` to review`
8144
+ )
7653
8145
  );
7654
8146
  }
7655
8147
  if (opts.injectBridge) {
7656
- const bridgeFile = opts.bridgeFile ? path13.resolve(opts.bridgeFile) : path13.join(root, "CLAUDE.md");
8148
+ const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
7657
8149
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
7658
8150
  await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
7659
8151
  }
@@ -7672,12 +8164,12 @@ function registerSync(program2) {
7672
8164
  }
7673
8165
  }
7674
8166
  if (!opts.quiet) {
7675
- const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
7676
- const usageForDecay = await loadUsageIndex12(paths);
8167
+ const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
8168
+ const usageForDecay = await loadUsageIndex13(paths);
7677
8169
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
7678
8170
  const fm = memory2.frontmatter;
7679
8171
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
7680
- const u = getUsage10(usageForDecay, fm.id);
8172
+ const u = getUsage11(usageForDecay, fm.id);
7681
8173
  return isDecaying2(u, fm.created_at);
7682
8174
  });
7683
8175
  if (decaying.length > 0) {
@@ -7759,11 +8251,11 @@ Attends une **confirmation explicite** avant d'agir.
7759
8251
  paths: [result.file],
7760
8252
  topic: `dep-bump-${slugParts}`
7761
8253
  });
7762
- const teamDir = path13.join(paths.memoriesDir, "team");
8254
+ const teamDir = path15.join(paths.memoriesDir, "team");
7763
8255
  await mkdir10(teamDir, { recursive: true });
7764
- await writeFile13(
7765
- path13.join(teamDir, `${fm.id}.md`),
7766
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8256
+ await writeFile15(
8257
+ path15.join(teamDir, `${fm.id}.md`),
8258
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7767
8259
  "utf8"
7768
8260
  );
7769
8261
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7826,11 +8318,11 @@ Attends une **confirmation explicite** avant d'agir.
7826
8318
  paths: [diff.file],
7827
8319
  topic: `contract-breaking-${diff.contract}`
7828
8320
  });
7829
- const teamDir = path13.join(paths.memoriesDir, "team");
8321
+ const teamDir = path15.join(paths.memoriesDir, "team");
7830
8322
  await mkdir10(teamDir, { recursive: true });
7831
- await writeFile13(
7832
- path13.join(teamDir, `${fm.id}.md`),
7833
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8323
+ await writeFile15(
8324
+ path15.join(teamDir, `${fm.id}.md`),
8325
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7834
8326
  "utf8"
7835
8327
  );
7836
8328
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7840,10 +8332,19 @@ Attends une **confirmation explicite** avant d'agir.
7840
8332
  ui.warn(`contract watcher failed: ${String(err)}`);
7841
8333
  }
7842
8334
  }
7843
- const existingMap = await loadCodeMap4(paths);
7844
- if (existingMap) {
8335
+ const existingMap = await loadCodeMap6(paths);
8336
+ if (!existingMap && (config.autopilot || autoRepair.codeMap)) {
8337
+ try {
8338
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8339
+ log(ui.dim("code-map: missing \u2014 building index\u2026"));
8340
+ const newMap = await buildCodeMap4(root);
8341
+ await saveCodeMap4(paths, newMap);
8342
+ log(ui.dim(`code-map: built (${Object.keys(newMap.files).length} files)`));
8343
+ } catch {
8344
+ }
8345
+ } else if (existingMap) {
7845
8346
  const mapAge = new Date(existingMap.generated_at).getTime();
7846
- const gitResult = spawnSync3(
8347
+ const gitResult = spawnSync4(
7847
8348
  "git",
7848
8349
  [
7849
8350
  "diff",
@@ -7868,22 +8369,32 @@ Attends une **confirmation explicite** avant d'agir.
7868
8369
  const changedSourceFiles = (gitResult.stdout ?? "").trim();
7869
8370
  if (changedSourceFiles.length > 0) {
7870
8371
  try {
7871
- const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
8372
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
7872
8373
  log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
7873
- const newMap = await buildCodeMap3(root);
7874
- await saveCodeMap3(paths, newMap);
8374
+ const newMap = await buildCodeMap4(root);
8375
+ await saveCodeMap4(paths, newMap);
7875
8376
  log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
7876
8377
  } catch {
7877
8378
  }
7878
8379
  }
7879
8380
  }
7880
- if (opts.embed) {
8381
+ if (opts.embed || autoRepair.codeSearch) {
7881
8382
  try {
7882
- const { Embedder, rebuildIndex } = await import("@hiveai/embeddings");
8383
+ const { Embedder, rebuildCodeIndex, rebuildIndex } = await import("@hiveai/embeddings");
7883
8384
  log(ui.dim("embed: rebuilding index\u2026"));
7884
8385
  const embedder = await Embedder.create();
7885
8386
  const { report } = await rebuildIndex(paths, embedder);
7886
- log(ui.dim(`embed: index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`));
8387
+ const { report: codeReport } = await rebuildCodeIndex(paths, embedder);
8388
+ log(
8389
+ ui.dim(
8390
+ `embed: memory index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`
8391
+ )
8392
+ );
8393
+ log(
8394
+ ui.dim(
8395
+ `embed: code index rebuilt (${codeReport.total} symbols, ${codeReport.added} added, ${codeReport.updated} updated, ${codeReport.removed} removed)`
8396
+ )
8397
+ );
7887
8398
  } catch {
7888
8399
  ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
7889
8400
  }
@@ -7891,8 +8402,8 @@ Attends une **confirmation explicite** avant d'agir.
7891
8402
  });
7892
8403
  }
7893
8404
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
7894
- if (!existsSync29(memoriesDir)) return;
7895
- const all = await loadMemoriesFromDir23(memoriesDir);
8405
+ if (!existsSync31(memoriesDir)) return;
8406
+ const all = await loadMemoriesFromDir24(memoriesDir);
7896
8407
  const top = all.filter(({ memory: memory2 }) => {
7897
8408
  const s = memory2.frontmatter.status;
7898
8409
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -7916,17 +8427,17 @@ ${m.memory.body.trim()}`;
7916
8427
  ` + block + `
7917
8428
 
7918
8429
  ${BRIDGE_END}`;
7919
- const fileExists = existsSync29(bridgeFile);
7920
- let existing = fileExists ? await readFile8(bridgeFile, "utf8") : "";
8430
+ const fileExists = existsSync31(bridgeFile);
8431
+ let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
7921
8432
  existing = existing.replace(/\r\n/g, "\n");
7922
8433
  const startIdx = existing.indexOf(BRIDGE_START);
7923
8434
  const endIdx = existing.indexOf(BRIDGE_END);
7924
8435
  if (startIdx !== -1 && endIdx === -1) {
7925
- ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
8436
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
7926
8437
  return;
7927
8438
  }
7928
8439
  if (startIdx === -1 && endIdx !== -1) {
7929
- ui.warn(`${path13.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
8440
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
7930
8441
  return;
7931
8442
  }
7932
8443
  let updated;
@@ -7934,19 +8445,19 @@ ${BRIDGE_END}`;
7934
8445
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
7935
8446
  } else {
7936
8447
  if (!fileExists && !quiet) {
7937
- ui.info(`Creating ${path13.relative(root, bridgeFile)} with haive memory block.`);
8448
+ ui.info(`Creating ${path15.relative(root, bridgeFile)} with haive memory block.`);
7938
8449
  }
7939
8450
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
7940
8451
  }
7941
- await writeFile13(bridgeFile, updated, "utf8");
8452
+ await writeFile15(bridgeFile, updated, "utf8");
7942
8453
  if (!quiet) {
7943
8454
  console.log(
7944
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path13.relative(root, bridgeFile)}`)
8455
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
7945
8456
  );
7946
8457
  }
7947
8458
  }
7948
8459
  function collectSinceChanges(root, ref) {
7949
- const result = spawnSync3(
8460
+ const result = spawnSync4(
7950
8461
  "git",
7951
8462
  ["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
7952
8463
  { encoding: "utf8" }
@@ -7966,18 +8477,19 @@ function collectSinceChanges(root, ref) {
7966
8477
 
7967
8478
  // src/commands/memory-add.ts
7968
8479
  import { createHash as createHash2 } from "crypto";
7969
- import { mkdir as mkdir11, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7970
- import { existsSync as existsSync30 } from "fs";
7971
- import path14 from "path";
8480
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile16 } from "fs/promises";
8481
+ import { existsSync as existsSync33 } from "fs";
8482
+ import path16 from "path";
7972
8483
  import "commander";
7973
8484
  import {
7974
8485
  buildFrontmatter as buildFrontmatter7,
7975
- findProjectRoot as findProjectRoot12,
8486
+ findProjectRoot as findProjectRoot13,
7976
8487
  inferModulesFromPaths as inferModulesFromPaths3,
7977
- loadMemoriesFromDir as loadMemoriesFromDir24,
8488
+ loadConfig as loadConfig6,
8489
+ loadMemoriesFromDir as loadMemoriesFromDir25,
7978
8490
  memoryFilePath as memoryFilePath6,
7979
- resolveHaivePaths as resolveHaivePaths9,
7980
- serializeMemory as serializeMemory12
8491
+ resolveHaivePaths as resolveHaivePaths10,
8492
+ serializeMemory as serializeMemory13
7981
8493
  } from "@hiveai/core";
7982
8494
  function registerMemoryAdd(memory2) {
7983
8495
  memory2.command("add").description(
@@ -8003,21 +8515,22 @@ function registerMemoryAdd(memory2) {
8003
8515
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
8004
8516
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
8005
8517
  `
8006
- ).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) => {
8007
- const root = findProjectRoot12(opts.dir);
8008
- const paths = resolveHaivePaths9(root);
8009
- if (!existsSync30(paths.haiveDir)) {
8518
+ ).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) => {
8519
+ const root = findProjectRoot13(opts.dir);
8520
+ const paths = resolveHaivePaths10(root);
8521
+ if (!existsSync33(paths.haiveDir)) {
8010
8522
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8011
8523
  process.exitCode = 1;
8012
8524
  return;
8013
8525
  }
8526
+ const config = await loadConfig6(paths);
8014
8527
  const userTags = parseCsv2(opts.tags);
8015
8528
  const anchorPaths = parseCsv2(opts.paths);
8016
8529
  const autoTagsEnabled = opts.autoTag !== false;
8017
8530
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
8018
8531
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
8019
8532
  if (anchorPaths.length > 0) {
8020
- const missing = anchorPaths.filter((p) => !existsSync30(path14.resolve(root, p)));
8533
+ const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
8021
8534
  if (missing.length > 0) {
8022
8535
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
8023
8536
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -8029,12 +8542,12 @@ function registerMemoryAdd(memory2) {
8029
8542
  const title = opts.title ?? opts.slug;
8030
8543
  let body;
8031
8544
  if (opts.bodyFile !== void 0) {
8032
- if (!existsSync30(opts.bodyFile)) {
8545
+ if (!existsSync33(opts.bodyFile)) {
8033
8546
  ui.error(`--body-file not found: ${opts.bodyFile}`);
8034
8547
  process.exitCode = 1;
8035
8548
  return;
8036
8549
  }
8037
- const fileContent = await readFile9(opts.bodyFile, "utf8");
8550
+ const fileContent = await readFile10(opts.bodyFile, "utf8");
8038
8551
  body = opts.title ? `# ${opts.title}
8039
8552
 
8040
8553
  ${fileContent.trim()}
@@ -8049,10 +8562,10 @@ ${opts.body}` : opts.body;
8049
8562
  TODO \u2014 write the memory body.
8050
8563
  `;
8051
8564
  }
8052
- const scope = opts.scope ?? "personal";
8053
- if (existsSync30(paths.memoriesDir)) {
8565
+ const scope = opts.scope ?? config.defaultScope ?? "personal";
8566
+ if (existsSync33(paths.memoriesDir)) {
8054
8567
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
8055
- const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
8568
+ const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
8056
8569
  const hashDup = allForHash.find(
8057
8570
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
8058
8571
  );
@@ -8063,8 +8576,8 @@ TODO \u2014 write the memory body.
8063
8576
  return;
8064
8577
  }
8065
8578
  }
8066
- if (opts.topic && existsSync30(paths.memoriesDir)) {
8067
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8579
+ if (opts.topic && existsSync33(paths.memoriesDir)) {
8580
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
8068
8581
  const topicMatch = existing.find(
8069
8582
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
8070
8583
  );
@@ -8081,8 +8594,8 @@ TODO \u2014 write the memory body.
8081
8594
  symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
8082
8595
  }
8083
8596
  };
8084
- await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
8085
- ui.success(`Updated (topic upsert) ${path14.relative(root, topicMatch.filePath)}`);
8597
+ await writeFile16(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
8598
+ ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
8086
8599
  ui.info(`id=${fm.id} revision=${revisionCount}`);
8087
8600
  return;
8088
8601
  }
@@ -8098,17 +8611,18 @@ TODO \u2014 write the memory body.
8098
8611
  paths: anchorPaths,
8099
8612
  symbols: parseCsv2(opts.symbols),
8100
8613
  commit: opts.commit,
8101
- topic: opts.topic
8614
+ topic: opts.topic,
8615
+ status: config.defaultStatus === "validated" ? "validated" : void 0
8102
8616
  });
8103
8617
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8104
- await mkdir11(path14.dirname(file), { recursive: true });
8105
- if (existsSync30(file)) {
8618
+ await mkdir11(path16.dirname(file), { recursive: true });
8619
+ if (existsSync33(file)) {
8106
8620
  ui.error(`Memory already exists at ${file}`);
8107
8621
  process.exitCode = 1;
8108
8622
  return;
8109
8623
  }
8110
- if (existsSync30(paths.memoriesDir)) {
8111
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8624
+ if (existsSync33(paths.memoriesDir)) {
8625
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
8112
8626
  const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
8113
8627
  const similar = existing.filter(({ memory: memory3 }) => {
8114
8628
  const id = memory3.frontmatter.id.toLowerCase();
@@ -8119,8 +8633,8 @@ TODO \u2014 write the memory body.
8119
8633
  ui.warn("Consider updating one of these with `haive memory update` instead.");
8120
8634
  }
8121
8635
  }
8122
- await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
8123
- ui.success(`Created ${path14.relative(root, file)}`);
8636
+ await writeFile16(file, serializeMemory13({ frontmatter, body }), "utf8");
8637
+ ui.success(`Created ${path16.relative(root, file)}`);
8124
8638
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
8125
8639
  if (inferredTags.length > 0) {
8126
8640
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
@@ -8131,7 +8645,9 @@ TODO \u2014 write the memory body.
8131
8645
  Add file anchors: haive memory update ${frontmatter.id} --paths <file1,file2>`
8132
8646
  );
8133
8647
  }
8134
- if (scope === "personal") {
8648
+ if (frontmatter.status === "validated") {
8649
+ console.log(ui.dim("\u2192 autopilot: memory is already validated and active"));
8650
+ } else if (scope === "personal") {
8135
8651
  console.log(
8136
8652
  ui.dim(
8137
8653
  `\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
@@ -8150,14 +8666,14 @@ function parseCsv2(value) {
8150
8666
  }
8151
8667
 
8152
8668
  // src/commands/memory-list.ts
8153
- import { existsSync as existsSync31 } from "fs";
8154
- import path15 from "path";
8669
+ import { existsSync as existsSync34 } from "fs";
8670
+ import path17 from "path";
8155
8671
  import "commander";
8156
- import { findProjectRoot as findProjectRoot13, resolveHaivePaths as resolveHaivePaths10 } from "@hiveai/core";
8672
+ import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
8157
8673
 
8158
8674
  // src/utils/fs.ts
8159
8675
  import {
8160
- loadMemoriesFromDir as loadMemoriesFromDir25,
8676
+ loadMemoriesFromDir as loadMemoriesFromDir26,
8161
8677
  loadMemory,
8162
8678
  listMarkdownFilesRecursive
8163
8679
  } from "@hiveai/core";
@@ -8165,14 +8681,14 @@ import {
8165
8681
  // src/commands/memory-list.ts
8166
8682
  function registerMemoryList(memory2) {
8167
8683
  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) => {
8168
- const root = findProjectRoot13(opts.dir);
8169
- const paths = resolveHaivePaths10(root);
8170
- if (!existsSync31(paths.memoriesDir)) {
8684
+ const root = findProjectRoot14(opts.dir);
8685
+ const paths = resolveHaivePaths11(root);
8686
+ if (!existsSync34(paths.memoriesDir)) {
8171
8687
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8172
8688
  process.exitCode = 1;
8173
8689
  return;
8174
8690
  }
8175
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8691
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8176
8692
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8177
8693
  const filtered = all.filter((m) => {
8178
8694
  if (!matchesFilters(m, opts)) return false;
@@ -8199,7 +8715,7 @@ function registerMemoryList(memory2) {
8199
8715
  console.log(
8200
8716
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
8201
8717
  );
8202
- console.log(` ${ui.dim(path15.relative(root, filePath))}`);
8718
+ console.log(` ${ui.dim(path17.relative(root, filePath))}`);
8203
8719
  }
8204
8720
  console.log(ui.dim(`
8205
8721
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
@@ -8234,26 +8750,26 @@ function matchesFilters(loaded, opts) {
8234
8750
  }
8235
8751
 
8236
8752
  // src/commands/memory-promote.ts
8237
- import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
8238
- import { existsSync as existsSync33 } from "fs";
8239
- import path16 from "path";
8753
+ import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
8754
+ import { existsSync as existsSync35 } from "fs";
8755
+ import path18 from "path";
8240
8756
  import "commander";
8241
8757
  import {
8242
- findProjectRoot as findProjectRoot14,
8758
+ findProjectRoot as findProjectRoot15,
8243
8759
  memoryFilePath as memoryFilePath7,
8244
- resolveHaivePaths as resolveHaivePaths11,
8245
- serializeMemory as serializeMemory13
8760
+ resolveHaivePaths as resolveHaivePaths12,
8761
+ serializeMemory as serializeMemory14
8246
8762
  } from "@hiveai/core";
8247
8763
  function registerMemoryPromote(memory2) {
8248
8764
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8249
- const root = findProjectRoot14(opts.dir);
8250
- const paths = resolveHaivePaths11(root);
8251
- if (!existsSync33(paths.memoriesDir)) {
8765
+ const root = findProjectRoot15(opts.dir);
8766
+ const paths = resolveHaivePaths12(root);
8767
+ if (!existsSync35(paths.memoriesDir)) {
8252
8768
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8253
8769
  process.exitCode = 1;
8254
8770
  return;
8255
8771
  }
8256
- const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
8772
+ const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
8257
8773
  const alreadyShared = teamAndModule.find(
8258
8774
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
8259
8775
  );
@@ -8267,7 +8783,7 @@ function registerMemoryPromote(memory2) {
8267
8783
  }
8268
8784
  return;
8269
8785
  }
8270
- const all = await loadMemoriesFromDir25(paths.personalDir);
8786
+ const all = await loadMemoriesFromDir26(paths.personalDir);
8271
8787
  const found = all.find((m) => m.memory.frontmatter.id === id);
8272
8788
  if (!found) {
8273
8789
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -8283,35 +8799,35 @@ function registerMemoryPromote(memory2) {
8283
8799
  body: found.memory.body
8284
8800
  };
8285
8801
  const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
8286
- await mkdir12(path16.dirname(newPath), { recursive: true });
8287
- await writeFile15(newPath, serializeMemory13(updated), "utf8");
8802
+ await mkdir12(path18.dirname(newPath), { recursive: true });
8803
+ await writeFile17(newPath, serializeMemory14(updated), "utf8");
8288
8804
  await unlink2(found.filePath);
8289
8805
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
8290
- ui.info(`Now at ${path16.relative(root, newPath)}`);
8806
+ ui.info(`Now at ${path18.relative(root, newPath)}`);
8291
8807
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
8292
8808
  });
8293
8809
  }
8294
8810
 
8295
8811
  // src/commands/memory-approve.ts
8296
- import { existsSync as existsSync34 } from "fs";
8297
- import { writeFile as writeFile16 } from "fs/promises";
8298
- import path17 from "path";
8812
+ import { existsSync as existsSync36 } from "fs";
8813
+ import { writeFile as writeFile18 } from "fs/promises";
8814
+ import path19 from "path";
8299
8815
  import "commander";
8300
8816
  import {
8301
- findProjectRoot as findProjectRoot15,
8302
- resolveHaivePaths as resolveHaivePaths12,
8303
- serializeMemory as serializeMemory14
8817
+ findProjectRoot as findProjectRoot16,
8818
+ resolveHaivePaths as resolveHaivePaths13,
8819
+ serializeMemory as serializeMemory15
8304
8820
  } from "@hiveai/core";
8305
8821
  function registerMemoryApprove(memory2) {
8306
8822
  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) => {
8307
- const root = findProjectRoot15(opts.dir);
8308
- const paths = resolveHaivePaths12(root);
8309
- if (!existsSync34(paths.memoriesDir)) {
8823
+ const root = findProjectRoot16(opts.dir);
8824
+ const paths = resolveHaivePaths13(root);
8825
+ if (!existsSync36(paths.memoriesDir)) {
8310
8826
  ui.error(`No .ai/memories at ${root}.`);
8311
8827
  process.exitCode = 1;
8312
8828
  return;
8313
8829
  }
8314
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8830
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8315
8831
  if (opts.all || opts.pending) {
8316
8832
  const candidates = all.filter((m) => {
8317
8833
  const s = m.memory.frontmatter.status;
@@ -8328,7 +8844,7 @@ function registerMemoryApprove(memory2) {
8328
8844
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
8329
8845
  body: found2.memory.body
8330
8846
  };
8331
- await writeFile16(found2.filePath, serializeMemory14(next2), "utf8");
8847
+ await writeFile18(found2.filePath, serializeMemory15(next2), "utf8");
8332
8848
  count++;
8333
8849
  }
8334
8850
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -8357,32 +8873,32 @@ function registerMemoryApprove(memory2) {
8357
8873
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
8358
8874
  body: found.memory.body
8359
8875
  };
8360
- await writeFile16(found.filePath, serializeMemory14(next), "utf8");
8876
+ await writeFile18(found.filePath, serializeMemory15(next), "utf8");
8361
8877
  ui.success(`Approved ${id} (status=validated)`);
8362
- ui.info(path17.relative(root, found.filePath));
8878
+ ui.info(path19.relative(root, found.filePath));
8363
8879
  });
8364
8880
  }
8365
8881
 
8366
8882
  // src/commands/memory-update.ts
8367
- import { writeFile as writeFile17 } from "fs/promises";
8368
- import { existsSync as existsSync35 } from "fs";
8369
- import path18 from "path";
8883
+ import { writeFile as writeFile19 } from "fs/promises";
8884
+ import { existsSync as existsSync37 } from "fs";
8885
+ import path20 from "path";
8370
8886
  import "commander";
8371
8887
  import {
8372
- findProjectRoot as findProjectRoot16,
8373
- resolveHaivePaths as resolveHaivePaths13,
8374
- serializeMemory as serializeMemory15
8888
+ findProjectRoot as findProjectRoot17,
8889
+ resolveHaivePaths as resolveHaivePaths14,
8890
+ serializeMemory as serializeMemory16
8375
8891
  } from "@hiveai/core";
8376
8892
  function registerMemoryUpdate(memory2) {
8377
8893
  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) => {
8378
- const root = findProjectRoot16(opts.dir);
8379
- const paths = resolveHaivePaths13(root);
8380
- if (!existsSync35(paths.memoriesDir)) {
8894
+ const root = findProjectRoot17(opts.dir);
8895
+ const paths = resolveHaivePaths14(root);
8896
+ if (!existsSync37(paths.memoriesDir)) {
8381
8897
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8382
8898
  process.exitCode = 1;
8383
8899
  return;
8384
8900
  }
8385
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8901
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8386
8902
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8387
8903
  if (!loaded) {
8388
8904
  ui.error(`No memory with id "${id}".`);
@@ -8424,12 +8940,12 @@ function registerMemoryUpdate(memory2) {
8424
8940
  ui.warn("Nothing to update \u2014 provide at least one option.");
8425
8941
  return;
8426
8942
  }
8427
- await writeFile17(
8943
+ await writeFile19(
8428
8944
  loaded.filePath,
8429
- serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
8945
+ serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
8430
8946
  "utf8"
8431
8947
  );
8432
- ui.success(`Updated ${path18.relative(root, loaded.filePath)}`);
8948
+ ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
8433
8949
  ui.info(`fields: ${updated.join(", ")}`);
8434
8950
  });
8435
8951
  }
@@ -8448,18 +8964,18 @@ function parseCsv3(value) {
8448
8964
  }
8449
8965
 
8450
8966
  // src/commands/memory-auto-promote.ts
8451
- import { writeFile as writeFile18 } from "fs/promises";
8452
- import { existsSync as existsSync36 } from "fs";
8453
- import path19 from "path";
8967
+ import { writeFile as writeFile20 } from "fs/promises";
8968
+ import { existsSync as existsSync38 } from "fs";
8969
+ import path21 from "path";
8454
8970
  import "commander";
8455
8971
  import {
8456
8972
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
8457
- findProjectRoot as findProjectRoot17,
8458
- getUsage as getUsage11,
8973
+ findProjectRoot as findProjectRoot18,
8974
+ getUsage as getUsage12,
8459
8975
  isAutoPromoteEligible as isAutoPromoteEligible3,
8460
- loadUsageIndex as loadUsageIndex13,
8461
- resolveHaivePaths as resolveHaivePaths14,
8462
- serializeMemory as serializeMemory16
8976
+ loadUsageIndex as loadUsageIndex14,
8977
+ resolveHaivePaths as resolveHaivePaths15,
8978
+ serializeMemory as serializeMemory17
8463
8979
  } from "@hiveai/core";
8464
8980
  function registerMemoryAutoPromote(memory2) {
8465
8981
  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(
@@ -8467,9 +8983,9 @@ function registerMemoryAutoPromote(memory2) {
8467
8983
  "memories with more rejections than this are skipped",
8468
8984
  String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8469
8985
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8470
- const root = findProjectRoot17(opts.dir);
8471
- const paths = resolveHaivePaths14(root);
8472
- if (!existsSync36(paths.memoriesDir)) {
8986
+ const root = findProjectRoot18(opts.dir);
8987
+ const paths = resolveHaivePaths15(root);
8988
+ if (!existsSync38(paths.memoriesDir)) {
8473
8989
  ui.error(`No .ai/memories at ${root}.`);
8474
8990
  process.exitCode = 1;
8475
8991
  return;
@@ -8478,10 +8994,10 @@ function registerMemoryAutoPromote(memory2) {
8478
8994
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
8479
8995
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8480
8996
  };
8481
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8482
- const usage = await loadUsageIndex13(paths);
8997
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8998
+ const usage = await loadUsageIndex14(paths);
8483
8999
  const eligible = memories.filter(
8484
- ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
9000
+ ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
8485
9001
  );
8486
9002
  if (eligible.length === 0) {
8487
9003
  ui.info(
@@ -8491,17 +9007,17 @@ function registerMemoryAutoPromote(memory2) {
8491
9007
  }
8492
9008
  let written = 0;
8493
9009
  for (const { memory: mem, filePath } of eligible) {
8494
- const u = getUsage11(usage, mem.frontmatter.id);
9010
+ const u = getUsage12(usage, mem.frontmatter.id);
8495
9011
  console.log(
8496
9012
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8497
9013
  );
8498
- console.log(` ${ui.dim(path19.relative(root, filePath))}`);
9014
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
8499
9015
  if (opts.apply) {
8500
9016
  const next = {
8501
9017
  frontmatter: { ...mem.frontmatter, status: "validated" },
8502
9018
  body: mem.body
8503
9019
  };
8504
- await writeFile18(filePath, serializeMemory16(next), "utf8");
9020
+ await writeFile20(filePath, serializeMemory17(next), "utf8");
8505
9021
  written++;
8506
9022
  }
8507
9023
  }
@@ -8512,25 +9028,25 @@ function registerMemoryAutoPromote(memory2) {
8512
9028
 
8513
9029
  // src/commands/memory-edit.ts
8514
9030
  import { spawn as spawn3 } from "child_process";
8515
- import { existsSync as existsSync37 } from "fs";
8516
- import { readFile as readFile10 } from "fs/promises";
8517
- import path20 from "path";
9031
+ import { existsSync as existsSync39 } from "fs";
9032
+ import { readFile as readFile11 } from "fs/promises";
9033
+ import path23 from "path";
8518
9034
  import "commander";
8519
9035
  import {
8520
- findProjectRoot as findProjectRoot18,
9036
+ findProjectRoot as findProjectRoot19,
8521
9037
  parseMemory,
8522
- resolveHaivePaths as resolveHaivePaths15
9038
+ resolveHaivePaths as resolveHaivePaths16
8523
9039
  } from "@hiveai/core";
8524
9040
  function registerMemoryEdit(memory2) {
8525
9041
  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) => {
8526
- const root = findProjectRoot18(opts.dir);
8527
- const paths = resolveHaivePaths15(root);
8528
- if (!existsSync37(paths.memoriesDir)) {
9042
+ const root = findProjectRoot19(opts.dir);
9043
+ const paths = resolveHaivePaths16(root);
9044
+ if (!existsSync39(paths.memoriesDir)) {
8529
9045
  ui.error(`No .ai/memories at ${root}.`);
8530
9046
  process.exitCode = 1;
8531
9047
  return;
8532
9048
  }
8533
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9049
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8534
9050
  const found = all.find((m) => m.memory.frontmatter.id === id);
8535
9051
  if (!found) {
8536
9052
  ui.error(`No memory with id "${id}".`);
@@ -8538,13 +9054,13 @@ function registerMemoryEdit(memory2) {
8538
9054
  return;
8539
9055
  }
8540
9056
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
8541
- ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
9057
+ ui.info(`Opening ${path23.relative(root, found.filePath)} with ${editor}\u2026`);
8542
9058
  const code = await runEditor(editor, found.filePath);
8543
9059
  if (code !== 0) {
8544
9060
  ui.warn(`Editor exited with status ${code}.`);
8545
9061
  }
8546
9062
  try {
8547
- const fresh = await readFile10(found.filePath, "utf8");
9063
+ const fresh = await readFile11(found.filePath, "utf8");
8548
9064
  parseMemory(fresh);
8549
9065
  ui.success("Memory still parses cleanly.");
8550
9066
  } catch (err) {
@@ -8565,29 +9081,29 @@ function runEditor(editor, file) {
8565
9081
  }
8566
9082
 
8567
9083
  // src/commands/memory-for-files.ts
8568
- import { existsSync as existsSync38 } from "fs";
8569
- import path21 from "path";
9084
+ import { existsSync as existsSync40 } from "fs";
9085
+ import path24 from "path";
8570
9086
  import "commander";
8571
9087
  import {
8572
9088
  deriveConfidence as deriveConfidence9,
8573
- findProjectRoot as findProjectRoot19,
8574
- getUsage as getUsage12,
9089
+ findProjectRoot as findProjectRoot20,
9090
+ getUsage as getUsage13,
8575
9091
  inferModulesFromPaths as inferModulesFromPaths4,
8576
- loadUsageIndex as loadUsageIndex14,
9092
+ loadUsageIndex as loadUsageIndex15,
8577
9093
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
8578
- resolveHaivePaths as resolveHaivePaths16
9094
+ resolveHaivePaths as resolveHaivePaths17
8579
9095
  } from "@hiveai/core";
8580
9096
  function registerMemoryForFiles(memory2) {
8581
9097
  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) => {
8582
- const root = findProjectRoot19(opts.dir);
8583
- const paths = resolveHaivePaths16(root);
8584
- if (!existsSync38(paths.memoriesDir)) {
9098
+ const root = findProjectRoot20(opts.dir);
9099
+ const paths = resolveHaivePaths17(root);
9100
+ if (!existsSync40(paths.memoriesDir)) {
8585
9101
  ui.error(`No .ai/memories at ${root}.`);
8586
9102
  process.exitCode = 1;
8587
9103
  return;
8588
9104
  }
8589
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8590
- const usage = await loadUsageIndex14(paths);
9105
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9106
+ const usage = await loadUsageIndex15(paths);
8591
9107
  const inferred = inferModulesFromPaths4(files);
8592
9108
  const byAnchor = [];
8593
9109
  const byModule = [];
@@ -8685,44 +9201,44 @@ function printGroup(root, label, loaded, usage) {
8685
9201
  \u2014 ${label} \u2014`));
8686
9202
  for (const { memory: mem, filePath } of loaded) {
8687
9203
  const fm = mem.frontmatter;
8688
- const u = getUsage12(usage, fm.id);
9204
+ const u = getUsage13(usage, fm.id);
8689
9205
  const conf = deriveConfidence9(fm, u);
8690
9206
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
8691
- console.log(` ${ui.dim(path21.relative(root, filePath))}`);
9207
+ console.log(` ${ui.dim(path24.relative(root, filePath))}`);
8692
9208
  }
8693
9209
  }
8694
9210
 
8695
9211
  // src/commands/memory-hot.ts
8696
- import { existsSync as existsSync39 } from "fs";
8697
- import path23 from "path";
9212
+ import { existsSync as existsSync41 } from "fs";
9213
+ import path25 from "path";
8698
9214
  import "commander";
8699
9215
  import {
8700
- findProjectRoot as findProjectRoot20,
8701
- getUsage as getUsage13,
8702
- loadUsageIndex as loadUsageIndex15,
8703
- resolveHaivePaths as resolveHaivePaths17
9216
+ findProjectRoot as findProjectRoot21,
9217
+ getUsage as getUsage14,
9218
+ loadUsageIndex as loadUsageIndex16,
9219
+ resolveHaivePaths as resolveHaivePaths18
8704
9220
  } from "@hiveai/core";
8705
9221
  function registerMemoryHot(memory2) {
8706
9222
  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) => {
8707
- const root = findProjectRoot20(opts.dir);
8708
- const paths = resolveHaivePaths17(root);
8709
- if (!existsSync39(paths.memoriesDir)) {
9223
+ const root = findProjectRoot21(opts.dir);
9224
+ const paths = resolveHaivePaths18(root);
9225
+ if (!existsSync41(paths.memoriesDir)) {
8710
9226
  ui.error(`No .ai/memories at ${root}.`);
8711
9227
  process.exitCode = 1;
8712
9228
  return;
8713
9229
  }
8714
9230
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
8715
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8716
- const usage = await loadUsageIndex15(paths);
9231
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9232
+ const usage = await loadUsageIndex16(paths);
8717
9233
  const candidates = all.filter(({ memory: mem }) => {
8718
9234
  const fm = mem.frontmatter;
8719
9235
  if (opts.status && fm.status !== opts.status) return false;
8720
9236
  if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
8721
9237
  return false;
8722
9238
  }
8723
- return getUsage13(usage, fm.id).read_count >= threshold;
9239
+ return getUsage14(usage, fm.id).read_count >= threshold;
8724
9240
  }).sort(
8725
- (a, b) => getUsage13(usage, b.memory.frontmatter.id).read_count - getUsage13(usage, a.memory.frontmatter.id).read_count
9241
+ (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
8726
9242
  );
8727
9243
  if (candidates.length === 0) {
8728
9244
  ui.info(`No hot memories (threshold=${threshold}).`);
@@ -8730,11 +9246,11 @@ function registerMemoryHot(memory2) {
8730
9246
  }
8731
9247
  for (const { memory: mem, filePath } of candidates) {
8732
9248
  const fm = mem.frontmatter;
8733
- const u = getUsage13(usage, fm.id);
9249
+ const u = getUsage14(usage, fm.id);
8734
9250
  console.log(
8735
9251
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8736
9252
  );
8737
- console.log(` ${ui.dim(path23.relative(root, filePath))}`);
9253
+ console.log(` ${ui.dim(path25.relative(root, filePath))}`);
8738
9254
  }
8739
9255
  ui.info(
8740
9256
  `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
@@ -8743,16 +9259,16 @@ function registerMemoryHot(memory2) {
8743
9259
  }
8744
9260
 
8745
9261
  // src/commands/memory-tried.ts
8746
- import { mkdir as mkdir13, writeFile as writeFile19 } from "fs/promises";
8747
- import { existsSync as existsSync40 } from "fs";
8748
- import path24 from "path";
9262
+ import { mkdir as mkdir13, writeFile as writeFile21 } from "fs/promises";
9263
+ import { existsSync as existsSync43 } from "fs";
9264
+ import path26 from "path";
8749
9265
  import "commander";
8750
9266
  import {
8751
9267
  buildFrontmatter as buildFrontmatter8,
8752
- findProjectRoot as findProjectRoot21,
9268
+ findProjectRoot as findProjectRoot22,
8753
9269
  memoryFilePath as memoryFilePath8,
8754
- resolveHaivePaths as resolveHaivePaths18,
8755
- serializeMemory as serializeMemory17
9270
+ resolveHaivePaths as resolveHaivePaths19,
9271
+ serializeMemory as serializeMemory18
8756
9272
  } from "@hiveai/core";
8757
9273
  function registerMemoryTried(memory2) {
8758
9274
  memory2.command("tried").description(
@@ -8771,9 +9287,9 @@ function registerMemoryTried(memory2) {
8771
9287
  --paths packages/cli/src/index.ts
8772
9288
  `
8773
9289
  ).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) => {
8774
- const root = findProjectRoot21(opts.dir);
8775
- const paths = resolveHaivePaths18(root);
8776
- if (!existsSync40(paths.haiveDir)) {
9290
+ const root = findProjectRoot22(opts.dir);
9291
+ const paths = resolveHaivePaths19(root);
9292
+ if (!existsSync43(paths.haiveDir)) {
8777
9293
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8778
9294
  process.exitCode = 1;
8779
9295
  return;
@@ -8796,14 +9312,14 @@ function registerMemoryTried(memory2) {
8796
9312
  }
8797
9313
  const body = lines.join("\n") + "\n";
8798
9314
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8799
- await mkdir13(path24.dirname(file), { recursive: true });
8800
- if (existsSync40(file)) {
9315
+ await mkdir13(path26.dirname(file), { recursive: true });
9316
+ if (existsSync43(file)) {
8801
9317
  ui.error(`Memory already exists at ${file}`);
8802
9318
  process.exitCode = 1;
8803
9319
  return;
8804
9320
  }
8805
- await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
8806
- ui.success(`Recorded: ${path24.relative(root, file)}`);
9321
+ await writeFile21(file, serializeMemory18({ frontmatter, body }), "utf8");
9322
+ ui.success(`Recorded: ${path26.relative(root, file)}`);
8807
9323
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
8808
9324
  });
8809
9325
  }
@@ -8813,26 +9329,26 @@ function parseCsv4(value) {
8813
9329
  }
8814
9330
 
8815
9331
  // src/commands/memory-pending.ts
8816
- import { existsSync as existsSync41 } from "fs";
8817
- import path25 from "path";
9332
+ import { existsSync as existsSync44 } from "fs";
9333
+ import path27 from "path";
8818
9334
  import "commander";
8819
9335
  import {
8820
- findProjectRoot as findProjectRoot22,
8821
- getUsage as getUsage14,
8822
- loadUsageIndex as loadUsageIndex16,
8823
- resolveHaivePaths as resolveHaivePaths19
9336
+ findProjectRoot as findProjectRoot23,
9337
+ getUsage as getUsage15,
9338
+ loadUsageIndex as loadUsageIndex17,
9339
+ resolveHaivePaths as resolveHaivePaths20
8824
9340
  } from "@hiveai/core";
8825
9341
  function registerMemoryPending(memory2) {
8826
9342
  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) => {
8827
- const root = findProjectRoot22(opts.dir);
8828
- const paths = resolveHaivePaths19(root);
8829
- if (!existsSync41(paths.memoriesDir)) {
9343
+ const root = findProjectRoot23(opts.dir);
9344
+ const paths = resolveHaivePaths20(root);
9345
+ if (!existsSync44(paths.memoriesDir)) {
8830
9346
  ui.error(`No .ai/memories at ${root}.`);
8831
9347
  process.exitCode = 1;
8832
9348
  return;
8833
9349
  }
8834
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8835
- const usage = await loadUsageIndex16(paths);
9350
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9351
+ const usage = await loadUsageIndex17(paths);
8836
9352
  const proposed = all.filter(({ memory: mem }) => {
8837
9353
  if (mem.frontmatter.status !== "proposed") return false;
8838
9354
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -8843,42 +9359,42 @@ function registerMemoryPending(memory2) {
8843
9359
  return;
8844
9360
  }
8845
9361
  proposed.sort(
8846
- (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
9362
+ (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
8847
9363
  );
8848
9364
  const now = Date.now();
8849
9365
  for (const { memory: mem, filePath } of proposed) {
8850
9366
  const fm = mem.frontmatter;
8851
- const u = getUsage14(usage, fm.id);
9367
+ const u = getUsage15(usage, fm.id);
8852
9368
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
8853
9369
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
8854
9370
  console.log(
8855
9371
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
8856
9372
  );
8857
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
9373
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
8858
9374
  }
8859
9375
  ui.info(`${proposed.length} pending`);
8860
9376
  });
8861
9377
  }
8862
9378
 
8863
9379
  // src/commands/memory-query.ts
8864
- import { existsSync as existsSync43 } from "fs";
8865
- import path26 from "path";
9380
+ import { existsSync as existsSync45 } from "fs";
9381
+ import path28 from "path";
8866
9382
  import "commander";
8867
9383
  import {
8868
9384
  extractSnippet as extractSnippet2,
8869
- findProjectRoot as findProjectRoot23,
9385
+ findProjectRoot as findProjectRoot24,
8870
9386
  literalMatchesAllTokens as literalMatchesAllTokens3,
8871
9387
  literalMatchesAnyToken as literalMatchesAnyToken4,
8872
9388
  pickSnippetNeedle as pickSnippetNeedle2,
8873
- resolveHaivePaths as resolveHaivePaths20,
9389
+ resolveHaivePaths as resolveHaivePaths21,
8874
9390
  tokenizeQuery as tokenizeQuery6,
8875
9391
  trackReads as trackReads4
8876
9392
  } from "@hiveai/core";
8877
9393
  function registerMemoryQuery(memory2) {
8878
9394
  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) => {
8879
- const root = findProjectRoot23(opts.dir);
8880
- const paths = resolveHaivePaths20(root);
8881
- if (!existsSync43(paths.memoriesDir)) {
9395
+ const root = findProjectRoot24(opts.dir);
9396
+ const paths = resolveHaivePaths21(root);
9397
+ if (!existsSync45(paths.memoriesDir)) {
8882
9398
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8883
9399
  process.exitCode = 1;
8884
9400
  return;
@@ -8889,7 +9405,7 @@ function registerMemoryQuery(memory2) {
8889
9405
  return;
8890
9406
  }
8891
9407
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8892
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9408
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8893
9409
  const passesFilters2 = (mem) => {
8894
9410
  const fm = mem.frontmatter;
8895
9411
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -8919,7 +9435,7 @@ function registerMemoryQuery(memory2) {
8919
9435
  const fm = mem.frontmatter;
8920
9436
  const statusBadge = ui.statusBadge(fm.status);
8921
9437
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
8922
- console.log(` ${ui.dim(path26.relative(root, filePath))}`);
9438
+ console.log(` ${ui.dim(path28.relative(root, filePath))}`);
8923
9439
  const snippet = extractSnippet2(mem.body, snippetNeedle);
8924
9440
  if (snippet) console.log(` ${snippet}`);
8925
9441
  }
@@ -8936,36 +9452,36 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
8936
9452
  }
8937
9453
 
8938
9454
  // src/commands/memory-reject.ts
8939
- import { writeFile as writeFile20 } from "fs/promises";
8940
- import { existsSync as existsSync44 } from "fs";
9455
+ import { writeFile as writeFile23 } from "fs/promises";
9456
+ import { existsSync as existsSync46 } from "fs";
8941
9457
  import "commander";
8942
9458
  import {
8943
- findProjectRoot as findProjectRoot24,
8944
- loadUsageIndex as loadUsageIndex17,
9459
+ findProjectRoot as findProjectRoot25,
9460
+ loadUsageIndex as loadUsageIndex18,
8945
9461
  recordRejection as recordRejection2,
8946
- resolveHaivePaths as resolveHaivePaths21,
9462
+ resolveHaivePaths as resolveHaivePaths22,
8947
9463
  saveUsageIndex as saveUsageIndex3,
8948
- serializeMemory as serializeMemory18
9464
+ serializeMemory as serializeMemory19
8949
9465
  } from "@hiveai/core";
8950
9466
  function registerMemoryReject(memory2) {
8951
9467
  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) => {
8952
- const root = findProjectRoot24(opts.dir);
8953
- const paths = resolveHaivePaths21(root);
8954
- if (!existsSync44(paths.memoriesDir)) {
9468
+ const root = findProjectRoot25(opts.dir);
9469
+ const paths = resolveHaivePaths22(root);
9470
+ if (!existsSync46(paths.memoriesDir)) {
8955
9471
  ui.error(`No .ai/memories at ${root}.`);
8956
9472
  process.exitCode = 1;
8957
9473
  return;
8958
9474
  }
8959
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9475
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8960
9476
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8961
9477
  if (!loaded) {
8962
9478
  ui.error(`No memory with id "${id}".`);
8963
9479
  process.exitCode = 1;
8964
9480
  return;
8965
9481
  }
8966
- await writeFile20(
9482
+ await writeFile23(
8967
9483
  loaded.filePath,
8968
- serializeMemory18({
9484
+ serializeMemory19({
8969
9485
  frontmatter: {
8970
9486
  ...loaded.memory.frontmatter,
8971
9487
  status: "rejected",
@@ -8975,7 +9491,7 @@ function registerMemoryReject(memory2) {
8975
9491
  }),
8976
9492
  "utf8"
8977
9493
  );
8978
- const idx = await loadUsageIndex17(paths);
9494
+ const idx = await loadUsageIndex18(paths);
8979
9495
  recordRejection2(idx, id, opts.reason ?? null);
8980
9496
  await saveUsageIndex3(paths, idx);
8981
9497
  const u = idx.by_id[id];
@@ -8987,34 +9503,34 @@ function registerMemoryReject(memory2) {
8987
9503
  }
8988
9504
 
8989
9505
  // src/commands/memory-rm.ts
8990
- import { existsSync as existsSync45 } from "fs";
9506
+ import { existsSync as existsSync47 } from "fs";
8991
9507
  import { unlink as unlink3 } from "fs/promises";
8992
- import path27 from "path";
9508
+ import path29 from "path";
8993
9509
  import { createInterface as createInterface2 } from "readline/promises";
8994
9510
  import "commander";
8995
9511
  import {
8996
- findProjectRoot as findProjectRoot25,
8997
- loadUsageIndex as loadUsageIndex18,
8998
- resolveHaivePaths as resolveHaivePaths22,
9512
+ findProjectRoot as findProjectRoot26,
9513
+ loadUsageIndex as loadUsageIndex19,
9514
+ resolveHaivePaths as resolveHaivePaths23,
8999
9515
  saveUsageIndex as saveUsageIndex4
9000
9516
  } from "@hiveai/core";
9001
9517
  function registerMemoryRm(memory2) {
9002
9518
  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) => {
9003
- const root = findProjectRoot25(opts.dir);
9004
- const paths = resolveHaivePaths22(root);
9005
- if (!existsSync45(paths.memoriesDir)) {
9519
+ const root = findProjectRoot26(opts.dir);
9520
+ const paths = resolveHaivePaths23(root);
9521
+ if (!existsSync47(paths.memoriesDir)) {
9006
9522
  ui.error(`No .ai/memories at ${root}.`);
9007
9523
  process.exitCode = 1;
9008
9524
  return;
9009
9525
  }
9010
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9526
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9011
9527
  const found = all.find((m) => m.memory.frontmatter.id === id);
9012
9528
  if (!found) {
9013
9529
  ui.error(`No memory with id "${id}".`);
9014
9530
  process.exitCode = 1;
9015
9531
  return;
9016
9532
  }
9017
- const rel = path27.relative(root, found.filePath);
9533
+ const rel = path29.relative(root, found.filePath);
9018
9534
  if (!opts.yes) {
9019
9535
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
9020
9536
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -9027,7 +9543,7 @@ function registerMemoryRm(memory2) {
9027
9543
  await unlink3(found.filePath);
9028
9544
  ui.success(`Deleted ${rel}`);
9029
9545
  if (!opts.keepUsage) {
9030
- const idx = await loadUsageIndex18(paths);
9546
+ const idx = await loadUsageIndex19(paths);
9031
9547
  if (idx.by_id[id]) {
9032
9548
  delete idx.by_id[id];
9033
9549
  await saveUsageIndex4(paths, idx);
@@ -9038,27 +9554,27 @@ function registerMemoryRm(memory2) {
9038
9554
  }
9039
9555
 
9040
9556
  // src/commands/memory-show.ts
9041
- import { existsSync as existsSync46 } from "fs";
9042
- import { readFile as readFile11 } from "fs/promises";
9043
- import path28 from "path";
9557
+ import { existsSync as existsSync48 } from "fs";
9558
+ import { readFile as readFile12 } from "fs/promises";
9559
+ import path30 from "path";
9044
9560
  import "commander";
9045
9561
  import {
9046
9562
  deriveConfidence as deriveConfidence10,
9047
- findProjectRoot as findProjectRoot26,
9048
- getUsage as getUsage15,
9049
- loadUsageIndex as loadUsageIndex19,
9050
- resolveHaivePaths as resolveHaivePaths23
9563
+ findProjectRoot as findProjectRoot27,
9564
+ getUsage as getUsage16,
9565
+ loadUsageIndex as loadUsageIndex20,
9566
+ resolveHaivePaths as resolveHaivePaths24
9051
9567
  } from "@hiveai/core";
9052
9568
  function registerMemoryShow(memory2) {
9053
9569
  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) => {
9054
- const root = findProjectRoot26(opts.dir);
9055
- const paths = resolveHaivePaths23(root);
9056
- if (!existsSync46(paths.memoriesDir)) {
9570
+ const root = findProjectRoot27(opts.dir);
9571
+ const paths = resolveHaivePaths24(root);
9572
+ if (!existsSync48(paths.memoriesDir)) {
9057
9573
  ui.error(`No .ai/memories at ${root}.`);
9058
9574
  process.exitCode = 1;
9059
9575
  return;
9060
9576
  }
9061
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9577
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9062
9578
  const found = all.find((m) => m.memory.frontmatter.id === id);
9063
9579
  if (!found) {
9064
9580
  ui.error(`No memory with id "${id}".`);
@@ -9066,12 +9582,12 @@ function registerMemoryShow(memory2) {
9066
9582
  return;
9067
9583
  }
9068
9584
  if (opts.raw) {
9069
- console.log(await readFile11(found.filePath, "utf8"));
9585
+ console.log(await readFile12(found.filePath, "utf8"));
9070
9586
  return;
9071
9587
  }
9072
9588
  const fm = found.memory.frontmatter;
9073
- const usage = await loadUsageIndex19(paths);
9074
- const u = getUsage15(usage, fm.id);
9589
+ const usage = await loadUsageIndex20(paths);
9590
+ const u = getUsage16(usage, fm.id);
9075
9591
  const conf = deriveConfidence10(fm, u);
9076
9592
  console.log(ui.bold(fm.id));
9077
9593
  console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
@@ -9082,7 +9598,7 @@ function registerMemoryShow(memory2) {
9082
9598
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
9083
9599
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
9084
9600
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
9085
- console.log(`${ui.dim("file:")} ${path28.relative(root, found.filePath)}`);
9601
+ console.log(`${ui.dim("file:")} ${path30.relative(root, found.filePath)}`);
9086
9602
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
9087
9603
  console.log(ui.dim("anchor:"));
9088
9604
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -9097,38 +9613,38 @@ function registerMemoryShow(memory2) {
9097
9613
  }
9098
9614
 
9099
9615
  // src/commands/memory-stats.ts
9100
- import { existsSync as existsSync47 } from "fs";
9101
- import path29 from "path";
9616
+ import { existsSync as existsSync49 } from "fs";
9617
+ import path31 from "path";
9102
9618
  import "commander";
9103
9619
  import {
9104
9620
  deriveConfidence as deriveConfidence11,
9105
- findProjectRoot as findProjectRoot27,
9106
- getUsage as getUsage16,
9107
- loadUsageIndex as loadUsageIndex20,
9108
- resolveHaivePaths as resolveHaivePaths24
9621
+ findProjectRoot as findProjectRoot28,
9622
+ getUsage as getUsage17,
9623
+ loadUsageIndex as loadUsageIndex21,
9624
+ resolveHaivePaths as resolveHaivePaths25
9109
9625
  } from "@hiveai/core";
9110
9626
  function registerMemoryStats(memory2) {
9111
9627
  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) => {
9112
- const root = findProjectRoot27(opts.dir);
9113
- const paths = resolveHaivePaths24(root);
9114
- if (!existsSync47(paths.memoriesDir)) {
9628
+ const root = findProjectRoot28(opts.dir);
9629
+ const paths = resolveHaivePaths25(root);
9630
+ if (!existsSync49(paths.memoriesDir)) {
9115
9631
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9116
9632
  process.exitCode = 1;
9117
9633
  return;
9118
9634
  }
9119
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9120
- const usage = await loadUsageIndex20(paths);
9635
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9636
+ const usage = await loadUsageIndex21(paths);
9121
9637
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
9122
9638
  if (target.length === 0) {
9123
9639
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
9124
9640
  return;
9125
9641
  }
9126
9642
  target.sort(
9127
- (a, b) => getUsage16(usage, b.memory.frontmatter.id).read_count - getUsage16(usage, a.memory.frontmatter.id).read_count
9643
+ (a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
9128
9644
  );
9129
9645
  for (const { memory: mem, filePath } of target) {
9130
9646
  const fm = mem.frontmatter;
9131
- const u = getUsage16(usage, fm.id);
9647
+ const u = getUsage17(usage, fm.id);
9132
9648
  const conf = deriveConfidence11(fm, u);
9133
9649
  console.log(
9134
9650
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
@@ -9136,34 +9652,34 @@ function registerMemoryStats(memory2) {
9136
9652
  console.log(
9137
9653
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
9138
9654
  );
9139
- console.log(` ${ui.dim(path29.relative(root, filePath))}`);
9655
+ console.log(` ${ui.dim(path31.relative(root, filePath))}`);
9140
9656
  }
9141
9657
  });
9142
9658
  }
9143
9659
 
9144
9660
  // src/commands/memory-verify.ts
9145
- import { writeFile as writeFile21 } from "fs/promises";
9146
- import { existsSync as existsSync48 } from "fs";
9147
- import path30 from "path";
9661
+ import { writeFile as writeFile24 } from "fs/promises";
9662
+ import { existsSync as existsSync50 } from "fs";
9663
+ import path33 from "path";
9148
9664
  import "commander";
9149
9665
  import {
9150
- findProjectRoot as findProjectRoot28,
9151
- resolveHaivePaths as resolveHaivePaths25,
9152
- serializeMemory as serializeMemory19,
9666
+ findProjectRoot as findProjectRoot29,
9667
+ resolveHaivePaths as resolveHaivePaths26,
9668
+ serializeMemory as serializeMemory20,
9153
9669
  verifyAnchor as verifyAnchor3
9154
9670
  } from "@hiveai/core";
9155
9671
  function registerMemoryVerify(memory2) {
9156
9672
  memory2.command("verify").description(
9157
9673
  "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"
9158
9674
  ).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) => {
9159
- const root = findProjectRoot28(opts.dir);
9160
- const paths = resolveHaivePaths25(root);
9161
- if (!existsSync48(paths.memoriesDir)) {
9675
+ const root = findProjectRoot29(opts.dir);
9676
+ const paths = resolveHaivePaths26(root);
9677
+ if (!existsSync50(paths.memoriesDir)) {
9162
9678
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9163
9679
  process.exitCode = 1;
9164
9680
  return;
9165
9681
  }
9166
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9682
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9167
9683
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
9168
9684
  if (opts.id && targets.length === 0) {
9169
9685
  ui.error(`No memory with id "${opts.id}".`);
@@ -9181,7 +9697,7 @@ function registerMemoryVerify(memory2) {
9181
9697
  anchorlessIds.push(mem.frontmatter.id);
9182
9698
  continue;
9183
9699
  }
9184
- const rel = path30.relative(root, filePath);
9700
+ const rel = path33.relative(root, filePath);
9185
9701
  if (result.stale) {
9186
9702
  staleCount++;
9187
9703
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -9196,7 +9712,7 @@ function registerMemoryVerify(memory2) {
9196
9712
  }
9197
9713
  if (opts.update) {
9198
9714
  const next = applyVerification2(mem, result);
9199
- await writeFile21(filePath, serializeMemory19(next), "utf8");
9715
+ await writeFile24(filePath, serializeMemory20(next), "utf8");
9200
9716
  updated++;
9201
9717
  }
9202
9718
  }
@@ -9244,30 +9760,30 @@ function applyVerification2(mem, result) {
9244
9760
  }
9245
9761
 
9246
9762
  // src/commands/memory-import.ts
9247
- import { readFile as readFile12 } from "fs/promises";
9248
- import { existsSync as existsSync49 } from "fs";
9763
+ import { readFile as readFile13 } from "fs/promises";
9764
+ import { existsSync as existsSync51 } from "fs";
9249
9765
  import "commander";
9250
9766
  import {
9251
- findProjectRoot as findProjectRoot29,
9252
- resolveHaivePaths as resolveHaivePaths26
9767
+ findProjectRoot as findProjectRoot30,
9768
+ resolveHaivePaths as resolveHaivePaths27
9253
9769
  } from "@hiveai/core";
9254
9770
  function registerMemoryImport(memory2) {
9255
9771
  memory2.command("import").description(
9256
9772
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
9257
9773
  ).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) => {
9258
- const root = findProjectRoot29(opts.dir);
9259
- const paths = resolveHaivePaths26(root);
9260
- if (!existsSync49(paths.haiveDir)) {
9774
+ const root = findProjectRoot30(opts.dir);
9775
+ const paths = resolveHaivePaths27(root);
9776
+ if (!existsSync51(paths.haiveDir)) {
9261
9777
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9262
9778
  process.exitCode = 1;
9263
9779
  return;
9264
9780
  }
9265
- if (!existsSync49(opts.from)) {
9781
+ if (!existsSync51(opts.from)) {
9266
9782
  ui.error(`File not found: ${opts.from}`);
9267
9783
  process.exitCode = 1;
9268
9784
  return;
9269
9785
  }
9270
- const content = await readFile12(opts.from, "utf8");
9786
+ const content = await readFile13(opts.from, "utf8");
9271
9787
  const scope = opts.scope ?? "team";
9272
9788
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
9273
9789
  ui.info(`Content length: ${content.length} chars`);
@@ -9295,15 +9811,15 @@ function registerMemoryImport(memory2) {
9295
9811
  }
9296
9812
 
9297
9813
  // src/commands/memory-import-changelog.ts
9298
- import { existsSync as existsSync50 } from "fs";
9299
- import { readFile as readFile13, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
9300
- import path31 from "path";
9814
+ import { existsSync as existsSync53 } from "fs";
9815
+ import { readFile as readFile14, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
9816
+ import path34 from "path";
9301
9817
  import "commander";
9302
9818
  import {
9303
9819
  buildFrontmatter as buildFrontmatter9,
9304
- findProjectRoot as findProjectRoot30,
9305
- resolveHaivePaths as resolveHaivePaths27,
9306
- serializeMemory as serializeMemory20
9820
+ findProjectRoot as findProjectRoot31,
9821
+ resolveHaivePaths as resolveHaivePaths28,
9822
+ serializeMemory as serializeMemory21
9307
9823
  } from "@hiveai/core";
9308
9824
  function parseChangelog(content) {
9309
9825
  const entries = [];
@@ -9367,15 +9883,15 @@ function registerMemoryImportChangelog(memory2) {
9367
9883
  "--versions <csv>",
9368
9884
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
9369
9885
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
9370
- const root = findProjectRoot30(opts.dir);
9371
- const paths = resolveHaivePaths27(root);
9372
- const changelogPath = path31.resolve(root, opts.fromChangelog);
9373
- if (!existsSync50(changelogPath)) {
9886
+ const root = findProjectRoot31(opts.dir);
9887
+ const paths = resolveHaivePaths28(root);
9888
+ const changelogPath = path34.resolve(root, opts.fromChangelog);
9889
+ if (!existsSync53(changelogPath)) {
9374
9890
  ui.error(`CHANGELOG not found: ${changelogPath}`);
9375
9891
  process.exitCode = 1;
9376
9892
  return;
9377
9893
  }
9378
- const content = await readFile13(changelogPath, "utf8");
9894
+ const content = await readFile14(changelogPath, "utf8");
9379
9895
  let entries = parseChangelog(content);
9380
9896
  if (entries.length === 0) {
9381
9897
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -9390,9 +9906,9 @@ function registerMemoryImportChangelog(memory2) {
9390
9906
  entries = entries.filter((e) => requested.includes(e.version));
9391
9907
  }
9392
9908
  }
9393
- const pkgName = opts.package ?? path31.basename(path31.dirname(changelogPath));
9909
+ const pkgName = opts.package ?? path34.basename(path34.dirname(changelogPath));
9394
9910
  const scope = opts.scope ?? "team";
9395
- const teamDir = path31.join(paths.memoriesDir, scope);
9911
+ const teamDir = path34.join(paths.memoriesDir, scope);
9396
9912
  await mkdir14(teamDir, { recursive: true });
9397
9913
  let saved = 0;
9398
9914
  for (const entry of entries) {
@@ -9415,7 +9931,7 @@ function registerMemoryImportChangelog(memory2) {
9415
9931
  lines.push("");
9416
9932
  }
9417
9933
  lines.push(
9418
- `**Source:** \`${path31.relative(root, changelogPath)}\`
9934
+ `**Source:** \`${path34.relative(root, changelogPath)}\`
9419
9935
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
9420
9936
  );
9421
9937
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -9430,12 +9946,12 @@ function registerMemoryImportChangelog(memory2) {
9430
9946
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
9431
9947
  `v${entry.version}`
9432
9948
  ],
9433
- paths: [path31.relative(root, changelogPath)],
9949
+ paths: [path34.relative(root, changelogPath)],
9434
9950
  topic: `changelog-${pkgName}-${entry.version}`
9435
9951
  });
9436
- await writeFile23(
9437
- path31.join(teamDir, `${fm.id}.md`),
9438
- serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
9952
+ await writeFile25(
9953
+ path34.join(teamDir, `${fm.id}.md`),
9954
+ serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
9439
9955
  "utf8"
9440
9956
  );
9441
9957
  console.log(ui.green(` \u2713 ${fm.id}`));
@@ -9457,17 +9973,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
9457
9973
  }
9458
9974
 
9459
9975
  // src/commands/memory-digest.ts
9460
- import { existsSync as existsSync51 } from "fs";
9461
- import { writeFile as writeFile24 } from "fs/promises";
9462
- import path33 from "path";
9976
+ import { existsSync as existsSync54 } from "fs";
9977
+ import { writeFile as writeFile26 } from "fs/promises";
9978
+ import path35 from "path";
9463
9979
  import "commander";
9464
9980
  import {
9465
9981
  deriveConfidence as deriveConfidence12,
9466
- findProjectRoot as findProjectRoot31,
9467
- getUsage as getUsage17,
9468
- loadMemoriesFromDir as loadMemoriesFromDir26,
9469
- loadUsageIndex as loadUsageIndex21,
9470
- resolveHaivePaths as resolveHaivePaths28
9982
+ findProjectRoot as findProjectRoot32,
9983
+ getUsage as getUsage18,
9984
+ loadMemoriesFromDir as loadMemoriesFromDir27,
9985
+ loadUsageIndex as loadUsageIndex23,
9986
+ resolveHaivePaths as resolveHaivePaths29
9471
9987
  } from "@hiveai/core";
9472
9988
  var CONFIDENCE_EMOJI = {
9473
9989
  unverified: "\u2B1C",
@@ -9480,9 +9996,9 @@ function registerMemoryDigest(program2) {
9480
9996
  program2.command("digest").description(
9481
9997
  "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"
9482
9998
  ).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) => {
9483
- const root = findProjectRoot31(opts.dir);
9484
- const paths = resolveHaivePaths28(root);
9485
- if (!existsSync51(paths.memoriesDir)) {
9999
+ const root = findProjectRoot32(opts.dir);
10000
+ const paths = resolveHaivePaths29(root);
10001
+ if (!existsSync54(paths.memoriesDir)) {
9486
10002
  ui.error("No .ai/memories found. Run `haive init` first.");
9487
10003
  process.exitCode = 1;
9488
10004
  return;
@@ -9490,8 +10006,8 @@ function registerMemoryDigest(program2) {
9490
10006
  const days = Math.max(1, Number(opts.days ?? 7));
9491
10007
  const scopeFilter = opts.scope ?? "team";
9492
10008
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
9493
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9494
- const usage = await loadUsageIndex21(paths);
10009
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10010
+ const usage = await loadUsageIndex23(paths);
9495
10011
  const recent = all.filter(({ memory: mem }) => {
9496
10012
  const fm = mem.frontmatter;
9497
10013
  if (fm.type === "session_recap") return false;
@@ -9522,7 +10038,7 @@ function registerMemoryDigest(program2) {
9522
10038
  lines.push(``);
9523
10039
  for (const { memory: mem } of mems) {
9524
10040
  const fm = mem.frontmatter;
9525
- const u = getUsage17(usage, fm.id);
10041
+ const u = getUsage18(usage, fm.id);
9526
10042
  const confidence = deriveConfidence12(fm, u);
9527
10043
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
9528
10044
  const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
@@ -9554,8 +10070,8 @@ function registerMemoryDigest(program2) {
9554
10070
  );
9555
10071
  const digest = lines.join("\n");
9556
10072
  if (opts.out) {
9557
- const outPath = path33.resolve(process.cwd(), opts.out);
9558
- await writeFile24(outPath, digest, "utf8");
10073
+ const outPath = path35.resolve(process.cwd(), opts.out);
10074
+ await writeFile26(outPath, digest, "utf8");
9559
10075
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
9560
10076
  } else {
9561
10077
  console.log(digest);
@@ -9564,22 +10080,22 @@ function registerMemoryDigest(program2) {
9564
10080
  }
9565
10081
 
9566
10082
  // src/commands/session-end.ts
9567
- import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile14, rm as rm2 } from "fs/promises";
9568
- import { existsSync as existsSync53 } from "fs";
9569
- import path34 from "path";
10083
+ import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile15, rm as rm2 } from "fs/promises";
10084
+ import { existsSync as existsSync55 } from "fs";
10085
+ import path36 from "path";
9570
10086
  import "commander";
9571
10087
  import {
9572
10088
  buildFrontmatter as buildFrontmatter10,
9573
- findProjectRoot as findProjectRoot32,
9574
- loadMemoriesFromDir as loadMemoriesFromDir27,
10089
+ findProjectRoot as findProjectRoot33,
10090
+ loadMemoriesFromDir as loadMemoriesFromDir28,
9575
10091
  memoryFilePath as memoryFilePath9,
9576
- resolveHaivePaths as resolveHaivePaths29,
9577
- serializeMemory as serializeMemory21
10092
+ resolveHaivePaths as resolveHaivePaths30,
10093
+ serializeMemory as serializeMemory23
9578
10094
  } from "@hiveai/core";
9579
10095
  async function buildAutoRecap(paths) {
9580
- const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9581
- if (!existsSync53(obsFile)) return null;
9582
- const raw = await readFile14(obsFile, "utf8").catch(() => "");
10096
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10097
+ if (!existsSync55(obsFile)) return null;
10098
+ const raw = await readFile15(obsFile, "utf8").catch(() => "");
9583
10099
  if (!raw.trim()) return null;
9584
10100
  const lines = raw.split("\n").filter(Boolean);
9585
10101
  const obs = [];
@@ -9657,9 +10173,9 @@ function registerSessionEnd(session2) {
9657
10173
  --next "Add integration tests for webhook signature validation"
9658
10174
  `
9659
10175
  ).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) => {
9660
- const root = findProjectRoot32(opts.dir);
9661
- const paths = resolveHaivePaths29(root);
9662
- if (!existsSync53(paths.haiveDir)) {
10176
+ const root = findProjectRoot33(opts.dir);
10177
+ const paths = resolveHaivePaths30(root);
10178
+ if (!existsSync55(paths.haiveDir)) {
9663
10179
  if (opts.auto || opts.quiet) return;
9664
10180
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9665
10181
  process.exitCode = 1;
@@ -9691,19 +10207,19 @@ function registerSessionEnd(session2) {
9691
10207
  });
9692
10208
  const topic = recapTopic2(scope, opts.module);
9693
10209
  const filesTouched = parseCsv5(resolvedFiles);
9694
- const missingPaths = filesTouched.filter((p) => !existsSync53(path34.resolve(root, p)));
10210
+ const missingPaths = filesTouched.filter((p) => !existsSync55(path36.resolve(root, p)));
9695
10211
  if (missingPaths.length > 0 && !opts.quiet) {
9696
10212
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
9697
10213
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
9698
10214
  }
9699
10215
  const cleanupObservations = async () => {
9700
10216
  if (!opts.auto) return;
9701
- const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9702
- if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
10217
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10218
+ if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
9703
10219
  });
9704
10220
  };
9705
- if (existsSync53(paths.memoriesDir)) {
9706
- const existing = await loadMemoriesFromDir27(paths.memoriesDir);
10221
+ if (existsSync55(paths.memoriesDir)) {
10222
+ const existing = await loadMemoriesFromDir28(paths.memoriesDir);
9707
10223
  const topicMatch = existing.find(
9708
10224
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
9709
10225
  );
@@ -9719,11 +10235,11 @@ function registerSessionEnd(session2) {
9719
10235
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
9720
10236
  }
9721
10237
  };
9722
- await writeFile25(topicMatch.filePath, serializeMemory21({ frontmatter: newFrontmatter, body }), "utf8");
10238
+ await writeFile27(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
9723
10239
  await cleanupObservations();
9724
10240
  if (!opts.quiet) {
9725
10241
  ui.success(`Session recap updated (revision #${revisionCount})`);
9726
- ui.info(`id=${fm.id} file=${path34.relative(root, topicMatch.filePath)}`);
10242
+ ui.info(`id=${fm.id} file=${path36.relative(root, topicMatch.filePath)}`);
9727
10243
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
9728
10244
  }
9729
10245
  return;
@@ -9740,12 +10256,12 @@ function registerSessionEnd(session2) {
9740
10256
  status: "validated"
9741
10257
  });
9742
10258
  const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9743
- await mkdir15(path34.dirname(file), { recursive: true });
9744
- await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
10259
+ await mkdir15(path36.dirname(file), { recursive: true });
10260
+ await writeFile27(file, serializeMemory23({ frontmatter, body }), "utf8");
9745
10261
  await cleanupObservations();
9746
10262
  if (!opts.quiet) {
9747
10263
  ui.success(`Session recap created`);
9748
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path34.relative(root, file)}`);
10264
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path36.relative(root, file)}`);
9749
10265
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
9750
10266
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
9751
10267
  }
@@ -9757,15 +10273,15 @@ function parseCsv5(value) {
9757
10273
  }
9758
10274
 
9759
10275
  // src/commands/snapshot.ts
9760
- import { existsSync as existsSync54 } from "fs";
10276
+ import { existsSync as existsSync56 } from "fs";
9761
10277
  import { readdir as readdir4 } from "fs/promises";
9762
- import path35 from "path";
10278
+ import path37 from "path";
9763
10279
  import "commander";
9764
10280
  import {
9765
10281
  diffContract,
9766
- findProjectRoot as findProjectRoot33,
9767
- loadConfig as loadConfig5,
9768
- resolveHaivePaths as resolveHaivePaths30,
10282
+ findProjectRoot as findProjectRoot34,
10283
+ loadConfig as loadConfig7,
10284
+ resolveHaivePaths as resolveHaivePaths31,
9769
10285
  snapshotContract
9770
10286
  } from "@hiveai/core";
9771
10287
  function registerSnapshot(program2) {
@@ -9790,16 +10306,16 @@ function registerSnapshot(program2) {
9790
10306
  "--format <format>",
9791
10307
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
9792
10308
  ).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) => {
9793
- const root = findProjectRoot33(opts.dir);
9794
- const paths = resolveHaivePaths30(root);
9795
- if (!existsSync54(paths.haiveDir)) {
10309
+ const root = findProjectRoot34(opts.dir);
10310
+ const paths = resolveHaivePaths31(root);
10311
+ if (!existsSync56(paths.haiveDir)) {
9796
10312
  ui.error("No .ai/ found. Run `haive init` first.");
9797
10313
  process.exitCode = 1;
9798
10314
  return;
9799
10315
  }
9800
10316
  if (opts.list) {
9801
- const contractsDir = path35.join(paths.haiveDir, "contracts");
9802
- if (!existsSync54(contractsDir)) {
10317
+ const contractsDir = path37.join(paths.haiveDir, "contracts");
10318
+ if (!existsSync56(contractsDir)) {
9803
10319
  console.log(ui.dim("No contract snapshots found."));
9804
10320
  return;
9805
10321
  }
@@ -9819,7 +10335,7 @@ function registerSnapshot(program2) {
9819
10335
  }
9820
10336
  if (opts.diff) {
9821
10337
  if (!opts.name) {
9822
- const config2 = await loadConfig5(paths);
10338
+ const config2 = await loadConfig7(paths);
9823
10339
  const contracts = config2.contractFiles ?? [];
9824
10340
  if (contracts.length === 0) {
9825
10341
  ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
@@ -9831,7 +10347,7 @@ function registerSnapshot(program2) {
9831
10347
  }
9832
10348
  return;
9833
10349
  }
9834
- const config = await loadConfig5(paths);
10350
+ const config = await loadConfig7(paths);
9835
10351
  const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
9836
10352
  if (!configured && !opts.contract) {
9837
10353
  ui.error(
@@ -9854,7 +10370,7 @@ function registerSnapshot(program2) {
9854
10370
  return;
9855
10371
  }
9856
10372
  const contractPath = opts.contract;
9857
- const name = opts.name ?? path35.basename(contractPath, path35.extname(contractPath));
10373
+ const name = opts.name ?? path37.basename(contractPath, path37.extname(contractPath));
9858
10374
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
9859
10375
  const contract = { name, path: contractPath, format };
9860
10376
  try {
@@ -9909,8 +10425,8 @@ async function runDiff(root, haiveDir, contract) {
9909
10425
  }
9910
10426
  }
9911
10427
  function detectFormat(filePath) {
9912
- const ext = path35.extname(filePath).toLowerCase();
9913
- const base = path35.basename(filePath).toLowerCase();
10428
+ const ext = path37.extname(filePath).toLowerCase();
10429
+ const base = path37.basename(filePath).toLowerCase();
9914
10430
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
9915
10431
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
9916
10432
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -9923,18 +10439,18 @@ function detectFormat(filePath) {
9923
10439
  }
9924
10440
 
9925
10441
  // src/commands/hub.ts
9926
- import { existsSync as existsSync55 } from "fs";
9927
- import { mkdir as mkdir16, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9928
- import path36 from "path";
9929
- import { spawnSync as spawnSync4 } from "child_process";
10442
+ import { existsSync as existsSync57 } from "fs";
10443
+ import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile28, copyFile } from "fs/promises";
10444
+ import path38 from "path";
10445
+ import { spawnSync as spawnSync5 } from "child_process";
9930
10446
  import "commander";
9931
10447
  import {
9932
- findProjectRoot as findProjectRoot34,
9933
- loadConfig as loadConfig6,
9934
- loadMemoriesFromDir as loadMemoriesFromDir28,
9935
- resolveHaivePaths as resolveHaivePaths31,
9936
- saveConfig as saveConfig2,
9937
- serializeMemory as serializeMemory23
10448
+ findProjectRoot as findProjectRoot35,
10449
+ loadConfig as loadConfig8,
10450
+ loadMemoriesFromDir as loadMemoriesFromDir29,
10451
+ resolveHaivePaths as resolveHaivePaths32,
10452
+ saveConfig as saveConfig3,
10453
+ serializeMemory as serializeMemory24
9938
10454
  } from "@hiveai/core";
9939
10455
  function registerHub(program2) {
9940
10456
  const hub = program2.command("hub").description(
@@ -9944,21 +10460,21 @@ function registerHub(program2) {
9944
10460
  hub.command("init <hubPath>").description(
9945
10461
  "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"
9946
10462
  ).action(async (hubPath) => {
9947
- const absPath = path36.resolve(hubPath);
10463
+ const absPath = path38.resolve(hubPath);
9948
10464
  await mkdir16(absPath, { recursive: true });
9949
- const gitCheck = spawnSync4("git", ["rev-parse", "--git-dir"], { cwd: absPath });
10465
+ const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9950
10466
  if (gitCheck.status !== 0) {
9951
- const init = spawnSync4("git", ["init"], { cwd: absPath, encoding: "utf8" });
10467
+ const init = spawnSync5("git", ["init"], { cwd: absPath, encoding: "utf8" });
9952
10468
  if (init.status !== 0) {
9953
10469
  ui.error(`git init failed: ${init.stderr}`);
9954
10470
  process.exitCode = 1;
9955
10471
  return;
9956
10472
  }
9957
10473
  }
9958
- const sharedDir = path36.join(absPath, ".ai", "memories", "shared");
10474
+ const sharedDir = path38.join(absPath, ".ai", "memories", "shared");
9959
10475
  await mkdir16(sharedDir, { recursive: true });
9960
- await writeFile26(
9961
- path36.join(absPath, ".ai", "README.md"),
10476
+ await writeFile28(
10477
+ path38.join(absPath, ".ai", "README.md"),
9962
10478
  `# hAIve Team Knowledge Hub
9963
10479
 
9964
10480
  This repo is a shared knowledge hub for hAIve.
@@ -9979,13 +10495,13 @@ haive hub pull # import into a project
9979
10495
  `,
9980
10496
  "utf8"
9981
10497
  );
9982
- await writeFile26(
9983
- path36.join(absPath, ".gitignore"),
10498
+ await writeFile28(
10499
+ path38.join(absPath, ".gitignore"),
9984
10500
  ".ai/.cache/\n.ai/memories/personal/\n",
9985
10501
  "utf8"
9986
10502
  );
9987
- spawnSync4("git", ["add", "."], { cwd: absPath });
9988
- spawnSync4("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
10503
+ spawnSync5("git", ["add", "."], { cwd: absPath });
10504
+ spawnSync5("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
9989
10505
  cwd: absPath,
9990
10506
  encoding: "utf8"
9991
10507
  });
@@ -9995,7 +10511,7 @@ haive hub pull # import into a project
9995
10511
  `
9996
10512
  Next steps:
9997
10513
  1. Add hubPath to your project's .ai/haive.config.json:
9998
- { "hubPath": "${path36.relative(process.cwd(), absPath)}" }
10514
+ { "hubPath": "${path38.relative(process.cwd(), absPath)}" }
9999
10515
  2. Run \`haive hub push\` to publish your shared memories
10000
10516
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
10001
10517
  `
@@ -10014,9 +10530,9 @@ Next steps:
10014
10530
  haive hub push --commit --message "feat: add payment API contract memories"
10015
10531
  `
10016
10532
  ).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) => {
10017
- const root = findProjectRoot34(opts.dir);
10018
- const paths = resolveHaivePaths31(root);
10019
- const config = await loadConfig6(paths);
10533
+ const root = findProjectRoot35(opts.dir);
10534
+ const paths = resolveHaivePaths32(root);
10535
+ const config = await loadConfig8(paths);
10020
10536
  if (!config.hubPath) {
10021
10537
  ui.error(
10022
10538
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -10024,16 +10540,16 @@ Next steps:
10024
10540
  process.exitCode = 1;
10025
10541
  return;
10026
10542
  }
10027
- const hubRoot = path36.resolve(root, config.hubPath);
10028
- if (!existsSync55(hubRoot)) {
10543
+ const hubRoot = path38.resolve(root, config.hubPath);
10544
+ if (!existsSync57(hubRoot)) {
10029
10545
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
10030
10546
  process.exitCode = 1;
10031
10547
  return;
10032
10548
  }
10033
- const projectName = path36.basename(root);
10034
- const destDir = path36.join(hubRoot, ".ai", "memories", "shared", projectName);
10549
+ const projectName = path38.basename(root);
10550
+ const destDir = path38.join(hubRoot, ".ai", "memories", "shared", projectName);
10035
10551
  await mkdir16(destDir, { recursive: true });
10036
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10552
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
10037
10553
  const shared = all.filter(
10038
10554
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
10039
10555
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -10050,18 +10566,18 @@ Next steps:
10050
10566
  for (const { memory: memory2 } of shared) {
10051
10567
  const fm = memory2.frontmatter;
10052
10568
  const fileName = `${fm.id}.md`;
10053
- const destPath = path36.join(destDir, fileName);
10054
- await writeFile26(destPath, serializeMemory23(memory2), "utf8");
10569
+ const destPath = path38.join(destDir, fileName);
10570
+ await writeFile28(destPath, serializeMemory24(memory2), "utf8");
10055
10571
  pushed++;
10056
10572
  }
10057
10573
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
10058
10574
  console.log(ui.dim(` Location: ${destDir}`));
10059
10575
  if (opts.commit) {
10060
10576
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
10061
- spawnSync4("git", ["add", path36.join(".ai", "memories", "shared", projectName)], {
10577
+ spawnSync5("git", ["add", path38.join(".ai", "memories", "shared", projectName)], {
10062
10578
  cwd: hubRoot
10063
10579
  });
10064
- const commit = spawnSync4("git", ["commit", "-m", message], {
10580
+ const commit = spawnSync5("git", ["commit", "-m", message], {
10065
10581
  cwd: hubRoot,
10066
10582
  encoding: "utf8"
10067
10583
  });
@@ -10083,9 +10599,9 @@ Next steps:
10083
10599
  hub.command("pull").description(
10084
10600
  "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"
10085
10601
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10086
- const root = findProjectRoot34(opts.dir);
10087
- const paths = resolveHaivePaths31(root);
10088
- const config = await loadConfig6(paths);
10602
+ const root = findProjectRoot35(opts.dir);
10603
+ const paths = resolveHaivePaths32(root);
10604
+ const config = await loadConfig8(paths);
10089
10605
  if (!config.hubPath) {
10090
10606
  ui.error(
10091
10607
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -10093,13 +10609,13 @@ Next steps:
10093
10609
  process.exitCode = 1;
10094
10610
  return;
10095
10611
  }
10096
- const hubRoot = path36.resolve(root, config.hubPath);
10097
- const hubSharedDir = path36.join(hubRoot, ".ai", "memories", "shared");
10098
- if (!existsSync55(hubSharedDir)) {
10612
+ const hubRoot = path38.resolve(root, config.hubPath);
10613
+ const hubSharedDir = path38.join(hubRoot, ".ai", "memories", "shared");
10614
+ if (!existsSync57(hubSharedDir)) {
10099
10615
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
10100
10616
  return;
10101
10617
  }
10102
- const projectName = path36.basename(root);
10618
+ const projectName = path38.basename(root);
10103
10619
  const { readdir: readdir6 } = await import("fs/promises");
10104
10620
  const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
10105
10621
  if (projectDirs.length === 0) {
@@ -10109,17 +10625,17 @@ Next steps:
10109
10625
  let totalImported = 0;
10110
10626
  let totalUpdated = 0;
10111
10627
  for (const sourceName of projectDirs) {
10112
- const sourceDir = path36.join(hubSharedDir, sourceName);
10113
- const destDir = path36.join(paths.memoriesDir, "shared", sourceName);
10628
+ const sourceDir = path38.join(hubSharedDir, sourceName);
10629
+ const destDir = path38.join(paths.memoriesDir, "shared", sourceName);
10114
10630
  await mkdir16(destDir, { recursive: true });
10115
10631
  const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
10116
10632
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
10117
10633
  const existingInDest = await loadDir(destDir);
10118
10634
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
10119
10635
  for (const file of sourceFiles) {
10120
- const srcPath = path36.join(sourceDir, file);
10121
- const destPath = path36.join(destDir, file);
10122
- const fileContent = await readFile15(srcPath, "utf8");
10636
+ const srcPath = path38.join(sourceDir, file);
10637
+ const destPath = path38.join(destDir, file);
10638
+ const fileContent = await readFile16(srcPath, "utf8");
10123
10639
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
10124
10640
  if (!alreadyTagged) {
10125
10641
  await copyFile(srcPath, destPath);
@@ -10142,27 +10658,27 @@ Next steps:
10142
10658
  );
10143
10659
  });
10144
10660
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
10145
- const root = findProjectRoot34(opts.dir);
10146
- const paths = resolveHaivePaths31(root);
10147
- const config = await loadConfig6(paths);
10661
+ const root = findProjectRoot35(opts.dir);
10662
+ const paths = resolveHaivePaths32(root);
10663
+ const config = await loadConfig8(paths);
10148
10664
  console.log(ui.bold("Hub status"));
10149
10665
  console.log(
10150
10666
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
10151
10667
  );
10152
- const sharedDir = path36.join(paths.memoriesDir, "shared");
10153
- if (existsSync55(sharedDir)) {
10668
+ const sharedDir = path38.join(paths.memoriesDir, "shared");
10669
+ if (existsSync57(sharedDir)) {
10154
10670
  const { readdir: readdir6 } = await import("fs/promises");
10155
10671
  const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
10156
10672
  console.log(`
10157
10673
  Imported from ${sources.length} source(s):`);
10158
10674
  for (const src of sources) {
10159
- const files = (await readdir6(path36.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
10675
+ const files = (await readdir6(path38.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
10160
10676
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
10161
10677
  }
10162
10678
  } else {
10163
10679
  console.log(ui.dim(" No imported shared memories yet."));
10164
10680
  }
10165
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10681
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
10166
10682
  const outgoing = all.filter(
10167
10683
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
10168
10684
  );
@@ -10171,25 +10687,25 @@ Next steps:
10171
10687
  if (outgoing.length > 0) {
10172
10688
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
10173
10689
  }
10174
- void readFile15;
10175
- void writeFile26;
10176
- void saveConfig2;
10690
+ void readFile16;
10691
+ void writeFile28;
10692
+ void saveConfig3;
10177
10693
  });
10178
10694
  }
10179
10695
 
10180
10696
  // src/commands/stats.ts
10181
10697
  import "commander";
10182
- import { existsSync as existsSync56 } from "fs";
10183
- import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
10184
- import path37 from "path";
10698
+ import { existsSync as existsSync58 } from "fs";
10699
+ import { mkdir as mkdir17, writeFile as writeFile29 } from "fs/promises";
10700
+ import path39 from "path";
10185
10701
  import {
10186
10702
  aggregateUsage,
10187
- findProjectRoot as findProjectRoot35,
10188
- loadMemoriesFromDir as loadMemoriesFromDir29,
10189
- loadUsageIndex as loadUsageIndex23,
10703
+ findProjectRoot as findProjectRoot36,
10704
+ loadMemoriesFromDir as loadMemoriesFromDir30,
10705
+ loadUsageIndex as loadUsageIndex24,
10190
10706
  parseSince,
10191
10707
  readUsageEvents as readUsageEvents2,
10192
- resolveHaivePaths as resolveHaivePaths32,
10708
+ resolveHaivePaths as resolveHaivePaths33,
10193
10709
  usageLogSize
10194
10710
  } from "@hiveai/core";
10195
10711
  function registerStats(program2) {
@@ -10198,8 +10714,8 @@ function registerStats(program2) {
10198
10714
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
10199
10715
  void 0
10200
10716
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10201
- const root = findProjectRoot35(opts.dir);
10202
- const paths = resolveHaivePaths32(root);
10717
+ const root = findProjectRoot36(opts.dir);
10718
+ const paths = resolveHaivePaths33(root);
10203
10719
  if (opts.exportReport) {
10204
10720
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
10205
10721
  return;
@@ -10253,12 +10769,12 @@ function registerStats(program2) {
10253
10769
  });
10254
10770
  }
10255
10771
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10256
- const outAbs = path37.isAbsolute(outRelative) ? path37.resolve(outRelative) : path37.resolve(root, outRelative);
10772
+ const outAbs = path39.isAbsolute(outRelative) ? path39.resolve(outRelative) : path39.resolve(root, outRelative);
10257
10773
  const size = await usageLogSize(paths);
10258
10774
  let events = await readUsageEvents2(paths);
10259
10775
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
10260
- if (existsSync56(paths.memoriesDir)) {
10261
- const mems = await loadMemoriesFromDir29(paths.memoriesDir);
10776
+ if (existsSync58(paths.memoriesDir)) {
10777
+ const mems = await loadMemoriesFromDir30(paths.memoriesDir);
10262
10778
  for (const { memory: memory2 } of mems) {
10263
10779
  const fm = memory2.frontmatter;
10264
10780
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -10272,7 +10788,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10272
10788
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
10273
10789
  let memoryHitsLeader = null;
10274
10790
  try {
10275
- const usageIdx = await loadUsageIndex23(paths);
10791
+ const usageIdx = await loadUsageIndex24(paths);
10276
10792
  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);
10277
10793
  memoryHitsLeader = tops[0] ?? null;
10278
10794
  } catch {
@@ -10288,7 +10804,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10288
10804
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
10289
10805
  events = [];
10290
10806
  }
10291
- await mkdir17(path37.dirname(outAbs), { recursive: true });
10807
+ await mkdir17(path39.dirname(outAbs), { recursive: true });
10292
10808
  const payload = {
10293
10809
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
10294
10810
  project_root: root,
@@ -10300,11 +10816,11 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10300
10816
  top_memory_reads: memoryHitsLeader,
10301
10817
  roi_hints: roiHints
10302
10818
  };
10303
- await writeFile27(outAbs, JSON.stringify(payload, null, 2), "utf8");
10819
+ await writeFile29(outAbs, JSON.stringify(payload, null, 2), "utf8");
10304
10820
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
10305
10821
  }
10306
10822
  async function renderMemoryHits(paths, opts) {
10307
- const index = await loadUsageIndex23(paths);
10823
+ const index = await loadUsageIndex24(paths);
10308
10824
  const since = parseSince(opts.since ?? "30d");
10309
10825
  const sinceMs = since ? new Date(since).getTime() : null;
10310
10826
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -10352,13 +10868,13 @@ import { performance } from "perf_hooks";
10352
10868
  import "commander";
10353
10869
  import {
10354
10870
  estimateTokens as estimateTokens3,
10355
- findProjectRoot as findProjectRoot36,
10356
- resolveHaivePaths as resolveHaivePaths33
10871
+ findProjectRoot as findProjectRoot37,
10872
+ resolveHaivePaths as resolveHaivePaths34
10357
10873
  } from "@hiveai/core";
10358
10874
  function registerBench(program2) {
10359
10875
  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) => {
10360
- const root = findProjectRoot36(opts.dir);
10361
- const paths = resolveHaivePaths33(root);
10876
+ const root = findProjectRoot37(opts.dir);
10877
+ const paths = resolveHaivePaths34(root);
10362
10878
  const ctx = { paths };
10363
10879
  const task = opts.task ?? "audit dependencies for security risks";
10364
10880
  const scenarios = [
@@ -10477,11 +10993,11 @@ function summarize(name, t0, payload, notes) {
10477
10993
  }
10478
10994
 
10479
10995
  // src/commands/benchmark.ts
10480
- import { existsSync as existsSync57 } from "fs";
10481
- import { readdir as readdir5, readFile as readFile16, writeFile as writeFile28 } from "fs/promises";
10482
- import path38 from "path";
10996
+ import { existsSync as existsSync59 } from "fs";
10997
+ import { readdir as readdir5, readFile as readFile17, writeFile as writeFile30 } from "fs/promises";
10998
+ import path40 from "path";
10483
10999
  import "commander";
10484
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot37 } from "@hiveai/core";
11000
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
10485
11001
  function registerBenchmark(program2) {
10486
11002
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
10487
11003
  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) => {
@@ -10494,9 +11010,9 @@ function registerBenchmark(program2) {
10494
11010
  }
10495
11011
  const markdown = renderMarkdown(root, summary, rows);
10496
11012
  if (opts.out) {
10497
- const outFile = path38.isAbsolute(opts.out) ? opts.out : path38.join(root, opts.out);
10498
- await writeFile28(outFile, markdown, "utf8");
10499
- ui.success(`wrote ${path38.relative(process.cwd(), outFile)}`);
11013
+ const outFile = path40.isAbsolute(opts.out) ? opts.out : path40.join(root, opts.out);
11014
+ await writeFile30(outFile, markdown, "utf8");
11015
+ ui.success(`wrote ${path40.relative(process.cwd(), outFile)}`);
10500
11016
  return;
10501
11017
  }
10502
11018
  console.log(markdown);
@@ -10520,20 +11036,20 @@ function registerBenchmark(program2) {
10520
11036
  }
10521
11037
  function resolveBenchmarkRoot(dir) {
10522
11038
  const candidate = dir ?? "benchmarks/agent-benchmark";
10523
- if (path38.isAbsolute(candidate)) return candidate;
10524
- const projectRoot = findProjectRoot37(process.cwd());
10525
- return path38.join(projectRoot, candidate);
11039
+ if (path40.isAbsolute(candidate)) return candidate;
11040
+ const projectRoot = findProjectRoot38(process.cwd());
11041
+ return path40.join(projectRoot, candidate);
10526
11042
  }
10527
11043
  async function collectRows(root) {
10528
- if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
11044
+ if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
10529
11045
  const entries = await readdir5(root, { withFileTypes: true });
10530
11046
  const rows = [];
10531
11047
  for (const entry of entries) {
10532
11048
  if (!entry.isDirectory()) continue;
10533
- const fixtureDir = path38.join(root, entry.name);
10534
- const reportFile = path38.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
10535
- if (!existsSync57(reportFile)) continue;
10536
- const report = await readFile16(reportFile, "utf8");
11049
+ const fixtureDir = path40.join(root, entry.name);
11050
+ const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11051
+ if (!existsSync59(reportFile)) continue;
11052
+ const report = await readFile17(reportFile, "utf8");
10537
11053
  rows.push(parseAgentReport(entry.name, report));
10538
11054
  }
10539
11055
  return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
@@ -10622,20 +11138,21 @@ function escapeRegExp(value) {
10622
11138
  }
10623
11139
 
10624
11140
  // src/commands/memory-suggest.ts
10625
- import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
10626
- import { existsSync as existsSync58 } from "fs";
10627
- import path39 from "path";
11141
+ import { mkdir as mkdir18, writeFile as writeFile31 } from "fs/promises";
11142
+ import { existsSync as existsSync60 } from "fs";
11143
+ import path41 from "path";
10628
11144
  import "commander";
10629
11145
  import {
10630
11146
  aggregateUsage as aggregateUsage2,
10631
11147
  buildFrontmatter as buildFrontmatter11,
10632
- findProjectRoot as findProjectRoot38,
10633
- loadMemoriesFromDir as loadMemoriesFromDir30,
11148
+ findProjectRoot as findProjectRoot39,
11149
+ loadConfig as loadConfig9,
11150
+ loadMemoriesFromDir as loadMemoriesFromDir31,
10634
11151
  memoryFilePath as memoryFilePath10,
10635
11152
  parseSince as parseSince2,
10636
11153
  readUsageEvents as readUsageEvents3,
10637
- resolveHaivePaths as resolveHaivePaths34,
10638
- serializeMemory as serializeMemory24
11154
+ resolveHaivePaths as resolveHaivePaths35,
11155
+ serializeMemory as serializeMemory25
10639
11156
  } from "@hiveai/core";
10640
11157
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
10641
11158
  "mem_search",
@@ -10643,12 +11160,16 @@ var SEARCH_TOOLS = /* @__PURE__ */ new Set([
10643
11160
  "mem_relevant_to",
10644
11161
  "get_briefing"
10645
11162
  ]);
11163
+ var SYNTHETIC_QUERY_RE = /\b(auto-promote-marker|local enforcement smoke|cli-test-session)\b/i;
11164
+ function isSyntheticSuggestionQuery(query) {
11165
+ return SYNTHETIC_QUERY_RE.test(query);
11166
+ }
10646
11167
  function registerMemorySuggest(memory2) {
10647
11168
  memory2.command("suggest").description(
10648
- "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."
10649
- ).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) => {
10650
- const root = findProjectRoot38(opts.dir);
10651
- const paths = resolveHaivePaths34(root);
11169
+ "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
11170
+ ).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 saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11171
+ const root = findProjectRoot39(opts.dir);
11172
+ const paths = resolveHaivePaths35(root);
10652
11173
  const events = await readUsageEvents3(paths);
10653
11174
  if (events.length === 0) {
10654
11175
  if (opts.json) {
@@ -10667,6 +11188,7 @@ function registerMemorySuggest(memory2) {
10667
11188
  if (!SEARCH_TOOLS.has(e.tool)) continue;
10668
11189
  const key = (e.summary ?? "").toLowerCase().trim();
10669
11190
  if (!key) continue;
11191
+ if (isSyntheticSuggestionQuery(key)) continue;
10670
11192
  const prior = queries.get(key);
10671
11193
  if (prior) {
10672
11194
  prior.count++;
@@ -10685,16 +11207,18 @@ function registerMemorySuggest(memory2) {
10685
11207
  inferred_type: inferType(v.tools, query)
10686
11208
  })).sort((a, b) => b.count - a.count);
10687
11209
  if (opts.autoSave) {
11210
+ const config = await loadConfig9(paths);
10688
11211
  const topN = Math.max(1, parseInt(opts.topN ?? "3", 10));
10689
- const scope = opts.scope === "team" ? "team" : "personal";
11212
+ const scope = opts.scope === "personal" || opts.scope === "team" ? opts.scope : config.defaultScope ?? "personal";
11213
+ const status = config.defaultStatus === "validated" ? "validated" : "draft";
10690
11214
  const top = suggestions.slice(0, topN);
10691
11215
  if (top.length === 0) {
10692
- ui.warn(`No suggestions met --min=${minCount} \u2014 nothing to draft.`);
11216
+ ui.warn(`No suggestions met --min=${minCount} \u2014 nothing to save.`);
10693
11217
  return;
10694
11218
  }
10695
11219
  const created = [];
10696
11220
  const skipped = [];
10697
- const existing = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
11221
+ const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
10698
11222
  for (const s of top) {
10699
11223
  const slug = slugify(s.query);
10700
11224
  if (!slug) {
@@ -10712,25 +11236,25 @@ function registerMemorySuggest(memory2) {
10712
11236
  scope,
10713
11237
  tags: ["auto-suggested", ...s.tools],
10714
11238
  paths: [],
10715
- symbols: []
11239
+ symbols: [],
11240
+ status
10716
11241
  });
10717
- fm.status = "draft";
10718
- const body = renderTemplate(s);
11242
+ const body = renderTemplate(s, fm.id, status);
10719
11243
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
10720
- await mkdir18(path39.dirname(file), { recursive: true });
10721
- if (existsSync58(file)) {
10722
- skipped.push({ query: s.query, reason: `file already exists at ${path39.relative(root, file)}` });
11244
+ await mkdir18(path41.dirname(file), { recursive: true });
11245
+ if (existsSync60(file)) {
11246
+ skipped.push({ query: s.query, reason: `file already exists at ${path41.relative(root, file)}` });
10723
11247
  continue;
10724
11248
  }
10725
- await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
10726
- created.push({ id: fm.id, file: path39.relative(root, file), query: s.query });
11249
+ await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
11250
+ created.push({ id: fm.id, file: path41.relative(root, file), query: s.query });
10727
11251
  }
10728
11252
  if (opts.json) {
10729
11253
  console.log(JSON.stringify({ created, skipped }, null, 2));
10730
11254
  return;
10731
11255
  }
10732
11256
  for (const c of created) {
10733
- ui.success(`Drafted ${c.id} \u2192 ${c.file}`);
11257
+ ui.success(`${status === "validated" ? "Saved" : "Drafted"} ${c.id} \u2192 ${c.file}`);
10734
11258
  console.log(` ${ui.dim("from query:")} ${truncate2(c.query, 60)}`);
10735
11259
  }
10736
11260
  for (const s of skipped) {
@@ -10738,7 +11262,11 @@ function registerMemorySuggest(memory2) {
10738
11262
  }
10739
11263
  if (created.length > 0) {
10740
11264
  console.log();
10741
- ui.info("Drafts are status=draft \u2014 edit them, then `haive memory promote` to validate.");
11265
+ if (status === "validated") {
11266
+ ui.info("Autopilot defaults applied: suggestions are status=validated and active.");
11267
+ } else {
11268
+ ui.info("Drafts are status=draft \u2014 edit them, then run `haive memory promote <id>`.");
11269
+ }
10742
11270
  }
10743
11271
  return;
10744
11272
  }
@@ -10763,7 +11291,7 @@ function registerMemorySuggest(memory2) {
10763
11291
  console.log(` ${ui.dim("\u2192")} ${s.reason}`);
10764
11292
  }
10765
11293
  console.log();
10766
- ui.info("Run with --auto-save to draft the top-3 as draft memories.");
11294
+ ui.info("Run with --auto-save to save the top-3 using the project defaults.");
10767
11295
  });
10768
11296
  }
10769
11297
  function chooseReason(tools, count) {
@@ -10784,7 +11312,8 @@ function inferType(tools, query) {
10784
11312
  }
10785
11313
  return "convention";
10786
11314
  }
10787
- function renderTemplate(s) {
11315
+ function renderTemplate(s, id, status) {
11316
+ const nextStep = status === "validated" ? `This record is already active because project autopilot defaults set status=validated. Replace the template body with the actual answer when known.` : `Then run \`haive memory promote ${id}\` to move it into team review.`;
10788
11317
  return [
10789
11318
  `# Auto-drafted from recurring searches`,
10790
11319
  ``,
@@ -10804,7 +11333,7 @@ function renderTemplate(s) {
10804
11333
  `- **Why** \u2014 the rationale or root cause`,
10805
11334
  `- **How to apply** \u2014 what an agent should do when this comes up again`,
10806
11335
  ``,
10807
- `Then run \`haive memory promote ${truncate2(s.query, 30)}\` to mark it validated.`
11336
+ nextStep
10808
11337
  ].join("\n");
10809
11338
  }
10810
11339
  function slugify(s) {
@@ -10816,26 +11345,26 @@ function truncate2(text, max) {
10816
11345
  }
10817
11346
 
10818
11347
  // src/commands/memory-archive.ts
10819
- import { existsSync as existsSync59 } from "fs";
10820
- import { writeFile as writeFile30 } from "fs/promises";
10821
- import path40 from "path";
11348
+ import { existsSync as existsSync61 } from "fs";
11349
+ import { writeFile as writeFile33 } from "fs/promises";
11350
+ import path43 from "path";
10822
11351
  import "commander";
10823
11352
  import {
10824
- findProjectRoot as findProjectRoot39,
10825
- getUsage as getUsage18,
10826
- loadMemoriesFromDir as loadMemoriesFromDir31,
10827
- loadUsageIndex as loadUsageIndex24,
10828
- resolveHaivePaths as resolveHaivePaths35,
10829
- serializeMemory as serializeMemory25
11353
+ findProjectRoot as findProjectRoot40,
11354
+ getUsage as getUsage19,
11355
+ loadMemoriesFromDir as loadMemoriesFromDir32,
11356
+ loadUsageIndex as loadUsageIndex25,
11357
+ resolveHaivePaths as resolveHaivePaths36,
11358
+ serializeMemory as serializeMemory26
10830
11359
  } from "@hiveai/core";
10831
11360
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
10832
11361
  function registerMemoryArchive(memory2) {
10833
11362
  memory2.command("archive").description(
10834
11363
  "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."
10835
11364
  ).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) => {
10836
- const root = findProjectRoot39(opts.dir);
10837
- const paths = resolveHaivePaths35(root);
10838
- if (!existsSync59(paths.memoriesDir)) {
11365
+ const root = findProjectRoot40(opts.dir);
11366
+ const paths = resolveHaivePaths36(root);
11367
+ if (!existsSync61(paths.memoriesDir)) {
10839
11368
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10840
11369
  process.exitCode = 1;
10841
11370
  return;
@@ -10847,8 +11376,8 @@ function registerMemoryArchive(memory2) {
10847
11376
  return;
10848
11377
  }
10849
11378
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
10850
- const all = await loadMemoriesFromDir31(paths.memoriesDir);
10851
- const usage = await loadUsageIndex24(paths);
11379
+ const all = await loadMemoriesFromDir32(paths.memoriesDir);
11380
+ const usage = await loadUsageIndex25(paths);
10852
11381
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
10853
11382
  const candidates = [];
10854
11383
  for (const { memory: mem, filePath } of all) {
@@ -10856,10 +11385,10 @@ function registerMemoryArchive(memory2) {
10856
11385
  if (typeFilter && fm.type !== typeFilter) continue;
10857
11386
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
10858
11387
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
10859
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync59(path40.join(paths.root, p)));
11388
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path43.join(paths.root, p)));
10860
11389
  const isAnchorless = !hasAnyAnchor;
10861
11390
  if (!isAnchorless && !allPathsGone) continue;
10862
- const u = getUsage18(usage, fm.id);
11391
+ const u = getUsage19(usage, fm.id);
10863
11392
  const lastSeen = u.last_read_at ?? fm.created_at;
10864
11393
  if (Date.parse(lastSeen) >= cutoff) continue;
10865
11394
  candidates.push({
@@ -10904,7 +11433,7 @@ function registerMemoryArchive(memory2) {
10904
11433
  if (!found) continue;
10905
11434
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
10906
11435
  try {
10907
- await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
11436
+ await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
10908
11437
  archived++;
10909
11438
  } catch (err) {
10910
11439
  if (!opts.json) {
@@ -10930,31 +11459,33 @@ function parseDays(input) {
10930
11459
  }
10931
11460
 
10932
11461
  // src/commands/doctor.ts
10933
- import { existsSync as existsSync60 } from "fs";
10934
- import { readFile as readFile17, stat } from "fs/promises";
10935
- import path41 from "path";
11462
+ import { existsSync as existsSync63 } from "fs";
11463
+ import { readFile as readFile18, stat } from "fs/promises";
11464
+ import path44 from "path";
10936
11465
  import { execFileSync, execSync as execSync3 } from "child_process";
10937
11466
  import "commander";
10938
11467
  import {
10939
11468
  codeMapPath as codeMapPath2,
10940
- findProjectRoot as findProjectRoot40,
10941
- getUsage as getUsage19,
10942
- loadCodeMap as loadCodeMap5,
10943
- loadConfig as loadConfig7,
10944
- loadMemoriesFromDir as loadMemoriesFromDir32,
10945
- loadUsageIndex as loadUsageIndex25,
11469
+ findProjectRoot as findProjectRoot41,
11470
+ getUsage as getUsage20,
11471
+ loadCodeMap as loadCodeMap7,
11472
+ loadConfig as loadConfig10,
11473
+ loadMemoriesFromDir as loadMemoriesFromDir33,
11474
+ loadUsageIndex as loadUsageIndex26,
10946
11475
  readUsageEvents as readUsageEvents4,
10947
- resolveHaivePaths as resolveHaivePaths36
11476
+ resolveHaivePaths as resolveHaivePaths37
10948
11477
  } from "@hiveai/core";
10949
11478
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
10950
11479
  function registerDoctor(program2) {
10951
11480
  program2.command("doctor").description(
10952
11481
  "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."
10953
11482
  ).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) => {
10954
- const root = findProjectRoot40(opts.dir);
10955
- const paths = resolveHaivePaths36(root);
11483
+ const root = findProjectRoot41(opts.dir);
11484
+ const paths = resolveHaivePaths37(root);
10956
11485
  const findings = [];
10957
- if (!existsSync60(paths.haiveDir)) {
11486
+ const repairs = [];
11487
+ const config = await loadConfig10(paths);
11488
+ if (!existsSync63(paths.haiveDir)) {
10958
11489
  findings.push({
10959
11490
  severity: "error",
10960
11491
  code: "not-initialized",
@@ -10963,7 +11494,18 @@ function registerDoctor(program2) {
10963
11494
  });
10964
11495
  return emit(findings, opts);
10965
11496
  }
10966
- if (!existsSync60(paths.projectContext)) {
11497
+ if (opts.fix && !opts.dryRun) {
11498
+ repairs.push(
11499
+ ...await applyAutopilotRepairs(root, paths, {
11500
+ applyConfig: true,
11501
+ applyContext: true,
11502
+ applyCorpus: true,
11503
+ applyCodeMap: true,
11504
+ applyCodeSearch: true
11505
+ })
11506
+ );
11507
+ }
11508
+ if (!existsSync63(paths.projectContext)) {
10967
11509
  findings.push({
10968
11510
  severity: "warn",
10969
11511
  code: "no-project-context",
@@ -10971,8 +11513,8 @@ function registerDoctor(program2) {
10971
11513
  fix: "haive init"
10972
11514
  });
10973
11515
  } else {
10974
- const { readFile: readFile19 } = await import("fs/promises");
10975
- const content = await readFile19(paths.projectContext, "utf8");
11516
+ const { readFile: readFile20 } = await import("fs/promises");
11517
+ const content = await readFile20(paths.projectContext, "utf8");
10976
11518
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10977
11519
  if (isTemplate) {
10978
11520
  findings.push({
@@ -10982,8 +11524,17 @@ function registerDoctor(program2) {
10982
11524
  fix: "Invoke the bootstrap_project MCP prompt from your AI client."
10983
11525
  });
10984
11526
  }
11527
+ const versionStatus = await projectContextVersionStatus(root, paths);
11528
+ if (versionStatus.mismatch) {
11529
+ findings.push({
11530
+ severity: "warn",
11531
+ code: "project-context-version-mismatch",
11532
+ message: `.ai/project-context.md version metadata (${versionStatus.currentVersion ?? "missing"}) does not match package.json (${versionStatus.expectedVersion}).`,
11533
+ fix: "haive doctor --fix"
11534
+ });
11535
+ }
10985
11536
  }
10986
- const memories = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
11537
+ const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10987
11538
  const now = Date.now();
10988
11539
  if (memories.length === 0) {
10989
11540
  findings.push({
@@ -10992,7 +11543,7 @@ function registerDoctor(program2) {
10992
11543
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
10993
11544
  });
10994
11545
  } else {
10995
- const usage = await loadUsageIndex25(paths);
11546
+ const usage = await loadUsageIndex26(paths);
10996
11547
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
10997
11548
  if (stale.length > 0) {
10998
11549
  findings.push({
@@ -11024,7 +11575,7 @@ function registerDoctor(program2) {
11024
11575
  }
11025
11576
  const decayCandidates = memories.filter((m) => {
11026
11577
  if (m.memory.frontmatter.status !== "validated") return false;
11027
- const u = getUsage19(usage, m.memory.frontmatter.id);
11578
+ const u = getUsage20(usage, m.memory.frontmatter.id);
11028
11579
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
11029
11580
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
11030
11581
  });
@@ -11037,7 +11588,19 @@ function registerDoctor(program2) {
11037
11588
  });
11038
11589
  }
11039
11590
  }
11040
- const codeMap = await loadCodeMap5(paths);
11591
+ const lintReport = await lintMemoriesAsync(root);
11592
+ if (lintReport.findings.length > 0) {
11593
+ const warnCount = lintReport.findings.filter((finding) => finding.severity === "warn").length;
11594
+ const errorCount = lintReport.findings.filter((finding) => finding.severity === "error").length;
11595
+ const severity = errorCount > 0 ? "error" : warnCount > 0 ? "warn" : "info";
11596
+ findings.push({
11597
+ severity,
11598
+ code: "memory-lint-findings",
11599
+ message: `memory lint reports ${lintReport.findings.length} finding${lintReport.findings.length === 1 ? "" : "s"} (${errorCount} error, ${warnCount} warn, ${lintReport.findings.length - errorCount - warnCount} info).`,
11600
+ fix: "haive memory lint --fix --apply"
11601
+ });
11602
+ }
11603
+ const codeMap = await loadCodeMap7(paths);
11041
11604
  if (!codeMap) {
11042
11605
  findings.push({
11043
11606
  severity: "warn",
@@ -11058,6 +11621,7 @@ function registerDoctor(program2) {
11058
11621
  });
11059
11622
  }
11060
11623
  }
11624
+ findings.push(...await collectSemanticIndexFindings(paths, config, memories.length, codeMap));
11061
11625
  const events = await readUsageEvents4(paths);
11062
11626
  if (events.length === 0) {
11063
11627
  findings.push({
@@ -11071,6 +11635,7 @@ function registerDoctor(program2) {
11071
11635
  if (!isSearchTool(e.tool)) continue;
11072
11636
  const key = (e.summary ?? "").toLowerCase().trim();
11073
11637
  if (!key) continue;
11638
+ if (isSyntheticSuggestionQuery(key)) continue;
11074
11639
  queryRepeats.set(key, (queryRepeats.get(key) ?? 0) + 1);
11075
11640
  }
11076
11641
  const repeated = [...queryRepeats.entries()].filter(([, n]) => n >= 3);
@@ -11092,14 +11657,13 @@ function registerDoctor(program2) {
11092
11657
  });
11093
11658
  }
11094
11659
  }
11095
- const config = await loadConfig7(paths);
11096
11660
  if (config.enforcement?.requireBriefingFirst) {
11097
- const claudeSettings = path41.join(root, ".claude", "settings.local.json");
11661
+ const claudeSettings = path44.join(root, ".claude", "settings.local.json");
11098
11662
  let hasClaudeEnforcement = false;
11099
- if (existsSync60(claudeSettings)) {
11663
+ if (existsSync63(claudeSettings)) {
11100
11664
  try {
11101
- const { readFile: readFile19 } = await import("fs/promises");
11102
- const raw = await readFile19(claudeSettings, "utf8");
11665
+ const { readFile: readFile20 } = await import("fs/promises");
11666
+ const raw = await readFile20(claudeSettings, "utf8");
11103
11667
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
11104
11668
  } catch {
11105
11669
  hasClaudeEnforcement = false;
@@ -11122,14 +11686,14 @@ function registerDoctor(program2) {
11122
11686
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
11123
11687
  });
11124
11688
  }
11125
- findings.push(...await collectInstallFindings(root, "0.9.17"));
11689
+ findings.push(...await collectInstallFindings(root, "0.9.19"));
11126
11690
  try {
11127
11691
  const legacyRaw = execSync3("haive-mcp --version", {
11128
11692
  encoding: "utf8",
11129
11693
  timeout: 3e3,
11130
11694
  stdio: ["ignore", "pipe", "ignore"]
11131
11695
  }).trim();
11132
- const cliVersion = "0.9.17";
11696
+ const cliVersion = "0.9.19";
11133
11697
  if (legacyRaw && legacyRaw !== cliVersion) {
11134
11698
  findings.push({
11135
11699
  severity: "warn",
@@ -11142,10 +11706,18 @@ npm uninstall -g @hiveai/mcp`
11142
11706
  }
11143
11707
  } catch {
11144
11708
  }
11145
- emit(findings, opts);
11709
+ if (repairs.length > 0) {
11710
+ findings.push({
11711
+ severity: "info",
11712
+ code: "autopilot-repairs-applied",
11713
+ message: repairs.map((repair) => repair.message).join(" "),
11714
+ section: "Next actions"
11715
+ });
11716
+ }
11717
+ emit(findings, opts, repairs);
11146
11718
  });
11147
11719
  }
11148
- function emit(findings, opts) {
11720
+ function emit(findings, opts, repairs = []) {
11149
11721
  const classified = findings.map((finding) => ({
11150
11722
  ...finding,
11151
11723
  section: finding.section ?? sectionForFinding(finding)
@@ -11157,12 +11729,13 @@ function emit(findings, opts) {
11157
11729
  findings: classified,
11158
11730
  sections: groupBySection(classified),
11159
11731
  next_actions: nextActions(classified),
11160
- fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "suggest" : "off"
11732
+ repairs,
11733
+ fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "apply" : "off"
11161
11734
  }, null, 2));
11162
11735
  return;
11163
11736
  }
11164
11737
  if (classified.length === 0) {
11165
- ui.success("hAIve doctor \u2014 no issues found.");
11738
+ ui.success(repairs.length > 0 ? "hAIve doctor \u2014 autopilot repairs applied." : "hAIve doctor \u2014 no issues found.");
11166
11739
  return;
11167
11740
  }
11168
11741
  console.log(ui.bold(`hAIve doctor \u2014 ${classified.length} finding${classified.length === 1 ? "" : "s"}`));
@@ -11198,6 +11771,11 @@ function emit(findings, opts) {
11198
11771
  }
11199
11772
  console.log();
11200
11773
  }
11774
+ if (repairs.length > 0) {
11775
+ console.log(ui.bold("Autopilot repairs applied"));
11776
+ for (const repair of repairs) console.log(` ${ui.dim("\u2713")} ${repair.message}`);
11777
+ console.log();
11778
+ }
11201
11779
  const actions = nextActions(classified);
11202
11780
  if (actions.length > 0) {
11203
11781
  console.log(ui.bold("Next actions"));
@@ -11245,6 +11823,56 @@ function groupBySection(findings) {
11245
11823
  function nextActions(findings) {
11246
11824
  return [...new Set(findings.flatMap((finding) => finding.fix ? finding.fix.split("\n") : []))].filter(Boolean);
11247
11825
  }
11826
+ async function collectSemanticIndexFindings(paths, config, memoryCount, codeMap) {
11827
+ const findings = [];
11828
+ const autoWantsCodeSearch = Boolean(config.autopilot || config.autoRepair?.codeSearch);
11829
+ let mod;
11830
+ try {
11831
+ mod = await import("@hiveai/embeddings");
11832
+ } catch {
11833
+ findings.push({
11834
+ severity: autoWantsCodeSearch ? "warn" : "info",
11835
+ code: "embeddings-unavailable",
11836
+ message: "@hiveai/embeddings is not available, so get_briefing falls back to lexical ranking and code_search cannot run.",
11837
+ fix: "npm install -g @hiveai/cli@latest\nhaive embeddings status",
11838
+ section: "Index health"
11839
+ });
11840
+ return findings;
11841
+ }
11842
+ if (memoryCount > 0) {
11843
+ const stat2 = await mod.indexStat(paths).catch(() => ({ exists: false, count: 0 }));
11844
+ if (!stat2.exists || stat2.count === 0) {
11845
+ findings.push({
11846
+ severity: "warn",
11847
+ code: "semantic-memory-index-missing",
11848
+ message: "Memory embeddings index is missing or empty; get_briefing will report literal_fallback instead of semantic ranking.",
11849
+ fix: "haive embeddings index",
11850
+ section: "Index health"
11851
+ });
11852
+ }
11853
+ }
11854
+ if (autoWantsCodeSearch || codeMap) {
11855
+ const codeIndex = await mod.loadCodeIndex(paths).catch(() => null);
11856
+ if (!codeIndex || codeIndex.entries.length === 0) {
11857
+ findings.push({
11858
+ severity: autoWantsCodeSearch ? "warn" : "info",
11859
+ code: "code-search-index-missing",
11860
+ message: "Code-search embeddings index is missing or empty; MCP code_search is unavailable until it is built.",
11861
+ fix: "haive index code-search",
11862
+ section: "Index health"
11863
+ });
11864
+ } else if (codeMap && codeIndex.source_generated_at !== codeMap.generated_at) {
11865
+ findings.push({
11866
+ severity: "info",
11867
+ code: "code-search-index-outdated",
11868
+ message: "Code-search embeddings index was built from an older code-map; semantic code search may miss recent symbols.",
11869
+ fix: "haive index code-search",
11870
+ section: "Index health"
11871
+ });
11872
+ }
11873
+ }
11874
+ return findings;
11875
+ }
11248
11876
  function isSearchTool(name) {
11249
11877
  return ["mem_search", "code_search", "mem_relevant_to", "get_briefing"].includes(name);
11250
11878
  }
@@ -11289,9 +11917,9 @@ which -a haive`
11289
11917
  ".vscode/mcp.json"
11290
11918
  ];
11291
11919
  for (const rel of integrationFiles) {
11292
- const file = path41.join(root, rel);
11293
- if (!existsSync60(file)) continue;
11294
- const text = await readFile17(file, "utf8").catch(() => "");
11920
+ const file = path44.join(root, rel);
11921
+ if (!existsSync63(file)) continue;
11922
+ const text = await readFile18(file, "utf8").catch(() => "");
11295
11923
  for (const bin of extractAbsoluteHaiveBins(text)) {
11296
11924
  const version = versionForBinary(bin);
11297
11925
  if (!version) {
@@ -11347,22 +11975,22 @@ function extractAbsoluteHaiveBins(text) {
11347
11975
  }
11348
11976
 
11349
11977
  // src/commands/playback.ts
11350
- import { existsSync as existsSync61 } from "fs";
11978
+ import { existsSync as existsSync64 } from "fs";
11351
11979
  import "commander";
11352
11980
  import {
11353
- findProjectRoot as findProjectRoot41,
11354
- loadMemoriesFromDir as loadMemoriesFromDir33,
11981
+ findProjectRoot as findProjectRoot42,
11982
+ loadMemoriesFromDir as loadMemoriesFromDir34,
11355
11983
  parseSince as parseSince3,
11356
11984
  readUsageEvents as readUsageEvents5,
11357
- resolveHaivePaths as resolveHaivePaths37
11985
+ resolveHaivePaths as resolveHaivePaths38
11358
11986
  } from "@hiveai/core";
11359
11987
  var MS_PER_MINUTE = 6e4;
11360
11988
  function registerPlayback(program2) {
11361
11989
  program2.command("playback").description(
11362
11990
  "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?'"
11363
11991
  ).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) => {
11364
- const root = findProjectRoot41(opts.dir);
11365
- const paths = resolveHaivePaths37(root);
11992
+ const root = findProjectRoot42(opts.dir);
11993
+ const paths = resolveHaivePaths38(root);
11366
11994
  const events = await readUsageEvents5(paths);
11367
11995
  if (events.length === 0) {
11368
11996
  if (opts.json) {
@@ -11377,7 +12005,7 @@ function registerPlayback(program2) {
11377
12005
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
11378
12006
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
11379
12007
  const sessions = bucketSessions(filtered, gapMs);
11380
- const all = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
12008
+ const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
11381
12009
  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);
11382
12010
  const enriched = sessions.map((s, i) => {
11383
12011
  const startMs = Date.parse(s.start);
@@ -11467,8 +12095,8 @@ function truncate3(text, max) {
11467
12095
  import { spawn as spawn4 } from "child_process";
11468
12096
  import "commander";
11469
12097
  import {
11470
- findProjectRoot as findProjectRoot42,
11471
- resolveHaivePaths as resolveHaivePaths38
12098
+ findProjectRoot as findProjectRoot43,
12099
+ resolveHaivePaths as resolveHaivePaths39
11472
12100
  } from "@hiveai/core";
11473
12101
  function registerPrecommit(program2) {
11474
12102
  program2.command("precommit").description(
@@ -11478,8 +12106,8 @@ function registerPrecommit(program2) {
11478
12106
  "'any' | 'high-confidence' (default) | 'never' (report only)",
11479
12107
  "high-confidence"
11480
12108
  ).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) => {
11481
- const root = findProjectRoot42(opts.dir);
11482
- const paths = resolveHaivePaths38(root);
12109
+ const root = findProjectRoot43(opts.dir);
12110
+ const paths = resolveHaivePaths39(root);
11483
12111
  const ctx = { paths };
11484
12112
  let diff = "";
11485
12113
  let touchedPaths = opts.paths ?? [];
@@ -11513,401 +12141,142 @@ function registerPrecommit(program2) {
11513
12141
  ` anti-patterns: ${result.summary.anti_patterns} blocking: ${result.summary.blocking_warnings ?? result.summary.anti_patterns} review: ${result.summary.review_warnings ?? 0} info: ${result.summary.info_warnings ?? 0} relevant memories: ${result.summary.relevant_memories} stale anchors: ${result.summary.stale_anchors}`
11514
12142
  )
11515
12143
  );
11516
- console.log();
11517
- const blocking = result.warnings.filter((w) => w.level === "blocking");
11518
- const review = result.warnings.filter((w) => w.level === "review");
11519
- const info = result.warnings.filter((w) => w.level === "info");
11520
- printWarnings("Blocking anti-patterns", blocking, "error");
11521
- printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
11522
- if (info.length > 0) {
11523
- console.log(
11524
- ui.dim(
11525
- `${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
11526
- )
11527
- );
11528
- console.log();
11529
- }
11530
- if (result.relevant_memories.length > 0) {
11531
- console.log(ui.bold("\u{1F4CC} Relevant conventions/decisions:"));
11532
- for (const m of result.relevant_memories) {
11533
- console.log(` \u2022 ${m.id} ${ui.dim(`(${m.type}, ${m.confidence})`)}`);
11534
- }
11535
- console.log();
11536
- }
11537
- if (result.stale_anchors.length > 0) {
11538
- console.log(ui.bold("\u{1F552} Stale anchored memories:"));
11539
- for (const s of result.stale_anchors) {
11540
- console.log(` \u2022 ${s.id}`);
11541
- if (s.body_preview) console.log(` ${ui.dim(s.body_preview)}`);
11542
- }
11543
- console.log();
11544
- }
11545
- if (result.should_block) {
11546
- ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
11547
- process.exit(1);
11548
- }
11549
- if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
11550
- ui.success("No anti-patterns or stale anchors found.");
11551
- } else {
11552
- ui.success("Check passed (block_on threshold not met).");
11553
- }
11554
- });
11555
- }
11556
- function printWarnings(title, warnings, tone) {
11557
- if (warnings.length === 0) return;
11558
- console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
11559
- for (const w of warnings) {
11560
- const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
11561
- console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
11562
- for (const line of w.body_preview.split("\n").slice(0, 3)) {
11563
- console.log(` ${ui.dim(line)}`);
11564
- }
11565
- console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
11566
- if (w.affected_files && w.affected_files.length > 0) {
11567
- console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
11568
- }
11569
- if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
11570
- if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
11571
- }
11572
- console.log();
11573
- }
11574
- function runCommand3(cmd, args, cwd) {
11575
- return new Promise((resolve, reject) => {
11576
- const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
11577
- let stdout = "";
11578
- let stderr = "";
11579
- proc.stdout.on("data", (chunk) => {
11580
- stdout += chunk.toString();
11581
- });
11582
- proc.stderr.on("data", (chunk) => {
11583
- stderr += chunk.toString();
11584
- });
11585
- proc.on("error", reject);
11586
- proc.on("close", (code) => {
11587
- if (code === 0) resolve(stdout);
11588
- else reject(new Error(stderr || `${cmd} exited with code ${code}`));
11589
- });
11590
- });
11591
- }
11592
-
11593
- // src/commands/welcome.ts
11594
- import { existsSync as existsSync63 } from "fs";
11595
- import "commander";
11596
- import {
11597
- findProjectRoot as findProjectRoot43,
11598
- loadMemoriesFromDir as loadMemoriesFromDir34,
11599
- resolveHaivePaths as resolveHaivePaths39
11600
- } from "@hiveai/core";
11601
- var TYPE_RANK = {
11602
- decision: 0,
11603
- architecture: 1,
11604
- convention: 2,
11605
- glossary: 3,
11606
- gotcha: 4,
11607
- attempt: 5
11608
- };
11609
- function registerWelcome(program2) {
11610
- program2.command("welcome").description(
11611
- "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"
11612
- ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
11613
- const root = findProjectRoot43(opts.dir);
11614
- const paths = resolveHaivePaths39(root);
11615
- if (!existsSync63(paths.memoriesDir)) {
11616
- ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
11617
- process.exitCode = 1;
11618
- return;
11619
- }
11620
- const all = await loadMemoriesFromDir34(paths.memoriesDir);
11621
- const team = all.filter(
11622
- ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
11623
- );
11624
- team.sort((a, b) => {
11625
- const ta = TYPE_RANK[a.memory.frontmatter.type] ?? 99;
11626
- const tb = TYPE_RANK[b.memory.frontmatter.type] ?? 99;
11627
- if (ta !== tb) return ta - tb;
11628
- const sta = a.memory.frontmatter.status === "validated" ? 0 : 1;
11629
- const stb = b.memory.frontmatter.status === "validated" ? 0 : 1;
11630
- if (sta !== stb) return sta - stb;
11631
- return b.memory.frontmatter.created_at.localeCompare(a.memory.frontmatter.created_at);
11632
- });
11633
- const cap = Math.max(1, Math.min(500, Number(opts.limit) || 20));
11634
- const pick = team.slice(0, cap);
11635
- console.log(ui.bold(`hAIve welcome \u2014 ${pick.length} team memories (${root})`));
11636
- console.log(ui.dim(`Next: invoke get_briefing with your task or run 'haive briefing --task "\u2026"'`));
11637
- if (pick.length === 0) {
11638
- ui.warn("No team memories yet \u2014 add some with 'haive memory add' or promote personal ones.");
11639
- return;
11640
- }
11641
- let i = 1;
11642
- for (const { memory: memory2 } of pick) {
11643
- const fm = memory2.frontmatter;
11644
- const head = memory2.body.match(/^#\s+(.+)/m)?.[1]?.trim();
11645
- const line = head ?? fm.id;
11646
- console.log(
11647
- `${String(i).padStart(2, " ")} ${fm.type.padEnd(12)} ${fm.status.padEnd(10)} ${ui.dim(fm.id)}
11648
- ${line}`
11649
- );
11650
- i++;
11651
- }
11652
- });
11653
- }
11654
-
11655
- // src/commands/memory-lint.ts
11656
- import { existsSync as existsSync64 } from "fs";
11657
- import { writeFile as writeFile31 } from "fs/promises";
11658
- import path43 from "path";
11659
- import "commander";
11660
- import {
11661
- findProjectRoot as findProjectRoot44,
11662
- getUsage as getUsage20,
11663
- loadCodeMap as loadCodeMap6,
11664
- loadMemoriesFromDir as loadMemoriesFromDir35,
11665
- loadUsageIndex as loadUsageIndex26,
11666
- resolveHaivePaths as resolveHaivePaths40,
11667
- serializeMemory as serializeMemory26
11668
- } from "@hiveai/core";
11669
- async function lintMemoriesAsync(root, options = {}) {
11670
- const paths = resolveHaivePaths40(root);
11671
- const out = [];
11672
- const fixes = [];
11673
- if (!existsSync64(paths.memoriesDir)) return { findings: out, fixes };
11674
- const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
11675
- const usage = await loadUsageIndex26(paths);
11676
- const codeMap = await loadCodeMap6(paths);
11677
- const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
11678
- const actionableWords = /\b(always|never|prefer|use|avoid|because|instead|why|rationale|do not|must|should)\b/i;
11679
- for (const { filePath, memory: memory2 } of loaded) {
11680
- const fm = memory2.frontmatter;
11681
- if (fm.type === "session_recap") continue;
11682
- const body = memory2.body.trim();
11683
- const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
11684
- if (naked.length < 40 && fm.status !== "rejected") {
11685
- out.push({
11686
- file: filePath,
11687
- id: fm.id,
11688
- severity: "warn",
11689
- code: "SHORT_BODY",
11690
- message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
11691
- });
11692
- }
11693
- if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
11694
- out.push({
11695
- file: filePath,
11696
- id: fm.id,
11697
- severity: "info",
11698
- code: "LOW_ACTIONABILITY",
11699
- message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
11700
- });
11701
- }
11702
- const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap);
11703
- if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
11704
- out.push({
11705
- file: filePath,
11706
- id: fm.id,
11707
- severity: "warn",
11708
- code: "MISSING_ANCHOR",
11709
- message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
11710
- ...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
11711
- });
11712
- }
11713
- if (fm.status === "stale" && !fm.stale_reason) {
11714
- out.push({
11715
- file: filePath,
11716
- id: fm.id,
11717
- severity: "info",
11718
- code: "STALE_NO_REASON",
11719
- message: "Status is stale but stale_reason is empty \u2014 document why when possible."
11720
- });
11721
- }
11722
- if (fm.type === "glossary" && naked.length > 6e3) {
11723
- out.push({
11724
- file: filePath,
11725
- id: fm.id,
11726
- severity: "info",
11727
- code: "LONG_GLOSSARY",
11728
- message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
11729
- });
11730
- }
11731
- const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
11732
- if (!hasMarkdownHeading) {
11733
- out.push({
11734
- file: filePath,
11735
- id: fm.id,
11736
- severity: "warn",
11737
- code: "NO_MD_HEADING",
11738
- message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
11739
- });
11740
- }
11741
- const u = getUsage20(usage, fm.id);
11742
- if (fm.status === "validated" && u.read_count === 0) {
11743
- out.push({
11744
- file: filePath,
11745
- id: fm.id,
11746
- severity: "info",
11747
- code: "NEVER_READ",
11748
- message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
11749
- });
11750
- }
11751
- if (options.fix) {
11752
- const actions = [];
11753
- let nextBody = memory2.body;
11754
- let nextFrontmatter = memory2.frontmatter;
11755
- if (!hasMarkdownHeading) {
11756
- nextBody = `# ${titleFromId(fm.id)}
11757
-
11758
- ${nextBody.trim()}`;
11759
- actions.push("add missing Markdown heading");
11760
- }
11761
- if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.anchor.symbols.length === 0 && fm.status === "validated" && !fm.tags.includes("needs_anchor")) {
11762
- nextFrontmatter = {
11763
- ...nextFrontmatter,
11764
- tags: [...nextFrontmatter.tags, "needs_anchor"]
11765
- };
11766
- actions.push("tag validated anchorless record with needs_anchor");
11767
- }
11768
- if (actions.length > 0) {
11769
- fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
11770
- if (options.apply) {
11771
- await writeFile31(
11772
- filePath,
11773
- serializeMemory26({ frontmatter: nextFrontmatter, body: nextBody }),
11774
- "utf8"
11775
- );
11776
- }
12144
+ console.log();
12145
+ const blocking = result.warnings.filter((w) => w.level === "blocking");
12146
+ const review = result.warnings.filter((w) => w.level === "review");
12147
+ const info = result.warnings.filter((w) => w.level === "info");
12148
+ printWarnings("Blocking anti-patterns", blocking, "error");
12149
+ printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
12150
+ if (info.length > 0) {
12151
+ console.log(
12152
+ ui.dim(
12153
+ `${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
12154
+ )
12155
+ );
12156
+ console.log();
12157
+ }
12158
+ if (result.relevant_memories.length > 0) {
12159
+ console.log(ui.bold("\u{1F4CC} Relevant conventions/decisions:"));
12160
+ for (const m of result.relevant_memories) {
12161
+ console.log(` \u2022 ${m.id} ${ui.dim(`(${m.type}, ${m.confidence})`)}`);
11777
12162
  }
12163
+ console.log();
11778
12164
  }
11779
- }
11780
- for (const dup of nearDuplicatePairs(loaded)) {
11781
- out.push({
11782
- file: dup.file,
11783
- id: dup.id,
11784
- severity: "warn",
11785
- code: "NEAR_DUPLICATE",
11786
- message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
11787
- });
11788
- }
11789
- return { findings: out, fixes };
11790
- }
11791
- function titleFromId(id) {
11792
- const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
11793
- return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
11794
- }
11795
- function suggestAnchors(root, loaded, codeMap) {
11796
- const body = loaded.memory.body;
11797
- const paths = /* @__PURE__ */ new Set();
11798
- const symbols = /* @__PURE__ */ new Set();
11799
- for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
11800
- const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
11801
- if (!candidate || candidate.startsWith("http")) continue;
11802
- if (existsSync64(path43.join(root, candidate))) paths.add(candidate);
11803
- }
11804
- if (codeMap) {
11805
- const lowered = body.toLowerCase();
11806
- for (const [file, entry] of Object.entries(codeMap.files)) {
11807
- for (const exp of entry.exports) {
11808
- if (!exp.name || exp.name.length < 4) continue;
11809
- if (lowered.includes(exp.name.toLowerCase())) {
11810
- paths.add(file);
11811
- symbols.add(exp.name);
11812
- }
11813
- if (paths.size >= 5 && symbols.size >= 5) break;
12165
+ if (result.stale_anchors.length > 0) {
12166
+ console.log(ui.bold("\u{1F552} Stale anchored memories:"));
12167
+ for (const s of result.stale_anchors) {
12168
+ console.log(` \u2022 ${s.id}`);
12169
+ if (s.body_preview) console.log(` ${ui.dim(s.body_preview)}`);
11814
12170
  }
11815
- if (paths.size >= 5 && symbols.size >= 5) break;
12171
+ console.log();
12172
+ }
12173
+ if (result.should_block) {
12174
+ ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
12175
+ process.exit(1);
12176
+ }
12177
+ if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
12178
+ ui.success("No anti-patterns or stale anchors found.");
12179
+ } else {
12180
+ ui.success("Check passed (block_on threshold not met).");
11816
12181
  }
11817
- }
11818
- return {
11819
- paths: [...paths].slice(0, 5),
11820
- symbols: [...symbols].slice(0, 5)
11821
- };
11822
- }
11823
- function nearDuplicatePairs(loaded) {
11824
- const out = [];
11825
- const candidates = loaded.filter(({ memory: memory2 }) => {
11826
- const fm = memory2.frontmatter;
11827
- return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
11828
12182
  });
11829
- for (let i = 0; i < candidates.length; i++) {
11830
- for (let j = i + 1; j < candidates.length; j++) {
11831
- const a = candidates[i];
11832
- const b = candidates[j];
11833
- if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
11834
- if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
11835
- const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
11836
- if (score >= 0.72) {
11837
- out.push({
11838
- id: a.memory.frontmatter.id,
11839
- otherId: b.memory.frontmatter.id,
11840
- file: a.filePath,
11841
- score
11842
- });
11843
- }
12183
+ }
12184
+ function printWarnings(title, warnings, tone) {
12185
+ if (warnings.length === 0) return;
12186
+ console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
12187
+ for (const w of warnings) {
12188
+ const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
12189
+ console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
12190
+ for (const line of w.body_preview.split("\n").slice(0, 3)) {
12191
+ console.log(` ${ui.dim(line)}`);
12192
+ }
12193
+ console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
12194
+ if (w.affected_files && w.affected_files.length > 0) {
12195
+ console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
11844
12196
  }
12197
+ if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
12198
+ if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
11845
12199
  }
11846
- return out;
11847
- }
11848
- function tokenSet(body) {
11849
- return new Set(
11850
- (body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
11851
- );
12200
+ console.log();
11852
12201
  }
11853
- function jaccard2(a, b) {
11854
- if (a.size === 0 || b.size === 0) return 0;
11855
- let inter = 0;
11856
- for (const item of a) if (b.has(item)) inter++;
11857
- return inter / (a.size + b.size - inter);
12202
+ function runCommand3(cmd, args, cwd) {
12203
+ return new Promise((resolve, reject) => {
12204
+ const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
12205
+ let stdout = "";
12206
+ let stderr = "";
12207
+ proc.stdout.on("data", (chunk) => {
12208
+ stdout += chunk.toString();
12209
+ });
12210
+ proc.stderr.on("data", (chunk) => {
12211
+ stderr += chunk.toString();
12212
+ });
12213
+ proc.on("error", reject);
12214
+ proc.on("close", (code) => {
12215
+ if (code === 0) resolve(stdout);
12216
+ else reject(new Error(stderr || `${cmd} exited with code ${code}`));
12217
+ });
12218
+ });
11858
12219
  }
11859
- function registerMemoryLint(parent) {
11860
- parent.command("lint").description(
11861
- "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
11862
- ).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) => {
12220
+
12221
+ // src/commands/welcome.ts
12222
+ import { existsSync as existsSync65 } from "fs";
12223
+ import "commander";
12224
+ import {
12225
+ findProjectRoot as findProjectRoot44,
12226
+ loadMemoriesFromDir as loadMemoriesFromDir35,
12227
+ resolveHaivePaths as resolveHaivePaths40
12228
+ } from "@hiveai/core";
12229
+ var TYPE_RANK = {
12230
+ decision: 0,
12231
+ architecture: 1,
12232
+ convention: 2,
12233
+ glossary: 3,
12234
+ gotcha: 4,
12235
+ attempt: 5
12236
+ };
12237
+ function registerWelcome(program2) {
12238
+ program2.command("welcome").description(
12239
+ "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"
12240
+ ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
11863
12241
  const root = findProjectRoot44(opts.dir);
11864
- const apply = Boolean(opts.fix && opts.apply);
11865
- const dryRun = Boolean(opts.fix && (opts.dryRun || !opts.apply));
11866
- const report = await lintMemoriesAsync(root, { fix: Boolean(opts.fix), apply });
11867
- const findings = report.findings;
11868
- if (opts.json) {
11869
- console.log(JSON.stringify({
11870
- findings_count: findings.length,
11871
- findings,
11872
- fixes_count: report.fixes.length,
11873
- fixes: report.fixes,
11874
- fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
11875
- }, null, 2));
11876
- process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
12242
+ const paths = resolveHaivePaths40(root);
12243
+ if (!existsSync65(paths.memoriesDir)) {
12244
+ ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
12245
+ process.exitCode = 1;
11877
12246
  return;
11878
12247
  }
11879
- if (findings.length === 0) {
11880
- ui.success(`memory lint OK \u2014 ${root}`);
12248
+ const all = await loadMemoriesFromDir35(paths.memoriesDir);
12249
+ const team = all.filter(
12250
+ ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
12251
+ );
12252
+ team.sort((a, b) => {
12253
+ const ta = TYPE_RANK[a.memory.frontmatter.type] ?? 99;
12254
+ const tb = TYPE_RANK[b.memory.frontmatter.type] ?? 99;
12255
+ if (ta !== tb) return ta - tb;
12256
+ const sta = a.memory.frontmatter.status === "validated" ? 0 : 1;
12257
+ const stb = b.memory.frontmatter.status === "validated" ? 0 : 1;
12258
+ if (sta !== stb) return sta - stb;
12259
+ return b.memory.frontmatter.created_at.localeCompare(a.memory.frontmatter.created_at);
12260
+ });
12261
+ const cap = Math.max(1, Math.min(500, Number(opts.limit) || 20));
12262
+ const pick = team.slice(0, cap);
12263
+ console.log(ui.bold(`hAIve welcome \u2014 ${pick.length} team memories (${root})`));
12264
+ console.log(ui.dim(`Next: invoke get_briefing with your task or run 'haive briefing --task "\u2026"'`));
12265
+ if (pick.length === 0) {
12266
+ ui.warn("No team memories yet \u2014 add some with 'haive memory add' or promote personal ones.");
11881
12267
  return;
11882
12268
  }
11883
- console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
11884
- `);
11885
- if (opts.fix) {
11886
- const mode = apply ? "apply" : dryRun ? "dry-run" : "dry-run";
11887
- const verb = apply ? "changed" : "would change";
11888
- console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
11889
- for (const fix of report.fixes) {
11890
- console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
11891
- console.log(ui.dim(` \u2192 ${fix.file}`));
11892
- }
11893
- console.log();
11894
- }
11895
- const order = { error: 0, warn: 1, info: 2 };
11896
- findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
11897
- for (const f of findings) {
11898
- const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
12269
+ let i = 1;
12270
+ for (const { memory: memory2 } of pick) {
12271
+ const fm = memory2.frontmatter;
12272
+ const head = memory2.body.match(/^#\s+(.+)/m)?.[1]?.trim();
12273
+ const line = head ?? fm.id;
11899
12274
  console.log(
11900
- `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
12275
+ `${String(i).padStart(2, " ")} ${fm.type.padEnd(12)} ${fm.status.padEnd(10)} ${ui.dim(fm.id)}
12276
+ ${line}`
11901
12277
  );
11902
- console.log(` ${f.message}`);
11903
- if (f.suggested_anchors) {
11904
- const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
11905
- const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
11906
- console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
11907
- }
11908
- console.log(ui.dim(` \u2192 ${f.file}`));
12278
+ i++;
11909
12279
  }
11910
- process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
11911
12280
  });
11912
12281
  }
11913
12282
 
@@ -11930,21 +12299,21 @@ function registerMemorySuggestTopic(memory2) {
11930
12299
  }
11931
12300
 
11932
12301
  // src/commands/resolve-project.ts
11933
- import path44 from "path";
12302
+ import path45 from "path";
11934
12303
  import "commander";
11935
12304
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
11936
12305
  function registerResolveProject(program2) {
11937
12306
  program2.command("resolve-project").description(
11938
12307
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
11939
12308
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
11940
- const info = resolveProjectInfo2({ cwd: path44.resolve(opts.dir) });
12309
+ const info = resolveProjectInfo2({ cwd: path45.resolve(opts.dir) });
11941
12310
  console.log(JSON.stringify({ ok: true, info }, null, 2));
11942
12311
  });
11943
12312
  }
11944
12313
 
11945
12314
  // src/commands/runtime-journal.ts
11946
- import { existsSync as existsSync65 } from "fs";
11947
- import path45 from "path";
12315
+ import { existsSync as existsSync66 } from "fs";
12316
+ import path46 from "path";
11948
12317
  import "commander";
11949
12318
  import {
11950
12319
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
@@ -11958,18 +12327,18 @@ function registerRuntime(program2) {
11958
12327
  );
11959
12328
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
11960
12329
  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) => {
11961
- const root = path45.resolve(opts.dir ?? process.cwd());
12330
+ const root = path46.resolve(opts.dir ?? process.cwd());
11962
12331
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11963
12332
  const raw = opts.kind ?? "note";
11964
12333
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
11965
12334
  await appendRuntimeJournalEntry3(paths, { kind, message });
11966
- ui.success(`Appended to ${path45.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
12335
+ ui.success(`Appended to ${path46.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
11967
12336
  });
11968
12337
  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) => {
11969
- const root = path45.resolve(opts.dir ?? process.cwd());
12338
+ const root = path46.resolve(opts.dir ?? process.cwd());
11970
12339
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11971
12340
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
11972
- if (!existsSync65(paths.haiveDir)) {
12341
+ if (!existsSync66(paths.haiveDir)) {
11973
12342
  ui.error("No .ai/ \u2014 run `haive init` first.");
11974
12343
  process.exitCode = 1;
11975
12344
  return;
@@ -11984,8 +12353,8 @@ function registerRuntime(program2) {
11984
12353
  }
11985
12354
 
11986
12355
  // src/commands/memory-timeline.ts
11987
- import { existsSync as existsSync66 } from "fs";
11988
- import path46 from "path";
12356
+ import { existsSync as existsSync67 } from "fs";
12357
+ import path47 from "path";
11989
12358
  import "commander";
11990
12359
  import {
11991
12360
  collectTimelineEntries as collectTimelineEntries2,
@@ -12001,15 +12370,15 @@ function registerMemoryTimeline(memory2) {
12001
12370
  process.exitCode = 1;
12002
12371
  return;
12003
12372
  }
12004
- const root = path46.resolve(opts.dir ?? process.cwd());
12373
+ const root = path47.resolve(opts.dir ?? process.cwd());
12005
12374
  const paths = resolveHaivePaths42(findProjectRoot46(root));
12006
- if (!existsSync66(paths.memoriesDir)) {
12375
+ if (!existsSync67(paths.memoriesDir)) {
12007
12376
  ui.error("No memories \u2014 run `haive init`.");
12008
12377
  process.exitCode = 1;
12009
12378
  return;
12010
12379
  }
12011
12380
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
12012
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12381
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
12013
12382
  const { entries, notice } = collectTimelineEntries2(all, {
12014
12383
  memoryId: opts.id,
12015
12384
  topic: opts.topic,
@@ -12021,8 +12390,8 @@ function registerMemoryTimeline(memory2) {
12021
12390
  }
12022
12391
 
12023
12392
  // src/commands/memory-conflict-candidates.ts
12024
- import { existsSync as existsSync67 } from "fs";
12025
- import path47 from "path";
12393
+ import { existsSync as existsSync68 } from "fs";
12394
+ import path48 from "path";
12026
12395
  import "commander";
12027
12396
  import {
12028
12397
  findLexicalConflictPairs as findLexicalConflictPairs2,
@@ -12044,9 +12413,9 @@ function registerMemoryConflictCandidates(memory2) {
12044
12413
  "decision,architecture,convention,gotcha (lexical scan)",
12045
12414
  "decision,architecture"
12046
12415
  ).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) => {
12047
- const root = path47.resolve(opts.dir ?? process.cwd());
12416
+ const root = path48.resolve(opts.dir ?? process.cwd());
12048
12417
  const paths = resolveHaivePaths43(findProjectRoot47(root));
12049
- if (!existsSync67(paths.memoriesDir)) {
12418
+ if (!existsSync68(paths.memoriesDir)) {
12050
12419
  ui.error("No memories \u2014 run `haive init`.");
12051
12420
  process.exitCode = 1;
12052
12421
  return;
@@ -12056,7 +12425,7 @@ function registerMemoryConflictCandidates(memory2) {
12056
12425
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
12057
12426
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
12058
12427
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
12059
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12428
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
12060
12429
  const lexical = findLexicalConflictPairs2(all, {
12061
12430
  sinceDays,
12062
12431
  types: parseTypes(opts.types),
@@ -12082,21 +12451,21 @@ function registerMemoryConflictCandidates(memory2) {
12082
12451
 
12083
12452
  // src/commands/enforce.ts
12084
12453
  import { execFileSync as execFileSync2, spawn as spawn5 } from "child_process";
12085
- import { existsSync as existsSync68 } from "fs";
12086
- import { chmod as chmod2, mkdir as mkdir19, readFile as readFile18, rm as rm3, writeFile as writeFile33 } from "fs/promises";
12087
- import path48 from "path";
12454
+ import { existsSync as existsSync69 } from "fs";
12455
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile19, rm as rm3, writeFile as writeFile34 } from "fs/promises";
12456
+ import path49 from "path";
12088
12457
  import "commander";
12089
12458
  import {
12090
12459
  findProjectRoot as findProjectRoot48,
12091
12460
  hasRecentBriefingMarker,
12092
12461
  isFreshIsoDate,
12093
- loadConfig as loadConfig8,
12462
+ loadConfig as loadConfig11,
12094
12463
  loadMemoriesFromDir as loadMemoriesFromDir36,
12095
12464
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
12096
12465
  readRecentBriefingMarker,
12097
12466
  resolveBriefingBudget as resolveBriefingBudget3,
12098
12467
  resolveHaivePaths as resolveHaivePaths44,
12099
- saveConfig as saveConfig3,
12468
+ saveConfig as saveConfig4,
12100
12469
  SESSION_RECAP_TTL_MS,
12101
12470
  verifyAnchor as verifyAnchor4,
12102
12471
  writeBriefingMarker as writeBriefingMarker2
@@ -12111,8 +12480,8 @@ function registerEnforce(program2) {
12111
12480
  const root = findProjectRoot48(opts.dir);
12112
12481
  const paths = resolveHaivePaths44(root);
12113
12482
  await mkdir19(paths.haiveDir, { recursive: true });
12114
- const current = await loadConfig8(paths);
12115
- await saveConfig3(paths, {
12483
+ const current = await loadConfig11(paths);
12484
+ await saveConfig4(paths, {
12116
12485
  ...current,
12117
12486
  enforcement: {
12118
12487
  ...current.enforcement,
@@ -12134,7 +12503,7 @@ function registerEnforce(program2) {
12134
12503
  if (opts.claude !== false) {
12135
12504
  try {
12136
12505
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
12137
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path48.relative(root, result.settingsPath)})`);
12506
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path49.relative(root, result.settingsPath)})`);
12138
12507
  } catch (err) {
12139
12508
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
12140
12509
  }
@@ -12156,12 +12525,12 @@ function registerEnforce(program2) {
12156
12525
  const root = findProjectRoot48(opts.dir);
12157
12526
  const paths = resolveHaivePaths44(root);
12158
12527
  const targets = [
12159
- path48.join(paths.haiveDir, ".cache"),
12160
- path48.join(paths.haiveDir, ".runtime")
12528
+ path49.join(paths.haiveDir, ".cache"),
12529
+ path49.join(paths.haiveDir, ".runtime")
12161
12530
  ];
12162
12531
  for (const target of targets) {
12163
- if (!existsSync68(target)) continue;
12164
- const rel = path48.relative(root, target);
12532
+ if (!existsSync69(target)) continue;
12533
+ const rel = path49.relative(root, target);
12165
12534
  if (opts.dryRun) ui.info(`would remove ${rel}`);
12166
12535
  else {
12167
12536
  await rm3(target, { recursive: true, force: true });
@@ -12179,7 +12548,7 @@ function registerEnforce(program2) {
12179
12548
  const root = resolveRoot(opts.dir, payload);
12180
12549
  if (!root) return;
12181
12550
  const paths = resolveHaivePaths44(root);
12182
- if (!existsSync68(paths.haiveDir)) return;
12551
+ if (!existsSync69(paths.haiveDir)) return;
12183
12552
  await mkdir19(paths.runtimeDir, { recursive: true });
12184
12553
  const sessionId = opts.sessionId ?? payload.session_id;
12185
12554
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -12241,7 +12610,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
12241
12610
  const root = resolveRoot(opts.dir, payload);
12242
12611
  if (!root) return;
12243
12612
  const paths = resolveHaivePaths44(root);
12244
- if (!existsSync68(paths.haiveDir)) return;
12613
+ if (!existsSync69(paths.haiveDir)) return;
12245
12614
  if (!isWriteLikeTool(payload)) return;
12246
12615
  const ok = await hasRecentBriefingMarker(paths, payload.session_id);
12247
12616
  if (ok) return;
@@ -12265,7 +12634,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
12265
12634
  async function runWithEnforcement(command, args, opts) {
12266
12635
  const root = findProjectRoot48(opts.dir);
12267
12636
  const paths = resolveHaivePaths44(root);
12268
- if (!existsSync68(paths.haiveDir)) {
12637
+ if (!existsSync69(paths.haiveDir)) {
12269
12638
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
12270
12639
  process.exit(1);
12271
12640
  }
@@ -12284,7 +12653,7 @@ async function runWithEnforcement(command, args, opts) {
12284
12653
  process.exit(2);
12285
12654
  }
12286
12655
  ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
12287
- ui.info(`Briefing written to ${path48.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
12656
+ ui.info(`Briefing written to ${path49.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
12288
12657
  const child = spawn5(command, args, {
12289
12658
  cwd: root,
12290
12659
  stdio: "inherit",
@@ -12333,9 +12702,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
12333
12702
  source: "haive-run",
12334
12703
  memoryIds: briefing.memories.map((m) => m.id)
12335
12704
  });
12336
- const dir = path48.join(paths.runtimeDir, "enforcement", "briefings");
12705
+ const dir = path49.join(paths.runtimeDir, "enforcement", "briefings");
12337
12706
  await mkdir19(dir, { recursive: true });
12338
- const file = path48.join(dir, `${sessionId}.md`);
12707
+ const file = path49.join(dir, `${sessionId}.md`);
12339
12708
  const parts = [
12340
12709
  "# hAIve Briefing",
12341
12710
  "",
@@ -12353,14 +12722,14 @@ async function writeWrapperBriefing(paths, sessionId, task) {
12353
12722
  if (briefing.setup_warnings.length > 0) {
12354
12723
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
12355
12724
  }
12356
- await writeFile33(file, parts.join("\n") + "\n", "utf8");
12725
+ await writeFile34(file, parts.join("\n") + "\n", "utf8");
12357
12726
  return file;
12358
12727
  }
12359
12728
  async function buildEnforcementReport(dir, stage, sessionId) {
12360
12729
  const root = findProjectRoot48(dir);
12361
12730
  const paths = resolveHaivePaths44(root);
12362
- const initialized = existsSync68(paths.haiveDir);
12363
- const config = initialized ? await loadConfig8(paths) : {};
12731
+ const initialized = existsSync69(paths.haiveDir);
12732
+ const config = initialized ? await loadConfig11(paths) : {};
12364
12733
  const mode = config.enforcement?.mode ?? "strict";
12365
12734
  const findings = [];
12366
12735
  if (!initialized) {
@@ -12389,7 +12758,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
12389
12758
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
12390
12759
  });
12391
12760
  }
12392
- findings.push(...await inspectIntegrationVersions(root, "0.9.17"));
12761
+ findings.push(...await inspectIntegrationVersions(root, "0.9.19"));
12393
12762
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
12394
12763
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
12395
12764
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -12459,7 +12828,7 @@ function withCategories(report) {
12459
12828
  };
12460
12829
  }
12461
12830
  async function hasRecentSessionRecap(paths) {
12462
- if (!existsSync68(paths.memoriesDir)) return false;
12831
+ if (!existsSync69(paths.memoriesDir)) return false;
12463
12832
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12464
12833
  return all.some(({ memory: memory2 }) => {
12465
12834
  const fm = memory2.frontmatter;
@@ -12468,7 +12837,7 @@ async function hasRecentSessionRecap(paths) {
12468
12837
  });
12469
12838
  }
12470
12839
  async function verifyMemoryPolicy(paths, config) {
12471
- if (!existsSync68(paths.memoriesDir)) return [];
12840
+ if (!existsSync69(paths.memoriesDir)) return [];
12472
12841
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12473
12842
  const findings = [];
12474
12843
  const staleImportant = [];
@@ -12506,7 +12875,7 @@ async function verifyMemoryPolicy(paths, config) {
12506
12875
  return findings;
12507
12876
  }
12508
12877
  async function verifyDecisionCoverage(paths, stage, sessionId) {
12509
- if (!existsSync68(paths.memoriesDir)) return [];
12878
+ if (!existsSync69(paths.memoriesDir)) return [];
12510
12879
  const changedFiles = await getChangedFiles(paths.root, stage);
12511
12880
  if (changedFiles.length === 0) {
12512
12881
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -12599,9 +12968,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
12599
12968
  ];
12600
12969
  const findings = [];
12601
12970
  for (const rel of files) {
12602
- const file = path48.join(root, rel);
12603
- if (!existsSync68(file)) continue;
12604
- const text = await readFile18(file, "utf8").catch(() => "");
12971
+ const file = path49.join(root, rel);
12972
+ if (!existsSync69(file)) continue;
12973
+ const text = await readFile19(file, "utf8").catch(() => "");
12605
12974
  for (const bin of extractAbsoluteHaiveBins2(text)) {
12606
12975
  const version = versionForBinary2(bin);
12607
12976
  if (!version) {
@@ -12689,8 +13058,8 @@ function buildScore(findings, threshold = 80) {
12689
13058
  };
12690
13059
  }
12691
13060
  async function installGitEnforcement(root) {
12692
- const hooksDir = path48.join(root, ".git", "hooks");
12693
- if (!existsSync68(path48.join(root, ".git"))) {
13061
+ const hooksDir = path49.join(root, ".git", "hooks");
13062
+ if (!existsSync69(path49.join(root, ".git"))) {
12694
13063
  ui.warn("No .git directory found; git enforcement hooks skipped.");
12695
13064
  return;
12696
13065
  }
@@ -12712,31 +13081,31 @@ haive enforce check --stage pre-push --dir . || exit $?
12712
13081
  }
12713
13082
  ];
12714
13083
  for (const hook of hooks) {
12715
- const file = path48.join(hooksDir, hook.name);
12716
- if (existsSync68(file)) {
12717
- const current = await readFile18(file, "utf8").catch(() => "");
13084
+ const file = path49.join(hooksDir, hook.name);
13085
+ if (existsSync69(file)) {
13086
+ const current = await readFile19(file, "utf8").catch(() => "");
12718
13087
  if (current.includes(ENFORCE_HOOK_MARKER)) {
12719
- await writeFile33(file, hook.body, "utf8");
13088
+ await writeFile34(file, hook.body, "utf8");
12720
13089
  } else {
12721
- await writeFile33(file, `${current.trimEnd()}
13090
+ await writeFile34(file, `${current.trimEnd()}
12722
13091
 
12723
13092
  ${hook.body}`, "utf8");
12724
13093
  }
12725
13094
  } else {
12726
- await writeFile33(file, hook.body, "utf8");
13095
+ await writeFile34(file, hook.body, "utf8");
12727
13096
  }
12728
13097
  await chmod2(file, 493);
12729
13098
  }
12730
13099
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
12731
13100
  }
12732
13101
  async function installCiEnforcement(root) {
12733
- const workflowPath = path48.join(root, ".github", "workflows", "haive-enforcement.yml");
12734
- await mkdir19(path48.dirname(workflowPath), { recursive: true });
12735
- if (existsSync68(workflowPath)) {
13102
+ const workflowPath = path49.join(root, ".github", "workflows", "haive-enforcement.yml");
13103
+ await mkdir19(path49.dirname(workflowPath), { recursive: true });
13104
+ if (existsSync69(workflowPath)) {
12736
13105
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
12737
13106
  return;
12738
13107
  }
12739
- await writeFile33(workflowPath, `name: haive-enforcement
13108
+ await writeFile34(workflowPath, `name: haive-enforcement
12740
13109
 
12741
13110
  on:
12742
13111
  pull_request:
@@ -12760,7 +13129,7 @@ jobs:
12760
13129
  - name: Enforce hAIve policy
12761
13130
  run: haive enforce ci
12762
13131
  `, "utf8");
12763
- ui.success(`Created ${path48.relative(root, workflowPath)}`);
13132
+ ui.success(`Created ${path49.relative(root, workflowPath)}`);
12764
13133
  }
12765
13134
  function printReport(report, json, explain = false) {
12766
13135
  if (json) {
@@ -12877,7 +13246,7 @@ function registerRun(program2) {
12877
13246
 
12878
13247
  // src/index.ts
12879
13248
  var program = new Command51();
12880
- program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.17").option("--advanced", "show maintenance and experimental commands in help");
13249
+ program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.19").option("--advanced", "show maintenance and experimental commands in help");
12881
13250
  registerInit(program);
12882
13251
  registerWelcome(program);
12883
13252
  registerResolveProject(program);