@hiveai/cli 0.9.17 → 0.9.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -198,7 +198,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
198
198
  if (!f) continue;
199
199
  counts.set(f, (counts.get(f) ?? 0) + 1);
200
200
  }
201
- let entries = [...counts.entries()].map(([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.18";
6519
6519
  function jsonResult(data) {
6520
6520
  return {
6521
6521
  content: [
@@ -7483,30 +7483,511 @@ function registerMcp(program2) {
7483
7483
  }
7484
7484
 
7485
7485
  // 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";
7486
+ import { spawnSync as spawnSync4 } from "child_process";
7487
+ import { readFile as readFile9, writeFile as writeFile15, mkdir as mkdir10 } from "fs/promises";
7488
+ import { existsSync as existsSync31 } from "fs";
7489
+ import path15 from "path";
7490
7490
  import "commander";
7491
7491
  import {
7492
7492
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
7493
7493
  buildFrontmatter as buildFrontmatter6,
7494
- findProjectRoot as findProjectRoot11,
7495
- getUsage as getUsage10,
7494
+ findProjectRoot as findProjectRoot12,
7495
+ getUsage as getUsage11,
7496
7496
  isAutoPromoteEligible as isAutoPromoteEligible2,
7497
7497
  isDecaying as isDecaying2,
7498
- loadCodeMap as loadCodeMap4,
7499
- loadConfig as loadConfig4,
7500
- loadMemoriesFromDir as loadMemoriesFromDir23,
7501
- loadUsageIndex as loadUsageIndex12,
7498
+ loadCodeMap as loadCodeMap6,
7499
+ loadConfig as loadConfig5,
7500
+ loadMemoriesFromDir as loadMemoriesFromDir24,
7501
+ loadUsageIndex as loadUsageIndex13,
7502
7502
  pullCrossRepoSources,
7503
- resolveHaivePaths as resolveHaivePaths8,
7503
+ resolveHaivePaths as resolveHaivePaths9,
7504
7504
  resolveManifestFiles,
7505
- serializeMemory as serializeMemory11,
7505
+ serializeMemory as serializeMemory12,
7506
7506
  trackDependencies,
7507
7507
  verifyAnchor as verifyAnchor2,
7508
7508
  watchContracts
7509
7509
  } from "@hiveai/core";
7510
+
7511
+ // src/utils/autopilot.ts
7512
+ import { existsSync as existsSync30 } from "fs";
7513
+ import { readFile as readFile8, writeFile as writeFile14 } from "fs/promises";
7514
+ import path14 from "path";
7515
+ import {
7516
+ AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
7517
+ buildCodeMap as buildCodeMap3,
7518
+ loadCodeMap as loadCodeMap5,
7519
+ loadConfig as loadConfig4,
7520
+ saveCodeMap as saveCodeMap3,
7521
+ saveConfig as saveConfig2
7522
+ } from "@hiveai/core";
7523
+
7524
+ // src/commands/memory-lint.ts
7525
+ import { existsSync as existsSync29 } from "fs";
7526
+ import { writeFile as writeFile13 } from "fs/promises";
7527
+ import { spawnSync as spawnSync3 } from "child_process";
7528
+ import path13 from "path";
7529
+ import "commander";
7530
+ import {
7531
+ findProjectRoot as findProjectRoot11,
7532
+ getUsage as getUsage10,
7533
+ loadCodeMap as loadCodeMap4,
7534
+ loadMemoriesFromDir as loadMemoriesFromDir23,
7535
+ loadUsageIndex as loadUsageIndex12,
7536
+ resolveHaivePaths as resolveHaivePaths8,
7537
+ serializeMemory as serializeMemory11
7538
+ } from "@hiveai/core";
7539
+ async function lintMemoriesAsync(root, options = {}) {
7540
+ const paths = resolveHaivePaths8(root);
7541
+ const out = [];
7542
+ const fixes = [];
7543
+ if (!existsSync29(paths.memoriesDir)) return { findings: out, fixes };
7544
+ const loaded = await loadMemoriesFromDir23(paths.memoriesDir);
7545
+ const usage = await loadUsageIndex12(paths);
7546
+ const codeMap = await loadCodeMap4(paths);
7547
+ const trackedFiles = gitTrackedFiles(root);
7548
+ const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
7549
+ const actionableWords = /\b(always|never|prefer|use|run|avoid|because|instead|why|rationale|do not|must|should|require|required|requires|fix|fail|failed|fails|prevent|prevents|allow|allows|lets|ensure|ensures|catch|catches)\b/i;
7550
+ for (const { filePath, memory: memory2 } of loaded) {
7551
+ const fm = memory2.frontmatter;
7552
+ if (fm.type === "session_recap") continue;
7553
+ const body = memory2.body.trim();
7554
+ const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
7555
+ if (naked.length < 40 && fm.status !== "rejected") {
7556
+ out.push({
7557
+ file: filePath,
7558
+ id: fm.id,
7559
+ severity: "warn",
7560
+ code: "SHORT_BODY",
7561
+ message: "Body looks very short (< ~40 chars of prose after headings). Prefer actionable detail."
7562
+ });
7563
+ }
7564
+ if (["decision", "gotcha", "convention", "architecture", "attempt"].includes(fm.type) && fm.status !== "rejected" && !actionableWords.test(naked)) {
7565
+ out.push({
7566
+ file: filePath,
7567
+ id: fm.id,
7568
+ severity: "info",
7569
+ code: "LOW_ACTIONABILITY",
7570
+ message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
7571
+ });
7572
+ }
7573
+ const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles);
7574
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
7575
+ out.push({
7576
+ file: filePath,
7577
+ id: fm.id,
7578
+ severity: "warn",
7579
+ code: "MISSING_ANCHOR",
7580
+ message: `${fm.type} is validated without anchor paths \u2014 add anchor.paths so haive sync can flag staleness.`,
7581
+ ...suggestedAnchors.paths.length > 0 || suggestedAnchors.symbols.length > 0 ? { suggested_anchors: suggestedAnchors } : {}
7582
+ });
7583
+ }
7584
+ if (fm.status === "stale" && !fm.stale_reason) {
7585
+ out.push({
7586
+ file: filePath,
7587
+ id: fm.id,
7588
+ severity: "info",
7589
+ code: "STALE_NO_REASON",
7590
+ message: "Status is stale but stale_reason is empty \u2014 document why when possible."
7591
+ });
7592
+ }
7593
+ if (fm.type === "glossary" && naked.length > 6e3) {
7594
+ out.push({
7595
+ file: filePath,
7596
+ id: fm.id,
7597
+ severity: "info",
7598
+ code: "LONG_GLOSSARY",
7599
+ message: "Very long glossary \u2014 consider splitting concepts for tighter briefings."
7600
+ });
7601
+ }
7602
+ const hasMarkdownHeading = /^#{1,3}\s+\S/m.test(memory2.body.trim());
7603
+ if (!hasMarkdownHeading) {
7604
+ out.push({
7605
+ file: filePath,
7606
+ id: fm.id,
7607
+ severity: "warn",
7608
+ code: "NO_MD_HEADING",
7609
+ message: "No Markdown heading (#/##/###) \u2014 add one so humans and auditors can skim the memo quickly."
7610
+ });
7611
+ }
7612
+ const u = getUsage10(usage, fm.id);
7613
+ if (fm.status === "validated" && u.read_count === 0) {
7614
+ out.push({
7615
+ file: filePath,
7616
+ id: fm.id,
7617
+ severity: "info",
7618
+ code: "NEVER_READ",
7619
+ message: "Validated record has never been surfaced/read. Consider improving tags/anchors or archiving it if it is not useful."
7620
+ });
7621
+ }
7622
+ if (options.fix) {
7623
+ const actions = [];
7624
+ let nextBody = memory2.body;
7625
+ let nextFrontmatter = memory2.frontmatter;
7626
+ if (!hasMarkdownHeading) {
7627
+ nextBody = `# ${titleFromId(fm.id)}
7628
+
7629
+ ${nextBody.trim()}`;
7630
+ actions.push("add missing Markdown heading");
7631
+ }
7632
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && suggestedAnchors.paths.length > 0) {
7633
+ nextFrontmatter = {
7634
+ ...nextFrontmatter,
7635
+ anchor: {
7636
+ ...nextFrontmatter.anchor,
7637
+ paths: [.../* @__PURE__ */ new Set([...nextFrontmatter.anchor.paths, ...suggestedAnchors.paths])],
7638
+ symbols: [
7639
+ .../* @__PURE__ */ new Set([...nextFrontmatter.anchor.symbols, ...suggestedAnchors.symbols])
7640
+ ]
7641
+ },
7642
+ tags: nextFrontmatter.tags.filter((tag) => tag !== "needs_anchor")
7643
+ };
7644
+ actions.push("add suggested tracked anchor paths");
7645
+ if (suggestedAnchors.symbols.length > 0) {
7646
+ actions.push("add suggested anchor symbols");
7647
+ }
7648
+ }
7649
+ if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.anchor.symbols.length === 0 && suggestedAnchors.paths.length === 0 && fm.status === "validated" && !fm.tags.includes("needs_anchor")) {
7650
+ nextFrontmatter = {
7651
+ ...nextFrontmatter,
7652
+ tags: [...nextFrontmatter.tags, "needs_anchor"]
7653
+ };
7654
+ actions.push("tag validated anchorless record with needs_anchor");
7655
+ }
7656
+ if (actions.length > 0) {
7657
+ fixes.push({ file: filePath, id: fm.id, actions, applied: Boolean(options.apply) });
7658
+ if (options.apply) {
7659
+ await writeFile13(
7660
+ filePath,
7661
+ serializeMemory11({ frontmatter: nextFrontmatter, body: nextBody }),
7662
+ "utf8"
7663
+ );
7664
+ }
7665
+ }
7666
+ }
7667
+ }
7668
+ for (const dup of nearDuplicatePairs(loaded)) {
7669
+ out.push({
7670
+ file: dup.file,
7671
+ id: dup.id,
7672
+ severity: "warn",
7673
+ code: "NEAR_DUPLICATE",
7674
+ message: `Body overlaps ~${Math.round(dup.score * 100)}% with ${dup.otherId}. Merge or deprecate one record to reduce briefing noise.`
7675
+ });
7676
+ }
7677
+ return { findings: out, fixes };
7678
+ }
7679
+ function titleFromId(id) {
7680
+ const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
7681
+ return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
7682
+ }
7683
+ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
7684
+ const body = loaded.memory.body;
7685
+ const paths = /* @__PURE__ */ new Set();
7686
+ const symbols = /* @__PURE__ */ new Set();
7687
+ for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
7688
+ const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
7689
+ if (!candidate || candidate.startsWith("http")) continue;
7690
+ if (existsSync29(path13.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
7691
+ paths.add(candidate);
7692
+ }
7693
+ }
7694
+ if (codeMap) {
7695
+ const lowered = body.toLowerCase();
7696
+ for (const [file, entry] of Object.entries(codeMap.files)) {
7697
+ for (const exp of entry.exports) {
7698
+ if (!exp.name || exp.name.length < 4) continue;
7699
+ if (lowered.includes(exp.name.toLowerCase())) {
7700
+ if (isSafeAnchorPath(file, trackedFiles)) {
7701
+ paths.add(file);
7702
+ symbols.add(exp.name);
7703
+ }
7704
+ }
7705
+ if (paths.size >= 5 && symbols.size >= 5) break;
7706
+ }
7707
+ if (paths.size >= 5 && symbols.size >= 5) break;
7708
+ }
7709
+ }
7710
+ return {
7711
+ paths: [...paths].slice(0, 5),
7712
+ symbols: [...symbols].slice(0, 5)
7713
+ };
7714
+ }
7715
+ function gitTrackedFiles(root) {
7716
+ const result = spawnSync3("git", ["ls-files"], {
7717
+ cwd: root,
7718
+ encoding: "utf8",
7719
+ stdio: ["ignore", "pipe", "ignore"]
7720
+ });
7721
+ if (result.status !== 0) return null;
7722
+ const files = result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
7723
+ return new Set(files);
7724
+ }
7725
+ function isSafeAnchorPath(file, trackedFiles) {
7726
+ const normalized = file.replace(/\\/g, "/").replace(/^\.?\//, "");
7727
+ if (normalized.startsWith(".ai/.cache/") || normalized.startsWith(".ai/.runtime/")) return false;
7728
+ if (normalized.includes("/node_modules/") || normalized.startsWith("node_modules/")) return false;
7729
+ if (normalized.includes("/dist/") || normalized.startsWith("dist/")) return false;
7730
+ if (trackedFiles && !trackedFiles.has(normalized)) return false;
7731
+ return true;
7732
+ }
7733
+ function nearDuplicatePairs(loaded) {
7734
+ const out = [];
7735
+ const candidates = loaded.filter(({ memory: memory2 }) => {
7736
+ const fm = memory2.frontmatter;
7737
+ return fm.type !== "session_recap" && fm.status !== "rejected" && fm.status !== "deprecated";
7738
+ });
7739
+ for (let i = 0; i < candidates.length; i++) {
7740
+ for (let j = i + 1; j < candidates.length; j++) {
7741
+ const a = candidates[i];
7742
+ const b = candidates[j];
7743
+ if (a.memory.frontmatter.scope !== b.memory.frontmatter.scope) continue;
7744
+ if (a.memory.frontmatter.type !== b.memory.frontmatter.type) continue;
7745
+ const score = jaccard2(tokenSet(a.memory.body), tokenSet(b.memory.body));
7746
+ if (score >= 0.72) {
7747
+ out.push({
7748
+ id: a.memory.frontmatter.id,
7749
+ otherId: b.memory.frontmatter.id,
7750
+ file: a.filePath,
7751
+ score
7752
+ });
7753
+ }
7754
+ }
7755
+ }
7756
+ return out;
7757
+ }
7758
+ function tokenSet(body) {
7759
+ return new Set(
7760
+ (body.toLowerCase().match(/\b[a-z0-9]{4,}\b/g) ?? []).filter((word) => !["this", "that", "with", "from", "have"].includes(word))
7761
+ );
7762
+ }
7763
+ function jaccard2(a, b) {
7764
+ if (a.size === 0 || b.size === 0) return 0;
7765
+ let inter = 0;
7766
+ for (const item of a) if (b.has(item)) inter++;
7767
+ return inter / (a.size + b.size - inter);
7768
+ }
7769
+ function registerMemoryLint(parent) {
7770
+ parent.command("lint").description(
7771
+ "Heuristic corpus checks (anchors on key types, headings, verbosity). Static analysis only."
7772
+ ).option("--json", "emit findings as JSON", false).option("--fix", "prepare simple automatic fixes (use with --dry-run or --apply)", false).option("--dry-run", "with --fix, show files that would change without writing", false).option("--apply", "with --fix, write simple fixes to disk", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
7773
+ const root = findProjectRoot11(opts.dir);
7774
+ const apply = Boolean(opts.fix && opts.apply);
7775
+ const dryRun = Boolean(opts.fix && (opts.dryRun || !opts.apply));
7776
+ const report = await lintMemoriesAsync(root, { fix: Boolean(opts.fix), apply });
7777
+ const findings = report.findings;
7778
+ if (opts.json) {
7779
+ console.log(JSON.stringify({
7780
+ findings_count: findings.length,
7781
+ findings,
7782
+ fixes_count: report.fixes.length,
7783
+ fixes: report.fixes,
7784
+ fix_mode: opts.fix ? apply ? "apply" : "dry-run" : "off"
7785
+ }, null, 2));
7786
+ process.exitCode = findings.some((f) => f.severity === "error") ? 1 : 0;
7787
+ return;
7788
+ }
7789
+ if (findings.length === 0) {
7790
+ ui.success(`memory lint OK \u2014 ${root}`);
7791
+ return;
7792
+ }
7793
+ console.log(ui.bold(`memory lint (${findings.length} finding${findings.length === 1 ? "" : "s"})`) + `
7794
+ `);
7795
+ if (opts.fix) {
7796
+ const mode = apply ? "apply" : dryRun ? "dry-run" : "dry-run";
7797
+ const verb = apply ? "changed" : "would change";
7798
+ console.log(ui.bold(`fix ${mode}: ${report.fixes.length} file${report.fixes.length === 1 ? "" : "s"} ${verb}`));
7799
+ for (const fix of report.fixes) {
7800
+ console.log(` ${ui.dim(fix.id)} ${fix.actions.join("; ")}`);
7801
+ console.log(ui.dim(` \u2192 ${fix.file}`));
7802
+ }
7803
+ console.log();
7804
+ }
7805
+ const order = { error: 0, warn: 1, info: 2 };
7806
+ findings.sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
7807
+ for (const f of findings) {
7808
+ const color = f.severity === "error" ? ui.red : f.severity === "warn" ? ui.yellow : ui.dim;
7809
+ console.log(
7810
+ `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
7811
+ );
7812
+ console.log(` ${f.message}`);
7813
+ if (f.suggested_anchors) {
7814
+ const pathHints = f.suggested_anchors.paths.length > 0 ? `paths: ${f.suggested_anchors.paths.join(", ")}` : "";
7815
+ const symbolHints = f.suggested_anchors.symbols.length > 0 ? `symbols: ${f.suggested_anchors.symbols.join(", ")}` : "";
7816
+ console.log(ui.dim(` suggested anchors: ${[pathHints, symbolHints].filter(Boolean).join(" \xB7 ")}`));
7817
+ }
7818
+ console.log(ui.dim(` \u2192 ${f.file}`));
7819
+ }
7820
+ process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
7821
+ });
7822
+ }
7823
+
7824
+ // src/utils/autopilot.ts
7825
+ async function applyAutopilotRepairs(root, paths, options = {}) {
7826
+ const repairs = [];
7827
+ const config = await loadConfig4(paths);
7828
+ if (options.applyConfig) {
7829
+ const changed = await ensureAutopilotConfig(paths, config);
7830
+ if (changed) {
7831
+ repairs.push({
7832
+ code: "autopilot-config",
7833
+ message: "Enabled autopilot defaults in .ai/haive.config.json."
7834
+ });
7835
+ }
7836
+ }
7837
+ const current = await loadConfig4(paths);
7838
+ const autoRepair = current.autoRepair ?? {};
7839
+ if (options.applyContext ?? autoRepair.context ?? current.autopilot) {
7840
+ const changed = await syncProjectContextVersion(root, paths);
7841
+ if (changed) {
7842
+ repairs.push({
7843
+ code: "project-context-version",
7844
+ message: "Updated .ai/project-context.md version metadata from package.json."
7845
+ });
7846
+ }
7847
+ }
7848
+ if (options.applyCorpus ?? autoRepair.corpus ?? current.autopilot) {
7849
+ const report = await lintMemoriesAsync(root, { fix: true, apply: true });
7850
+ const applied = report.fixes.filter((fix) => fix.applied);
7851
+ if (applied.length > 0) {
7852
+ repairs.push({
7853
+ code: "memory-lint-fix",
7854
+ message: `Applied ${applied.length} safe memory lint fix${applied.length === 1 ? "" : "es"}.`
7855
+ });
7856
+ }
7857
+ }
7858
+ if (options.applyCodeMap ?? autoRepair.codeMap ?? current.autopilot) {
7859
+ const refreshed = await refreshCodeMap(root, paths, Boolean(options.forceCodeMap));
7860
+ if (refreshed) {
7861
+ repairs.push({
7862
+ code: "code-map-refresh",
7863
+ message: "Refreshed .ai/code-map.json."
7864
+ });
7865
+ }
7866
+ }
7867
+ if (options.applyCodeSearch ?? autoRepair.codeSearch ?? current.autopilot) {
7868
+ const indexed = await refreshCodeSearchIndex(paths);
7869
+ if (indexed) {
7870
+ repairs.push({
7871
+ code: "code-search-index",
7872
+ message: "Refreshed code-search embeddings index."
7873
+ });
7874
+ }
7875
+ }
7876
+ return repairs;
7877
+ }
7878
+ async function ensureAutopilotConfig(paths, currentConfig) {
7879
+ const current = currentConfig ?? await loadConfig4(paths);
7880
+ const next = {
7881
+ ...current,
7882
+ autopilot: true,
7883
+ defaultScope: "team",
7884
+ defaultStatus: "validated",
7885
+ autoApproveDelayHours: current.autoApproveDelayHours ?? AUTOPILOT_DEFAULTS2.autoApproveDelayHours,
7886
+ autoPromoteMinReads: current.autoPromoteMinReads ?? AUTOPILOT_DEFAULTS2.autoPromoteMinReads,
7887
+ autoSessionEnd: true,
7888
+ autoContext: true,
7889
+ autoRepair: {
7890
+ context: true,
7891
+ corpus: true,
7892
+ codeMap: true,
7893
+ codeSearch: current.autoRepair?.codeSearch ?? true
7894
+ },
7895
+ enforcement: {
7896
+ ...AUTOPILOT_DEFAULTS2.enforcement,
7897
+ ...current.enforcement,
7898
+ mode: "strict",
7899
+ requireBriefingFirst: true,
7900
+ requireSessionRecap: true,
7901
+ requireMemoryVerify: true,
7902
+ blockStaleDecisionChanges: true,
7903
+ requireDecisionCoverage: true,
7904
+ cleanupGeneratedArtifacts: true,
7905
+ toolProfile: current.enforcement?.toolProfile ?? "enforcement"
7906
+ }
7907
+ };
7908
+ if (JSON.stringify(current) === JSON.stringify(next)) return false;
7909
+ await saveConfig2(paths, next);
7910
+ return true;
7911
+ }
7912
+ async function syncProjectContextVersion(root, paths) {
7913
+ const status = await projectContextVersionStatus(root, paths);
7914
+ if (!status.canSync || !status.expectedVersion) return false;
7915
+ const original = await readFile8(paths.projectContext, "utf8");
7916
+ let updated = original.replace(
7917
+ /^# Project context — hAIve \(v[^)]+\)$/m,
7918
+ `# Project context \u2014 hAIve (v${status.expectedVersion})`
7919
+ ).replace(
7920
+ /> \*\*Current version\*\*: [^—\n]+—/m,
7921
+ `> **Current version**: ${status.expectedVersion} \u2014`
7922
+ );
7923
+ if (updated === original && !original.includes("Current version")) {
7924
+ updated = original.replace(
7925
+ /^(> Repo-native context enforcement[^\n]*\n)/m,
7926
+ `$1> **Current version**: ${status.expectedVersion} \u2014 @hiveai/core, cli, mcp, embeddings are versioned together.
7927
+ `
7928
+ );
7929
+ }
7930
+ if (updated === original) return false;
7931
+ await writeFile14(paths.projectContext, updated, "utf8");
7932
+ return true;
7933
+ }
7934
+ async function projectContextVersionStatus(root, paths) {
7935
+ if (!existsSync30(paths.projectContext)) {
7936
+ return { mismatch: false, canSync: false };
7937
+ }
7938
+ const packagePath = path14.join(root, "package.json");
7939
+ if (!existsSync30(packagePath)) {
7940
+ return { mismatch: false, canSync: false };
7941
+ }
7942
+ const packageJson = JSON.parse(await readFile8(packagePath, "utf8"));
7943
+ const expectedVersion = packageJson.version;
7944
+ if (!expectedVersion) {
7945
+ return { mismatch: false, canSync: false };
7946
+ }
7947
+ const content = await readFile8(paths.projectContext, "utf8");
7948
+ const headingVersion = content.match(/^# Project context — hAIve \(v([^)]+)\)$/m)?.[1];
7949
+ const currentLineVersion = content.match(/^> \*\*Current version\*\*: ([^—\n]+)—/m)?.[1]?.trim();
7950
+ const currentVersion = currentLineVersion ?? headingVersion;
7951
+ return {
7952
+ expectedVersion,
7953
+ currentVersion,
7954
+ mismatch: currentVersion !== expectedVersion,
7955
+ canSync: true
7956
+ };
7957
+ }
7958
+ async function refreshCodeMap(root, paths, force) {
7959
+ if (!force) {
7960
+ const existing = await loadCodeMap5(paths);
7961
+ if (existing) return false;
7962
+ }
7963
+ const map = await buildCodeMap3(root, {
7964
+ excludeDirs: [
7965
+ "node_modules",
7966
+ "dist",
7967
+ "build",
7968
+ "out",
7969
+ ".git",
7970
+ ".next",
7971
+ ".turbo",
7972
+ ".vitest-cache",
7973
+ "coverage"
7974
+ ]
7975
+ });
7976
+ await saveCodeMap3(paths, map);
7977
+ return true;
7978
+ }
7979
+ async function refreshCodeSearchIndex(paths) {
7980
+ try {
7981
+ const mod = await import("@hiveai/embeddings");
7982
+ const embedder = await mod.Embedder.create();
7983
+ const { report } = await mod.rebuildCodeIndex(paths, embedder);
7984
+ return report.added > 0 || report.updated > 0 || report.removed > 0;
7985
+ } catch {
7986
+ return false;
7987
+ }
7988
+ }
7989
+
7990
+ // src/commands/sync.ts
7510
7991
  var BRIDGE_START = "<!-- haive:memories-start -->";
7511
7992
  var BRIDGE_END = "<!-- haive:memories-end -->";
7512
7993
  function registerSync(program2) {
@@ -7519,9 +8000,9 @@ function registerSync(program2) {
7519
8000
  "--inject-bridge",
7520
8001
  "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
7521
8002
  ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").action(async (opts) => {
7522
- const root = findProjectRoot11(opts.dir);
7523
- const paths = resolveHaivePaths8(root);
7524
- if (!existsSync29(paths.memoriesDir)) {
8003
+ const root = findProjectRoot12(opts.dir);
8004
+ const paths = resolveHaivePaths9(root);
8005
+ if (!existsSync31(paths.memoriesDir)) {
7525
8006
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7526
8007
  process.exitCode = 1;
7527
8008
  return;
@@ -7529,21 +8010,22 @@ function registerSync(program2) {
7529
8010
  const log = (msg) => {
7530
8011
  if (!opts.quiet) console.log(msg);
7531
8012
  };
7532
- const config = await loadConfig4(paths);
8013
+ const config = await loadConfig5(paths);
7533
8014
  const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
7534
8015
  const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads;
8016
+ const autoRepair = config.autoRepair ?? {};
7535
8017
  let staleMarked = 0;
7536
8018
  let revalidated = 0;
7537
8019
  let promoted = 0;
7538
8020
  let autoApproved = 0;
7539
8021
  if (opts.verify !== false) {
7540
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
8022
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
7541
8023
  for (const { memory: memory2, filePath } of memories) {
7542
8024
  if (memory2.frontmatter.type === "session_recap") {
7543
8025
  if (memory2.frontmatter.status === "stale") {
7544
- await writeFile13(
8026
+ await writeFile15(
7545
8027
  filePath,
7546
- serializeMemory11({
8028
+ serializeMemory12({
7547
8029
  frontmatter: {
7548
8030
  ...memory2.frontmatter,
7549
8031
  status: "validated",
@@ -7564,9 +8046,9 @@ function registerSync(program2) {
7564
8046
  const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
7565
8047
  if (result.stale) {
7566
8048
  if (memory2.frontmatter.status !== "stale") {
7567
- await writeFile13(
8049
+ await writeFile15(
7568
8050
  filePath,
7569
- serializeMemory11({
8051
+ serializeMemory12({
7570
8052
  frontmatter: {
7571
8053
  ...memory2.frontmatter,
7572
8054
  status: "stale",
@@ -7580,9 +8062,9 @@ function registerSync(program2) {
7580
8062
  staleMarked++;
7581
8063
  }
7582
8064
  } else if (memory2.frontmatter.status === "stale") {
7583
- await writeFile13(
8065
+ await writeFile15(
7584
8066
  filePath,
7585
- serializeMemory11({
8067
+ serializeMemory12({
7586
8068
  frontmatter: {
7587
8069
  ...memory2.frontmatter,
7588
8070
  status: "validated",
@@ -7598,19 +8080,19 @@ function registerSync(program2) {
7598
8080
  }
7599
8081
  }
7600
8082
  if (opts.promote !== false) {
7601
- const memories = await loadMemoriesFromDir23(paths.memoriesDir);
7602
- const usage = await loadUsageIndex12(paths);
8083
+ const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8084
+ const usage = await loadUsageIndex13(paths);
7603
8085
  const nowMs = Date.now();
7604
8086
  for (const { memory: memory2, filePath } of memories) {
7605
8087
  const fm = memory2.frontmatter;
7606
8088
  if (fm.type === "session_recap") continue;
7607
- if (isAutoPromoteEligible2(fm, getUsage10(usage, fm.id), {
8089
+ if (isAutoPromoteEligible2(fm, getUsage11(usage, fm.id), {
7608
8090
  minReads: autoPromoteMinReads,
7609
8091
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
7610
8092
  })) {
7611
- await writeFile13(
8093
+ await writeFile15(
7612
8094
  filePath,
7613
- serializeMemory11({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
8095
+ serializeMemory12({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
7614
8096
  "utf8"
7615
8097
  );
7616
8098
  promoted++;
@@ -7619,9 +8101,9 @@ function registerSync(program2) {
7619
8101
  if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
7620
8102
  const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
7621
8103
  if (ageHours >= autoApproveDelayHours) {
7622
- await writeFile13(
8104
+ await writeFile15(
7623
8105
  filePath,
7624
- serializeMemory11({
8106
+ serializeMemory12({
7625
8107
  frontmatter: {
7626
8108
  ...fm,
7627
8109
  status: "validated",
@@ -7636,8 +8118,17 @@ function registerSync(program2) {
7636
8118
  }
7637
8119
  }
7638
8120
  }
8121
+ if (config.autopilot || autoRepair.context || autoRepair.corpus) {
8122
+ const repairs = await applyAutopilotRepairs(root, paths, {
8123
+ applyContext: autoRepair.context ?? config.autopilot,
8124
+ applyCorpus: autoRepair.corpus ?? config.autopilot,
8125
+ applyCodeMap: false,
8126
+ applyCodeSearch: false
8127
+ });
8128
+ for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
8129
+ }
7639
8130
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
7640
- const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
8131
+ const draftMemories = (await loadMemoriesFromDir24(paths.memoriesDir)).filter(
7641
8132
  (m) => m.memory.frontmatter.status === "draft"
7642
8133
  );
7643
8134
  const draftCount = draftMemories.length;
@@ -7653,7 +8144,7 @@ function registerSync(program2) {
7653
8144
  );
7654
8145
  }
7655
8146
  if (opts.injectBridge) {
7656
- const bridgeFile = opts.bridgeFile ? path13.resolve(opts.bridgeFile) : path13.join(root, "CLAUDE.md");
8147
+ const bridgeFile = opts.bridgeFile ? path15.resolve(opts.bridgeFile) : path15.join(root, "CLAUDE.md");
7657
8148
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
7658
8149
  await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
7659
8150
  }
@@ -7672,12 +8163,12 @@ function registerSync(program2) {
7672
8163
  }
7673
8164
  }
7674
8165
  if (!opts.quiet) {
7675
- const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
7676
- const usageForDecay = await loadUsageIndex12(paths);
8166
+ const allForDecay = await loadMemoriesFromDir24(paths.memoriesDir);
8167
+ const usageForDecay = await loadUsageIndex13(paths);
7677
8168
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
7678
8169
  const fm = memory2.frontmatter;
7679
8170
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
7680
- const u = getUsage10(usageForDecay, fm.id);
8171
+ const u = getUsage11(usageForDecay, fm.id);
7681
8172
  return isDecaying2(u, fm.created_at);
7682
8173
  });
7683
8174
  if (decaying.length > 0) {
@@ -7759,11 +8250,11 @@ Attends une **confirmation explicite** avant d'agir.
7759
8250
  paths: [result.file],
7760
8251
  topic: `dep-bump-${slugParts}`
7761
8252
  });
7762
- const teamDir = path13.join(paths.memoriesDir, "team");
8253
+ const teamDir = path15.join(paths.memoriesDir, "team");
7763
8254
  await mkdir10(teamDir, { recursive: true });
7764
- await writeFile13(
7765
- path13.join(teamDir, `${fm.id}.md`),
7766
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8255
+ await writeFile15(
8256
+ path15.join(teamDir, `${fm.id}.md`),
8257
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7767
8258
  "utf8"
7768
8259
  );
7769
8260
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7826,11 +8317,11 @@ Attends une **confirmation explicite** avant d'agir.
7826
8317
  paths: [diff.file],
7827
8318
  topic: `contract-breaking-${diff.contract}`
7828
8319
  });
7829
- const teamDir = path13.join(paths.memoriesDir, "team");
8320
+ const teamDir = path15.join(paths.memoriesDir, "team");
7830
8321
  await mkdir10(teamDir, { recursive: true });
7831
- await writeFile13(
7832
- path13.join(teamDir, `${fm.id}.md`),
7833
- serializeMemory11({ frontmatter: { ...fm, requires_human_approval: true }, body }),
8322
+ await writeFile15(
8323
+ path15.join(teamDir, `${fm.id}.md`),
8324
+ serializeMemory12({ frontmatter: { ...fm, requires_human_approval: true }, body }),
7834
8325
  "utf8"
7835
8326
  );
7836
8327
  log(ui.yellow(` \u2192 memory created: ${fm.id}`));
@@ -7840,10 +8331,19 @@ Attends une **confirmation explicite** avant d'agir.
7840
8331
  ui.warn(`contract watcher failed: ${String(err)}`);
7841
8332
  }
7842
8333
  }
7843
- const existingMap = await loadCodeMap4(paths);
7844
- if (existingMap) {
8334
+ const existingMap = await loadCodeMap6(paths);
8335
+ if (!existingMap && (config.autopilot || autoRepair.codeMap)) {
8336
+ try {
8337
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
8338
+ log(ui.dim("code-map: missing \u2014 building index\u2026"));
8339
+ const newMap = await buildCodeMap4(root);
8340
+ await saveCodeMap4(paths, newMap);
8341
+ log(ui.dim(`code-map: built (${Object.keys(newMap.files).length} files)`));
8342
+ } catch {
8343
+ }
8344
+ } else if (existingMap) {
7845
8345
  const mapAge = new Date(existingMap.generated_at).getTime();
7846
- const gitResult = spawnSync3(
8346
+ const gitResult = spawnSync4(
7847
8347
  "git",
7848
8348
  [
7849
8349
  "diff",
@@ -7868,22 +8368,32 @@ Attends une **confirmation explicite** avant d'agir.
7868
8368
  const changedSourceFiles = (gitResult.stdout ?? "").trim();
7869
8369
  if (changedSourceFiles.length > 0) {
7870
8370
  try {
7871
- const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
8371
+ const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
7872
8372
  log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
7873
- const newMap = await buildCodeMap3(root);
7874
- await saveCodeMap3(paths, newMap);
8373
+ const newMap = await buildCodeMap4(root);
8374
+ await saveCodeMap4(paths, newMap);
7875
8375
  log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
7876
8376
  } catch {
7877
8377
  }
7878
8378
  }
7879
8379
  }
7880
- if (opts.embed) {
8380
+ if (opts.embed || autoRepair.codeSearch) {
7881
8381
  try {
7882
- const { Embedder, rebuildIndex } = await import("@hiveai/embeddings");
8382
+ const { Embedder, rebuildCodeIndex, rebuildIndex } = await import("@hiveai/embeddings");
7883
8383
  log(ui.dim("embed: rebuilding index\u2026"));
7884
8384
  const embedder = await Embedder.create();
7885
8385
  const { report } = await rebuildIndex(paths, embedder);
7886
- log(ui.dim(`embed: index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`));
8386
+ const { report: codeReport } = await rebuildCodeIndex(paths, embedder);
8387
+ log(
8388
+ ui.dim(
8389
+ `embed: memory index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`
8390
+ )
8391
+ );
8392
+ log(
8393
+ ui.dim(
8394
+ `embed: code index rebuilt (${codeReport.total} symbols, ${codeReport.added} added, ${codeReport.updated} updated, ${codeReport.removed} removed)`
8395
+ )
8396
+ );
7887
8397
  } catch {
7888
8398
  ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
7889
8399
  }
@@ -7891,8 +8401,8 @@ Attends une **confirmation explicite** avant d'agir.
7891
8401
  });
7892
8402
  }
7893
8403
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
7894
- if (!existsSync29(memoriesDir)) return;
7895
- const all = await loadMemoriesFromDir23(memoriesDir);
8404
+ if (!existsSync31(memoriesDir)) return;
8405
+ const all = await loadMemoriesFromDir24(memoriesDir);
7896
8406
  const top = all.filter(({ memory: memory2 }) => {
7897
8407
  const s = memory2.frontmatter.status;
7898
8408
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -7916,17 +8426,17 @@ ${m.memory.body.trim()}`;
7916
8426
  ` + block + `
7917
8427
 
7918
8428
  ${BRIDGE_END}`;
7919
- const fileExists = existsSync29(bridgeFile);
7920
- let existing = fileExists ? await readFile8(bridgeFile, "utf8") : "";
8429
+ const fileExists = existsSync31(bridgeFile);
8430
+ let existing = fileExists ? await readFile9(bridgeFile, "utf8") : "";
7921
8431
  existing = existing.replace(/\r\n/g, "\n");
7922
8432
  const startIdx = existing.indexOf(BRIDGE_START);
7923
8433
  const endIdx = existing.indexOf(BRIDGE_END);
7924
8434
  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.`);
8435
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
7926
8436
  return;
7927
8437
  }
7928
8438
  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.`);
8439
+ ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
7930
8440
  return;
7931
8441
  }
7932
8442
  let updated;
@@ -7934,19 +8444,19 @@ ${BRIDGE_END}`;
7934
8444
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
7935
8445
  } else {
7936
8446
  if (!fileExists && !quiet) {
7937
- ui.info(`Creating ${path13.relative(root, bridgeFile)} with haive memory block.`);
8447
+ ui.info(`Creating ${path15.relative(root, bridgeFile)} with haive memory block.`);
7938
8448
  }
7939
8449
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
7940
8450
  }
7941
- await writeFile13(bridgeFile, updated, "utf8");
8451
+ await writeFile15(bridgeFile, updated, "utf8");
7942
8452
  if (!quiet) {
7943
8453
  console.log(
7944
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path13.relative(root, bridgeFile)}`)
8454
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
7945
8455
  );
7946
8456
  }
7947
8457
  }
7948
8458
  function collectSinceChanges(root, ref) {
7949
- const result = spawnSync3(
8459
+ const result = spawnSync4(
7950
8460
  "git",
7951
8461
  ["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
7952
8462
  { encoding: "utf8" }
@@ -7966,18 +8476,19 @@ function collectSinceChanges(root, ref) {
7966
8476
 
7967
8477
  // src/commands/memory-add.ts
7968
8478
  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";
8479
+ import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile16 } from "fs/promises";
8480
+ import { existsSync as existsSync33 } from "fs";
8481
+ import path16 from "path";
7972
8482
  import "commander";
7973
8483
  import {
7974
8484
  buildFrontmatter as buildFrontmatter7,
7975
- findProjectRoot as findProjectRoot12,
8485
+ findProjectRoot as findProjectRoot13,
7976
8486
  inferModulesFromPaths as inferModulesFromPaths3,
7977
- loadMemoriesFromDir as loadMemoriesFromDir24,
8487
+ loadConfig as loadConfig6,
8488
+ loadMemoriesFromDir as loadMemoriesFromDir25,
7978
8489
  memoryFilePath as memoryFilePath6,
7979
- resolveHaivePaths as resolveHaivePaths9,
7980
- serializeMemory as serializeMemory12
8490
+ resolveHaivePaths as resolveHaivePaths10,
8491
+ serializeMemory as serializeMemory13
7981
8492
  } from "@hiveai/core";
7982
8493
  function registerMemoryAdd(memory2) {
7983
8494
  memory2.command("add").description(
@@ -8003,21 +8514,22 @@ function registerMemoryAdd(memory2) {
8003
8514
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
8004
8515
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
8005
8516
  `
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)) {
8517
+ ).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case identifier used in the file name").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8518
+ const root = findProjectRoot13(opts.dir);
8519
+ const paths = resolveHaivePaths10(root);
8520
+ if (!existsSync33(paths.haiveDir)) {
8010
8521
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8011
8522
  process.exitCode = 1;
8012
8523
  return;
8013
8524
  }
8525
+ const config = await loadConfig6(paths);
8014
8526
  const userTags = parseCsv2(opts.tags);
8015
8527
  const anchorPaths = parseCsv2(opts.paths);
8016
8528
  const autoTagsEnabled = opts.autoTag !== false;
8017
8529
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
8018
8530
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
8019
8531
  if (anchorPaths.length > 0) {
8020
- const missing = anchorPaths.filter((p) => !existsSync30(path14.resolve(root, p)));
8532
+ const missing = anchorPaths.filter((p) => !existsSync33(path16.resolve(root, p)));
8021
8533
  if (missing.length > 0) {
8022
8534
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
8023
8535
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -8029,12 +8541,12 @@ function registerMemoryAdd(memory2) {
8029
8541
  const title = opts.title ?? opts.slug;
8030
8542
  let body;
8031
8543
  if (opts.bodyFile !== void 0) {
8032
- if (!existsSync30(opts.bodyFile)) {
8544
+ if (!existsSync33(opts.bodyFile)) {
8033
8545
  ui.error(`--body-file not found: ${opts.bodyFile}`);
8034
8546
  process.exitCode = 1;
8035
8547
  return;
8036
8548
  }
8037
- const fileContent = await readFile9(opts.bodyFile, "utf8");
8549
+ const fileContent = await readFile10(opts.bodyFile, "utf8");
8038
8550
  body = opts.title ? `# ${opts.title}
8039
8551
 
8040
8552
  ${fileContent.trim()}
@@ -8049,10 +8561,10 @@ ${opts.body}` : opts.body;
8049
8561
  TODO \u2014 write the memory body.
8050
8562
  `;
8051
8563
  }
8052
- const scope = opts.scope ?? "personal";
8053
- if (existsSync30(paths.memoriesDir)) {
8564
+ const scope = opts.scope ?? config.defaultScope ?? "personal";
8565
+ if (existsSync33(paths.memoriesDir)) {
8054
8566
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
8055
- const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
8567
+ const allForHash = await loadMemoriesFromDir25(paths.memoriesDir);
8056
8568
  const hashDup = allForHash.find(
8057
8569
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
8058
8570
  );
@@ -8063,8 +8575,8 @@ TODO \u2014 write the memory body.
8063
8575
  return;
8064
8576
  }
8065
8577
  }
8066
- if (opts.topic && existsSync30(paths.memoriesDir)) {
8067
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8578
+ if (opts.topic && existsSync33(paths.memoriesDir)) {
8579
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
8068
8580
  const topicMatch = existing.find(
8069
8581
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
8070
8582
  );
@@ -8081,8 +8593,8 @@ TODO \u2014 write the memory body.
8081
8593
  symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
8082
8594
  }
8083
8595
  };
8084
- await writeFile14(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
8085
- ui.success(`Updated (topic upsert) ${path14.relative(root, topicMatch.filePath)}`);
8596
+ await writeFile16(topicMatch.filePath, serializeMemory13({ frontmatter: newFrontmatter, body }), "utf8");
8597
+ ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
8086
8598
  ui.info(`id=${fm.id} revision=${revisionCount}`);
8087
8599
  return;
8088
8600
  }
@@ -8098,17 +8610,18 @@ TODO \u2014 write the memory body.
8098
8610
  paths: anchorPaths,
8099
8611
  symbols: parseCsv2(opts.symbols),
8100
8612
  commit: opts.commit,
8101
- topic: opts.topic
8613
+ topic: opts.topic,
8614
+ status: config.defaultStatus === "validated" ? "validated" : void 0
8102
8615
  });
8103
8616
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8104
- await mkdir11(path14.dirname(file), { recursive: true });
8105
- if (existsSync30(file)) {
8617
+ await mkdir11(path16.dirname(file), { recursive: true });
8618
+ if (existsSync33(file)) {
8106
8619
  ui.error(`Memory already exists at ${file}`);
8107
8620
  process.exitCode = 1;
8108
8621
  return;
8109
8622
  }
8110
- if (existsSync30(paths.memoriesDir)) {
8111
- const existing = await loadMemoriesFromDir24(paths.memoriesDir);
8623
+ if (existsSync33(paths.memoriesDir)) {
8624
+ const existing = await loadMemoriesFromDir25(paths.memoriesDir);
8112
8625
  const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
8113
8626
  const similar = existing.filter(({ memory: memory3 }) => {
8114
8627
  const id = memory3.frontmatter.id.toLowerCase();
@@ -8119,8 +8632,8 @@ TODO \u2014 write the memory body.
8119
8632
  ui.warn("Consider updating one of these with `haive memory update` instead.");
8120
8633
  }
8121
8634
  }
8122
- await writeFile14(file, serializeMemory12({ frontmatter, body }), "utf8");
8123
- ui.success(`Created ${path14.relative(root, file)}`);
8635
+ await writeFile16(file, serializeMemory13({ frontmatter, body }), "utf8");
8636
+ ui.success(`Created ${path16.relative(root, file)}`);
8124
8637
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
8125
8638
  if (inferredTags.length > 0) {
8126
8639
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
@@ -8131,7 +8644,9 @@ TODO \u2014 write the memory body.
8131
8644
  Add file anchors: haive memory update ${frontmatter.id} --paths <file1,file2>`
8132
8645
  );
8133
8646
  }
8134
- if (scope === "personal") {
8647
+ if (frontmatter.status === "validated") {
8648
+ console.log(ui.dim("\u2192 autopilot: memory is already validated and active"));
8649
+ } else if (scope === "personal") {
8135
8650
  console.log(
8136
8651
  ui.dim(
8137
8652
  `\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
@@ -8150,14 +8665,14 @@ function parseCsv2(value) {
8150
8665
  }
8151
8666
 
8152
8667
  // src/commands/memory-list.ts
8153
- import { existsSync as existsSync31 } from "fs";
8154
- import path15 from "path";
8668
+ import { existsSync as existsSync34 } from "fs";
8669
+ import path17 from "path";
8155
8670
  import "commander";
8156
- import { findProjectRoot as findProjectRoot13, resolveHaivePaths as resolveHaivePaths10 } from "@hiveai/core";
8671
+ import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
8157
8672
 
8158
8673
  // src/utils/fs.ts
8159
8674
  import {
8160
- loadMemoriesFromDir as loadMemoriesFromDir25,
8675
+ loadMemoriesFromDir as loadMemoriesFromDir26,
8161
8676
  loadMemory,
8162
8677
  listMarkdownFilesRecursive
8163
8678
  } from "@hiveai/core";
@@ -8165,14 +8680,14 @@ import {
8165
8680
  // src/commands/memory-list.ts
8166
8681
  function registerMemoryList(memory2) {
8167
8682
  memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8168
- const root = findProjectRoot13(opts.dir);
8169
- const paths = resolveHaivePaths10(root);
8170
- if (!existsSync31(paths.memoriesDir)) {
8683
+ const root = findProjectRoot14(opts.dir);
8684
+ const paths = resolveHaivePaths11(root);
8685
+ if (!existsSync34(paths.memoriesDir)) {
8171
8686
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8172
8687
  process.exitCode = 1;
8173
8688
  return;
8174
8689
  }
8175
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8690
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8176
8691
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8177
8692
  const filtered = all.filter((m) => {
8178
8693
  if (!matchesFilters(m, opts)) return false;
@@ -8199,7 +8714,7 @@ function registerMemoryList(memory2) {
8199
8714
  console.log(
8200
8715
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
8201
8716
  );
8202
- console.log(` ${ui.dim(path15.relative(root, filePath))}`);
8717
+ console.log(` ${ui.dim(path17.relative(root, filePath))}`);
8203
8718
  }
8204
8719
  console.log(ui.dim(`
8205
8720
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
@@ -8234,26 +8749,26 @@ function matchesFilters(loaded, opts) {
8234
8749
  }
8235
8750
 
8236
8751
  // 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";
8752
+ import { mkdir as mkdir12, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
8753
+ import { existsSync as existsSync35 } from "fs";
8754
+ import path18 from "path";
8240
8755
  import "commander";
8241
8756
  import {
8242
- findProjectRoot as findProjectRoot14,
8757
+ findProjectRoot as findProjectRoot15,
8243
8758
  memoryFilePath as memoryFilePath7,
8244
- resolveHaivePaths as resolveHaivePaths11,
8245
- serializeMemory as serializeMemory13
8759
+ resolveHaivePaths as resolveHaivePaths12,
8760
+ serializeMemory as serializeMemory14
8246
8761
  } from "@hiveai/core";
8247
8762
  function registerMemoryPromote(memory2) {
8248
8763
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8249
- const root = findProjectRoot14(opts.dir);
8250
- const paths = resolveHaivePaths11(root);
8251
- if (!existsSync33(paths.memoriesDir)) {
8764
+ const root = findProjectRoot15(opts.dir);
8765
+ const paths = resolveHaivePaths12(root);
8766
+ if (!existsSync35(paths.memoriesDir)) {
8252
8767
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8253
8768
  process.exitCode = 1;
8254
8769
  return;
8255
8770
  }
8256
- const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
8771
+ const teamAndModule = await loadMemoriesFromDir26(paths.memoriesDir);
8257
8772
  const alreadyShared = teamAndModule.find(
8258
8773
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
8259
8774
  );
@@ -8267,7 +8782,7 @@ function registerMemoryPromote(memory2) {
8267
8782
  }
8268
8783
  return;
8269
8784
  }
8270
- const all = await loadMemoriesFromDir25(paths.personalDir);
8785
+ const all = await loadMemoriesFromDir26(paths.personalDir);
8271
8786
  const found = all.find((m) => m.memory.frontmatter.id === id);
8272
8787
  if (!found) {
8273
8788
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -8283,35 +8798,35 @@ function registerMemoryPromote(memory2) {
8283
8798
  body: found.memory.body
8284
8799
  };
8285
8800
  const newPath = memoryFilePath7(paths, "team", updated.frontmatter.id);
8286
- await mkdir12(path16.dirname(newPath), { recursive: true });
8287
- await writeFile15(newPath, serializeMemory13(updated), "utf8");
8801
+ await mkdir12(path18.dirname(newPath), { recursive: true });
8802
+ await writeFile17(newPath, serializeMemory14(updated), "utf8");
8288
8803
  await unlink2(found.filePath);
8289
8804
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
8290
- ui.info(`Now at ${path16.relative(root, newPath)}`);
8805
+ ui.info(`Now at ${path18.relative(root, newPath)}`);
8291
8806
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
8292
8807
  });
8293
8808
  }
8294
8809
 
8295
8810
  // 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";
8811
+ import { existsSync as existsSync36 } from "fs";
8812
+ import { writeFile as writeFile18 } from "fs/promises";
8813
+ import path19 from "path";
8299
8814
  import "commander";
8300
8815
  import {
8301
- findProjectRoot as findProjectRoot15,
8302
- resolveHaivePaths as resolveHaivePaths12,
8303
- serializeMemory as serializeMemory14
8816
+ findProjectRoot as findProjectRoot16,
8817
+ resolveHaivePaths as resolveHaivePaths13,
8818
+ serializeMemory as serializeMemory15
8304
8819
  } from "@hiveai/core";
8305
8820
  function registerMemoryApprove(memory2) {
8306
8821
  memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8307
- const root = findProjectRoot15(opts.dir);
8308
- const paths = resolveHaivePaths12(root);
8309
- if (!existsSync34(paths.memoriesDir)) {
8822
+ const root = findProjectRoot16(opts.dir);
8823
+ const paths = resolveHaivePaths13(root);
8824
+ if (!existsSync36(paths.memoriesDir)) {
8310
8825
  ui.error(`No .ai/memories at ${root}.`);
8311
8826
  process.exitCode = 1;
8312
8827
  return;
8313
8828
  }
8314
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8829
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8315
8830
  if (opts.all || opts.pending) {
8316
8831
  const candidates = all.filter((m) => {
8317
8832
  const s = m.memory.frontmatter.status;
@@ -8328,7 +8843,7 @@ function registerMemoryApprove(memory2) {
8328
8843
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
8329
8844
  body: found2.memory.body
8330
8845
  };
8331
- await writeFile16(found2.filePath, serializeMemory14(next2), "utf8");
8846
+ await writeFile18(found2.filePath, serializeMemory15(next2), "utf8");
8332
8847
  count++;
8333
8848
  }
8334
8849
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -8357,32 +8872,32 @@ function registerMemoryApprove(memory2) {
8357
8872
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
8358
8873
  body: found.memory.body
8359
8874
  };
8360
- await writeFile16(found.filePath, serializeMemory14(next), "utf8");
8875
+ await writeFile18(found.filePath, serializeMemory15(next), "utf8");
8361
8876
  ui.success(`Approved ${id} (status=validated)`);
8362
- ui.info(path17.relative(root, found.filePath));
8877
+ ui.info(path19.relative(root, found.filePath));
8363
8878
  });
8364
8879
  }
8365
8880
 
8366
8881
  // 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";
8882
+ import { writeFile as writeFile19 } from "fs/promises";
8883
+ import { existsSync as existsSync37 } from "fs";
8884
+ import path20 from "path";
8370
8885
  import "commander";
8371
8886
  import {
8372
- findProjectRoot as findProjectRoot16,
8373
- resolveHaivePaths as resolveHaivePaths13,
8374
- serializeMemory as serializeMemory15
8887
+ findProjectRoot as findProjectRoot17,
8888
+ resolveHaivePaths as resolveHaivePaths14,
8889
+ serializeMemory as serializeMemory16
8375
8890
  } from "@hiveai/core";
8376
8891
  function registerMemoryUpdate(memory2) {
8377
8892
  memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8378
- const root = findProjectRoot16(opts.dir);
8379
- const paths = resolveHaivePaths13(root);
8380
- if (!existsSync35(paths.memoriesDir)) {
8893
+ const root = findProjectRoot17(opts.dir);
8894
+ const paths = resolveHaivePaths14(root);
8895
+ if (!existsSync37(paths.memoriesDir)) {
8381
8896
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8382
8897
  process.exitCode = 1;
8383
8898
  return;
8384
8899
  }
8385
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8900
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8386
8901
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8387
8902
  if (!loaded) {
8388
8903
  ui.error(`No memory with id "${id}".`);
@@ -8424,12 +8939,12 @@ function registerMemoryUpdate(memory2) {
8424
8939
  ui.warn("Nothing to update \u2014 provide at least one option.");
8425
8940
  return;
8426
8941
  }
8427
- await writeFile17(
8942
+ await writeFile19(
8428
8943
  loaded.filePath,
8429
- serializeMemory15({ frontmatter: newFrontmatter, body: newBody }),
8944
+ serializeMemory16({ frontmatter: newFrontmatter, body: newBody }),
8430
8945
  "utf8"
8431
8946
  );
8432
- ui.success(`Updated ${path18.relative(root, loaded.filePath)}`);
8947
+ ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
8433
8948
  ui.info(`fields: ${updated.join(", ")}`);
8434
8949
  });
8435
8950
  }
@@ -8448,18 +8963,18 @@ function parseCsv3(value) {
8448
8963
  }
8449
8964
 
8450
8965
  // 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";
8966
+ import { writeFile as writeFile20 } from "fs/promises";
8967
+ import { existsSync as existsSync38 } from "fs";
8968
+ import path21 from "path";
8454
8969
  import "commander";
8455
8970
  import {
8456
8971
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
8457
- findProjectRoot as findProjectRoot17,
8458
- getUsage as getUsage11,
8972
+ findProjectRoot as findProjectRoot18,
8973
+ getUsage as getUsage12,
8459
8974
  isAutoPromoteEligible as isAutoPromoteEligible3,
8460
- loadUsageIndex as loadUsageIndex13,
8461
- resolveHaivePaths as resolveHaivePaths14,
8462
- serializeMemory as serializeMemory16
8975
+ loadUsageIndex as loadUsageIndex14,
8976
+ resolveHaivePaths as resolveHaivePaths15,
8977
+ serializeMemory as serializeMemory17
8463
8978
  } from "@hiveai/core";
8464
8979
  function registerMemoryAutoPromote(memory2) {
8465
8980
  memory2.command("auto-promote").description("Promote eligible 'proposed' memories to 'validated' based on usage").option("--min-reads <n>", "minimum read_count to qualify", String(DEFAULT_AUTO_PROMOTE_RULE3.minReads)).option(
@@ -8467,9 +8982,9 @@ function registerMemoryAutoPromote(memory2) {
8467
8982
  "memories with more rejections than this are skipped",
8468
8983
  String(DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8469
8984
  ).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)) {
8985
+ const root = findProjectRoot18(opts.dir);
8986
+ const paths = resolveHaivePaths15(root);
8987
+ if (!existsSync38(paths.memoriesDir)) {
8473
8988
  ui.error(`No .ai/memories at ${root}.`);
8474
8989
  process.exitCode = 1;
8475
8990
  return;
@@ -8478,10 +8993,10 @@ function registerMemoryAutoPromote(memory2) {
8478
8993
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
8479
8994
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
8480
8995
  };
8481
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8482
- const usage = await loadUsageIndex13(paths);
8996
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8997
+ const usage = await loadUsageIndex14(paths);
8483
8998
  const eligible = memories.filter(
8484
- ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
8999
+ ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
8485
9000
  );
8486
9001
  if (eligible.length === 0) {
8487
9002
  ui.info(
@@ -8491,17 +9006,17 @@ function registerMemoryAutoPromote(memory2) {
8491
9006
  }
8492
9007
  let written = 0;
8493
9008
  for (const { memory: mem, filePath } of eligible) {
8494
- const u = getUsage11(usage, mem.frontmatter.id);
9009
+ const u = getUsage12(usage, mem.frontmatter.id);
8495
9010
  console.log(
8496
9011
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8497
9012
  );
8498
- console.log(` ${ui.dim(path19.relative(root, filePath))}`);
9013
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
8499
9014
  if (opts.apply) {
8500
9015
  const next = {
8501
9016
  frontmatter: { ...mem.frontmatter, status: "validated" },
8502
9017
  body: mem.body
8503
9018
  };
8504
- await writeFile18(filePath, serializeMemory16(next), "utf8");
9019
+ await writeFile20(filePath, serializeMemory17(next), "utf8");
8505
9020
  written++;
8506
9021
  }
8507
9022
  }
@@ -8512,25 +9027,25 @@ function registerMemoryAutoPromote(memory2) {
8512
9027
 
8513
9028
  // src/commands/memory-edit.ts
8514
9029
  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";
9030
+ import { existsSync as existsSync39 } from "fs";
9031
+ import { readFile as readFile11 } from "fs/promises";
9032
+ import path23 from "path";
8518
9033
  import "commander";
8519
9034
  import {
8520
- findProjectRoot as findProjectRoot18,
9035
+ findProjectRoot as findProjectRoot19,
8521
9036
  parseMemory,
8522
- resolveHaivePaths as resolveHaivePaths15
9037
+ resolveHaivePaths as resolveHaivePaths16
8523
9038
  } from "@hiveai/core";
8524
9039
  function registerMemoryEdit(memory2) {
8525
9040
  memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8526
- const root = findProjectRoot18(opts.dir);
8527
- const paths = resolveHaivePaths15(root);
8528
- if (!existsSync37(paths.memoriesDir)) {
9041
+ const root = findProjectRoot19(opts.dir);
9042
+ const paths = resolveHaivePaths16(root);
9043
+ if (!existsSync39(paths.memoriesDir)) {
8529
9044
  ui.error(`No .ai/memories at ${root}.`);
8530
9045
  process.exitCode = 1;
8531
9046
  return;
8532
9047
  }
8533
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9048
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8534
9049
  const found = all.find((m) => m.memory.frontmatter.id === id);
8535
9050
  if (!found) {
8536
9051
  ui.error(`No memory with id "${id}".`);
@@ -8538,13 +9053,13 @@ function registerMemoryEdit(memory2) {
8538
9053
  return;
8539
9054
  }
8540
9055
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
8541
- ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
9056
+ ui.info(`Opening ${path23.relative(root, found.filePath)} with ${editor}\u2026`);
8542
9057
  const code = await runEditor(editor, found.filePath);
8543
9058
  if (code !== 0) {
8544
9059
  ui.warn(`Editor exited with status ${code}.`);
8545
9060
  }
8546
9061
  try {
8547
- const fresh = await readFile10(found.filePath, "utf8");
9062
+ const fresh = await readFile11(found.filePath, "utf8");
8548
9063
  parseMemory(fresh);
8549
9064
  ui.success("Memory still parses cleanly.");
8550
9065
  } catch (err) {
@@ -8565,29 +9080,29 @@ function runEditor(editor, file) {
8565
9080
  }
8566
9081
 
8567
9082
  // src/commands/memory-for-files.ts
8568
- import { existsSync as existsSync38 } from "fs";
8569
- import path21 from "path";
9083
+ import { existsSync as existsSync40 } from "fs";
9084
+ import path24 from "path";
8570
9085
  import "commander";
8571
9086
  import {
8572
9087
  deriveConfidence as deriveConfidence9,
8573
- findProjectRoot as findProjectRoot19,
8574
- getUsage as getUsage12,
9088
+ findProjectRoot as findProjectRoot20,
9089
+ getUsage as getUsage13,
8575
9090
  inferModulesFromPaths as inferModulesFromPaths4,
8576
- loadUsageIndex as loadUsageIndex14,
9091
+ loadUsageIndex as loadUsageIndex15,
8577
9092
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths5,
8578
- resolveHaivePaths as resolveHaivePaths16
9093
+ resolveHaivePaths as resolveHaivePaths17
8579
9094
  } from "@hiveai/core";
8580
9095
  function registerMemoryForFiles(memory2) {
8581
9096
  memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
8582
- const root = findProjectRoot19(opts.dir);
8583
- const paths = resolveHaivePaths16(root);
8584
- if (!existsSync38(paths.memoriesDir)) {
9097
+ const root = findProjectRoot20(opts.dir);
9098
+ const paths = resolveHaivePaths17(root);
9099
+ if (!existsSync40(paths.memoriesDir)) {
8585
9100
  ui.error(`No .ai/memories at ${root}.`);
8586
9101
  process.exitCode = 1;
8587
9102
  return;
8588
9103
  }
8589
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8590
- const usage = await loadUsageIndex14(paths);
9104
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9105
+ const usage = await loadUsageIndex15(paths);
8591
9106
  const inferred = inferModulesFromPaths4(files);
8592
9107
  const byAnchor = [];
8593
9108
  const byModule = [];
@@ -8685,44 +9200,44 @@ function printGroup(root, label, loaded, usage) {
8685
9200
  \u2014 ${label} \u2014`));
8686
9201
  for (const { memory: mem, filePath } of loaded) {
8687
9202
  const fm = mem.frontmatter;
8688
- const u = getUsage12(usage, fm.id);
9203
+ const u = getUsage13(usage, fm.id);
8689
9204
  const conf = deriveConfidence9(fm, u);
8690
9205
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
8691
- console.log(` ${ui.dim(path21.relative(root, filePath))}`);
9206
+ console.log(` ${ui.dim(path24.relative(root, filePath))}`);
8692
9207
  }
8693
9208
  }
8694
9209
 
8695
9210
  // src/commands/memory-hot.ts
8696
- import { existsSync as existsSync39 } from "fs";
8697
- import path23 from "path";
9211
+ import { existsSync as existsSync41 } from "fs";
9212
+ import path25 from "path";
8698
9213
  import "commander";
8699
9214
  import {
8700
- findProjectRoot as findProjectRoot20,
8701
- getUsage as getUsage13,
8702
- loadUsageIndex as loadUsageIndex15,
8703
- resolveHaivePaths as resolveHaivePaths17
9215
+ findProjectRoot as findProjectRoot21,
9216
+ getUsage as getUsage14,
9217
+ loadUsageIndex as loadUsageIndex16,
9218
+ resolveHaivePaths as resolveHaivePaths18
8704
9219
  } from "@hiveai/core";
8705
9220
  function registerMemoryHot(memory2) {
8706
9221
  memory2.command("hot").description("List memories actively used but not yet validated (good promotion candidates)").option("--threshold <n>", "minimum read_count to qualify", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8707
- const root = findProjectRoot20(opts.dir);
8708
- const paths = resolveHaivePaths17(root);
8709
- if (!existsSync39(paths.memoriesDir)) {
9222
+ const root = findProjectRoot21(opts.dir);
9223
+ const paths = resolveHaivePaths18(root);
9224
+ if (!existsSync41(paths.memoriesDir)) {
8710
9225
  ui.error(`No .ai/memories at ${root}.`);
8711
9226
  process.exitCode = 1;
8712
9227
  return;
8713
9228
  }
8714
9229
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
8715
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8716
- const usage = await loadUsageIndex15(paths);
9230
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9231
+ const usage = await loadUsageIndex16(paths);
8717
9232
  const candidates = all.filter(({ memory: mem }) => {
8718
9233
  const fm = mem.frontmatter;
8719
9234
  if (opts.status && fm.status !== opts.status) return false;
8720
9235
  if (opts.status === void 0 && fm.status !== "draft" && fm.status !== "proposed") {
8721
9236
  return false;
8722
9237
  }
8723
- return getUsage13(usage, fm.id).read_count >= threshold;
9238
+ return getUsage14(usage, fm.id).read_count >= threshold;
8724
9239
  }).sort(
8725
- (a, b) => getUsage13(usage, b.memory.frontmatter.id).read_count - getUsage13(usage, a.memory.frontmatter.id).read_count
9240
+ (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
8726
9241
  );
8727
9242
  if (candidates.length === 0) {
8728
9243
  ui.info(`No hot memories (threshold=${threshold}).`);
@@ -8730,11 +9245,11 @@ function registerMemoryHot(memory2) {
8730
9245
  }
8731
9246
  for (const { memory: mem, filePath } of candidates) {
8732
9247
  const fm = mem.frontmatter;
8733
- const u = getUsage13(usage, fm.id);
9248
+ const u = getUsage14(usage, fm.id);
8734
9249
  console.log(
8735
9250
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
8736
9251
  );
8737
- console.log(` ${ui.dim(path23.relative(root, filePath))}`);
9252
+ console.log(` ${ui.dim(path25.relative(root, filePath))}`);
8738
9253
  }
8739
9254
  ui.info(
8740
9255
  `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
@@ -8743,16 +9258,16 @@ function registerMemoryHot(memory2) {
8743
9258
  }
8744
9259
 
8745
9260
  // 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";
9261
+ import { mkdir as mkdir13, writeFile as writeFile21 } from "fs/promises";
9262
+ import { existsSync as existsSync43 } from "fs";
9263
+ import path26 from "path";
8749
9264
  import "commander";
8750
9265
  import {
8751
9266
  buildFrontmatter as buildFrontmatter8,
8752
- findProjectRoot as findProjectRoot21,
9267
+ findProjectRoot as findProjectRoot22,
8753
9268
  memoryFilePath as memoryFilePath8,
8754
- resolveHaivePaths as resolveHaivePaths18,
8755
- serializeMemory as serializeMemory17
9269
+ resolveHaivePaths as resolveHaivePaths19,
9270
+ serializeMemory as serializeMemory18
8756
9271
  } from "@hiveai/core";
8757
9272
  function registerMemoryTried(memory2) {
8758
9273
  memory2.command("tried").description(
@@ -8771,9 +9286,9 @@ function registerMemoryTried(memory2) {
8771
9286
  --paths packages/cli/src/index.ts
8772
9287
  `
8773
9288
  ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
8774
- const root = findProjectRoot21(opts.dir);
8775
- const paths = resolveHaivePaths18(root);
8776
- if (!existsSync40(paths.haiveDir)) {
9289
+ const root = findProjectRoot22(opts.dir);
9290
+ const paths = resolveHaivePaths19(root);
9291
+ if (!existsSync43(paths.haiveDir)) {
8777
9292
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8778
9293
  process.exitCode = 1;
8779
9294
  return;
@@ -8796,14 +9311,14 @@ function registerMemoryTried(memory2) {
8796
9311
  }
8797
9312
  const body = lines.join("\n") + "\n";
8798
9313
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
8799
- await mkdir13(path24.dirname(file), { recursive: true });
8800
- if (existsSync40(file)) {
9314
+ await mkdir13(path26.dirname(file), { recursive: true });
9315
+ if (existsSync43(file)) {
8801
9316
  ui.error(`Memory already exists at ${file}`);
8802
9317
  process.exitCode = 1;
8803
9318
  return;
8804
9319
  }
8805
- await writeFile19(file, serializeMemory17({ frontmatter, body }), "utf8");
8806
- ui.success(`Recorded: ${path24.relative(root, file)}`);
9320
+ await writeFile21(file, serializeMemory18({ frontmatter, body }), "utf8");
9321
+ ui.success(`Recorded: ${path26.relative(root, file)}`);
8807
9322
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
8808
9323
  });
8809
9324
  }
@@ -8813,26 +9328,26 @@ function parseCsv4(value) {
8813
9328
  }
8814
9329
 
8815
9330
  // src/commands/memory-pending.ts
8816
- import { existsSync as existsSync41 } from "fs";
8817
- import path25 from "path";
9331
+ import { existsSync as existsSync44 } from "fs";
9332
+ import path27 from "path";
8818
9333
  import "commander";
8819
9334
  import {
8820
- findProjectRoot as findProjectRoot22,
8821
- getUsage as getUsage14,
8822
- loadUsageIndex as loadUsageIndex16,
8823
- resolveHaivePaths as resolveHaivePaths19
9335
+ findProjectRoot as findProjectRoot23,
9336
+ getUsage as getUsage15,
9337
+ loadUsageIndex as loadUsageIndex17,
9338
+ resolveHaivePaths as resolveHaivePaths20
8824
9339
  } from "@hiveai/core";
8825
9340
  function registerMemoryPending(memory2) {
8826
9341
  memory2.command("pending").description("List 'proposed' memories awaiting review (sorted by reads desc)").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
8827
- const root = findProjectRoot22(opts.dir);
8828
- const paths = resolveHaivePaths19(root);
8829
- if (!existsSync41(paths.memoriesDir)) {
9342
+ const root = findProjectRoot23(opts.dir);
9343
+ const paths = resolveHaivePaths20(root);
9344
+ if (!existsSync44(paths.memoriesDir)) {
8830
9345
  ui.error(`No .ai/memories at ${root}.`);
8831
9346
  process.exitCode = 1;
8832
9347
  return;
8833
9348
  }
8834
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8835
- const usage = await loadUsageIndex16(paths);
9349
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9350
+ const usage = await loadUsageIndex17(paths);
8836
9351
  const proposed = all.filter(({ memory: mem }) => {
8837
9352
  if (mem.frontmatter.status !== "proposed") return false;
8838
9353
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -8843,42 +9358,42 @@ function registerMemoryPending(memory2) {
8843
9358
  return;
8844
9359
  }
8845
9360
  proposed.sort(
8846
- (a, b) => getUsage14(usage, b.memory.frontmatter.id).read_count - getUsage14(usage, a.memory.frontmatter.id).read_count
9361
+ (a, b) => getUsage15(usage, b.memory.frontmatter.id).read_count - getUsage15(usage, a.memory.frontmatter.id).read_count
8847
9362
  );
8848
9363
  const now = Date.now();
8849
9364
  for (const { memory: mem, filePath } of proposed) {
8850
9365
  const fm = mem.frontmatter;
8851
- const u = getUsage14(usage, fm.id);
9366
+ const u = getUsage15(usage, fm.id);
8852
9367
  const ageDays = Math.floor((now - new Date(fm.created_at).getTime()) / 864e5);
8853
9368
  const ageStr = ageDays === 0 ? "today" : `${ageDays}d`;
8854
9369
  console.log(
8855
9370
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
8856
9371
  );
8857
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
9372
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
8858
9373
  }
8859
9374
  ui.info(`${proposed.length} pending`);
8860
9375
  });
8861
9376
  }
8862
9377
 
8863
9378
  // src/commands/memory-query.ts
8864
- import { existsSync as existsSync43 } from "fs";
8865
- import path26 from "path";
9379
+ import { existsSync as existsSync45 } from "fs";
9380
+ import path28 from "path";
8866
9381
  import "commander";
8867
9382
  import {
8868
9383
  extractSnippet as extractSnippet2,
8869
- findProjectRoot as findProjectRoot23,
9384
+ findProjectRoot as findProjectRoot24,
8870
9385
  literalMatchesAllTokens as literalMatchesAllTokens3,
8871
9386
  literalMatchesAnyToken as literalMatchesAnyToken4,
8872
9387
  pickSnippetNeedle as pickSnippetNeedle2,
8873
- resolveHaivePaths as resolveHaivePaths20,
9388
+ resolveHaivePaths as resolveHaivePaths21,
8874
9389
  tokenizeQuery as tokenizeQuery6,
8875
9390
  trackReads as trackReads4
8876
9391
  } from "@hiveai/core";
8877
9392
  function registerMemoryQuery(memory2) {
8878
9393
  memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
8879
- const root = findProjectRoot23(opts.dir);
8880
- const paths = resolveHaivePaths20(root);
8881
- if (!existsSync43(paths.memoriesDir)) {
9394
+ const root = findProjectRoot24(opts.dir);
9395
+ const paths = resolveHaivePaths21(root);
9396
+ if (!existsSync45(paths.memoriesDir)) {
8882
9397
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
8883
9398
  process.exitCode = 1;
8884
9399
  return;
@@ -8889,7 +9404,7 @@ function registerMemoryQuery(memory2) {
8889
9404
  return;
8890
9405
  }
8891
9406
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
8892
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9407
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8893
9408
  const passesFilters2 = (mem) => {
8894
9409
  const fm = mem.frontmatter;
8895
9410
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -8919,7 +9434,7 @@ function registerMemoryQuery(memory2) {
8919
9434
  const fm = mem.frontmatter;
8920
9435
  const statusBadge = ui.statusBadge(fm.status);
8921
9436
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
8922
- console.log(` ${ui.dim(path26.relative(root, filePath))}`);
9437
+ console.log(` ${ui.dim(path28.relative(root, filePath))}`);
8923
9438
  const snippet = extractSnippet2(mem.body, snippetNeedle);
8924
9439
  if (snippet) console.log(` ${snippet}`);
8925
9440
  }
@@ -8936,36 +9451,36 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
8936
9451
  }
8937
9452
 
8938
9453
  // src/commands/memory-reject.ts
8939
- import { writeFile as writeFile20 } from "fs/promises";
8940
- import { existsSync as existsSync44 } from "fs";
9454
+ import { writeFile as writeFile23 } from "fs/promises";
9455
+ import { existsSync as existsSync46 } from "fs";
8941
9456
  import "commander";
8942
9457
  import {
8943
- findProjectRoot as findProjectRoot24,
8944
- loadUsageIndex as loadUsageIndex17,
9458
+ findProjectRoot as findProjectRoot25,
9459
+ loadUsageIndex as loadUsageIndex18,
8945
9460
  recordRejection as recordRejection2,
8946
- resolveHaivePaths as resolveHaivePaths21,
9461
+ resolveHaivePaths as resolveHaivePaths22,
8947
9462
  saveUsageIndex as saveUsageIndex3,
8948
- serializeMemory as serializeMemory18
9463
+ serializeMemory as serializeMemory19
8949
9464
  } from "@hiveai/core";
8950
9465
  function registerMemoryReject(memory2) {
8951
9466
  memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
8952
- const root = findProjectRoot24(opts.dir);
8953
- const paths = resolveHaivePaths21(root);
8954
- if (!existsSync44(paths.memoriesDir)) {
9467
+ const root = findProjectRoot25(opts.dir);
9468
+ const paths = resolveHaivePaths22(root);
9469
+ if (!existsSync46(paths.memoriesDir)) {
8955
9470
  ui.error(`No .ai/memories at ${root}.`);
8956
9471
  process.exitCode = 1;
8957
9472
  return;
8958
9473
  }
8959
- const memories = await loadMemoriesFromDir25(paths.memoriesDir);
9474
+ const memories = await loadMemoriesFromDir26(paths.memoriesDir);
8960
9475
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8961
9476
  if (!loaded) {
8962
9477
  ui.error(`No memory with id "${id}".`);
8963
9478
  process.exitCode = 1;
8964
9479
  return;
8965
9480
  }
8966
- await writeFile20(
9481
+ await writeFile23(
8967
9482
  loaded.filePath,
8968
- serializeMemory18({
9483
+ serializeMemory19({
8969
9484
  frontmatter: {
8970
9485
  ...loaded.memory.frontmatter,
8971
9486
  status: "rejected",
@@ -8975,7 +9490,7 @@ function registerMemoryReject(memory2) {
8975
9490
  }),
8976
9491
  "utf8"
8977
9492
  );
8978
- const idx = await loadUsageIndex17(paths);
9493
+ const idx = await loadUsageIndex18(paths);
8979
9494
  recordRejection2(idx, id, opts.reason ?? null);
8980
9495
  await saveUsageIndex3(paths, idx);
8981
9496
  const u = idx.by_id[id];
@@ -8987,34 +9502,34 @@ function registerMemoryReject(memory2) {
8987
9502
  }
8988
9503
 
8989
9504
  // src/commands/memory-rm.ts
8990
- import { existsSync as existsSync45 } from "fs";
9505
+ import { existsSync as existsSync47 } from "fs";
8991
9506
  import { unlink as unlink3 } from "fs/promises";
8992
- import path27 from "path";
9507
+ import path29 from "path";
8993
9508
  import { createInterface as createInterface2 } from "readline/promises";
8994
9509
  import "commander";
8995
9510
  import {
8996
- findProjectRoot as findProjectRoot25,
8997
- loadUsageIndex as loadUsageIndex18,
8998
- resolveHaivePaths as resolveHaivePaths22,
9511
+ findProjectRoot as findProjectRoot26,
9512
+ loadUsageIndex as loadUsageIndex19,
9513
+ resolveHaivePaths as resolveHaivePaths23,
8999
9514
  saveUsageIndex as saveUsageIndex4
9000
9515
  } from "@hiveai/core";
9001
9516
  function registerMemoryRm(memory2) {
9002
9517
  memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9003
- const root = findProjectRoot25(opts.dir);
9004
- const paths = resolveHaivePaths22(root);
9005
- if (!existsSync45(paths.memoriesDir)) {
9518
+ const root = findProjectRoot26(opts.dir);
9519
+ const paths = resolveHaivePaths23(root);
9520
+ if (!existsSync47(paths.memoriesDir)) {
9006
9521
  ui.error(`No .ai/memories at ${root}.`);
9007
9522
  process.exitCode = 1;
9008
9523
  return;
9009
9524
  }
9010
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9525
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9011
9526
  const found = all.find((m) => m.memory.frontmatter.id === id);
9012
9527
  if (!found) {
9013
9528
  ui.error(`No memory with id "${id}".`);
9014
9529
  process.exitCode = 1;
9015
9530
  return;
9016
9531
  }
9017
- const rel = path27.relative(root, found.filePath);
9532
+ const rel = path29.relative(root, found.filePath);
9018
9533
  if (!opts.yes) {
9019
9534
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
9020
9535
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -9027,7 +9542,7 @@ function registerMemoryRm(memory2) {
9027
9542
  await unlink3(found.filePath);
9028
9543
  ui.success(`Deleted ${rel}`);
9029
9544
  if (!opts.keepUsage) {
9030
- const idx = await loadUsageIndex18(paths);
9545
+ const idx = await loadUsageIndex19(paths);
9031
9546
  if (idx.by_id[id]) {
9032
9547
  delete idx.by_id[id];
9033
9548
  await saveUsageIndex4(paths, idx);
@@ -9038,27 +9553,27 @@ function registerMemoryRm(memory2) {
9038
9553
  }
9039
9554
 
9040
9555
  // 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";
9556
+ import { existsSync as existsSync48 } from "fs";
9557
+ import { readFile as readFile12 } from "fs/promises";
9558
+ import path30 from "path";
9044
9559
  import "commander";
9045
9560
  import {
9046
9561
  deriveConfidence as deriveConfidence10,
9047
- findProjectRoot as findProjectRoot26,
9048
- getUsage as getUsage15,
9049
- loadUsageIndex as loadUsageIndex19,
9050
- resolveHaivePaths as resolveHaivePaths23
9562
+ findProjectRoot as findProjectRoot27,
9563
+ getUsage as getUsage16,
9564
+ loadUsageIndex as loadUsageIndex20,
9565
+ resolveHaivePaths as resolveHaivePaths24
9051
9566
  } from "@hiveai/core";
9052
9567
  function registerMemoryShow(memory2) {
9053
9568
  memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
9054
- const root = findProjectRoot26(opts.dir);
9055
- const paths = resolveHaivePaths23(root);
9056
- if (!existsSync46(paths.memoriesDir)) {
9569
+ const root = findProjectRoot27(opts.dir);
9570
+ const paths = resolveHaivePaths24(root);
9571
+ if (!existsSync48(paths.memoriesDir)) {
9057
9572
  ui.error(`No .ai/memories at ${root}.`);
9058
9573
  process.exitCode = 1;
9059
9574
  return;
9060
9575
  }
9061
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9576
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9062
9577
  const found = all.find((m) => m.memory.frontmatter.id === id);
9063
9578
  if (!found) {
9064
9579
  ui.error(`No memory with id "${id}".`);
@@ -9066,12 +9581,12 @@ function registerMemoryShow(memory2) {
9066
9581
  return;
9067
9582
  }
9068
9583
  if (opts.raw) {
9069
- console.log(await readFile11(found.filePath, "utf8"));
9584
+ console.log(await readFile12(found.filePath, "utf8"));
9070
9585
  return;
9071
9586
  }
9072
9587
  const fm = found.memory.frontmatter;
9073
- const usage = await loadUsageIndex19(paths);
9074
- const u = getUsage15(usage, fm.id);
9588
+ const usage = await loadUsageIndex20(paths);
9589
+ const u = getUsage16(usage, fm.id);
9075
9590
  const conf = deriveConfidence10(fm, u);
9076
9591
  console.log(ui.bold(fm.id));
9077
9592
  console.log(`${ui.dim("scope:")} ${fm.scope}${fm.module ? ` / ${fm.module}` : ""}`);
@@ -9082,7 +9597,7 @@ function registerMemoryShow(memory2) {
9082
9597
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
9083
9598
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
9084
9599
  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)}`);
9600
+ console.log(`${ui.dim("file:")} ${path30.relative(root, found.filePath)}`);
9086
9601
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
9087
9602
  console.log(ui.dim("anchor:"));
9088
9603
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -9097,38 +9612,38 @@ function registerMemoryShow(memory2) {
9097
9612
  }
9098
9613
 
9099
9614
  // src/commands/memory-stats.ts
9100
- import { existsSync as existsSync47 } from "fs";
9101
- import path29 from "path";
9615
+ import { existsSync as existsSync49 } from "fs";
9616
+ import path31 from "path";
9102
9617
  import "commander";
9103
9618
  import {
9104
9619
  deriveConfidence as deriveConfidence11,
9105
- findProjectRoot as findProjectRoot27,
9106
- getUsage as getUsage16,
9107
- loadUsageIndex as loadUsageIndex20,
9108
- resolveHaivePaths as resolveHaivePaths24
9620
+ findProjectRoot as findProjectRoot28,
9621
+ getUsage as getUsage17,
9622
+ loadUsageIndex as loadUsageIndex21,
9623
+ resolveHaivePaths as resolveHaivePaths25
9109
9624
  } from "@hiveai/core";
9110
9625
  function registerMemoryStats(memory2) {
9111
9626
  memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
9112
- const root = findProjectRoot27(opts.dir);
9113
- const paths = resolveHaivePaths24(root);
9114
- if (!existsSync47(paths.memoriesDir)) {
9627
+ const root = findProjectRoot28(opts.dir);
9628
+ const paths = resolveHaivePaths25(root);
9629
+ if (!existsSync49(paths.memoriesDir)) {
9115
9630
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9116
9631
  process.exitCode = 1;
9117
9632
  return;
9118
9633
  }
9119
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9120
- const usage = await loadUsageIndex20(paths);
9634
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9635
+ const usage = await loadUsageIndex21(paths);
9121
9636
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
9122
9637
  if (target.length === 0) {
9123
9638
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
9124
9639
  return;
9125
9640
  }
9126
9641
  target.sort(
9127
- (a, b) => getUsage16(usage, b.memory.frontmatter.id).read_count - getUsage16(usage, a.memory.frontmatter.id).read_count
9642
+ (a, b) => getUsage17(usage, b.memory.frontmatter.id).read_count - getUsage17(usage, a.memory.frontmatter.id).read_count
9128
9643
  );
9129
9644
  for (const { memory: mem, filePath } of target) {
9130
9645
  const fm = mem.frontmatter;
9131
- const u = getUsage16(usage, fm.id);
9646
+ const u = getUsage17(usage, fm.id);
9132
9647
  const conf = deriveConfidence11(fm, u);
9133
9648
  console.log(
9134
9649
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`
@@ -9136,34 +9651,34 @@ function registerMemoryStats(memory2) {
9136
9651
  console.log(
9137
9652
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
9138
9653
  );
9139
- console.log(` ${ui.dim(path29.relative(root, filePath))}`);
9654
+ console.log(` ${ui.dim(path31.relative(root, filePath))}`);
9140
9655
  }
9141
9656
  });
9142
9657
  }
9143
9658
 
9144
9659
  // 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";
9660
+ import { writeFile as writeFile24 } from "fs/promises";
9661
+ import { existsSync as existsSync50 } from "fs";
9662
+ import path33 from "path";
9148
9663
  import "commander";
9149
9664
  import {
9150
- findProjectRoot as findProjectRoot28,
9151
- resolveHaivePaths as resolveHaivePaths25,
9152
- serializeMemory as serializeMemory19,
9665
+ findProjectRoot as findProjectRoot29,
9666
+ resolveHaivePaths as resolveHaivePaths26,
9667
+ serializeMemory as serializeMemory20,
9153
9668
  verifyAnchor as verifyAnchor3
9154
9669
  } from "@hiveai/core";
9155
9670
  function registerMemoryVerify(memory2) {
9156
9671
  memory2.command("verify").description(
9157
9672
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
9158
9673
  ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
9159
- const root = findProjectRoot28(opts.dir);
9160
- const paths = resolveHaivePaths25(root);
9161
- if (!existsSync48(paths.memoriesDir)) {
9674
+ const root = findProjectRoot29(opts.dir);
9675
+ const paths = resolveHaivePaths26(root);
9676
+ if (!existsSync50(paths.memoriesDir)) {
9162
9677
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9163
9678
  process.exitCode = 1;
9164
9679
  return;
9165
9680
  }
9166
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
9681
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
9167
9682
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
9168
9683
  if (opts.id && targets.length === 0) {
9169
9684
  ui.error(`No memory with id "${opts.id}".`);
@@ -9181,7 +9696,7 @@ function registerMemoryVerify(memory2) {
9181
9696
  anchorlessIds.push(mem.frontmatter.id);
9182
9697
  continue;
9183
9698
  }
9184
- const rel = path30.relative(root, filePath);
9699
+ const rel = path33.relative(root, filePath);
9185
9700
  if (result.stale) {
9186
9701
  staleCount++;
9187
9702
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -9196,7 +9711,7 @@ function registerMemoryVerify(memory2) {
9196
9711
  }
9197
9712
  if (opts.update) {
9198
9713
  const next = applyVerification2(mem, result);
9199
- await writeFile21(filePath, serializeMemory19(next), "utf8");
9714
+ await writeFile24(filePath, serializeMemory20(next), "utf8");
9200
9715
  updated++;
9201
9716
  }
9202
9717
  }
@@ -9244,30 +9759,30 @@ function applyVerification2(mem, result) {
9244
9759
  }
9245
9760
 
9246
9761
  // src/commands/memory-import.ts
9247
- import { readFile as readFile12 } from "fs/promises";
9248
- import { existsSync as existsSync49 } from "fs";
9762
+ import { readFile as readFile13 } from "fs/promises";
9763
+ import { existsSync as existsSync51 } from "fs";
9249
9764
  import "commander";
9250
9765
  import {
9251
- findProjectRoot as findProjectRoot29,
9252
- resolveHaivePaths as resolveHaivePaths26
9766
+ findProjectRoot as findProjectRoot30,
9767
+ resolveHaivePaths as resolveHaivePaths27
9253
9768
  } from "@hiveai/core";
9254
9769
  function registerMemoryImport(memory2) {
9255
9770
  memory2.command("import").description(
9256
9771
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
9257
9772
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
9258
- const root = findProjectRoot29(opts.dir);
9259
- const paths = resolveHaivePaths26(root);
9260
- if (!existsSync49(paths.haiveDir)) {
9773
+ const root = findProjectRoot30(opts.dir);
9774
+ const paths = resolveHaivePaths27(root);
9775
+ if (!existsSync51(paths.haiveDir)) {
9261
9776
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9262
9777
  process.exitCode = 1;
9263
9778
  return;
9264
9779
  }
9265
- if (!existsSync49(opts.from)) {
9780
+ if (!existsSync51(opts.from)) {
9266
9781
  ui.error(`File not found: ${opts.from}`);
9267
9782
  process.exitCode = 1;
9268
9783
  return;
9269
9784
  }
9270
- const content = await readFile12(opts.from, "utf8");
9785
+ const content = await readFile13(opts.from, "utf8");
9271
9786
  const scope = opts.scope ?? "team";
9272
9787
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
9273
9788
  ui.info(`Content length: ${content.length} chars`);
@@ -9295,15 +9810,15 @@ function registerMemoryImport(memory2) {
9295
9810
  }
9296
9811
 
9297
9812
  // 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";
9813
+ import { existsSync as existsSync53 } from "fs";
9814
+ import { readFile as readFile14, mkdir as mkdir14, writeFile as writeFile25 } from "fs/promises";
9815
+ import path34 from "path";
9301
9816
  import "commander";
9302
9817
  import {
9303
9818
  buildFrontmatter as buildFrontmatter9,
9304
- findProjectRoot as findProjectRoot30,
9305
- resolveHaivePaths as resolveHaivePaths27,
9306
- serializeMemory as serializeMemory20
9819
+ findProjectRoot as findProjectRoot31,
9820
+ resolveHaivePaths as resolveHaivePaths28,
9821
+ serializeMemory as serializeMemory21
9307
9822
  } from "@hiveai/core";
9308
9823
  function parseChangelog(content) {
9309
9824
  const entries = [];
@@ -9367,15 +9882,15 @@ function registerMemoryImportChangelog(memory2) {
9367
9882
  "--versions <csv>",
9368
9883
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
9369
9884
  ).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)) {
9885
+ const root = findProjectRoot31(opts.dir);
9886
+ const paths = resolveHaivePaths28(root);
9887
+ const changelogPath = path34.resolve(root, opts.fromChangelog);
9888
+ if (!existsSync53(changelogPath)) {
9374
9889
  ui.error(`CHANGELOG not found: ${changelogPath}`);
9375
9890
  process.exitCode = 1;
9376
9891
  return;
9377
9892
  }
9378
- const content = await readFile13(changelogPath, "utf8");
9893
+ const content = await readFile14(changelogPath, "utf8");
9379
9894
  let entries = parseChangelog(content);
9380
9895
  if (entries.length === 0) {
9381
9896
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -9390,9 +9905,9 @@ function registerMemoryImportChangelog(memory2) {
9390
9905
  entries = entries.filter((e) => requested.includes(e.version));
9391
9906
  }
9392
9907
  }
9393
- const pkgName = opts.package ?? path31.basename(path31.dirname(changelogPath));
9908
+ const pkgName = opts.package ?? path34.basename(path34.dirname(changelogPath));
9394
9909
  const scope = opts.scope ?? "team";
9395
- const teamDir = path31.join(paths.memoriesDir, scope);
9910
+ const teamDir = path34.join(paths.memoriesDir, scope);
9396
9911
  await mkdir14(teamDir, { recursive: true });
9397
9912
  let saved = 0;
9398
9913
  for (const entry of entries) {
@@ -9415,7 +9930,7 @@ function registerMemoryImportChangelog(memory2) {
9415
9930
  lines.push("");
9416
9931
  }
9417
9932
  lines.push(
9418
- `**Source:** \`${path31.relative(root, changelogPath)}\`
9933
+ `**Source:** \`${path34.relative(root, changelogPath)}\`
9419
9934
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
9420
9935
  );
9421
9936
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -9430,12 +9945,12 @@ function registerMemoryImportChangelog(memory2) {
9430
9945
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
9431
9946
  `v${entry.version}`
9432
9947
  ],
9433
- paths: [path31.relative(root, changelogPath)],
9948
+ paths: [path34.relative(root, changelogPath)],
9434
9949
  topic: `changelog-${pkgName}-${entry.version}`
9435
9950
  });
9436
- await writeFile23(
9437
- path31.join(teamDir, `${fm.id}.md`),
9438
- serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
9951
+ await writeFile25(
9952
+ path34.join(teamDir, `${fm.id}.md`),
9953
+ serializeMemory21({ frontmatter: fm, body: lines.join("\n") }),
9439
9954
  "utf8"
9440
9955
  );
9441
9956
  console.log(ui.green(` \u2713 ${fm.id}`));
@@ -9457,17 +9972,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
9457
9972
  }
9458
9973
 
9459
9974
  // 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";
9975
+ import { existsSync as existsSync54 } from "fs";
9976
+ import { writeFile as writeFile26 } from "fs/promises";
9977
+ import path35 from "path";
9463
9978
  import "commander";
9464
9979
  import {
9465
9980
  deriveConfidence as deriveConfidence12,
9466
- findProjectRoot as findProjectRoot31,
9467
- getUsage as getUsage17,
9468
- loadMemoriesFromDir as loadMemoriesFromDir26,
9469
- loadUsageIndex as loadUsageIndex21,
9470
- resolveHaivePaths as resolveHaivePaths28
9981
+ findProjectRoot as findProjectRoot32,
9982
+ getUsage as getUsage18,
9983
+ loadMemoriesFromDir as loadMemoriesFromDir27,
9984
+ loadUsageIndex as loadUsageIndex23,
9985
+ resolveHaivePaths as resolveHaivePaths29
9471
9986
  } from "@hiveai/core";
9472
9987
  var CONFIDENCE_EMOJI = {
9473
9988
  unverified: "\u2B1C",
@@ -9480,9 +9995,9 @@ function registerMemoryDigest(program2) {
9480
9995
  program2.command("digest").description(
9481
9996
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
9482
9997
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
9483
- const root = findProjectRoot31(opts.dir);
9484
- const paths = resolveHaivePaths28(root);
9485
- if (!existsSync51(paths.memoriesDir)) {
9998
+ const root = findProjectRoot32(opts.dir);
9999
+ const paths = resolveHaivePaths29(root);
10000
+ if (!existsSync54(paths.memoriesDir)) {
9486
10001
  ui.error("No .ai/memories found. Run `haive init` first.");
9487
10002
  process.exitCode = 1;
9488
10003
  return;
@@ -9490,8 +10005,8 @@ function registerMemoryDigest(program2) {
9490
10005
  const days = Math.max(1, Number(opts.days ?? 7));
9491
10006
  const scopeFilter = opts.scope ?? "team";
9492
10007
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
9493
- const all = await loadMemoriesFromDir26(paths.memoriesDir);
9494
- const usage = await loadUsageIndex21(paths);
10008
+ const all = await loadMemoriesFromDir27(paths.memoriesDir);
10009
+ const usage = await loadUsageIndex23(paths);
9495
10010
  const recent = all.filter(({ memory: mem }) => {
9496
10011
  const fm = mem.frontmatter;
9497
10012
  if (fm.type === "session_recap") return false;
@@ -9522,7 +10037,7 @@ function registerMemoryDigest(program2) {
9522
10037
  lines.push(``);
9523
10038
  for (const { memory: mem } of mems) {
9524
10039
  const fm = mem.frontmatter;
9525
- const u = getUsage17(usage, fm.id);
10040
+ const u = getUsage18(usage, fm.id);
9526
10041
  const confidence = deriveConfidence12(fm, u);
9527
10042
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
9528
10043
  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 +10069,8 @@ function registerMemoryDigest(program2) {
9554
10069
  );
9555
10070
  const digest = lines.join("\n");
9556
10071
  if (opts.out) {
9557
- const outPath = path33.resolve(process.cwd(), opts.out);
9558
- await writeFile24(outPath, digest, "utf8");
10072
+ const outPath = path35.resolve(process.cwd(), opts.out);
10073
+ await writeFile26(outPath, digest, "utf8");
9559
10074
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
9560
10075
  } else {
9561
10076
  console.log(digest);
@@ -9564,22 +10079,22 @@ function registerMemoryDigest(program2) {
9564
10079
  }
9565
10080
 
9566
10081
  // 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";
10082
+ import { writeFile as writeFile27, mkdir as mkdir15, readFile as readFile15, rm as rm2 } from "fs/promises";
10083
+ import { existsSync as existsSync55 } from "fs";
10084
+ import path36 from "path";
9570
10085
  import "commander";
9571
10086
  import {
9572
10087
  buildFrontmatter as buildFrontmatter10,
9573
- findProjectRoot as findProjectRoot32,
9574
- loadMemoriesFromDir as loadMemoriesFromDir27,
10088
+ findProjectRoot as findProjectRoot33,
10089
+ loadMemoriesFromDir as loadMemoriesFromDir28,
9575
10090
  memoryFilePath as memoryFilePath9,
9576
- resolveHaivePaths as resolveHaivePaths29,
9577
- serializeMemory as serializeMemory21
10091
+ resolveHaivePaths as resolveHaivePaths30,
10092
+ serializeMemory as serializeMemory23
9578
10093
  } from "@hiveai/core";
9579
10094
  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(() => "");
10095
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10096
+ if (!existsSync55(obsFile)) return null;
10097
+ const raw = await readFile15(obsFile, "utf8").catch(() => "");
9583
10098
  if (!raw.trim()) return null;
9584
10099
  const lines = raw.split("\n").filter(Boolean);
9585
10100
  const obs = [];
@@ -9657,9 +10172,9 @@ function registerSessionEnd(session2) {
9657
10172
  --next "Add integration tests for webhook signature validation"
9658
10173
  `
9659
10174
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9660
- const root = findProjectRoot32(opts.dir);
9661
- const paths = resolveHaivePaths29(root);
9662
- if (!existsSync53(paths.haiveDir)) {
10175
+ const root = findProjectRoot33(opts.dir);
10176
+ const paths = resolveHaivePaths30(root);
10177
+ if (!existsSync55(paths.haiveDir)) {
9663
10178
  if (opts.auto || opts.quiet) return;
9664
10179
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
9665
10180
  process.exitCode = 1;
@@ -9691,19 +10206,19 @@ function registerSessionEnd(session2) {
9691
10206
  });
9692
10207
  const topic = recapTopic2(scope, opts.module);
9693
10208
  const filesTouched = parseCsv5(resolvedFiles);
9694
- const missingPaths = filesTouched.filter((p) => !existsSync53(path34.resolve(root, p)));
10209
+ const missingPaths = filesTouched.filter((p) => !existsSync55(path36.resolve(root, p)));
9695
10210
  if (missingPaths.length > 0 && !opts.quiet) {
9696
10211
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
9697
10212
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
9698
10213
  }
9699
10214
  const cleanupObservations = async () => {
9700
10215
  if (!opts.auto) return;
9701
- const obsFile = path34.join(paths.haiveDir, ".cache", "observations.jsonl");
9702
- if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
10216
+ const obsFile = path36.join(paths.haiveDir, ".cache", "observations.jsonl");
10217
+ if (existsSync55(obsFile)) await rm2(obsFile).catch(() => {
9703
10218
  });
9704
10219
  };
9705
- if (existsSync53(paths.memoriesDir)) {
9706
- const existing = await loadMemoriesFromDir27(paths.memoriesDir);
10220
+ if (existsSync55(paths.memoriesDir)) {
10221
+ const existing = await loadMemoriesFromDir28(paths.memoriesDir);
9707
10222
  const topicMatch = existing.find(
9708
10223
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
9709
10224
  );
@@ -9719,11 +10234,11 @@ function registerSessionEnd(session2) {
9719
10234
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
9720
10235
  }
9721
10236
  };
9722
- await writeFile25(topicMatch.filePath, serializeMemory21({ frontmatter: newFrontmatter, body }), "utf8");
10237
+ await writeFile27(topicMatch.filePath, serializeMemory23({ frontmatter: newFrontmatter, body }), "utf8");
9723
10238
  await cleanupObservations();
9724
10239
  if (!opts.quiet) {
9725
10240
  ui.success(`Session recap updated (revision #${revisionCount})`);
9726
- ui.info(`id=${fm.id} file=${path34.relative(root, topicMatch.filePath)}`);
10241
+ ui.info(`id=${fm.id} file=${path36.relative(root, topicMatch.filePath)}`);
9727
10242
  ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
9728
10243
  }
9729
10244
  return;
@@ -9740,12 +10255,12 @@ function registerSessionEnd(session2) {
9740
10255
  status: "validated"
9741
10256
  });
9742
10257
  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");
10258
+ await mkdir15(path36.dirname(file), { recursive: true });
10259
+ await writeFile27(file, serializeMemory23({ frontmatter, body }), "utf8");
9745
10260
  await cleanupObservations();
9746
10261
  if (!opts.quiet) {
9747
10262
  ui.success(`Session recap created`);
9748
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path34.relative(root, file)}`);
10263
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path36.relative(root, file)}`);
9749
10264
  ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
9750
10265
  ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
9751
10266
  }
@@ -9757,15 +10272,15 @@ function parseCsv5(value) {
9757
10272
  }
9758
10273
 
9759
10274
  // src/commands/snapshot.ts
9760
- import { existsSync as existsSync54 } from "fs";
10275
+ import { existsSync as existsSync56 } from "fs";
9761
10276
  import { readdir as readdir4 } from "fs/promises";
9762
- import path35 from "path";
10277
+ import path37 from "path";
9763
10278
  import "commander";
9764
10279
  import {
9765
10280
  diffContract,
9766
- findProjectRoot as findProjectRoot33,
9767
- loadConfig as loadConfig5,
9768
- resolveHaivePaths as resolveHaivePaths30,
10281
+ findProjectRoot as findProjectRoot34,
10282
+ loadConfig as loadConfig7,
10283
+ resolveHaivePaths as resolveHaivePaths31,
9769
10284
  snapshotContract
9770
10285
  } from "@hiveai/core";
9771
10286
  function registerSnapshot(program2) {
@@ -9790,16 +10305,16 @@ function registerSnapshot(program2) {
9790
10305
  "--format <format>",
9791
10306
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
9792
10307
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
9793
- const root = findProjectRoot33(opts.dir);
9794
- const paths = resolveHaivePaths30(root);
9795
- if (!existsSync54(paths.haiveDir)) {
10308
+ const root = findProjectRoot34(opts.dir);
10309
+ const paths = resolveHaivePaths31(root);
10310
+ if (!existsSync56(paths.haiveDir)) {
9796
10311
  ui.error("No .ai/ found. Run `haive init` first.");
9797
10312
  process.exitCode = 1;
9798
10313
  return;
9799
10314
  }
9800
10315
  if (opts.list) {
9801
- const contractsDir = path35.join(paths.haiveDir, "contracts");
9802
- if (!existsSync54(contractsDir)) {
10316
+ const contractsDir = path37.join(paths.haiveDir, "contracts");
10317
+ if (!existsSync56(contractsDir)) {
9803
10318
  console.log(ui.dim("No contract snapshots found."));
9804
10319
  return;
9805
10320
  }
@@ -9819,7 +10334,7 @@ function registerSnapshot(program2) {
9819
10334
  }
9820
10335
  if (opts.diff) {
9821
10336
  if (!opts.name) {
9822
- const config2 = await loadConfig5(paths);
10337
+ const config2 = await loadConfig7(paths);
9823
10338
  const contracts = config2.contractFiles ?? [];
9824
10339
  if (contracts.length === 0) {
9825
10340
  ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
@@ -9831,7 +10346,7 @@ function registerSnapshot(program2) {
9831
10346
  }
9832
10347
  return;
9833
10348
  }
9834
- const config = await loadConfig5(paths);
10349
+ const config = await loadConfig7(paths);
9835
10350
  const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
9836
10351
  if (!configured && !opts.contract) {
9837
10352
  ui.error(
@@ -9854,7 +10369,7 @@ function registerSnapshot(program2) {
9854
10369
  return;
9855
10370
  }
9856
10371
  const contractPath = opts.contract;
9857
- const name = opts.name ?? path35.basename(contractPath, path35.extname(contractPath));
10372
+ const name = opts.name ?? path37.basename(contractPath, path37.extname(contractPath));
9858
10373
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
9859
10374
  const contract = { name, path: contractPath, format };
9860
10375
  try {
@@ -9909,8 +10424,8 @@ async function runDiff(root, haiveDir, contract) {
9909
10424
  }
9910
10425
  }
9911
10426
  function detectFormat(filePath) {
9912
- const ext = path35.extname(filePath).toLowerCase();
9913
- const base = path35.basename(filePath).toLowerCase();
10427
+ const ext = path37.extname(filePath).toLowerCase();
10428
+ const base = path37.basename(filePath).toLowerCase();
9914
10429
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
9915
10430
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
9916
10431
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -9923,18 +10438,18 @@ function detectFormat(filePath) {
9923
10438
  }
9924
10439
 
9925
10440
  // 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";
10441
+ import { existsSync as existsSync57 } from "fs";
10442
+ import { mkdir as mkdir16, readFile as readFile16, writeFile as writeFile28, copyFile } from "fs/promises";
10443
+ import path38 from "path";
10444
+ import { spawnSync as spawnSync5 } from "child_process";
9930
10445
  import "commander";
9931
10446
  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
10447
+ findProjectRoot as findProjectRoot35,
10448
+ loadConfig as loadConfig8,
10449
+ loadMemoriesFromDir as loadMemoriesFromDir29,
10450
+ resolveHaivePaths as resolveHaivePaths32,
10451
+ saveConfig as saveConfig3,
10452
+ serializeMemory as serializeMemory24
9938
10453
  } from "@hiveai/core";
9939
10454
  function registerHub(program2) {
9940
10455
  const hub = program2.command("hub").description(
@@ -9944,21 +10459,21 @@ function registerHub(program2) {
9944
10459
  hub.command("init <hubPath>").description(
9945
10460
  "Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
9946
10461
  ).action(async (hubPath) => {
9947
- const absPath = path36.resolve(hubPath);
10462
+ const absPath = path38.resolve(hubPath);
9948
10463
  await mkdir16(absPath, { recursive: true });
9949
- const gitCheck = spawnSync4("git", ["rev-parse", "--git-dir"], { cwd: absPath });
10464
+ const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
9950
10465
  if (gitCheck.status !== 0) {
9951
- const init = spawnSync4("git", ["init"], { cwd: absPath, encoding: "utf8" });
10466
+ const init = spawnSync5("git", ["init"], { cwd: absPath, encoding: "utf8" });
9952
10467
  if (init.status !== 0) {
9953
10468
  ui.error(`git init failed: ${init.stderr}`);
9954
10469
  process.exitCode = 1;
9955
10470
  return;
9956
10471
  }
9957
10472
  }
9958
- const sharedDir = path36.join(absPath, ".ai", "memories", "shared");
10473
+ const sharedDir = path38.join(absPath, ".ai", "memories", "shared");
9959
10474
  await mkdir16(sharedDir, { recursive: true });
9960
- await writeFile26(
9961
- path36.join(absPath, ".ai", "README.md"),
10475
+ await writeFile28(
10476
+ path38.join(absPath, ".ai", "README.md"),
9962
10477
  `# hAIve Team Knowledge Hub
9963
10478
 
9964
10479
  This repo is a shared knowledge hub for hAIve.
@@ -9979,13 +10494,13 @@ haive hub pull # import into a project
9979
10494
  `,
9980
10495
  "utf8"
9981
10496
  );
9982
- await writeFile26(
9983
- path36.join(absPath, ".gitignore"),
10497
+ await writeFile28(
10498
+ path38.join(absPath, ".gitignore"),
9984
10499
  ".ai/.cache/\n.ai/memories/personal/\n",
9985
10500
  "utf8"
9986
10501
  );
9987
- spawnSync4("git", ["add", "."], { cwd: absPath });
9988
- spawnSync4("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
10502
+ spawnSync5("git", ["add", "."], { cwd: absPath });
10503
+ spawnSync5("git", ["commit", "-m", "chore: initialize hAIve team-knowledge hub"], {
9989
10504
  cwd: absPath,
9990
10505
  encoding: "utf8"
9991
10506
  });
@@ -9995,7 +10510,7 @@ haive hub pull # import into a project
9995
10510
  `
9996
10511
  Next steps:
9997
10512
  1. Add hubPath to your project's .ai/haive.config.json:
9998
- { "hubPath": "${path36.relative(process.cwd(), absPath)}" }
10513
+ { "hubPath": "${path38.relative(process.cwd(), absPath)}" }
9999
10514
  2. Run \`haive hub push\` to publish your shared memories
10000
10515
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
10001
10516
  `
@@ -10014,9 +10529,9 @@ Next steps:
10014
10529
  haive hub push --commit --message "feat: add payment API contract memories"
10015
10530
  `
10016
10531
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
10017
- const root = findProjectRoot34(opts.dir);
10018
- const paths = resolveHaivePaths31(root);
10019
- const config = await loadConfig6(paths);
10532
+ const root = findProjectRoot35(opts.dir);
10533
+ const paths = resolveHaivePaths32(root);
10534
+ const config = await loadConfig8(paths);
10020
10535
  if (!config.hubPath) {
10021
10536
  ui.error(
10022
10537
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -10024,16 +10539,16 @@ Next steps:
10024
10539
  process.exitCode = 1;
10025
10540
  return;
10026
10541
  }
10027
- const hubRoot = path36.resolve(root, config.hubPath);
10028
- if (!existsSync55(hubRoot)) {
10542
+ const hubRoot = path38.resolve(root, config.hubPath);
10543
+ if (!existsSync57(hubRoot)) {
10029
10544
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
10030
10545
  process.exitCode = 1;
10031
10546
  return;
10032
10547
  }
10033
- const projectName = path36.basename(root);
10034
- const destDir = path36.join(hubRoot, ".ai", "memories", "shared", projectName);
10548
+ const projectName = path38.basename(root);
10549
+ const destDir = path38.join(hubRoot, ".ai", "memories", "shared", projectName);
10035
10550
  await mkdir16(destDir, { recursive: true });
10036
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10551
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
10037
10552
  const shared = all.filter(
10038
10553
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
10039
10554
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -10050,18 +10565,18 @@ Next steps:
10050
10565
  for (const { memory: memory2 } of shared) {
10051
10566
  const fm = memory2.frontmatter;
10052
10567
  const fileName = `${fm.id}.md`;
10053
- const destPath = path36.join(destDir, fileName);
10054
- await writeFile26(destPath, serializeMemory23(memory2), "utf8");
10568
+ const destPath = path38.join(destDir, fileName);
10569
+ await writeFile28(destPath, serializeMemory24(memory2), "utf8");
10055
10570
  pushed++;
10056
10571
  }
10057
10572
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
10058
10573
  console.log(ui.dim(` Location: ${destDir}`));
10059
10574
  if (opts.commit) {
10060
10575
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
10061
- spawnSync4("git", ["add", path36.join(".ai", "memories", "shared", projectName)], {
10576
+ spawnSync5("git", ["add", path38.join(".ai", "memories", "shared", projectName)], {
10062
10577
  cwd: hubRoot
10063
10578
  });
10064
- const commit = spawnSync4("git", ["commit", "-m", message], {
10579
+ const commit = spawnSync5("git", ["commit", "-m", message], {
10065
10580
  cwd: hubRoot,
10066
10581
  encoding: "utf8"
10067
10582
  });
@@ -10083,9 +10598,9 @@ Next steps:
10083
10598
  hub.command("pull").description(
10084
10599
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
10085
10600
  ).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);
10601
+ const root = findProjectRoot35(opts.dir);
10602
+ const paths = resolveHaivePaths32(root);
10603
+ const config = await loadConfig8(paths);
10089
10604
  if (!config.hubPath) {
10090
10605
  ui.error(
10091
10606
  'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
@@ -10093,13 +10608,13 @@ Next steps:
10093
10608
  process.exitCode = 1;
10094
10609
  return;
10095
10610
  }
10096
- const hubRoot = path36.resolve(root, config.hubPath);
10097
- const hubSharedDir = path36.join(hubRoot, ".ai", "memories", "shared");
10098
- if (!existsSync55(hubSharedDir)) {
10611
+ const hubRoot = path38.resolve(root, config.hubPath);
10612
+ const hubSharedDir = path38.join(hubRoot, ".ai", "memories", "shared");
10613
+ if (!existsSync57(hubSharedDir)) {
10099
10614
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
10100
10615
  return;
10101
10616
  }
10102
- const projectName = path36.basename(root);
10617
+ const projectName = path38.basename(root);
10103
10618
  const { readdir: readdir6 } = await import("fs/promises");
10104
10619
  const projectDirs = (await readdir6(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
10105
10620
  if (projectDirs.length === 0) {
@@ -10109,17 +10624,17 @@ Next steps:
10109
10624
  let totalImported = 0;
10110
10625
  let totalUpdated = 0;
10111
10626
  for (const sourceName of projectDirs) {
10112
- const sourceDir = path36.join(hubSharedDir, sourceName);
10113
- const destDir = path36.join(paths.memoriesDir, "shared", sourceName);
10627
+ const sourceDir = path38.join(hubSharedDir, sourceName);
10628
+ const destDir = path38.join(paths.memoriesDir, "shared", sourceName);
10114
10629
  await mkdir16(destDir, { recursive: true });
10115
10630
  const sourceFiles = (await readdir6(sourceDir)).filter((f) => f.endsWith(".md"));
10116
10631
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
10117
10632
  const existingInDest = await loadDir(destDir);
10118
10633
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
10119
10634
  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");
10635
+ const srcPath = path38.join(sourceDir, file);
10636
+ const destPath = path38.join(destDir, file);
10637
+ const fileContent = await readFile16(srcPath, "utf8");
10123
10638
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
10124
10639
  if (!alreadyTagged) {
10125
10640
  await copyFile(srcPath, destPath);
@@ -10142,27 +10657,27 @@ Next steps:
10142
10657
  );
10143
10658
  });
10144
10659
  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);
10660
+ const root = findProjectRoot35(opts.dir);
10661
+ const paths = resolveHaivePaths32(root);
10662
+ const config = await loadConfig8(paths);
10148
10663
  console.log(ui.bold("Hub status"));
10149
10664
  console.log(
10150
10665
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
10151
10666
  );
10152
- const sharedDir = path36.join(paths.memoriesDir, "shared");
10153
- if (existsSync55(sharedDir)) {
10667
+ const sharedDir = path38.join(paths.memoriesDir, "shared");
10668
+ if (existsSync57(sharedDir)) {
10154
10669
  const { readdir: readdir6 } = await import("fs/promises");
10155
10670
  const sources = (await readdir6(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
10156
10671
  console.log(`
10157
10672
  Imported from ${sources.length} source(s):`);
10158
10673
  for (const src of sources) {
10159
- const files = (await readdir6(path36.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
10674
+ const files = (await readdir6(path38.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
10160
10675
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
10161
10676
  }
10162
10677
  } else {
10163
10678
  console.log(ui.dim(" No imported shared memories yet."));
10164
10679
  }
10165
- const all = await loadMemoriesFromDir28(paths.memoriesDir);
10680
+ const all = await loadMemoriesFromDir29(paths.memoriesDir);
10166
10681
  const outgoing = all.filter(
10167
10682
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
10168
10683
  );
@@ -10171,25 +10686,25 @@ Next steps:
10171
10686
  if (outgoing.length > 0) {
10172
10687
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
10173
10688
  }
10174
- void readFile15;
10175
- void writeFile26;
10176
- void saveConfig2;
10689
+ void readFile16;
10690
+ void writeFile28;
10691
+ void saveConfig3;
10177
10692
  });
10178
10693
  }
10179
10694
 
10180
10695
  // src/commands/stats.ts
10181
10696
  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";
10697
+ import { existsSync as existsSync58 } from "fs";
10698
+ import { mkdir as mkdir17, writeFile as writeFile29 } from "fs/promises";
10699
+ import path39 from "path";
10185
10700
  import {
10186
10701
  aggregateUsage,
10187
- findProjectRoot as findProjectRoot35,
10188
- loadMemoriesFromDir as loadMemoriesFromDir29,
10189
- loadUsageIndex as loadUsageIndex23,
10702
+ findProjectRoot as findProjectRoot36,
10703
+ loadMemoriesFromDir as loadMemoriesFromDir30,
10704
+ loadUsageIndex as loadUsageIndex24,
10190
10705
  parseSince,
10191
10706
  readUsageEvents as readUsageEvents2,
10192
- resolveHaivePaths as resolveHaivePaths32,
10707
+ resolveHaivePaths as resolveHaivePaths33,
10193
10708
  usageLogSize
10194
10709
  } from "@hiveai/core";
10195
10710
  function registerStats(program2) {
@@ -10198,8 +10713,8 @@ function registerStats(program2) {
10198
10713
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
10199
10714
  void 0
10200
10715
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10201
- const root = findProjectRoot35(opts.dir);
10202
- const paths = resolveHaivePaths32(root);
10716
+ const root = findProjectRoot36(opts.dir);
10717
+ const paths = resolveHaivePaths33(root);
10203
10718
  if (opts.exportReport) {
10204
10719
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
10205
10720
  return;
@@ -10253,12 +10768,12 @@ function registerStats(program2) {
10253
10768
  });
10254
10769
  }
10255
10770
  async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10256
- const outAbs = path37.isAbsolute(outRelative) ? path37.resolve(outRelative) : path37.resolve(root, outRelative);
10771
+ const outAbs = path39.isAbsolute(outRelative) ? path39.resolve(outRelative) : path39.resolve(root, outRelative);
10257
10772
  const size = await usageLogSize(paths);
10258
10773
  let events = await readUsageEvents2(paths);
10259
10774
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
10260
- if (existsSync56(paths.memoriesDir)) {
10261
- const mems = await loadMemoriesFromDir29(paths.memoriesDir);
10775
+ if (existsSync58(paths.memoriesDir)) {
10776
+ const mems = await loadMemoriesFromDir30(paths.memoriesDir);
10262
10777
  for (const { memory: memory2 } of mems) {
10263
10778
  const fm = memory2.frontmatter;
10264
10779
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -10272,7 +10787,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10272
10787
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
10273
10788
  let memoryHitsLeader = null;
10274
10789
  try {
10275
- const usageIdx = await loadUsageIndex23(paths);
10790
+ const usageIdx = await loadUsageIndex24(paths);
10276
10791
  const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
10277
10792
  memoryHitsLeader = tops[0] ?? null;
10278
10793
  } catch {
@@ -10288,7 +10803,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10288
10803
  ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
10289
10804
  events = [];
10290
10805
  }
10291
- await mkdir17(path37.dirname(outAbs), { recursive: true });
10806
+ await mkdir17(path39.dirname(outAbs), { recursive: true });
10292
10807
  const payload = {
10293
10808
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
10294
10809
  project_root: root,
@@ -10300,11 +10815,11 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
10300
10815
  top_memory_reads: memoryHitsLeader,
10301
10816
  roi_hints: roiHints
10302
10817
  };
10303
- await writeFile27(outAbs, JSON.stringify(payload, null, 2), "utf8");
10818
+ await writeFile29(outAbs, JSON.stringify(payload, null, 2), "utf8");
10304
10819
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
10305
10820
  }
10306
10821
  async function renderMemoryHits(paths, opts) {
10307
- const index = await loadUsageIndex23(paths);
10822
+ const index = await loadUsageIndex24(paths);
10308
10823
  const since = parseSince(opts.since ?? "30d");
10309
10824
  const sinceMs = since ? new Date(since).getTime() : null;
10310
10825
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -10352,13 +10867,13 @@ import { performance } from "perf_hooks";
10352
10867
  import "commander";
10353
10868
  import {
10354
10869
  estimateTokens as estimateTokens3,
10355
- findProjectRoot as findProjectRoot36,
10356
- resolveHaivePaths as resolveHaivePaths33
10870
+ findProjectRoot as findProjectRoot37,
10871
+ resolveHaivePaths as resolveHaivePaths34
10357
10872
  } from "@hiveai/core";
10358
10873
  function registerBench(program2) {
10359
10874
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10360
- const root = findProjectRoot36(opts.dir);
10361
- const paths = resolveHaivePaths33(root);
10875
+ const root = findProjectRoot37(opts.dir);
10876
+ const paths = resolveHaivePaths34(root);
10362
10877
  const ctx = { paths };
10363
10878
  const task = opts.task ?? "audit dependencies for security risks";
10364
10879
  const scenarios = [
@@ -10477,11 +10992,11 @@ function summarize(name, t0, payload, notes) {
10477
10992
  }
10478
10993
 
10479
10994
  // 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";
10995
+ import { existsSync as existsSync59 } from "fs";
10996
+ import { readdir as readdir5, readFile as readFile17, writeFile as writeFile30 } from "fs/promises";
10997
+ import path40 from "path";
10483
10998
  import "commander";
10484
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot37 } from "@hiveai/core";
10999
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot38 } from "@hiveai/core";
10485
11000
  function registerBenchmark(program2) {
10486
11001
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
10487
11002
  benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
@@ -10494,9 +11009,9 @@ function registerBenchmark(program2) {
10494
11009
  }
10495
11010
  const markdown = renderMarkdown(root, summary, rows);
10496
11011
  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)}`);
11012
+ const outFile = path40.isAbsolute(opts.out) ? opts.out : path40.join(root, opts.out);
11013
+ await writeFile30(outFile, markdown, "utf8");
11014
+ ui.success(`wrote ${path40.relative(process.cwd(), outFile)}`);
10500
11015
  return;
10501
11016
  }
10502
11017
  console.log(markdown);
@@ -10520,20 +11035,20 @@ function registerBenchmark(program2) {
10520
11035
  }
10521
11036
  function resolveBenchmarkRoot(dir) {
10522
11037
  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);
11038
+ if (path40.isAbsolute(candidate)) return candidate;
11039
+ const projectRoot = findProjectRoot38(process.cwd());
11040
+ return path40.join(projectRoot, candidate);
10526
11041
  }
10527
11042
  async function collectRows(root) {
10528
- if (!existsSync57(root)) throw new Error(`Benchmark directory not found: ${root}`);
11043
+ if (!existsSync59(root)) throw new Error(`Benchmark directory not found: ${root}`);
10529
11044
  const entries = await readdir5(root, { withFileTypes: true });
10530
11045
  const rows = [];
10531
11046
  for (const entry of entries) {
10532
11047
  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");
11048
+ const fixtureDir = path40.join(root, entry.name);
11049
+ const reportFile = path40.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
11050
+ if (!existsSync59(reportFile)) continue;
11051
+ const report = await readFile17(reportFile, "utf8");
10537
11052
  rows.push(parseAgentReport(entry.name, report));
10538
11053
  }
10539
11054
  return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
@@ -10622,20 +11137,20 @@ function escapeRegExp(value) {
10622
11137
  }
10623
11138
 
10624
11139
  // 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";
11140
+ import { mkdir as mkdir18, writeFile as writeFile31 } from "fs/promises";
11141
+ import { existsSync as existsSync60 } from "fs";
11142
+ import path41 from "path";
10628
11143
  import "commander";
10629
11144
  import {
10630
11145
  aggregateUsage as aggregateUsage2,
10631
11146
  buildFrontmatter as buildFrontmatter11,
10632
- findProjectRoot as findProjectRoot38,
10633
- loadMemoriesFromDir as loadMemoriesFromDir30,
11147
+ findProjectRoot as findProjectRoot39,
11148
+ loadMemoriesFromDir as loadMemoriesFromDir31,
10634
11149
  memoryFilePath as memoryFilePath10,
10635
11150
  parseSince as parseSince2,
10636
11151
  readUsageEvents as readUsageEvents3,
10637
- resolveHaivePaths as resolveHaivePaths34,
10638
- serializeMemory as serializeMemory24
11152
+ resolveHaivePaths as resolveHaivePaths35,
11153
+ serializeMemory as serializeMemory25
10639
11154
  } from "@hiveai/core";
10640
11155
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
10641
11156
  "mem_search",
@@ -10647,8 +11162,8 @@ function registerMemorySuggest(memory2) {
10647
11162
  memory2.command("suggest").description(
10648
11163
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to draft the top-N suggestions as draft memories. They land\n in personal scope by default with status=draft, ready for you to edit and promote."
10649
11164
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of drafted memories (personal | team)", "personal").option("--auto-save", "draft top-N suggestions as draft memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10650
- const root = findProjectRoot38(opts.dir);
10651
- const paths = resolveHaivePaths34(root);
11165
+ const root = findProjectRoot39(opts.dir);
11166
+ const paths = resolveHaivePaths35(root);
10652
11167
  const events = await readUsageEvents3(paths);
10653
11168
  if (events.length === 0) {
10654
11169
  if (opts.json) {
@@ -10694,7 +11209,7 @@ function registerMemorySuggest(memory2) {
10694
11209
  }
10695
11210
  const created = [];
10696
11211
  const skipped = [];
10697
- const existing = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
11212
+ const existing = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
10698
11213
  for (const s of top) {
10699
11214
  const slug = slugify(s.query);
10700
11215
  if (!slug) {
@@ -10717,13 +11232,13 @@ function registerMemorySuggest(memory2) {
10717
11232
  fm.status = "draft";
10718
11233
  const body = renderTemplate(s);
10719
11234
  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)}` });
11235
+ await mkdir18(path41.dirname(file), { recursive: true });
11236
+ if (existsSync60(file)) {
11237
+ skipped.push({ query: s.query, reason: `file already exists at ${path41.relative(root, file)}` });
10723
11238
  continue;
10724
11239
  }
10725
- await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
10726
- created.push({ id: fm.id, file: path39.relative(root, file), query: s.query });
11240
+ await writeFile31(file, serializeMemory25({ frontmatter: fm, body }), "utf8");
11241
+ created.push({ id: fm.id, file: path41.relative(root, file), query: s.query });
10727
11242
  }
10728
11243
  if (opts.json) {
10729
11244
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -10816,26 +11331,26 @@ function truncate2(text, max) {
10816
11331
  }
10817
11332
 
10818
11333
  // 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";
11334
+ import { existsSync as existsSync61 } from "fs";
11335
+ import { writeFile as writeFile33 } from "fs/promises";
11336
+ import path43 from "path";
10822
11337
  import "commander";
10823
11338
  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
11339
+ findProjectRoot as findProjectRoot40,
11340
+ getUsage as getUsage19,
11341
+ loadMemoriesFromDir as loadMemoriesFromDir32,
11342
+ loadUsageIndex as loadUsageIndex25,
11343
+ resolveHaivePaths as resolveHaivePaths36,
11344
+ serializeMemory as serializeMemory26
10830
11345
  } from "@hiveai/core";
10831
11346
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
10832
11347
  function registerMemoryArchive(memory2) {
10833
11348
  memory2.command("archive").description(
10834
11349
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
10835
11350
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10836
- const root = findProjectRoot39(opts.dir);
10837
- const paths = resolveHaivePaths35(root);
10838
- if (!existsSync59(paths.memoriesDir)) {
11351
+ const root = findProjectRoot40(opts.dir);
11352
+ const paths = resolveHaivePaths36(root);
11353
+ if (!existsSync61(paths.memoriesDir)) {
10839
11354
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10840
11355
  process.exitCode = 1;
10841
11356
  return;
@@ -10847,8 +11362,8 @@ function registerMemoryArchive(memory2) {
10847
11362
  return;
10848
11363
  }
10849
11364
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
10850
- const all = await loadMemoriesFromDir31(paths.memoriesDir);
10851
- const usage = await loadUsageIndex24(paths);
11365
+ const all = await loadMemoriesFromDir32(paths.memoriesDir);
11366
+ const usage = await loadUsageIndex25(paths);
10852
11367
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
10853
11368
  const candidates = [];
10854
11369
  for (const { memory: mem, filePath } of all) {
@@ -10856,10 +11371,10 @@ function registerMemoryArchive(memory2) {
10856
11371
  if (typeFilter && fm.type !== typeFilter) continue;
10857
11372
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
10858
11373
  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)));
11374
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync61(path43.join(paths.root, p)));
10860
11375
  const isAnchorless = !hasAnyAnchor;
10861
11376
  if (!isAnchorless && !allPathsGone) continue;
10862
- const u = getUsage18(usage, fm.id);
11377
+ const u = getUsage19(usage, fm.id);
10863
11378
  const lastSeen = u.last_read_at ?? fm.created_at;
10864
11379
  if (Date.parse(lastSeen) >= cutoff) continue;
10865
11380
  candidates.push({
@@ -10904,7 +11419,7 @@ function registerMemoryArchive(memory2) {
10904
11419
  if (!found) continue;
10905
11420
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
10906
11421
  try {
10907
- await writeFile30(c.filePath, serializeMemory25({ frontmatter: fm, body: found.memory.body }), "utf8");
11422
+ await writeFile33(c.filePath, serializeMemory26({ frontmatter: fm, body: found.memory.body }), "utf8");
10908
11423
  archived++;
10909
11424
  } catch (err) {
10910
11425
  if (!opts.json) {
@@ -10930,31 +11445,32 @@ function parseDays(input) {
10930
11445
  }
10931
11446
 
10932
11447
  // 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";
11448
+ import { existsSync as existsSync63 } from "fs";
11449
+ import { readFile as readFile18, stat } from "fs/promises";
11450
+ import path44 from "path";
10936
11451
  import { execFileSync, execSync as execSync3 } from "child_process";
10937
11452
  import "commander";
10938
11453
  import {
10939
11454
  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,
11455
+ findProjectRoot as findProjectRoot41,
11456
+ getUsage as getUsage20,
11457
+ loadCodeMap as loadCodeMap7,
11458
+ loadConfig as loadConfig9,
11459
+ loadMemoriesFromDir as loadMemoriesFromDir33,
11460
+ loadUsageIndex as loadUsageIndex26,
10946
11461
  readUsageEvents as readUsageEvents4,
10947
- resolveHaivePaths as resolveHaivePaths36
11462
+ resolveHaivePaths as resolveHaivePaths37
10948
11463
  } from "@hiveai/core";
10949
11464
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
10950
11465
  function registerDoctor(program2) {
10951
11466
  program2.command("doctor").description(
10952
11467
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to suggest commands you can copy-paste."
10953
11468
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
10954
- const root = findProjectRoot40(opts.dir);
10955
- const paths = resolveHaivePaths36(root);
11469
+ const root = findProjectRoot41(opts.dir);
11470
+ const paths = resolveHaivePaths37(root);
10956
11471
  const findings = [];
10957
- if (!existsSync60(paths.haiveDir)) {
11472
+ const repairs = [];
11473
+ if (!existsSync63(paths.haiveDir)) {
10958
11474
  findings.push({
10959
11475
  severity: "error",
10960
11476
  code: "not-initialized",
@@ -10963,7 +11479,18 @@ function registerDoctor(program2) {
10963
11479
  });
10964
11480
  return emit(findings, opts);
10965
11481
  }
10966
- if (!existsSync60(paths.projectContext)) {
11482
+ if (opts.fix && !opts.dryRun) {
11483
+ repairs.push(
11484
+ ...await applyAutopilotRepairs(root, paths, {
11485
+ applyConfig: true,
11486
+ applyContext: true,
11487
+ applyCorpus: true,
11488
+ applyCodeMap: true,
11489
+ applyCodeSearch: true
11490
+ })
11491
+ );
11492
+ }
11493
+ if (!existsSync63(paths.projectContext)) {
10967
11494
  findings.push({
10968
11495
  severity: "warn",
10969
11496
  code: "no-project-context",
@@ -10971,8 +11498,8 @@ function registerDoctor(program2) {
10971
11498
  fix: "haive init"
10972
11499
  });
10973
11500
  } else {
10974
- const { readFile: readFile19 } = await import("fs/promises");
10975
- const content = await readFile19(paths.projectContext, "utf8");
11501
+ const { readFile: readFile20 } = await import("fs/promises");
11502
+ const content = await readFile20(paths.projectContext, "utf8");
10976
11503
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
10977
11504
  if (isTemplate) {
10978
11505
  findings.push({
@@ -10982,8 +11509,17 @@ function registerDoctor(program2) {
10982
11509
  fix: "Invoke the bootstrap_project MCP prompt from your AI client."
10983
11510
  });
10984
11511
  }
11512
+ const versionStatus = await projectContextVersionStatus(root, paths);
11513
+ if (versionStatus.mismatch) {
11514
+ findings.push({
11515
+ severity: "warn",
11516
+ code: "project-context-version-mismatch",
11517
+ message: `.ai/project-context.md version metadata (${versionStatus.currentVersion ?? "missing"}) does not match package.json (${versionStatus.expectedVersion}).`,
11518
+ fix: "haive doctor --fix"
11519
+ });
11520
+ }
10985
11521
  }
10986
- const memories = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
11522
+ const memories = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10987
11523
  const now = Date.now();
10988
11524
  if (memories.length === 0) {
10989
11525
  findings.push({
@@ -10992,7 +11528,7 @@ function registerDoctor(program2) {
10992
11528
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
10993
11529
  });
10994
11530
  } else {
10995
- const usage = await loadUsageIndex25(paths);
11531
+ const usage = await loadUsageIndex26(paths);
10996
11532
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
10997
11533
  if (stale.length > 0) {
10998
11534
  findings.push({
@@ -11024,7 +11560,7 @@ function registerDoctor(program2) {
11024
11560
  }
11025
11561
  const decayCandidates = memories.filter((m) => {
11026
11562
  if (m.memory.frontmatter.status !== "validated") return false;
11027
- const u = getUsage19(usage, m.memory.frontmatter.id);
11563
+ const u = getUsage20(usage, m.memory.frontmatter.id);
11028
11564
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
11029
11565
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
11030
11566
  });
@@ -11037,7 +11573,7 @@ function registerDoctor(program2) {
11037
11573
  });
11038
11574
  }
11039
11575
  }
11040
- const codeMap = await loadCodeMap5(paths);
11576
+ const codeMap = await loadCodeMap7(paths);
11041
11577
  if (!codeMap) {
11042
11578
  findings.push({
11043
11579
  severity: "warn",
@@ -11092,14 +11628,14 @@ function registerDoctor(program2) {
11092
11628
  });
11093
11629
  }
11094
11630
  }
11095
- const config = await loadConfig7(paths);
11631
+ const config = await loadConfig9(paths);
11096
11632
  if (config.enforcement?.requireBriefingFirst) {
11097
- const claudeSettings = path41.join(root, ".claude", "settings.local.json");
11633
+ const claudeSettings = path44.join(root, ".claude", "settings.local.json");
11098
11634
  let hasClaudeEnforcement = false;
11099
- if (existsSync60(claudeSettings)) {
11635
+ if (existsSync63(claudeSettings)) {
11100
11636
  try {
11101
- const { readFile: readFile19 } = await import("fs/promises");
11102
- const raw = await readFile19(claudeSettings, "utf8");
11637
+ const { readFile: readFile20 } = await import("fs/promises");
11638
+ const raw = await readFile20(claudeSettings, "utf8");
11103
11639
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
11104
11640
  } catch {
11105
11641
  hasClaudeEnforcement = false;
@@ -11122,14 +11658,14 @@ function registerDoctor(program2) {
11122
11658
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
11123
11659
  });
11124
11660
  }
11125
- findings.push(...await collectInstallFindings(root, "0.9.17"));
11661
+ findings.push(...await collectInstallFindings(root, "0.9.18"));
11126
11662
  try {
11127
11663
  const legacyRaw = execSync3("haive-mcp --version", {
11128
11664
  encoding: "utf8",
11129
11665
  timeout: 3e3,
11130
11666
  stdio: ["ignore", "pipe", "ignore"]
11131
11667
  }).trim();
11132
- const cliVersion = "0.9.17";
11668
+ const cliVersion = "0.9.18";
11133
11669
  if (legacyRaw && legacyRaw !== cliVersion) {
11134
11670
  findings.push({
11135
11671
  severity: "warn",
@@ -11142,10 +11678,18 @@ npm uninstall -g @hiveai/mcp`
11142
11678
  }
11143
11679
  } catch {
11144
11680
  }
11145
- emit(findings, opts);
11681
+ if (repairs.length > 0) {
11682
+ findings.push({
11683
+ severity: "info",
11684
+ code: "autopilot-repairs-applied",
11685
+ message: repairs.map((repair) => repair.message).join(" "),
11686
+ section: "Next actions"
11687
+ });
11688
+ }
11689
+ emit(findings, opts, repairs);
11146
11690
  });
11147
11691
  }
11148
- function emit(findings, opts) {
11692
+ function emit(findings, opts, repairs = []) {
11149
11693
  const classified = findings.map((finding) => ({
11150
11694
  ...finding,
11151
11695
  section: finding.section ?? sectionForFinding(finding)
@@ -11157,12 +11701,13 @@ function emit(findings, opts) {
11157
11701
  findings: classified,
11158
11702
  sections: groupBySection(classified),
11159
11703
  next_actions: nextActions(classified),
11160
- fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "suggest" : "off"
11704
+ repairs,
11705
+ fix_mode: opts.fix ? opts.dryRun ? "dry-run" : "apply" : "off"
11161
11706
  }, null, 2));
11162
11707
  return;
11163
11708
  }
11164
11709
  if (classified.length === 0) {
11165
- ui.success("hAIve doctor \u2014 no issues found.");
11710
+ ui.success(repairs.length > 0 ? "hAIve doctor \u2014 autopilot repairs applied." : "hAIve doctor \u2014 no issues found.");
11166
11711
  return;
11167
11712
  }
11168
11713
  console.log(ui.bold(`hAIve doctor \u2014 ${classified.length} finding${classified.length === 1 ? "" : "s"}`));
@@ -11198,6 +11743,11 @@ function emit(findings, opts) {
11198
11743
  }
11199
11744
  console.log();
11200
11745
  }
11746
+ if (repairs.length > 0) {
11747
+ console.log(ui.bold("Autopilot repairs applied"));
11748
+ for (const repair of repairs) console.log(` ${ui.dim("\u2713")} ${repair.message}`);
11749
+ console.log();
11750
+ }
11201
11751
  const actions = nextActions(classified);
11202
11752
  if (actions.length > 0) {
11203
11753
  console.log(ui.bold("Next actions"));
@@ -11289,9 +11839,9 @@ which -a haive`
11289
11839
  ".vscode/mcp.json"
11290
11840
  ];
11291
11841
  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(() => "");
11842
+ const file = path44.join(root, rel);
11843
+ if (!existsSync63(file)) continue;
11844
+ const text = await readFile18(file, "utf8").catch(() => "");
11295
11845
  for (const bin of extractAbsoluteHaiveBins(text)) {
11296
11846
  const version = versionForBinary(bin);
11297
11847
  if (!version) {
@@ -11347,22 +11897,22 @@ function extractAbsoluteHaiveBins(text) {
11347
11897
  }
11348
11898
 
11349
11899
  // src/commands/playback.ts
11350
- import { existsSync as existsSync61 } from "fs";
11900
+ import { existsSync as existsSync64 } from "fs";
11351
11901
  import "commander";
11352
11902
  import {
11353
- findProjectRoot as findProjectRoot41,
11354
- loadMemoriesFromDir as loadMemoriesFromDir33,
11903
+ findProjectRoot as findProjectRoot42,
11904
+ loadMemoriesFromDir as loadMemoriesFromDir34,
11355
11905
  parseSince as parseSince3,
11356
11906
  readUsageEvents as readUsageEvents5,
11357
- resolveHaivePaths as resolveHaivePaths37
11907
+ resolveHaivePaths as resolveHaivePaths38
11358
11908
  } from "@hiveai/core";
11359
11909
  var MS_PER_MINUTE = 6e4;
11360
11910
  function registerPlayback(program2) {
11361
11911
  program2.command("playback").description(
11362
11912
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
11363
11913
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11364
- const root = findProjectRoot41(opts.dir);
11365
- const paths = resolveHaivePaths37(root);
11914
+ const root = findProjectRoot42(opts.dir);
11915
+ const paths = resolveHaivePaths38(root);
11366
11916
  const events = await readUsageEvents5(paths);
11367
11917
  if (events.length === 0) {
11368
11918
  if (opts.json) {
@@ -11377,7 +11927,7 @@ function registerPlayback(program2) {
11377
11927
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
11378
11928
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
11379
11929
  const sessions = bucketSessions(filtered, gapMs);
11380
- const all = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
11930
+ const all = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
11381
11931
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
11382
11932
  const enriched = sessions.map((s, i) => {
11383
11933
  const startMs = Date.parse(s.start);
@@ -11467,8 +12017,8 @@ function truncate3(text, max) {
11467
12017
  import { spawn as spawn4 } from "child_process";
11468
12018
  import "commander";
11469
12019
  import {
11470
- findProjectRoot as findProjectRoot42,
11471
- resolveHaivePaths as resolveHaivePaths38
12020
+ findProjectRoot as findProjectRoot43,
12021
+ resolveHaivePaths as resolveHaivePaths39
11472
12022
  } from "@hiveai/core";
11473
12023
  function registerPrecommit(program2) {
11474
12024
  program2.command("precommit").description(
@@ -11478,8 +12028,8 @@ function registerPrecommit(program2) {
11478
12028
  "'any' | 'high-confidence' (default) | 'never' (report only)",
11479
12029
  "high-confidence"
11480
12030
  ).option("--no-semantic", "disable semantic search in anti-patterns matching").option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
11481
- const root = findProjectRoot42(opts.dir);
11482
- const paths = resolveHaivePaths38(root);
12031
+ const root = findProjectRoot43(opts.dir);
12032
+ const paths = resolveHaivePaths39(root);
11483
12033
  const ctx = { paths };
11484
12034
  let diff = "";
11485
12035
  let touchedPaths = opts.paths ?? [];
@@ -11513,401 +12063,142 @@ function registerPrecommit(program2) {
11513
12063
  ` 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
12064
  )
11515
12065
  );
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
- }
12066
+ console.log();
12067
+ const blocking = result.warnings.filter((w) => w.level === "blocking");
12068
+ const review = result.warnings.filter((w) => w.level === "review");
12069
+ const info = result.warnings.filter((w) => w.level === "info");
12070
+ printWarnings("Blocking anti-patterns", blocking, "error");
12071
+ printWarnings("Review anti-patterns", review.slice(0, 8), "warn");
12072
+ if (info.length > 0) {
12073
+ console.log(
12074
+ ui.dim(
12075
+ `${info.length} weak anti-pattern signal${info.length === 1 ? "" : "s"} hidden. Use --json to inspect FYI matches.`
12076
+ )
12077
+ );
12078
+ console.log();
12079
+ }
12080
+ if (result.relevant_memories.length > 0) {
12081
+ console.log(ui.bold("\u{1F4CC} Relevant conventions/decisions:"));
12082
+ for (const m of result.relevant_memories) {
12083
+ console.log(` \u2022 ${m.id} ${ui.dim(`(${m.type}, ${m.confidence})`)}`);
11777
12084
  }
12085
+ console.log();
11778
12086
  }
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;
12087
+ if (result.stale_anchors.length > 0) {
12088
+ console.log(ui.bold("\u{1F552} Stale anchored memories:"));
12089
+ for (const s of result.stale_anchors) {
12090
+ console.log(` \u2022 ${s.id}`);
12091
+ if (s.body_preview) console.log(` ${ui.dim(s.body_preview)}`);
11814
12092
  }
11815
- if (paths.size >= 5 && symbols.size >= 5) break;
12093
+ console.log();
12094
+ }
12095
+ if (result.should_block) {
12096
+ ui.error(`Blocking commit (block_on=${opts.blockOn ?? "high-confidence"}). Address the warnings above or pass --block-on never to bypass.`);
12097
+ process.exit(1);
12098
+ }
12099
+ if (result.warnings.length === 0 && result.stale_anchors.length === 0) {
12100
+ ui.success("No anti-patterns or stale anchors found.");
12101
+ } else {
12102
+ ui.success("Check passed (block_on threshold not met).");
11816
12103
  }
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
12104
  });
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
- }
12105
+ }
12106
+ function printWarnings(title, warnings, tone) {
12107
+ if (warnings.length === 0) return;
12108
+ console.log(ui.bold(tone === "error" ? `\u2717 ${title}:` : `\u26A0 ${title}:`));
12109
+ for (const w of warnings) {
12110
+ const marker = tone === "error" ? ui.red("\u2717") : ui.yellow("\u26A0");
12111
+ console.log(` ${marker} ${w.id} ${ui.dim(`(${w.type}, ${w.confidence})`)}`);
12112
+ for (const line of w.body_preview.split("\n").slice(0, 3)) {
12113
+ console.log(` ${ui.dim(line)}`);
12114
+ }
12115
+ console.log(` ${ui.dim("reasons:")} ${w.reasons.join(", ")}`);
12116
+ if (w.affected_files && w.affected_files.length > 0) {
12117
+ console.log(` ${ui.dim("files:")} ${w.affected_files.slice(0, 4).join(", ")}`);
11844
12118
  }
12119
+ if (w.rationale) console.log(` ${ui.dim("why shown:")} ${w.rationale}`);
12120
+ if (w.repair_command) console.log(` ${ui.dim("repair:")} ${w.repair_command}`);
11845
12121
  }
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
- );
12122
+ console.log();
11852
12123
  }
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);
12124
+ function runCommand3(cmd, args, cwd) {
12125
+ return new Promise((resolve, reject) => {
12126
+ const proc = spawn4(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
12127
+ let stdout = "";
12128
+ let stderr = "";
12129
+ proc.stdout.on("data", (chunk) => {
12130
+ stdout += chunk.toString();
12131
+ });
12132
+ proc.stderr.on("data", (chunk) => {
12133
+ stderr += chunk.toString();
12134
+ });
12135
+ proc.on("error", reject);
12136
+ proc.on("close", (code) => {
12137
+ if (code === 0) resolve(stdout);
12138
+ else reject(new Error(stderr || `${cmd} exited with code ${code}`));
12139
+ });
12140
+ });
11858
12141
  }
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) => {
12142
+
12143
+ // src/commands/welcome.ts
12144
+ import { existsSync as existsSync65 } from "fs";
12145
+ import "commander";
12146
+ import {
12147
+ findProjectRoot as findProjectRoot44,
12148
+ loadMemoriesFromDir as loadMemoriesFromDir35,
12149
+ resolveHaivePaths as resolveHaivePaths40
12150
+ } from "@hiveai/core";
12151
+ var TYPE_RANK = {
12152
+ decision: 0,
12153
+ architecture: 1,
12154
+ convention: 2,
12155
+ glossary: 3,
12156
+ gotcha: 4,
12157
+ attempt: 5
12158
+ };
12159
+ function registerWelcome(program2) {
12160
+ program2.command("welcome").description(
12161
+ "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
12162
+ ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
11863
12163
  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;
12164
+ const paths = resolveHaivePaths40(root);
12165
+ if (!existsSync65(paths.memoriesDir)) {
12166
+ ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
12167
+ process.exitCode = 1;
11877
12168
  return;
11878
12169
  }
11879
- if (findings.length === 0) {
11880
- ui.success(`memory lint OK \u2014 ${root}`);
12170
+ const all = await loadMemoriesFromDir35(paths.memoriesDir);
12171
+ const team = all.filter(
12172
+ ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
12173
+ );
12174
+ team.sort((a, b) => {
12175
+ const ta = TYPE_RANK[a.memory.frontmatter.type] ?? 99;
12176
+ const tb = TYPE_RANK[b.memory.frontmatter.type] ?? 99;
12177
+ if (ta !== tb) return ta - tb;
12178
+ const sta = a.memory.frontmatter.status === "validated" ? 0 : 1;
12179
+ const stb = b.memory.frontmatter.status === "validated" ? 0 : 1;
12180
+ if (sta !== stb) return sta - stb;
12181
+ return b.memory.frontmatter.created_at.localeCompare(a.memory.frontmatter.created_at);
12182
+ });
12183
+ const cap = Math.max(1, Math.min(500, Number(opts.limit) || 20));
12184
+ const pick = team.slice(0, cap);
12185
+ console.log(ui.bold(`hAIve welcome \u2014 ${pick.length} team memories (${root})`));
12186
+ console.log(ui.dim(`Next: invoke get_briefing with your task or run 'haive briefing --task "\u2026"'`));
12187
+ if (pick.length === 0) {
12188
+ ui.warn("No team memories yet \u2014 add some with 'haive memory add' or promote personal ones.");
11881
12189
  return;
11882
12190
  }
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;
12191
+ let i = 1;
12192
+ for (const { memory: memory2 } of pick) {
12193
+ const fm = memory2.frontmatter;
12194
+ const head = memory2.body.match(/^#\s+(.+)/m)?.[1]?.trim();
12195
+ const line = head ?? fm.id;
11899
12196
  console.log(
11900
- `${color(f.severity.padEnd(5))} ${ui.dim(f.code)} ${f.id}`
12197
+ `${String(i).padStart(2, " ")} ${fm.type.padEnd(12)} ${fm.status.padEnd(10)} ${ui.dim(fm.id)}
12198
+ ${line}`
11901
12199
  );
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}`));
12200
+ i++;
11909
12201
  }
11910
- process.exitCode = findings.some((x) => x.severity === "error") ? 1 : 0;
11911
12202
  });
11912
12203
  }
11913
12204
 
@@ -11930,21 +12221,21 @@ function registerMemorySuggestTopic(memory2) {
11930
12221
  }
11931
12222
 
11932
12223
  // src/commands/resolve-project.ts
11933
- import path44 from "path";
12224
+ import path45 from "path";
11934
12225
  import "commander";
11935
12226
  import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
11936
12227
  function registerResolveProject(program2) {
11937
12228
  program2.command("resolve-project").description(
11938
12229
  "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
11939
12230
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
11940
- const info = resolveProjectInfo2({ cwd: path44.resolve(opts.dir) });
12231
+ const info = resolveProjectInfo2({ cwd: path45.resolve(opts.dir) });
11941
12232
  console.log(JSON.stringify({ ok: true, info }, null, 2));
11942
12233
  });
11943
12234
  }
11944
12235
 
11945
12236
  // src/commands/runtime-journal.ts
11946
- import { existsSync as existsSync65 } from "fs";
11947
- import path45 from "path";
12237
+ import { existsSync as existsSync66 } from "fs";
12238
+ import path46 from "path";
11948
12239
  import "commander";
11949
12240
  import {
11950
12241
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
@@ -11958,18 +12249,18 @@ function registerRuntime(program2) {
11958
12249
  );
11959
12250
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
11960
12251
  journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
11961
- const root = path45.resolve(opts.dir ?? process.cwd());
12252
+ const root = path46.resolve(opts.dir ?? process.cwd());
11962
12253
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11963
12254
  const raw = opts.kind ?? "note";
11964
12255
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
11965
12256
  await appendRuntimeJournalEntry3(paths, { kind, message });
11966
- ui.success(`Appended to ${path45.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
12257
+ ui.success(`Appended to ${path46.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
11967
12258
  });
11968
12259
  journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
11969
- const root = path45.resolve(opts.dir ?? process.cwd());
12260
+ const root = path46.resolve(opts.dir ?? process.cwd());
11970
12261
  const paths = resolveHaivePaths41(findProjectRoot45(root));
11971
12262
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
11972
- if (!existsSync65(paths.haiveDir)) {
12263
+ if (!existsSync66(paths.haiveDir)) {
11973
12264
  ui.error("No .ai/ \u2014 run `haive init` first.");
11974
12265
  process.exitCode = 1;
11975
12266
  return;
@@ -11984,8 +12275,8 @@ function registerRuntime(program2) {
11984
12275
  }
11985
12276
 
11986
12277
  // src/commands/memory-timeline.ts
11987
- import { existsSync as existsSync66 } from "fs";
11988
- import path46 from "path";
12278
+ import { existsSync as existsSync67 } from "fs";
12279
+ import path47 from "path";
11989
12280
  import "commander";
11990
12281
  import {
11991
12282
  collectTimelineEntries as collectTimelineEntries2,
@@ -12001,15 +12292,15 @@ function registerMemoryTimeline(memory2) {
12001
12292
  process.exitCode = 1;
12002
12293
  return;
12003
12294
  }
12004
- const root = path46.resolve(opts.dir ?? process.cwd());
12295
+ const root = path47.resolve(opts.dir ?? process.cwd());
12005
12296
  const paths = resolveHaivePaths42(findProjectRoot46(root));
12006
- if (!existsSync66(paths.memoriesDir)) {
12297
+ if (!existsSync67(paths.memoriesDir)) {
12007
12298
  ui.error("No memories \u2014 run `haive init`.");
12008
12299
  process.exitCode = 1;
12009
12300
  return;
12010
12301
  }
12011
12302
  const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
12012
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12303
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
12013
12304
  const { entries, notice } = collectTimelineEntries2(all, {
12014
12305
  memoryId: opts.id,
12015
12306
  topic: opts.topic,
@@ -12021,8 +12312,8 @@ function registerMemoryTimeline(memory2) {
12021
12312
  }
12022
12313
 
12023
12314
  // src/commands/memory-conflict-candidates.ts
12024
- import { existsSync as existsSync67 } from "fs";
12025
- import path47 from "path";
12315
+ import { existsSync as existsSync68 } from "fs";
12316
+ import path48 from "path";
12026
12317
  import "commander";
12027
12318
  import {
12028
12319
  findLexicalConflictPairs as findLexicalConflictPairs2,
@@ -12044,9 +12335,9 @@ function registerMemoryConflictCandidates(memory2) {
12044
12335
  "decision,architecture,convention,gotcha (lexical scan)",
12045
12336
  "decision,architecture"
12046
12337
  ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
12047
- const root = path47.resolve(opts.dir ?? process.cwd());
12338
+ const root = path48.resolve(opts.dir ?? process.cwd());
12048
12339
  const paths = resolveHaivePaths43(findProjectRoot47(root));
12049
- if (!existsSync67(paths.memoriesDir)) {
12340
+ if (!existsSync68(paths.memoriesDir)) {
12050
12341
  ui.error("No memories \u2014 run `haive init`.");
12051
12342
  process.exitCode = 1;
12052
12343
  return;
@@ -12056,7 +12347,7 @@ function registerMemoryConflictCandidates(memory2) {
12056
12347
  const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
12057
12348
  const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
12058
12349
  const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
12059
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
12350
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
12060
12351
  const lexical = findLexicalConflictPairs2(all, {
12061
12352
  sinceDays,
12062
12353
  types: parseTypes(opts.types),
@@ -12082,21 +12373,21 @@ function registerMemoryConflictCandidates(memory2) {
12082
12373
 
12083
12374
  // src/commands/enforce.ts
12084
12375
  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";
12376
+ import { existsSync as existsSync69 } from "fs";
12377
+ import { chmod as chmod2, mkdir as mkdir19, readFile as readFile19, rm as rm3, writeFile as writeFile34 } from "fs/promises";
12378
+ import path49 from "path";
12088
12379
  import "commander";
12089
12380
  import {
12090
12381
  findProjectRoot as findProjectRoot48,
12091
12382
  hasRecentBriefingMarker,
12092
12383
  isFreshIsoDate,
12093
- loadConfig as loadConfig8,
12384
+ loadConfig as loadConfig10,
12094
12385
  loadMemoriesFromDir as loadMemoriesFromDir36,
12095
12386
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
12096
12387
  readRecentBriefingMarker,
12097
12388
  resolveBriefingBudget as resolveBriefingBudget3,
12098
12389
  resolveHaivePaths as resolveHaivePaths44,
12099
- saveConfig as saveConfig3,
12390
+ saveConfig as saveConfig4,
12100
12391
  SESSION_RECAP_TTL_MS,
12101
12392
  verifyAnchor as verifyAnchor4,
12102
12393
  writeBriefingMarker as writeBriefingMarker2
@@ -12111,8 +12402,8 @@ function registerEnforce(program2) {
12111
12402
  const root = findProjectRoot48(opts.dir);
12112
12403
  const paths = resolveHaivePaths44(root);
12113
12404
  await mkdir19(paths.haiveDir, { recursive: true });
12114
- const current = await loadConfig8(paths);
12115
- await saveConfig3(paths, {
12405
+ const current = await loadConfig10(paths);
12406
+ await saveConfig4(paths, {
12116
12407
  ...current,
12117
12408
  enforcement: {
12118
12409
  ...current.enforcement,
@@ -12134,7 +12425,7 @@ function registerEnforce(program2) {
12134
12425
  if (opts.claude !== false) {
12135
12426
  try {
12136
12427
  const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
12137
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path48.relative(root, result.settingsPath)})`);
12428
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path49.relative(root, result.settingsPath)})`);
12138
12429
  } catch (err) {
12139
12430
  ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
12140
12431
  }
@@ -12156,12 +12447,12 @@ function registerEnforce(program2) {
12156
12447
  const root = findProjectRoot48(opts.dir);
12157
12448
  const paths = resolveHaivePaths44(root);
12158
12449
  const targets = [
12159
- path48.join(paths.haiveDir, ".cache"),
12160
- path48.join(paths.haiveDir, ".runtime")
12450
+ path49.join(paths.haiveDir, ".cache"),
12451
+ path49.join(paths.haiveDir, ".runtime")
12161
12452
  ];
12162
12453
  for (const target of targets) {
12163
- if (!existsSync68(target)) continue;
12164
- const rel = path48.relative(root, target);
12454
+ if (!existsSync69(target)) continue;
12455
+ const rel = path49.relative(root, target);
12165
12456
  if (opts.dryRun) ui.info(`would remove ${rel}`);
12166
12457
  else {
12167
12458
  await rm3(target, { recursive: true, force: true });
@@ -12179,7 +12470,7 @@ function registerEnforce(program2) {
12179
12470
  const root = resolveRoot(opts.dir, payload);
12180
12471
  if (!root) return;
12181
12472
  const paths = resolveHaivePaths44(root);
12182
- if (!existsSync68(paths.haiveDir)) return;
12473
+ if (!existsSync69(paths.haiveDir)) return;
12183
12474
  await mkdir19(paths.runtimeDir, { recursive: true });
12184
12475
  const sessionId = opts.sessionId ?? payload.session_id;
12185
12476
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -12241,7 +12532,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
12241
12532
  const root = resolveRoot(opts.dir, payload);
12242
12533
  if (!root) return;
12243
12534
  const paths = resolveHaivePaths44(root);
12244
- if (!existsSync68(paths.haiveDir)) return;
12535
+ if (!existsSync69(paths.haiveDir)) return;
12245
12536
  if (!isWriteLikeTool(payload)) return;
12246
12537
  const ok = await hasRecentBriefingMarker(paths, payload.session_id);
12247
12538
  if (ok) return;
@@ -12265,7 +12556,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
12265
12556
  async function runWithEnforcement(command, args, opts) {
12266
12557
  const root = findProjectRoot48(opts.dir);
12267
12558
  const paths = resolveHaivePaths44(root);
12268
- if (!existsSync68(paths.haiveDir)) {
12559
+ if (!existsSync69(paths.haiveDir)) {
12269
12560
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
12270
12561
  process.exit(1);
12271
12562
  }
@@ -12284,7 +12575,7 @@ async function runWithEnforcement(command, args, opts) {
12284
12575
  process.exit(2);
12285
12576
  }
12286
12577
  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`);
12578
+ ui.info(`Briefing written to ${path49.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
12288
12579
  const child = spawn5(command, args, {
12289
12580
  cwd: root,
12290
12581
  stdio: "inherit",
@@ -12333,9 +12624,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
12333
12624
  source: "haive-run",
12334
12625
  memoryIds: briefing.memories.map((m) => m.id)
12335
12626
  });
12336
- const dir = path48.join(paths.runtimeDir, "enforcement", "briefings");
12627
+ const dir = path49.join(paths.runtimeDir, "enforcement", "briefings");
12337
12628
  await mkdir19(dir, { recursive: true });
12338
- const file = path48.join(dir, `${sessionId}.md`);
12629
+ const file = path49.join(dir, `${sessionId}.md`);
12339
12630
  const parts = [
12340
12631
  "# hAIve Briefing",
12341
12632
  "",
@@ -12353,14 +12644,14 @@ async function writeWrapperBriefing(paths, sessionId, task) {
12353
12644
  if (briefing.setup_warnings.length > 0) {
12354
12645
  parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
12355
12646
  }
12356
- await writeFile33(file, parts.join("\n") + "\n", "utf8");
12647
+ await writeFile34(file, parts.join("\n") + "\n", "utf8");
12357
12648
  return file;
12358
12649
  }
12359
12650
  async function buildEnforcementReport(dir, stage, sessionId) {
12360
12651
  const root = findProjectRoot48(dir);
12361
12652
  const paths = resolveHaivePaths44(root);
12362
- const initialized = existsSync68(paths.haiveDir);
12363
- const config = initialized ? await loadConfig8(paths) : {};
12653
+ const initialized = existsSync69(paths.haiveDir);
12654
+ const config = initialized ? await loadConfig10(paths) : {};
12364
12655
  const mode = config.enforcement?.mode ?? "strict";
12365
12656
  const findings = [];
12366
12657
  if (!initialized) {
@@ -12389,7 +12680,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
12389
12680
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
12390
12681
  });
12391
12682
  }
12392
- findings.push(...await inspectIntegrationVersions(root, "0.9.17"));
12683
+ findings.push(...await inspectIntegrationVersions(root, "0.9.18"));
12393
12684
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
12394
12685
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
12395
12686
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -12459,7 +12750,7 @@ function withCategories(report) {
12459
12750
  };
12460
12751
  }
12461
12752
  async function hasRecentSessionRecap(paths) {
12462
- if (!existsSync68(paths.memoriesDir)) return false;
12753
+ if (!existsSync69(paths.memoriesDir)) return false;
12463
12754
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12464
12755
  return all.some(({ memory: memory2 }) => {
12465
12756
  const fm = memory2.frontmatter;
@@ -12468,7 +12759,7 @@ async function hasRecentSessionRecap(paths) {
12468
12759
  });
12469
12760
  }
12470
12761
  async function verifyMemoryPolicy(paths, config) {
12471
- if (!existsSync68(paths.memoriesDir)) return [];
12762
+ if (!existsSync69(paths.memoriesDir)) return [];
12472
12763
  const all = await loadMemoriesFromDir36(paths.memoriesDir);
12473
12764
  const findings = [];
12474
12765
  const staleImportant = [];
@@ -12506,7 +12797,7 @@ async function verifyMemoryPolicy(paths, config) {
12506
12797
  return findings;
12507
12798
  }
12508
12799
  async function verifyDecisionCoverage(paths, stage, sessionId) {
12509
- if (!existsSync68(paths.memoriesDir)) return [];
12800
+ if (!existsSync69(paths.memoriesDir)) return [];
12510
12801
  const changedFiles = await getChangedFiles(paths.root, stage);
12511
12802
  if (changedFiles.length === 0) {
12512
12803
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -12599,9 +12890,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
12599
12890
  ];
12600
12891
  const findings = [];
12601
12892
  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(() => "");
12893
+ const file = path49.join(root, rel);
12894
+ if (!existsSync69(file)) continue;
12895
+ const text = await readFile19(file, "utf8").catch(() => "");
12605
12896
  for (const bin of extractAbsoluteHaiveBins2(text)) {
12606
12897
  const version = versionForBinary2(bin);
12607
12898
  if (!version) {
@@ -12689,8 +12980,8 @@ function buildScore(findings, threshold = 80) {
12689
12980
  };
12690
12981
  }
12691
12982
  async function installGitEnforcement(root) {
12692
- const hooksDir = path48.join(root, ".git", "hooks");
12693
- if (!existsSync68(path48.join(root, ".git"))) {
12983
+ const hooksDir = path49.join(root, ".git", "hooks");
12984
+ if (!existsSync69(path49.join(root, ".git"))) {
12694
12985
  ui.warn("No .git directory found; git enforcement hooks skipped.");
12695
12986
  return;
12696
12987
  }
@@ -12712,31 +13003,31 @@ haive enforce check --stage pre-push --dir . || exit $?
12712
13003
  }
12713
13004
  ];
12714
13005
  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(() => "");
13006
+ const file = path49.join(hooksDir, hook.name);
13007
+ if (existsSync69(file)) {
13008
+ const current = await readFile19(file, "utf8").catch(() => "");
12718
13009
  if (current.includes(ENFORCE_HOOK_MARKER)) {
12719
- await writeFile33(file, hook.body, "utf8");
13010
+ await writeFile34(file, hook.body, "utf8");
12720
13011
  } else {
12721
- await writeFile33(file, `${current.trimEnd()}
13012
+ await writeFile34(file, `${current.trimEnd()}
12722
13013
 
12723
13014
  ${hook.body}`, "utf8");
12724
13015
  }
12725
13016
  } else {
12726
- await writeFile33(file, hook.body, "utf8");
13017
+ await writeFile34(file, hook.body, "utf8");
12727
13018
  }
12728
13019
  await chmod2(file, 493);
12729
13020
  }
12730
13021
  ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
12731
13022
  }
12732
13023
  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)) {
13024
+ const workflowPath = path49.join(root, ".github", "workflows", "haive-enforcement.yml");
13025
+ await mkdir19(path49.dirname(workflowPath), { recursive: true });
13026
+ if (existsSync69(workflowPath)) {
12736
13027
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
12737
13028
  return;
12738
13029
  }
12739
- await writeFile33(workflowPath, `name: haive-enforcement
13030
+ await writeFile34(workflowPath, `name: haive-enforcement
12740
13031
 
12741
13032
  on:
12742
13033
  pull_request:
@@ -12760,7 +13051,7 @@ jobs:
12760
13051
  - name: Enforce hAIve policy
12761
13052
  run: haive enforce ci
12762
13053
  `, "utf8");
12763
- ui.success(`Created ${path48.relative(root, workflowPath)}`);
13054
+ ui.success(`Created ${path49.relative(root, workflowPath)}`);
12764
13055
  }
12765
13056
  function printReport(report, json, explain = false) {
12766
13057
  if (json) {
@@ -12877,7 +13168,7 @@ function registerRun(program2) {
12877
13168
 
12878
13169
  // src/index.ts
12879
13170
  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");
13171
+ program.name("haive").description("hAIve \u2014 policy enforcement layer for AI coding agents").version("0.9.18").option("--advanced", "show maintenance and experimental commands in help");
12881
13172
  registerInit(program);
12882
13173
  registerWelcome(program);
12883
13174
  registerResolveProject(program);