@hiveai/cli 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command42 } from "commander";
4
+ import { Command as Command44 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -197,7 +197,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
197
197
  if (!f) continue;
198
198
  counts.set(f, (counts.get(f) ?? 0) + 1);
199
199
  }
200
- let entries = [...counts.entries()].map(([path40, changes]) => ({ path: path40, changes }));
200
+ let entries = [...counts.entries()].map(([path41, changes]) => ({ path: path41, changes }));
201
201
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
202
202
  if (lowerPaths.length > 0) {
203
203
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -1882,6 +1882,7 @@ var PROJECT_CONTEXT_TEMPLATE = `# Project context
1882
1882
 
1883
1883
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
1884
1884
  > or invoke the MCP prompt \`bootstrap_project\` in your AI client for a richer AI-generated version.
1885
+ > **AI agents**: use the **haive** MCP (\`get_briefing\` / \`mem_relevant_to\`) before substantive edits \u2014 see bridge files and \`.cursor/rules/haive-mcp-required.mdc\`.
1885
1886
 
1886
1887
  ## Architecture
1887
1888
  TODO \u2014 high-level overview of the codebase.
@@ -1906,16 +1907,47 @@ Memories live under \`.ai/memories/\` (personal/team/module).
1906
1907
 
1907
1908
  ## hAIve \u2014 mandatory rules for AI agents
1908
1909
 
1910
+ Installing or initializing hAIve in this repository means **the team expects AI agents to drive work through hAIve**, not bypass it.
1911
+
1912
+ ### MCP first (default \u2014 never optional unless the developer says so)
1913
+ The **haive** MCP server must be enabled in your client. Until it is unavailable (misconfiguration report to the developer), you **must** use it for substantive work:
1914
+
1915
+ - **Before** you edit project source files, configs (\`package.json\`, tooling), or workflows for the current goal, invoke \`get_briefing\` with a short **task** string (and **files** / **symbols** when you know them). If project context was already refreshed in this session and you only need ranked memories, you may call \`mem_relevant_to\` instead\u2014but **still** consult hAIve before trusting ungrounded guesses.
1916
+ - **Before** declaring a coding task finished, invoke the MCP prompt \`post_task\` when the session produced learnings or substantive changes worth capturing.
1917
+
1918
+ Do **not** treat these as nice-to-have: they are **default-required** for coding agents here.
1919
+
1909
1920
  ### Session rules
1910
- - **Before** marking a task as done ("Done", "Finished", "C'est fait"), call the MCP prompt \`post_task\`.
1911
1921
  - **Immediately** when an approach fails (wrong API, wrong pattern, wrong assumption), call \`mem_tried\` \u2014 do not wait until the end of the session.
1912
- - When starting a new task, call \`get_briefing\` with the task description to load relevant memories and avoid repeating past mistakes.
1913
1922
 
1914
1923
  ### Safety rules \u2014 NEVER violate these
1915
1924
  - If \`get_briefing\` returns an \`action_required\` list, **stop and show each item to the developer** before doing anything. Use the exact \`developer_message\` provided. Wait for explicit confirmation.
1916
1925
  - **Never modify code autonomously** because of a breaking change detected in another project (dependency version bump, API contract change, removed field). Always ask first.
1917
1926
  - When in doubt about a cross-repo change: ask, don't act.
1918
1927
  `;
1928
+ var CURSOR_HAIVE_RULE_MDC = `---
1929
+ description: Require hAIve MCP (get_briefing / mem_relevant_to) before substantive repo edits
1930
+ alwaysApply: true
1931
+ ---
1932
+
1933
+ # hAIve \u2014 required for agents
1934
+
1935
+ This repository uses **hAIve**. Running \`haive init\` means the team expects agents to **use the haive MCP server by default**, not skip it.
1936
+
1937
+ ## Before you change the repo
1938
+
1939
+ 1. Call **\`get_briefing\`** with \`task\` (and \`files\` / \`symbols\` when known), **or** \`mem_relevant_to\` if full project context is already loaded and you only need ranked memories.
1940
+ 2. Then read/search the codebase as needed. Do not invert this order for non-trivial work.
1941
+
1942
+ ## During / after the task
1943
+
1944
+ - On failure: **\`mem_tried\`** immediately.
1945
+ - Before closing a substantive session: MCP prompt **\`post_task\`** when there is something worth capturing.
1946
+
1947
+ ## If haive MCP is missing
1948
+
1949
+ Tell the developer to enable the **haive** server (e.g. \`haive mcp --stdio\` in client config) and restart the client. Do not silently ignore hAIve.
1950
+ `;
1919
1951
  var CI_WORKFLOW = `name: haive-sync
1920
1952
 
1921
1953
  on:
@@ -2036,7 +2068,7 @@ jobs:
2036
2068
  function registerInit(program2) {
2037
2069
  program2.command("init").description(
2038
2070
  "Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Auto-bootstraps project-context.md from local files and seeds detected stack packs.\n Add --manual to control memory approval and session recaps yourself.\n Add --no-bootstrap and --stack none to disable the auto-features."
2039
- ).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
2071
+ ).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md / .cursor/rules/haive-mcp-required.mdc").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
2040
2072
  "--manual",
2041
2073
  "opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
2042
2074
  ).option(
@@ -2066,6 +2098,7 @@ function registerInit(program2) {
2066
2098
  await mkdir3(paths.teamDir, { recursive: true });
2067
2099
  await mkdir3(paths.moduleDir, { recursive: true });
2068
2100
  await mkdir3(paths.modulesContextDir, { recursive: true });
2101
+ await ensureAiRuntimeLayout(paths.runtimeDir);
2069
2102
  if (!existsSync6(paths.projectContext)) {
2070
2103
  if (wantBootstrap) {
2071
2104
  ui.info("Bootstrapping project context from local files\u2026");
@@ -2095,6 +2128,7 @@ function registerInit(program2) {
2095
2128
  await writeBridge(root, "CLAUDE.md");
2096
2129
  await writeBridge(root, ".cursorrules");
2097
2130
  await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
2131
+ await writeCursorHaiveRule(root);
2098
2132
  }
2099
2133
  const stacksToSeed = await resolveStacksToSeed(root, wantStack);
2100
2134
  if (stacksToSeed.length > 0) {
@@ -2237,6 +2271,17 @@ async function resolveStacksToSeed(root, stackOpt) {
2237
2271
  }
2238
2272
  return stackOpt.split(",").map((s) => s.trim().toLowerCase()).filter(isValidStack);
2239
2273
  }
2274
+ async function writeCursorHaiveRule(root) {
2275
+ const relPath = ".cursor/rules/haive-mcp-required.mdc";
2276
+ const target = path7.join(root, relPath);
2277
+ if (existsSync6(target)) {
2278
+ ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
2279
+ return;
2280
+ }
2281
+ await mkdir3(path7.dirname(target), { recursive: true });
2282
+ await writeFile3(target, CURSOR_HAIVE_RULE_MDC, "utf8");
2283
+ ui.success(`Created Cursor rule ${relPath}`);
2284
+ }
2240
2285
  async function writeBridge(root, relPath) {
2241
2286
  const target = path7.join(root, relPath);
2242
2287
  if (existsSync6(target)) {
@@ -2247,6 +2292,27 @@ async function writeBridge(root, relPath) {
2247
2292
  await writeFile3(target, BRIDGE_BODY, "utf8");
2248
2293
  ui.success(`Created bridge ${relPath}`);
2249
2294
  }
2295
+ var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
2296
+
2297
+ Not team truth. Use for machine-local session notes or tooling scratch files.
2298
+ Official memories belong in .ai/memories/ (versioned in Git).
2299
+ Only .gitignore and this README are meant to commit; everything else stays untracked.
2300
+ `;
2301
+ var RUNTIME_GITIGNORE_BODY = `*
2302
+ !.gitignore
2303
+ !README.md
2304
+ `;
2305
+ async function ensureAiRuntimeLayout(runtimeDir) {
2306
+ await mkdir3(runtimeDir, { recursive: true });
2307
+ const gi = path7.join(runtimeDir, ".gitignore");
2308
+ if (!existsSync6(gi)) {
2309
+ await writeFile3(gi, RUNTIME_GITIGNORE_BODY, "utf8");
2310
+ }
2311
+ const readme = path7.join(runtimeDir, "README.md");
2312
+ if (!existsSync6(readme)) {
2313
+ await writeFile3(readme, RUNTIME_README_BODY, "utf8");
2314
+ }
2315
+ }
2250
2316
  async function ensureGitignoreEntries(root, patterns) {
2251
2317
  try {
2252
2318
  const gitignorePath = path7.join(root, ".gitignore");
@@ -2631,6 +2697,7 @@ import {
2631
2697
  loadMemoriesFromDir as loadMemoriesFromDir3,
2632
2698
  loadUsageIndex as loadUsageIndex2,
2633
2699
  pickSnippetNeedle,
2700
+ rankMemoriesLexical,
2634
2701
  tokenizeQuery as tokenizeQuery2,
2635
2702
  trackReads as trackReads2
2636
2703
  } from "@hiveai/core";
@@ -2832,9 +2899,19 @@ import {
2832
2899
  serializeMemory as serializeMemory10
2833
2900
  } from "@hiveai/core";
2834
2901
  import { z as z29 } from "zod";
2902
+ import { existsSync as existsSync27 } from "fs";
2903
+ import { findLexicalConflictPairs, loadMemoriesFromDir as loadMemoriesFromDir21 } from "@hiveai/core";
2835
2904
  import { z as z30 } from "zod";
2905
+ import { resolveProjectInfo } from "@hiveai/core";
2836
2906
  import { z as z31 } from "zod";
2907
+ import { MemoryTypeSchema, suggestTopicKey } from "@hiveai/core";
2837
2908
  import { z as z32 } from "zod";
2909
+ import { existsSync as existsSync28 } from "fs";
2910
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
2911
+ import { z as z33 } from "zod";
2912
+ import { z as z34 } from "zod";
2913
+ import { z as z35 } from "zod";
2914
+ import { z as z36 } from "zod";
2838
2915
  function createContext(options = {}) {
2839
2916
  const env = options.env ?? process.env;
2840
2917
  const cwd = options.cwd ?? process.cwd();
@@ -3119,6 +3196,9 @@ var MemSearchInputSchema = {
3119
3196
  semantic: z5.boolean().default(false).describe(
3120
3197
  "Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
3121
3198
  ),
3199
+ lexical_rank: z5.boolean().default(false).describe(
3200
+ "When true (and semantic is false), rank the filtered corpus with Okapi-BM25-style lexical scoring instead of literal AND/OR. Helps phrase-like queries without embeddings."
3201
+ ),
3122
3202
  min_score: z5.number().min(0).max(1).default(0).describe("Minimum cosine similarity (semantic mode only)"),
3123
3203
  track: z5.boolean().default(true).describe("Increment read_count on returned memories (used for passive validation)")
3124
3204
  };
@@ -3144,6 +3224,27 @@ async function memSearch(input, ctx) {
3144
3224
  notice: "Semantic search unavailable (embeddings index missing or @hiveai/embeddings not installed). Falling back to literal search."
3145
3225
  };
3146
3226
  }
3227
+ } else if (input.lexical_rank && input.query.trim()) {
3228
+ const { ranked, scores } = rankMemoriesLexical(
3229
+ filtered,
3230
+ input.query,
3231
+ input.limit
3232
+ );
3233
+ if (ranked.length > 0) {
3234
+ const snippetNeedle = pickSnippetNeedle(input.query);
3235
+ result = {
3236
+ matches: ranked.map(
3237
+ (loaded, i) => lexicalHit(loaded, snippetNeedle, usage, scores[i])
3238
+ ),
3239
+ total: ranked.length,
3240
+ mode: "lexical_ranked"
3241
+ };
3242
+ } else {
3243
+ result = {
3244
+ ...buildLiteralResult(input, filtered, usage),
3245
+ notice: "Lexical ranking found no BM25-positive hits \u2014 showing literal matches instead."
3246
+ };
3247
+ }
3147
3248
  } else {
3148
3249
  result = buildLiteralResult(input, filtered, usage);
3149
3250
  }
@@ -3241,6 +3342,9 @@ function toHit(loaded, needle, usage) {
3241
3342
  file_path: loaded.filePath
3242
3343
  };
3243
3344
  }
3345
+ function lexicalHit(loaded, needle, usage, lexicalScore) {
3346
+ return { ...toHit(loaded, needle, usage), score: lexicalScore };
3347
+ }
3244
3348
  var MemVerifyInputSchema = {
3245
3349
  id: z6.string().optional().describe("If set, verify only this memory id"),
3246
3350
  update: z6.boolean().default(false).describe("Write the resulting status back to disk (status=stale or validated)")
@@ -5572,11 +5676,76 @@ function gitFileDiff(root, file, sinceDays) {
5572
5676
  return null;
5573
5677
  }
5574
5678
  }
5679
+ var MemConflictCandidatesInputSchema = {
5680
+ since_days: z30.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
5681
+ types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
5682
+ min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
5683
+ max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
5684
+ max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort.")
5685
+ };
5686
+ async function memConflictCandidates(input, ctx) {
5687
+ if (!existsSync27(ctx.paths.memoriesDir)) {
5688
+ return {
5689
+ pairs: [],
5690
+ scanned: 0,
5691
+ truncated: false,
5692
+ notice: "No .ai/memories directory."
5693
+ };
5694
+ }
5695
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
5696
+ const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
5697
+ sinceDays: input.since_days,
5698
+ types: input.types,
5699
+ minJaccard: input.min_jaccard,
5700
+ maxPairs: input.max_pairs,
5701
+ maxScan: input.max_scan
5702
+ });
5703
+ const notice = pairs.length === 0 ? "No lexical candidate pairs \u2265 threshold \u2014 try lowering min_jaccard or widen since_days/types." : void 0;
5704
+ return { pairs, scanned, truncated, notice };
5705
+ }
5706
+ var MemResolveProjectInputSchema = {
5707
+ cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
5708
+ };
5709
+ async function memResolveProject(input, _ctx) {
5710
+ void _ctx;
5711
+ return {
5712
+ ok: true,
5713
+ info: resolveProjectInfo({
5714
+ cwd: input.cwd
5715
+ })
5716
+ };
5717
+ }
5718
+ var MemSuggestTopicInputSchema = {
5719
+ type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
5720
+ title: z32.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
5721
+ };
5722
+ async function memSuggestTopic(input, _ctx) {
5723
+ void _ctx;
5724
+ const suggestion = suggestTopicKey(input.type, input.title);
5725
+ return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
5726
+ }
5727
+ var MemTimelineInputSchema = {
5728
+ memory_id: z33.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
5729
+ topic: z33.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
5730
+ limit: z33.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
5731
+ };
5732
+ async function memTimeline(input, ctx) {
5733
+ if (!existsSync28(ctx.paths.memoriesDir)) {
5734
+ return { entries: [], total: 0, notice: "No .ai/memories directory." };
5735
+ }
5736
+ const all = await loadMemoriesFromDir222(ctx.paths.memoriesDir);
5737
+ const { entries, notice } = collectTimelineEntries(all, {
5738
+ memoryId: input.memory_id,
5739
+ topic: input.topic,
5740
+ limit: input.limit
5741
+ });
5742
+ return { entries, total: entries.length, notice };
5743
+ }
5575
5744
  var BootstrapProjectArgsSchema = {
5576
- module: z30.string().optional().describe(
5745
+ module: z34.string().optional().describe(
5577
5746
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
5578
5747
  ),
5579
- focus: z30.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5748
+ focus: z34.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5580
5749
  };
5581
5750
  var ROOT_TEMPLATE = `# Project context
5582
5751
 
@@ -5657,8 +5826,8 @@ ${template}\`\`\`
5657
5826
  };
5658
5827
  }
5659
5828
  var PostTaskArgsSchema = {
5660
- task_summary: z31.string().optional().describe("One sentence describing what you just did"),
5661
- files_touched: z31.array(z31.string()).optional().describe("Files you created or modified during the task")
5829
+ task_summary: z35.string().optional().describe("One sentence describing what you just did"),
5830
+ files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
5662
5831
  };
5663
5832
  function postTaskPrompt(args, ctx) {
5664
5833
  const taskLine = args.task_summary ? `
@@ -5741,10 +5910,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
5741
5910
  };
5742
5911
  }
5743
5912
  var ImportDocsArgsSchema = {
5744
- content: z32.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
5745
- source: z32.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
5746
- scope: z32.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
5747
- dry_run: z32.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
5913
+ content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
5914
+ source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
5915
+ scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
5916
+ dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
5748
5917
  };
5749
5918
  function importDocsPrompt(args, ctx) {
5750
5919
  const sourceLine = args.source ? `
@@ -5807,7 +5976,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
5807
5976
  };
5808
5977
  }
5809
5978
  var SERVER_NAME = "haive";
5810
- var SERVER_VERSION = "0.9.3";
5979
+ var SERVER_VERSION = "0.9.5";
5811
5980
  function jsonResult(data) {
5812
5981
  return {
5813
5982
  content: [
@@ -5858,6 +6027,23 @@ function createHaiveServer(options = {}) {
5858
6027
  return jsonResult(await memSave(input, context));
5859
6028
  }
5860
6029
  );
6030
+ server.tool(
6031
+ "mem_suggest_topic",
6032
+ [
6033
+ "Propose a stable `topic` key (topic-upsert) from type + short title.",
6034
+ "",
6035
+ "USE BEFORE mem_save when you want deterministic updates to the same memory over time;",
6036
+ "families mimic Engram-style grouping (architecture/*, bug/*, decision/*, \u2026).",
6037
+ "",
6038
+ "PARAMETERS:",
6039
+ " type \u2014 convention | decision | gotcha | architecture | glossary | attempt | session_recap",
6040
+ " title \u2014 phrase to slugify under the suggested family prefix",
6041
+ "",
6042
+ "RETURNS: { topic_key, family, type }"
6043
+ ].join("\n"),
6044
+ MemSuggestTopicInputSchema,
6045
+ async (input) => jsonResult(await memSuggestTopic(input, context))
6046
+ );
5861
6047
  server.tool(
5862
6048
  "mem_tried",
5863
6049
  [
@@ -5949,8 +6135,12 @@ function createHaiveServer(options = {}) {
5949
6135
  server.tool(
5950
6136
  "get_briefing",
5951
6137
  [
5952
- "\u2B50 CALL THIS FIRST at the start of every task. One-shot onboarding that returns",
5953
- "everything relevant in a single call under a token budget.",
6138
+ "\u2B50 DEFAULT-FIRST for coding agents on any repo where `haive init` ran: call this BEFORE",
6139
+ "changing source or project config for the current goal (unless the developer explicitly opts out).",
6140
+ "One-shot onboarding: everything relevant in a single call under a token budget.",
6141
+ "",
6142
+ "PROGRESSIVE DISCLOSURE \u2014 after this, drill down only if needed:",
6143
+ " mem_relevant_to / mem_search (compact lists) \u2192 mem_get (full body + anchors).",
5954
6144
  "",
5955
6145
  "RETURNS (in order of priority):",
5956
6146
  " 0. action_required \u2014 \u26A0\uFE0F HANDLE THIS FIRST if non-empty (see protocol below)",
@@ -5985,7 +6175,7 @@ function createHaiveServer(options = {}) {
5985
6175
  " low \u2014 proposed, few reads (take with caution)",
5986
6176
  " unverified \u2014 draft (unverified: true flag set)",
5987
6177
  "",
5988
- "Replaces 4\u20135 separate tool calls. Always call this before any other tool."
6178
+ "Replaces 4\u20135 separate tool calls. Prefer this first; use mem_search / mem_get only for follow-up."
5989
6179
  ].join("\n"),
5990
6180
  GetBriefingInputSchema,
5991
6181
  async (input) => {
@@ -6004,6 +6194,8 @@ function createHaiveServer(options = {}) {
6004
6194
  "SEARCH MODES:",
6005
6195
  " Literal (default): AND search across id, tags, and body \u2014 all tokens must match.",
6006
6196
  " Falls back to OR automatically if no AND results (partial match).",
6197
+ " Lexical rank (lexical_rank: true, semantic: false): Okapi-BM25-style scoring on the",
6198
+ " filtered corpus \u2014 good for phrase-like queries without embeddings.",
6007
6199
  " Semantic (semantic: true): embedding-based similarity \u2014 finds related memories",
6008
6200
  " even with different wording. Requires haive embeddings index to be built.",
6009
6201
  "",
@@ -6012,6 +6204,7 @@ function createHaiveServer(options = {}) {
6012
6204
  " scope \u2014 filter by personal | team | module",
6013
6205
  " type \u2014 filter by convention | decision | gotcha | architecture | glossary",
6014
6206
  " semantic \u2014 true for embedding-based search (requires @hiveai/embeddings)",
6207
+ " lexical_rank \u2014 BM25-style ranking (ignored when semantic is true)",
6015
6208
  " limit \u2014 max results (default 10)",
6016
6209
  "",
6017
6210
  "RETURNS: array of { id, type, scope, status, confidence, body, match_quality }"
@@ -6022,6 +6215,22 @@ function createHaiveServer(options = {}) {
6022
6215
  return jsonResult(await memSearch(input, context));
6023
6216
  }
6024
6217
  );
6218
+ server.tool(
6219
+ "mem_timeline",
6220
+ [
6221
+ "Chronological view of related memories: by shared frontmatter.topic OR expanded from a seed id",
6222
+ "(related_ids, same topic, overlapping anchor paths \u2014 one extra hop on related_ids).",
6223
+ "",
6224
+ "PARAMETERS:",
6225
+ " memory_id \u2014 optional seed memory id",
6226
+ " topic \u2014 optional topic key (required if memory_id omitted)",
6227
+ " limit \u2014 max entries (default 30)",
6228
+ "",
6229
+ "RETURNS: { entries: [{ id, type, scope, created_at, one_line, topic? }], total, notice? }"
6230
+ ].join("\n"),
6231
+ MemTimelineInputSchema,
6232
+ async (input) => jsonResult(await memTimeline(input, context))
6233
+ );
6025
6234
  server.tool(
6026
6235
  "mem_for_files",
6027
6236
  [
@@ -6051,7 +6260,7 @@ function createHaiveServer(options = {}) {
6051
6260
  [
6052
6261
  "Fetch a single memory by its full id with all details.",
6053
6262
  "",
6054
- "USE WHEN get_briefing returned a memory in 'compact' format and you need",
6263
+ "USE WHEN get_briefing / mem_relevant_to / mem_search returned a compact hit and you need",
6055
6264
  "the full body, or when you know the exact id of a memory.",
6056
6265
  "",
6057
6266
  "PARAMETERS:",
@@ -6139,6 +6348,22 @@ function createHaiveServer(options = {}) {
6139
6348
  CodeMapInputSchema,
6140
6349
  async (input) => jsonResult(await codeMapTool(input, context))
6141
6350
  );
6351
+ server.tool(
6352
+ "mem_resolve_project",
6353
+ [
6354
+ "Diagnostics: resolve which project root hAIve is using (never throws).",
6355
+ "",
6356
+ "USE IN multi-root workspaces or when the agent CWD may not be the repo root \u2014",
6357
+ "mirrors HAIVE_PROJECT_ROOT, findProjectRoot markers, and presence of .ai/memories.",
6358
+ "",
6359
+ "PARAMETERS:",
6360
+ " cwd \u2014 optional directory used for discovery when HAIVE_PROJECT_ROOT is unset",
6361
+ "",
6362
+ "RETURNS: { ok: true, info: { cwd, resolved_root, haive_project_root_env, \u2026 } }"
6363
+ ].join("\n"),
6364
+ MemResolveProjectInputSchema,
6365
+ async (input) => jsonResult(await memResolveProject(input, context))
6366
+ );
6142
6367
  server.tool(
6143
6368
  "mem_update",
6144
6369
  [
@@ -6272,6 +6497,8 @@ function createHaiveServer(options = {}) {
6272
6497
  "One-shot ranked memories for a task \u2014 use instead of get_briefing when",
6273
6498
  "project context is already loaded and you only want the relevant memory layer.",
6274
6499
  "",
6500
+ "Second step in progressive disclosure (after get_briefing): narrow here, then mem_get for full text.",
6501
+ "",
6275
6502
  "Reuses the same ranking pipeline (anchor / module / literal / semantic) but",
6276
6503
  "skips project_context, modules, action_required, etc.",
6277
6504
  "",
@@ -6429,6 +6656,24 @@ function createHaiveServer(options = {}) {
6429
6656
  return jsonResult(await memConflicts(input, context));
6430
6657
  }
6431
6658
  );
6659
+ server.tool(
6660
+ "mem_conflict_candidates",
6661
+ [
6662
+ "Bulk lexical scan for decision/architecture-like pairs that look similar (Jaccard on tokens).",
6663
+ "",
6664
+ "Advisory only \u2014 follow with mem_conflicts_with on specific ids for real contradiction checks.",
6665
+ "",
6666
+ "PARAMETERS:",
6667
+ " since_days, types, min_jaccard, max_pairs, max_scan",
6668
+ "",
6669
+ "RETURNS: { pairs: [{ id_a, id_b, jaccard }], scanned, truncated, notice? }"
6670
+ ].join("\n"),
6671
+ MemConflictCandidatesInputSchema,
6672
+ async (input) => {
6673
+ tracker.record("mem_conflict_candidates", `${input.since_days}d`);
6674
+ return jsonResult(await memConflictCandidates(input, context));
6675
+ }
6676
+ );
6432
6677
  server.tool(
6433
6678
  "pre_commit_check",
6434
6679
  [
@@ -6566,7 +6811,7 @@ function registerMcp(program2) {
6566
6811
  // src/commands/sync.ts
6567
6812
  import { spawnSync as spawnSync2 } from "child_process";
6568
6813
  import { readFile as readFile8, writeFile as writeFile13, mkdir as mkdir8 } from "fs/promises";
6569
- import { existsSync as existsSync27 } from "fs";
6814
+ import { existsSync as existsSync29 } from "fs";
6570
6815
  import path12 from "path";
6571
6816
  import "commander";
6572
6817
  import {
@@ -6578,7 +6823,7 @@ import {
6578
6823
  isDecaying as isDecaying2,
6579
6824
  loadCodeMap as loadCodeMap4,
6580
6825
  loadConfig as loadConfig4,
6581
- loadMemoriesFromDir as loadMemoriesFromDir21,
6826
+ loadMemoriesFromDir as loadMemoriesFromDir23,
6582
6827
  loadUsageIndex as loadUsageIndex12,
6583
6828
  pullCrossRepoSources,
6584
6829
  resolveHaivePaths as resolveHaivePaths7,
@@ -6602,7 +6847,7 @@ function registerSync(program2) {
6602
6847
  ).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) => {
6603
6848
  const root = findProjectRoot10(opts.dir);
6604
6849
  const paths = resolveHaivePaths7(root);
6605
- if (!existsSync27(paths.memoriesDir)) {
6850
+ if (!existsSync29(paths.memoriesDir)) {
6606
6851
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
6607
6852
  process.exitCode = 1;
6608
6853
  return;
@@ -6618,7 +6863,7 @@ function registerSync(program2) {
6618
6863
  let promoted = 0;
6619
6864
  let autoApproved = 0;
6620
6865
  if (opts.verify !== false) {
6621
- const memories = await loadMemoriesFromDir21(paths.memoriesDir);
6866
+ const memories = await loadMemoriesFromDir23(paths.memoriesDir);
6622
6867
  for (const { memory: memory2, filePath } of memories) {
6623
6868
  if (memory2.frontmatter.type === "session_recap") {
6624
6869
  if (memory2.frontmatter.status === "stale") {
@@ -6679,7 +6924,7 @@ function registerSync(program2) {
6679
6924
  }
6680
6925
  }
6681
6926
  if (opts.promote !== false) {
6682
- const memories = await loadMemoriesFromDir21(paths.memoriesDir);
6927
+ const memories = await loadMemoriesFromDir23(paths.memoriesDir);
6683
6928
  const usage = await loadUsageIndex12(paths);
6684
6929
  const nowMs = Date.now();
6685
6930
  for (const { memory: memory2, filePath } of memories) {
@@ -6718,7 +6963,7 @@ function registerSync(program2) {
6718
6963
  }
6719
6964
  }
6720
6965
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
6721
- const draftMemories = (await loadMemoriesFromDir21(paths.memoriesDir)).filter(
6966
+ const draftMemories = (await loadMemoriesFromDir23(paths.memoriesDir)).filter(
6722
6967
  (m) => m.memory.frontmatter.status === "draft"
6723
6968
  );
6724
6969
  const draftCount = draftMemories.length;
@@ -6753,7 +6998,7 @@ function registerSync(program2) {
6753
6998
  }
6754
6999
  }
6755
7000
  if (!opts.quiet) {
6756
- const allForDecay = await loadMemoriesFromDir21(paths.memoriesDir);
7001
+ const allForDecay = await loadMemoriesFromDir23(paths.memoriesDir);
6757
7002
  const usageForDecay = await loadUsageIndex12(paths);
6758
7003
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
6759
7004
  const fm = memory2.frontmatter;
@@ -6971,8 +7216,8 @@ Attends une **confirmation explicite** avant d'agir.
6971
7216
  });
6972
7217
  }
6973
7218
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
6974
- if (!existsSync27(memoriesDir)) return;
6975
- const all = await loadMemoriesFromDir21(memoriesDir);
7219
+ if (!existsSync29(memoriesDir)) return;
7220
+ const all = await loadMemoriesFromDir23(memoriesDir);
6976
7221
  const top = all.filter(({ memory: memory2 }) => {
6977
7222
  const s = memory2.frontmatter.status;
6978
7223
  if (memory2.frontmatter.type === "session_recap") return false;
@@ -6996,7 +7241,7 @@ ${m.memory.body.trim()}`;
6996
7241
  ` + block + `
6997
7242
 
6998
7243
  ${BRIDGE_END}`;
6999
- const fileExists = existsSync27(bridgeFile);
7244
+ const fileExists = existsSync29(bridgeFile);
7000
7245
  let existing = fileExists ? await readFile8(bridgeFile, "utf8") : "";
7001
7246
  existing = existing.replace(/\r\n/g, "\n");
7002
7247
  const startIdx = existing.indexOf(BRIDGE_START);
@@ -7047,14 +7292,14 @@ function collectSinceChanges(root, ref) {
7047
7292
  // src/commands/memory-add.ts
7048
7293
  import { createHash as createHash2 } from "crypto";
7049
7294
  import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile14 } from "fs/promises";
7050
- import { existsSync as existsSync28 } from "fs";
7295
+ import { existsSync as existsSync30 } from "fs";
7051
7296
  import path13 from "path";
7052
7297
  import "commander";
7053
7298
  import {
7054
7299
  buildFrontmatter as buildFrontmatter7,
7055
7300
  findProjectRoot as findProjectRoot11,
7056
7301
  inferModulesFromPaths as inferModulesFromPaths3,
7057
- loadMemoriesFromDir as loadMemoriesFromDir23,
7302
+ loadMemoriesFromDir as loadMemoriesFromDir24,
7058
7303
  memoryFilePath as memoryFilePath6,
7059
7304
  resolveHaivePaths as resolveHaivePaths8,
7060
7305
  serializeMemory as serializeMemory12
@@ -7086,7 +7331,7 @@ function registerMemoryAdd(memory2) {
7086
7331
  ).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) => {
7087
7332
  const root = findProjectRoot11(opts.dir);
7088
7333
  const paths = resolveHaivePaths8(root);
7089
- if (!existsSync28(paths.haiveDir)) {
7334
+ if (!existsSync30(paths.haiveDir)) {
7090
7335
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
7091
7336
  process.exitCode = 1;
7092
7337
  return;
@@ -7097,7 +7342,7 @@ function registerMemoryAdd(memory2) {
7097
7342
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
7098
7343
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
7099
7344
  if (anchorPaths.length > 0) {
7100
- const missing = anchorPaths.filter((p) => !existsSync28(path13.resolve(root, p)));
7345
+ const missing = anchorPaths.filter((p) => !existsSync30(path13.resolve(root, p)));
7101
7346
  if (missing.length > 0) {
7102
7347
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
7103
7348
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -7109,7 +7354,7 @@ function registerMemoryAdd(memory2) {
7109
7354
  const title = opts.title ?? opts.slug;
7110
7355
  let body;
7111
7356
  if (opts.bodyFile !== void 0) {
7112
- if (!existsSync28(opts.bodyFile)) {
7357
+ if (!existsSync30(opts.bodyFile)) {
7113
7358
  ui.error(`--body-file not found: ${opts.bodyFile}`);
7114
7359
  process.exitCode = 1;
7115
7360
  return;
@@ -7130,9 +7375,9 @@ TODO \u2014 write the memory body.
7130
7375
  `;
7131
7376
  }
7132
7377
  const scope = opts.scope ?? "personal";
7133
- if (existsSync28(paths.memoriesDir)) {
7378
+ if (existsSync30(paths.memoriesDir)) {
7134
7379
  const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
7135
- const allForHash = await loadMemoriesFromDir23(paths.memoriesDir);
7380
+ const allForHash = await loadMemoriesFromDir24(paths.memoriesDir);
7136
7381
  const hashDup = allForHash.find(
7137
7382
  ({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
7138
7383
  );
@@ -7143,8 +7388,8 @@ TODO \u2014 write the memory body.
7143
7388
  return;
7144
7389
  }
7145
7390
  }
7146
- if (opts.topic && existsSync28(paths.memoriesDir)) {
7147
- const existing = await loadMemoriesFromDir23(paths.memoriesDir);
7391
+ if (opts.topic && existsSync30(paths.memoriesDir)) {
7392
+ const existing = await loadMemoriesFromDir24(paths.memoriesDir);
7148
7393
  const topicMatch = existing.find(
7149
7394
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
7150
7395
  );
@@ -7182,13 +7427,13 @@ TODO \u2014 write the memory body.
7182
7427
  });
7183
7428
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
7184
7429
  await mkdir9(path13.dirname(file), { recursive: true });
7185
- if (existsSync28(file)) {
7430
+ if (existsSync30(file)) {
7186
7431
  ui.error(`Memory already exists at ${file}`);
7187
7432
  process.exitCode = 1;
7188
7433
  return;
7189
7434
  }
7190
- if (existsSync28(paths.memoriesDir)) {
7191
- const existing = await loadMemoriesFromDir23(paths.memoriesDir);
7435
+ if (existsSync30(paths.memoriesDir)) {
7436
+ const existing = await loadMemoriesFromDir24(paths.memoriesDir);
7192
7437
  const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
7193
7438
  const similar = existing.filter(({ memory: memory3 }) => {
7194
7439
  const id = memory3.frontmatter.id.toLowerCase();
@@ -7230,14 +7475,14 @@ function parseCsv2(value) {
7230
7475
  }
7231
7476
 
7232
7477
  // src/commands/memory-list.ts
7233
- import { existsSync as existsSync29 } from "fs";
7478
+ import { existsSync as existsSync31 } from "fs";
7234
7479
  import path14 from "path";
7235
7480
  import "commander";
7236
7481
  import { findProjectRoot as findProjectRoot12, resolveHaivePaths as resolveHaivePaths9 } from "@hiveai/core";
7237
7482
 
7238
7483
  // src/utils/fs.ts
7239
7484
  import {
7240
- loadMemoriesFromDir as loadMemoriesFromDir24,
7485
+ loadMemoriesFromDir as loadMemoriesFromDir25,
7241
7486
  loadMemory,
7242
7487
  listMarkdownFilesRecursive
7243
7488
  } from "@hiveai/core";
@@ -7247,12 +7492,12 @@ function registerMemoryList(memory2) {
7247
7492
  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) => {
7248
7493
  const root = findProjectRoot12(opts.dir);
7249
7494
  const paths = resolveHaivePaths9(root);
7250
- if (!existsSync29(paths.memoriesDir)) {
7495
+ if (!existsSync31(paths.memoriesDir)) {
7251
7496
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
7252
7497
  process.exitCode = 1;
7253
7498
  return;
7254
7499
  }
7255
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
7500
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7256
7501
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
7257
7502
  const filtered = all.filter((m) => {
7258
7503
  if (!matchesFilters(m, opts)) return false;
@@ -7315,7 +7560,7 @@ function matchesFilters(loaded, opts) {
7315
7560
 
7316
7561
  // src/commands/memory-promote.ts
7317
7562
  import { mkdir as mkdir10, unlink as unlink2, writeFile as writeFile15 } from "fs/promises";
7318
- import { existsSync as existsSync30 } from "fs";
7563
+ import { existsSync as existsSync33 } from "fs";
7319
7564
  import path15 from "path";
7320
7565
  import "commander";
7321
7566
  import {
@@ -7328,12 +7573,12 @@ function registerMemoryPromote(memory2) {
7328
7573
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
7329
7574
  const root = findProjectRoot13(opts.dir);
7330
7575
  const paths = resolveHaivePaths10(root);
7331
- if (!existsSync30(paths.memoriesDir)) {
7576
+ if (!existsSync33(paths.memoriesDir)) {
7332
7577
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
7333
7578
  process.exitCode = 1;
7334
7579
  return;
7335
7580
  }
7336
- const teamAndModule = await loadMemoriesFromDir24(paths.memoriesDir);
7581
+ const teamAndModule = await loadMemoriesFromDir25(paths.memoriesDir);
7337
7582
  const alreadyShared = teamAndModule.find(
7338
7583
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
7339
7584
  );
@@ -7347,7 +7592,7 @@ function registerMemoryPromote(memory2) {
7347
7592
  }
7348
7593
  return;
7349
7594
  }
7350
- const all = await loadMemoriesFromDir24(paths.personalDir);
7595
+ const all = await loadMemoriesFromDir25(paths.personalDir);
7351
7596
  const found = all.find((m) => m.memory.frontmatter.id === id);
7352
7597
  if (!found) {
7353
7598
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -7373,7 +7618,7 @@ function registerMemoryPromote(memory2) {
7373
7618
  }
7374
7619
 
7375
7620
  // src/commands/memory-approve.ts
7376
- import { existsSync as existsSync31 } from "fs";
7621
+ import { existsSync as existsSync34 } from "fs";
7377
7622
  import { writeFile as writeFile16 } from "fs/promises";
7378
7623
  import path16 from "path";
7379
7624
  import "commander";
@@ -7386,12 +7631,12 @@ function registerMemoryApprove(memory2) {
7386
7631
  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) => {
7387
7632
  const root = findProjectRoot14(opts.dir);
7388
7633
  const paths = resolveHaivePaths11(root);
7389
- if (!existsSync31(paths.memoriesDir)) {
7634
+ if (!existsSync34(paths.memoriesDir)) {
7390
7635
  ui.error(`No .ai/memories at ${root}.`);
7391
7636
  process.exitCode = 1;
7392
7637
  return;
7393
7638
  }
7394
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
7639
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7395
7640
  if (opts.all || opts.pending) {
7396
7641
  const candidates = all.filter((m) => {
7397
7642
  const s = m.memory.frontmatter.status;
@@ -7445,7 +7690,7 @@ function registerMemoryApprove(memory2) {
7445
7690
 
7446
7691
  // src/commands/memory-update.ts
7447
7692
  import { writeFile as writeFile17 } from "fs/promises";
7448
- import { existsSync as existsSync33 } from "fs";
7693
+ import { existsSync as existsSync35 } from "fs";
7449
7694
  import path17 from "path";
7450
7695
  import "commander";
7451
7696
  import {
@@ -7457,12 +7702,12 @@ function registerMemoryUpdate(memory2) {
7457
7702
  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) => {
7458
7703
  const root = findProjectRoot15(opts.dir);
7459
7704
  const paths = resolveHaivePaths12(root);
7460
- if (!existsSync33(paths.memoriesDir)) {
7705
+ if (!existsSync35(paths.memoriesDir)) {
7461
7706
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
7462
7707
  process.exitCode = 1;
7463
7708
  return;
7464
7709
  }
7465
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
7710
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
7466
7711
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
7467
7712
  if (!loaded) {
7468
7713
  ui.error(`No memory with id "${id}".`);
@@ -7529,7 +7774,7 @@ function parseCsv3(value) {
7529
7774
 
7530
7775
  // src/commands/memory-auto-promote.ts
7531
7776
  import { writeFile as writeFile18 } from "fs/promises";
7532
- import { existsSync as existsSync34 } from "fs";
7777
+ import { existsSync as existsSync36 } from "fs";
7533
7778
  import path18 from "path";
7534
7779
  import "commander";
7535
7780
  import {
@@ -7549,7 +7794,7 @@ function registerMemoryAutoPromote(memory2) {
7549
7794
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
7550
7795
  const root = findProjectRoot16(opts.dir);
7551
7796
  const paths = resolveHaivePaths13(root);
7552
- if (!existsSync34(paths.memoriesDir)) {
7797
+ if (!existsSync36(paths.memoriesDir)) {
7553
7798
  ui.error(`No .ai/memories at ${root}.`);
7554
7799
  process.exitCode = 1;
7555
7800
  return;
@@ -7558,7 +7803,7 @@ function registerMemoryAutoPromote(memory2) {
7558
7803
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
7559
7804
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
7560
7805
  };
7561
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
7806
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
7562
7807
  const usage = await loadUsageIndex13(paths);
7563
7808
  const eligible = memories.filter(
7564
7809
  ({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage11(usage, memory3.frontmatter.id), rule)
@@ -7592,7 +7837,7 @@ function registerMemoryAutoPromote(memory2) {
7592
7837
 
7593
7838
  // src/commands/memory-edit.ts
7594
7839
  import { spawn as spawn3 } from "child_process";
7595
- import { existsSync as existsSync35 } from "fs";
7840
+ import { existsSync as existsSync37 } from "fs";
7596
7841
  import { readFile as readFile10 } from "fs/promises";
7597
7842
  import path19 from "path";
7598
7843
  import "commander";
@@ -7605,12 +7850,12 @@ function registerMemoryEdit(memory2) {
7605
7850
  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) => {
7606
7851
  const root = findProjectRoot17(opts.dir);
7607
7852
  const paths = resolveHaivePaths14(root);
7608
- if (!existsSync35(paths.memoriesDir)) {
7853
+ if (!existsSync37(paths.memoriesDir)) {
7609
7854
  ui.error(`No .ai/memories at ${root}.`);
7610
7855
  process.exitCode = 1;
7611
7856
  return;
7612
7857
  }
7613
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
7858
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7614
7859
  const found = all.find((m) => m.memory.frontmatter.id === id);
7615
7860
  if (!found) {
7616
7861
  ui.error(`No memory with id "${id}".`);
@@ -7645,7 +7890,7 @@ function runEditor(editor, file) {
7645
7890
  }
7646
7891
 
7647
7892
  // src/commands/memory-for-files.ts
7648
- import { existsSync as existsSync36 } from "fs";
7893
+ import { existsSync as existsSync38 } from "fs";
7649
7894
  import path20 from "path";
7650
7895
  import "commander";
7651
7896
  import {
@@ -7661,12 +7906,12 @@ function registerMemoryForFiles(memory2) {
7661
7906
  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) => {
7662
7907
  const root = findProjectRoot18(opts.dir);
7663
7908
  const paths = resolveHaivePaths15(root);
7664
- if (!existsSync36(paths.memoriesDir)) {
7909
+ if (!existsSync38(paths.memoriesDir)) {
7665
7910
  ui.error(`No .ai/memories at ${root}.`);
7666
7911
  process.exitCode = 1;
7667
7912
  return;
7668
7913
  }
7669
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
7914
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7670
7915
  const usage = await loadUsageIndex14(paths);
7671
7916
  const inferred = inferModulesFromPaths4(files);
7672
7917
  const byAnchor = [];
@@ -7773,7 +8018,7 @@ function printGroup(root, label, loaded, usage) {
7773
8018
  }
7774
8019
 
7775
8020
  // src/commands/memory-hot.ts
7776
- import { existsSync as existsSync37 } from "fs";
8021
+ import { existsSync as existsSync39 } from "fs";
7777
8022
  import path21 from "path";
7778
8023
  import "commander";
7779
8024
  import {
@@ -7786,13 +8031,13 @@ function registerMemoryHot(memory2) {
7786
8031
  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) => {
7787
8032
  const root = findProjectRoot19(opts.dir);
7788
8033
  const paths = resolveHaivePaths16(root);
7789
- if (!existsSync37(paths.memoriesDir)) {
8034
+ if (!existsSync39(paths.memoriesDir)) {
7790
8035
  ui.error(`No .ai/memories at ${root}.`);
7791
8036
  process.exitCode = 1;
7792
8037
  return;
7793
8038
  }
7794
8039
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
7795
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8040
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7796
8041
  const usage = await loadUsageIndex15(paths);
7797
8042
  const candidates = all.filter(({ memory: mem }) => {
7798
8043
  const fm = mem.frontmatter;
@@ -7824,7 +8069,7 @@ function registerMemoryHot(memory2) {
7824
8069
 
7825
8070
  // src/commands/memory-tried.ts
7826
8071
  import { mkdir as mkdir11, writeFile as writeFile19 } from "fs/promises";
7827
- import { existsSync as existsSync38 } from "fs";
8072
+ import { existsSync as existsSync40 } from "fs";
7828
8073
  import path23 from "path";
7829
8074
  import "commander";
7830
8075
  import {
@@ -7853,7 +8098,7 @@ function registerMemoryTried(memory2) {
7853
8098
  ).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) => {
7854
8099
  const root = findProjectRoot20(opts.dir);
7855
8100
  const paths = resolveHaivePaths17(root);
7856
- if (!existsSync38(paths.haiveDir)) {
8101
+ if (!existsSync40(paths.haiveDir)) {
7857
8102
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
7858
8103
  process.exitCode = 1;
7859
8104
  return;
@@ -7877,7 +8122,7 @@ function registerMemoryTried(memory2) {
7877
8122
  const body = lines.join("\n") + "\n";
7878
8123
  const file = memoryFilePath8(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
7879
8124
  await mkdir11(path23.dirname(file), { recursive: true });
7880
- if (existsSync38(file)) {
8125
+ if (existsSync40(file)) {
7881
8126
  ui.error(`Memory already exists at ${file}`);
7882
8127
  process.exitCode = 1;
7883
8128
  return;
@@ -7893,7 +8138,7 @@ function parseCsv4(value) {
7893
8138
  }
7894
8139
 
7895
8140
  // src/commands/memory-pending.ts
7896
- import { existsSync as existsSync39 } from "fs";
8141
+ import { existsSync as existsSync41 } from "fs";
7897
8142
  import path24 from "path";
7898
8143
  import "commander";
7899
8144
  import {
@@ -7906,12 +8151,12 @@ function registerMemoryPending(memory2) {
7906
8151
  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) => {
7907
8152
  const root = findProjectRoot21(opts.dir);
7908
8153
  const paths = resolveHaivePaths18(root);
7909
- if (!existsSync39(paths.memoriesDir)) {
8154
+ if (!existsSync41(paths.memoriesDir)) {
7910
8155
  ui.error(`No .ai/memories at ${root}.`);
7911
8156
  process.exitCode = 1;
7912
8157
  return;
7913
8158
  }
7914
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8159
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7915
8160
  const usage = await loadUsageIndex16(paths);
7916
8161
  const proposed = all.filter(({ memory: mem }) => {
7917
8162
  if (mem.frontmatter.status !== "proposed") return false;
@@ -7941,7 +8186,7 @@ function registerMemoryPending(memory2) {
7941
8186
  }
7942
8187
 
7943
8188
  // src/commands/memory-query.ts
7944
- import { existsSync as existsSync40 } from "fs";
8189
+ import { existsSync as existsSync43 } from "fs";
7945
8190
  import path25 from "path";
7946
8191
  import "commander";
7947
8192
  import {
@@ -7958,7 +8203,7 @@ function registerMemoryQuery(memory2) {
7958
8203
  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) => {
7959
8204
  const root = findProjectRoot22(opts.dir);
7960
8205
  const paths = resolveHaivePaths19(root);
7961
- if (!existsSync40(paths.memoriesDir)) {
8206
+ if (!existsSync43(paths.memoriesDir)) {
7962
8207
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
7963
8208
  process.exitCode = 1;
7964
8209
  return;
@@ -7969,7 +8214,7 @@ function registerMemoryQuery(memory2) {
7969
8214
  return;
7970
8215
  }
7971
8216
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
7972
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8217
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
7973
8218
  const passesFilters2 = (mem) => {
7974
8219
  const fm = mem.frontmatter;
7975
8220
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -8017,7 +8262,7 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
8017
8262
 
8018
8263
  // src/commands/memory-reject.ts
8019
8264
  import { writeFile as writeFile20 } from "fs/promises";
8020
- import { existsSync as existsSync41 } from "fs";
8265
+ import { existsSync as existsSync44 } from "fs";
8021
8266
  import "commander";
8022
8267
  import {
8023
8268
  findProjectRoot as findProjectRoot23,
@@ -8031,12 +8276,12 @@ function registerMemoryReject(memory2) {
8031
8276
  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) => {
8032
8277
  const root = findProjectRoot23(opts.dir);
8033
8278
  const paths = resolveHaivePaths20(root);
8034
- if (!existsSync41(paths.memoriesDir)) {
8279
+ if (!existsSync44(paths.memoriesDir)) {
8035
8280
  ui.error(`No .ai/memories at ${root}.`);
8036
8281
  process.exitCode = 1;
8037
8282
  return;
8038
8283
  }
8039
- const memories = await loadMemoriesFromDir24(paths.memoriesDir);
8284
+ const memories = await loadMemoriesFromDir25(paths.memoriesDir);
8040
8285
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
8041
8286
  if (!loaded) {
8042
8287
  ui.error(`No memory with id "${id}".`);
@@ -8067,7 +8312,7 @@ function registerMemoryReject(memory2) {
8067
8312
  }
8068
8313
 
8069
8314
  // src/commands/memory-rm.ts
8070
- import { existsSync as existsSync43 } from "fs";
8315
+ import { existsSync as existsSync45 } from "fs";
8071
8316
  import { unlink as unlink3 } from "fs/promises";
8072
8317
  import path26 from "path";
8073
8318
  import { createInterface } from "readline/promises";
@@ -8082,12 +8327,12 @@ function registerMemoryRm(memory2) {
8082
8327
  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) => {
8083
8328
  const root = findProjectRoot24(opts.dir);
8084
8329
  const paths = resolveHaivePaths21(root);
8085
- if (!existsSync43(paths.memoriesDir)) {
8330
+ if (!existsSync45(paths.memoriesDir)) {
8086
8331
  ui.error(`No .ai/memories at ${root}.`);
8087
8332
  process.exitCode = 1;
8088
8333
  return;
8089
8334
  }
8090
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8335
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
8091
8336
  const found = all.find((m) => m.memory.frontmatter.id === id);
8092
8337
  if (!found) {
8093
8338
  ui.error(`No memory with id "${id}".`);
@@ -8118,7 +8363,7 @@ function registerMemoryRm(memory2) {
8118
8363
  }
8119
8364
 
8120
8365
  // src/commands/memory-show.ts
8121
- import { existsSync as existsSync44 } from "fs";
8366
+ import { existsSync as existsSync46 } from "fs";
8122
8367
  import { readFile as readFile11 } from "fs/promises";
8123
8368
  import path27 from "path";
8124
8369
  import "commander";
@@ -8133,12 +8378,12 @@ function registerMemoryShow(memory2) {
8133
8378
  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) => {
8134
8379
  const root = findProjectRoot25(opts.dir);
8135
8380
  const paths = resolveHaivePaths22(root);
8136
- if (!existsSync44(paths.memoriesDir)) {
8381
+ if (!existsSync46(paths.memoriesDir)) {
8137
8382
  ui.error(`No .ai/memories at ${root}.`);
8138
8383
  process.exitCode = 1;
8139
8384
  return;
8140
8385
  }
8141
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8386
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
8142
8387
  const found = all.find((m) => m.memory.frontmatter.id === id);
8143
8388
  if (!found) {
8144
8389
  ui.error(`No memory with id "${id}".`);
@@ -8177,7 +8422,7 @@ function registerMemoryShow(memory2) {
8177
8422
  }
8178
8423
 
8179
8424
  // src/commands/memory-stats.ts
8180
- import { existsSync as existsSync45 } from "fs";
8425
+ import { existsSync as existsSync47 } from "fs";
8181
8426
  import path28 from "path";
8182
8427
  import "commander";
8183
8428
  import {
@@ -8191,12 +8436,12 @@ function registerMemoryStats(memory2) {
8191
8436
  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) => {
8192
8437
  const root = findProjectRoot26(opts.dir);
8193
8438
  const paths = resolveHaivePaths23(root);
8194
- if (!existsSync45(paths.memoriesDir)) {
8439
+ if (!existsSync47(paths.memoriesDir)) {
8195
8440
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8196
8441
  process.exitCode = 1;
8197
8442
  return;
8198
8443
  }
8199
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8444
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
8200
8445
  const usage = await loadUsageIndex20(paths);
8201
8446
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
8202
8447
  if (target.length === 0) {
@@ -8223,7 +8468,7 @@ function registerMemoryStats(memory2) {
8223
8468
 
8224
8469
  // src/commands/memory-verify.ts
8225
8470
  import { writeFile as writeFile21 } from "fs/promises";
8226
- import { existsSync as existsSync46 } from "fs";
8471
+ import { existsSync as existsSync48 } from "fs";
8227
8472
  import path29 from "path";
8228
8473
  import "commander";
8229
8474
  import {
@@ -8238,12 +8483,12 @@ function registerMemoryVerify(memory2) {
8238
8483
  ).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) => {
8239
8484
  const root = findProjectRoot27(opts.dir);
8240
8485
  const paths = resolveHaivePaths24(root);
8241
- if (!existsSync46(paths.memoriesDir)) {
8486
+ if (!existsSync48(paths.memoriesDir)) {
8242
8487
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
8243
8488
  process.exitCode = 1;
8244
8489
  return;
8245
8490
  }
8246
- const all = await loadMemoriesFromDir24(paths.memoriesDir);
8491
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
8247
8492
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
8248
8493
  if (opts.id && targets.length === 0) {
8249
8494
  ui.error(`No memory with id "${opts.id}".`);
@@ -8325,7 +8570,7 @@ function applyVerification2(mem, result) {
8325
8570
 
8326
8571
  // src/commands/memory-import.ts
8327
8572
  import { readFile as readFile12 } from "fs/promises";
8328
- import { existsSync as existsSync47 } from "fs";
8573
+ import { existsSync as existsSync49 } from "fs";
8329
8574
  import "commander";
8330
8575
  import {
8331
8576
  findProjectRoot as findProjectRoot28,
@@ -8337,12 +8582,12 @@ function registerMemoryImport(memory2) {
8337
8582
  ).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) => {
8338
8583
  const root = findProjectRoot28(opts.dir);
8339
8584
  const paths = resolveHaivePaths25(root);
8340
- if (!existsSync47(paths.haiveDir)) {
8585
+ if (!existsSync49(paths.haiveDir)) {
8341
8586
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8342
8587
  process.exitCode = 1;
8343
8588
  return;
8344
8589
  }
8345
- if (!existsSync47(opts.from)) {
8590
+ if (!existsSync49(opts.from)) {
8346
8591
  ui.error(`File not found: ${opts.from}`);
8347
8592
  process.exitCode = 1;
8348
8593
  return;
@@ -8375,7 +8620,7 @@ function registerMemoryImport(memory2) {
8375
8620
  }
8376
8621
 
8377
8622
  // src/commands/memory-import-changelog.ts
8378
- import { existsSync as existsSync48 } from "fs";
8623
+ import { existsSync as existsSync50 } from "fs";
8379
8624
  import { readFile as readFile13, mkdir as mkdir12, writeFile as writeFile23 } from "fs/promises";
8380
8625
  import path30 from "path";
8381
8626
  import "commander";
@@ -8449,7 +8694,7 @@ function registerMemoryImportChangelog(memory2) {
8449
8694
  const root = findProjectRoot29(opts.dir);
8450
8695
  const paths = resolveHaivePaths26(root);
8451
8696
  const changelogPath = path30.resolve(root, opts.fromChangelog);
8452
- if (!existsSync48(changelogPath)) {
8697
+ if (!existsSync50(changelogPath)) {
8453
8698
  ui.error(`CHANGELOG not found: ${changelogPath}`);
8454
8699
  process.exitCode = 1;
8455
8700
  return;
@@ -8535,7 +8780,7 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
8535
8780
  }
8536
8781
 
8537
8782
  // src/commands/memory-digest.ts
8538
- import { existsSync as existsSync49 } from "fs";
8783
+ import { existsSync as existsSync51 } from "fs";
8539
8784
  import { writeFile as writeFile24 } from "fs/promises";
8540
8785
  import path31 from "path";
8541
8786
  import "commander";
@@ -8543,7 +8788,7 @@ import {
8543
8788
  deriveConfidence as deriveConfidence12,
8544
8789
  findProjectRoot as findProjectRoot30,
8545
8790
  getUsage as getUsage17,
8546
- loadMemoriesFromDir as loadMemoriesFromDir25,
8791
+ loadMemoriesFromDir as loadMemoriesFromDir26,
8547
8792
  loadUsageIndex as loadUsageIndex21,
8548
8793
  resolveHaivePaths as resolveHaivePaths27
8549
8794
  } from "@hiveai/core";
@@ -8560,7 +8805,7 @@ function registerMemoryDigest(program2) {
8560
8805
  ).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) => {
8561
8806
  const root = findProjectRoot30(opts.dir);
8562
8807
  const paths = resolveHaivePaths27(root);
8563
- if (!existsSync49(paths.memoriesDir)) {
8808
+ if (!existsSync51(paths.memoriesDir)) {
8564
8809
  ui.error("No .ai/memories found. Run `haive init` first.");
8565
8810
  process.exitCode = 1;
8566
8811
  return;
@@ -8568,7 +8813,7 @@ function registerMemoryDigest(program2) {
8568
8813
  const days = Math.max(1, Number(opts.days ?? 7));
8569
8814
  const scopeFilter = opts.scope ?? "team";
8570
8815
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
8571
- const all = await loadMemoriesFromDir25(paths.memoriesDir);
8816
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
8572
8817
  const usage = await loadUsageIndex21(paths);
8573
8818
  const recent = all.filter(({ memory: mem }) => {
8574
8819
  const fm = mem.frontmatter;
@@ -8643,20 +8888,20 @@ function registerMemoryDigest(program2) {
8643
8888
 
8644
8889
  // src/commands/session-end.ts
8645
8890
  import { writeFile as writeFile25, mkdir as mkdir13, readFile as readFile14, rm as rm2 } from "fs/promises";
8646
- import { existsSync as existsSync50 } from "fs";
8891
+ import { existsSync as existsSync53 } from "fs";
8647
8892
  import path33 from "path";
8648
8893
  import "commander";
8649
8894
  import {
8650
8895
  buildFrontmatter as buildFrontmatter10,
8651
8896
  findProjectRoot as findProjectRoot31,
8652
- loadMemoriesFromDir as loadMemoriesFromDir26,
8897
+ loadMemoriesFromDir as loadMemoriesFromDir27,
8653
8898
  memoryFilePath as memoryFilePath9,
8654
8899
  resolveHaivePaths as resolveHaivePaths28,
8655
8900
  serializeMemory as serializeMemory21
8656
8901
  } from "@hiveai/core";
8657
8902
  async function buildAutoRecap(paths) {
8658
8903
  const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
8659
- if (!existsSync50(obsFile)) return null;
8904
+ if (!existsSync53(obsFile)) return null;
8660
8905
  const raw = await readFile14(obsFile, "utf8").catch(() => "");
8661
8906
  if (!raw.trim()) return null;
8662
8907
  const lines = raw.split("\n").filter(Boolean);
@@ -8737,7 +8982,7 @@ function registerSessionEnd(session2) {
8737
8982
  ).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) => {
8738
8983
  const root = findProjectRoot31(opts.dir);
8739
8984
  const paths = resolveHaivePaths28(root);
8740
- if (!existsSync50(paths.haiveDir)) {
8985
+ if (!existsSync53(paths.haiveDir)) {
8741
8986
  if (opts.auto || opts.quiet) return;
8742
8987
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
8743
8988
  process.exitCode = 1;
@@ -8769,7 +9014,7 @@ function registerSessionEnd(session2) {
8769
9014
  });
8770
9015
  const topic = recapTopic2(scope, opts.module);
8771
9016
  const filesTouched = parseCsv5(resolvedFiles);
8772
- const missingPaths = filesTouched.filter((p) => !existsSync50(path33.resolve(root, p)));
9017
+ const missingPaths = filesTouched.filter((p) => !existsSync53(path33.resolve(root, p)));
8773
9018
  if (missingPaths.length > 0 && !opts.quiet) {
8774
9019
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
8775
9020
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -8777,11 +9022,11 @@ function registerSessionEnd(session2) {
8777
9022
  const cleanupObservations = async () => {
8778
9023
  if (!opts.auto) return;
8779
9024
  const obsFile = path33.join(paths.haiveDir, ".cache", "observations.jsonl");
8780
- if (existsSync50(obsFile)) await rm2(obsFile).catch(() => {
9025
+ if (existsSync53(obsFile)) await rm2(obsFile).catch(() => {
8781
9026
  });
8782
9027
  };
8783
- if (existsSync50(paths.memoriesDir)) {
8784
- const existing = await loadMemoriesFromDir26(paths.memoriesDir);
9028
+ if (existsSync53(paths.memoriesDir)) {
9029
+ const existing = await loadMemoriesFromDir27(paths.memoriesDir);
8785
9030
  const topicMatch = existing.find(
8786
9031
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
8787
9032
  );
@@ -8834,7 +9079,7 @@ function parseCsv5(value) {
8834
9079
  }
8835
9080
 
8836
9081
  // src/commands/snapshot.ts
8837
- import { existsSync as existsSync51 } from "fs";
9082
+ import { existsSync as existsSync54 } from "fs";
8838
9083
  import { readdir as readdir4 } from "fs/promises";
8839
9084
  import path34 from "path";
8840
9085
  import "commander";
@@ -8869,14 +9114,14 @@ function registerSnapshot(program2) {
8869
9114
  ).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) => {
8870
9115
  const root = findProjectRoot32(opts.dir);
8871
9116
  const paths = resolveHaivePaths29(root);
8872
- if (!existsSync51(paths.haiveDir)) {
9117
+ if (!existsSync54(paths.haiveDir)) {
8873
9118
  ui.error("No .ai/ found. Run `haive init` first.");
8874
9119
  process.exitCode = 1;
8875
9120
  return;
8876
9121
  }
8877
9122
  if (opts.list) {
8878
9123
  const contractsDir = path34.join(paths.haiveDir, "contracts");
8879
- if (!existsSync51(contractsDir)) {
9124
+ if (!existsSync54(contractsDir)) {
8880
9125
  console.log(ui.dim("No contract snapshots found."));
8881
9126
  return;
8882
9127
  }
@@ -9000,7 +9245,7 @@ function detectFormat(filePath) {
9000
9245
  }
9001
9246
 
9002
9247
  // src/commands/hub.ts
9003
- import { existsSync as existsSync53 } from "fs";
9248
+ import { existsSync as existsSync55 } from "fs";
9004
9249
  import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile26, copyFile } from "fs/promises";
9005
9250
  import path35 from "path";
9006
9251
  import { spawnSync as spawnSync3 } from "child_process";
@@ -9008,7 +9253,7 @@ import "commander";
9008
9253
  import {
9009
9254
  findProjectRoot as findProjectRoot33,
9010
9255
  loadConfig as loadConfig6,
9011
- loadMemoriesFromDir as loadMemoriesFromDir27,
9256
+ loadMemoriesFromDir as loadMemoriesFromDir28,
9012
9257
  resolveHaivePaths as resolveHaivePaths30,
9013
9258
  saveConfig as saveConfig2,
9014
9259
  serializeMemory as serializeMemory23
@@ -9102,7 +9347,7 @@ Next steps:
9102
9347
  return;
9103
9348
  }
9104
9349
  const hubRoot = path35.resolve(root, config.hubPath);
9105
- if (!existsSync53(hubRoot)) {
9350
+ if (!existsSync55(hubRoot)) {
9106
9351
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
9107
9352
  process.exitCode = 1;
9108
9353
  return;
@@ -9110,7 +9355,7 @@ Next steps:
9110
9355
  const projectName = path35.basename(root);
9111
9356
  const destDir = path35.join(hubRoot, ".ai", "memories", "shared", projectName);
9112
9357
  await mkdir14(destDir, { recursive: true });
9113
- const all = await loadMemoriesFromDir27(paths.memoriesDir);
9358
+ const all = await loadMemoriesFromDir28(paths.memoriesDir);
9114
9359
  const shared = all.filter(
9115
9360
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
9116
9361
  !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
@@ -9172,7 +9417,7 @@ Next steps:
9172
9417
  }
9173
9418
  const hubRoot = path35.resolve(root, config.hubPath);
9174
9419
  const hubSharedDir = path35.join(hubRoot, ".ai", "memories", "shared");
9175
- if (!existsSync53(hubSharedDir)) {
9420
+ if (!existsSync55(hubSharedDir)) {
9176
9421
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
9177
9422
  return;
9178
9423
  }
@@ -9227,7 +9472,7 @@ Next steps:
9227
9472
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
9228
9473
  );
9229
9474
  const sharedDir = path35.join(paths.memoriesDir, "shared");
9230
- if (existsSync53(sharedDir)) {
9475
+ if (existsSync55(sharedDir)) {
9231
9476
  const { readdir: readdir5 } = await import("fs/promises");
9232
9477
  const sources = (await readdir5(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
9233
9478
  console.log(`
@@ -9239,7 +9484,7 @@ Next steps:
9239
9484
  } else {
9240
9485
  console.log(ui.dim(" No imported shared memories yet."));
9241
9486
  }
9242
- const all = await loadMemoriesFromDir27(paths.memoriesDir);
9487
+ const all = await loadMemoriesFromDir28(paths.memoriesDir);
9243
9488
  const outgoing = all.filter(
9244
9489
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
9245
9490
  );
@@ -9256,13 +9501,13 @@ Next steps:
9256
9501
 
9257
9502
  // src/commands/stats.ts
9258
9503
  import "commander";
9259
- import { existsSync as existsSync54 } from "fs";
9504
+ import { existsSync as existsSync56 } from "fs";
9260
9505
  import { mkdir as mkdir15, writeFile as writeFile27 } from "fs/promises";
9261
9506
  import path36 from "path";
9262
9507
  import {
9263
9508
  aggregateUsage,
9264
9509
  findProjectRoot as findProjectRoot34,
9265
- loadMemoriesFromDir as loadMemoriesFromDir28,
9510
+ loadMemoriesFromDir as loadMemoriesFromDir29,
9266
9511
  loadUsageIndex as loadUsageIndex23,
9267
9512
  parseSince,
9268
9513
  readUsageEvents as readUsageEvents2,
@@ -9334,8 +9579,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
9334
9579
  const size = await usageLogSize(paths);
9335
9580
  let events = await readUsageEvents2(paths);
9336
9581
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
9337
- if (existsSync54(paths.memoriesDir)) {
9338
- const mems = await loadMemoriesFromDir28(paths.memoriesDir);
9582
+ if (existsSync56(paths.memoriesDir)) {
9583
+ const mems = await loadMemoriesFromDir29(paths.memoriesDir);
9339
9584
  for (const { memory: memory2 } of mems) {
9340
9585
  const fm = memory2.frontmatter;
9341
9586
  if (fm.type === "session_recap") memoryCount.total_skipped_session++;
@@ -9555,14 +9800,14 @@ function summarize(name, t0, payload, notes) {
9555
9800
 
9556
9801
  // src/commands/memory-suggest.ts
9557
9802
  import { mkdir as mkdir16, writeFile as writeFile28 } from "fs/promises";
9558
- import { existsSync as existsSync55 } from "fs";
9803
+ import { existsSync as existsSync57 } from "fs";
9559
9804
  import path37 from "path";
9560
9805
  import "commander";
9561
9806
  import {
9562
9807
  aggregateUsage as aggregateUsage2,
9563
9808
  buildFrontmatter as buildFrontmatter11,
9564
9809
  findProjectRoot as findProjectRoot36,
9565
- loadMemoriesFromDir as loadMemoriesFromDir29,
9810
+ loadMemoriesFromDir as loadMemoriesFromDir30,
9566
9811
  memoryFilePath as memoryFilePath10,
9567
9812
  parseSince as parseSince2,
9568
9813
  readUsageEvents as readUsageEvents3,
@@ -9626,7 +9871,7 @@ function registerMemorySuggest(memory2) {
9626
9871
  }
9627
9872
  const created = [];
9628
9873
  const skipped = [];
9629
- const existing = existsSync55(paths.memoriesDir) ? await loadMemoriesFromDir29(paths.memoriesDir) : [];
9874
+ const existing = existsSync57(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
9630
9875
  for (const s of top) {
9631
9876
  const slug = slugify(s.query);
9632
9877
  if (!slug) {
@@ -9650,7 +9895,7 @@ function registerMemorySuggest(memory2) {
9650
9895
  const body = renderTemplate(s);
9651
9896
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
9652
9897
  await mkdir16(path37.dirname(file), { recursive: true });
9653
- if (existsSync55(file)) {
9898
+ if (existsSync57(file)) {
9654
9899
  skipped.push({ query: s.query, reason: `file already exists at ${path37.relative(root, file)}` });
9655
9900
  continue;
9656
9901
  }
@@ -9748,14 +9993,14 @@ function truncate2(text, max) {
9748
9993
  }
9749
9994
 
9750
9995
  // src/commands/memory-archive.ts
9751
- import { existsSync as existsSync56 } from "fs";
9996
+ import { existsSync as existsSync58 } from "fs";
9752
9997
  import { writeFile as writeFile29 } from "fs/promises";
9753
9998
  import path38 from "path";
9754
9999
  import "commander";
9755
10000
  import {
9756
10001
  findProjectRoot as findProjectRoot37,
9757
10002
  getUsage as getUsage18,
9758
- loadMemoriesFromDir as loadMemoriesFromDir30,
10003
+ loadMemoriesFromDir as loadMemoriesFromDir31,
9759
10004
  loadUsageIndex as loadUsageIndex24,
9760
10005
  resolveHaivePaths as resolveHaivePaths34,
9761
10006
  serializeMemory as serializeMemory25
@@ -9767,7 +10012,7 @@ function registerMemoryArchive(memory2) {
9767
10012
  ).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) => {
9768
10013
  const root = findProjectRoot37(opts.dir);
9769
10014
  const paths = resolveHaivePaths34(root);
9770
- if (!existsSync56(paths.memoriesDir)) {
10015
+ if (!existsSync58(paths.memoriesDir)) {
9771
10016
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
9772
10017
  process.exitCode = 1;
9773
10018
  return;
@@ -9779,7 +10024,7 @@ function registerMemoryArchive(memory2) {
9779
10024
  return;
9780
10025
  }
9781
10026
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
9782
- const all = await loadMemoriesFromDir30(paths.memoriesDir);
10027
+ const all = await loadMemoriesFromDir31(paths.memoriesDir);
9783
10028
  const usage = await loadUsageIndex24(paths);
9784
10029
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
9785
10030
  const candidates = [];
@@ -9788,7 +10033,7 @@ function registerMemoryArchive(memory2) {
9788
10033
  if (typeFilter && fm.type !== typeFilter) continue;
9789
10034
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
9790
10035
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
9791
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync56(path38.join(paths.root, p)));
10036
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync58(path38.join(paths.root, p)));
9792
10037
  const isAnchorless = !hasAnyAnchor;
9793
10038
  if (!isAnchorless && !allPathsGone) continue;
9794
10039
  const u = getUsage18(usage, fm.id);
@@ -9862,7 +10107,7 @@ function parseDays(input) {
9862
10107
  }
9863
10108
 
9864
10109
  // src/commands/doctor.ts
9865
- import { existsSync as existsSync57 } from "fs";
10110
+ import { existsSync as existsSync59 } from "fs";
9866
10111
  import { stat } from "fs/promises";
9867
10112
  import "path";
9868
10113
  import { execSync as execSync3 } from "child_process";
@@ -9873,7 +10118,7 @@ import {
9873
10118
  getUsage as getUsage19,
9874
10119
  loadCodeMap as loadCodeMap5,
9875
10120
  loadConfig as loadConfig7,
9876
- loadMemoriesFromDir as loadMemoriesFromDir31,
10121
+ loadMemoriesFromDir as loadMemoriesFromDir32,
9877
10122
  loadUsageIndex as loadUsageIndex25,
9878
10123
  readUsageEvents as readUsageEvents4,
9879
10124
  resolveHaivePaths as resolveHaivePaths35
@@ -9886,7 +10131,7 @@ function registerDoctor(program2) {
9886
10131
  const root = findProjectRoot38(opts.dir);
9887
10132
  const paths = resolveHaivePaths35(root);
9888
10133
  const findings = [];
9889
- if (!existsSync57(paths.haiveDir)) {
10134
+ if (!existsSync59(paths.haiveDir)) {
9890
10135
  findings.push({
9891
10136
  severity: "error",
9892
10137
  code: "not-initialized",
@@ -9895,7 +10140,7 @@ function registerDoctor(program2) {
9895
10140
  });
9896
10141
  return emit(findings, opts);
9897
10142
  }
9898
- if (!existsSync57(paths.projectContext)) {
10143
+ if (!existsSync59(paths.projectContext)) {
9899
10144
  findings.push({
9900
10145
  severity: "warn",
9901
10146
  code: "no-project-context",
@@ -9915,7 +10160,7 @@ function registerDoctor(program2) {
9915
10160
  });
9916
10161
  }
9917
10162
  }
9918
- const memories = existsSync57(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
10163
+ const memories = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
9919
10164
  const now = Date.now();
9920
10165
  if (memories.length === 0) {
9921
10166
  findings.push({
@@ -10039,7 +10284,7 @@ function registerDoctor(program2) {
10039
10284
  timeout: 3e3,
10040
10285
  stdio: ["ignore", "pipe", "ignore"]
10041
10286
  }).trim();
10042
- const cliVersion = "0.9.3";
10287
+ const cliVersion = "0.9.5";
10043
10288
  if (legacyRaw && legacyRaw !== cliVersion) {
10044
10289
  findings.push({
10045
10290
  severity: "warn",
@@ -10088,11 +10333,11 @@ function isSearchTool(name) {
10088
10333
  }
10089
10334
 
10090
10335
  // src/commands/playback.ts
10091
- import { existsSync as existsSync58 } from "fs";
10336
+ import { existsSync as existsSync60 } from "fs";
10092
10337
  import "commander";
10093
10338
  import {
10094
10339
  findProjectRoot as findProjectRoot39,
10095
- loadMemoriesFromDir as loadMemoriesFromDir32,
10340
+ loadMemoriesFromDir as loadMemoriesFromDir33,
10096
10341
  parseSince as parseSince3,
10097
10342
  readUsageEvents as readUsageEvents5,
10098
10343
  resolveHaivePaths as resolveHaivePaths36
@@ -10118,7 +10363,7 @@ function registerPlayback(program2) {
10118
10363
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
10119
10364
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
10120
10365
  const sessions = bucketSessions(filtered, gapMs);
10121
- const all = existsSync58(paths.memoriesDir) ? await loadMemoriesFromDir32(paths.memoriesDir) : [];
10366
+ const all = existsSync60(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
10122
10367
  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);
10123
10368
  const enriched = sessions.map((s, i) => {
10124
10369
  const startMs = Date.parse(s.start);
@@ -10312,11 +10557,11 @@ function runCommand3(cmd, args, cwd) {
10312
10557
  }
10313
10558
 
10314
10559
  // src/commands/welcome.ts
10315
- import { existsSync as existsSync59 } from "fs";
10560
+ import { existsSync as existsSync61 } from "fs";
10316
10561
  import "commander";
10317
10562
  import {
10318
10563
  findProjectRoot as findProjectRoot41,
10319
- loadMemoriesFromDir as loadMemoriesFromDir33,
10564
+ loadMemoriesFromDir as loadMemoriesFromDir34,
10320
10565
  resolveHaivePaths as resolveHaivePaths38
10321
10566
  } from "@hiveai/core";
10322
10567
  var TYPE_RANK = {
@@ -10333,12 +10578,12 @@ function registerWelcome(program2) {
10333
10578
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
10334
10579
  const root = findProjectRoot41(opts.dir);
10335
10580
  const paths = resolveHaivePaths38(root);
10336
- if (!existsSync59(paths.memoriesDir)) {
10581
+ if (!existsSync61(paths.memoriesDir)) {
10337
10582
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
10338
10583
  process.exitCode = 1;
10339
10584
  return;
10340
10585
  }
10341
- const all = await loadMemoriesFromDir33(paths.memoriesDir);
10586
+ const all = await loadMemoriesFromDir34(paths.memoriesDir);
10342
10587
  const team = all.filter(
10343
10588
  ({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
10344
10589
  );
@@ -10374,18 +10619,18 @@ function registerWelcome(program2) {
10374
10619
  }
10375
10620
 
10376
10621
  // src/commands/memory-lint.ts
10377
- import { existsSync as existsSync60 } from "fs";
10622
+ import { existsSync as existsSync63 } from "fs";
10378
10623
  import "commander";
10379
10624
  import {
10380
10625
  findProjectRoot as findProjectRoot42,
10381
- loadMemoriesFromDir as loadMemoriesFromDir34,
10626
+ loadMemoriesFromDir as loadMemoriesFromDir35,
10382
10627
  resolveHaivePaths as resolveHaivePaths39
10383
10628
  } from "@hiveai/core";
10384
10629
  async function lintMemoriesAsync(root) {
10385
10630
  const paths = resolveHaivePaths39(root);
10386
10631
  const out = [];
10387
- if (!existsSync60(paths.memoriesDir)) return out;
10388
- const loaded = await loadMemoriesFromDir34(paths.memoriesDir);
10632
+ if (!existsSync63(paths.memoriesDir)) return out;
10633
+ const loaded = await loadMemoriesFromDir35(paths.memoriesDir);
10389
10634
  const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
10390
10635
  for (const { filePath, memory: memory2 } of loaded) {
10391
10636
  const fm = memory2.frontmatter;
@@ -10472,11 +10717,43 @@ function registerMemoryLint(parent) {
10472
10717
  });
10473
10718
  }
10474
10719
 
10720
+ // src/commands/memory-suggest-topic.ts
10721
+ import "commander";
10722
+ import { MemoryTypeSchema as MemoryTypeSchema2, suggestTopicKey as suggestTopicKey2 } from "@hiveai/core";
10723
+ function registerMemorySuggestTopic(memory2) {
10724
+ memory2.command("suggest-topic").description("Suggest a stable topic key (topic-upsert) from type + title phrase").requiredOption(
10725
+ "--type <type>",
10726
+ "convention | decision | gotcha | architecture | glossary | attempt | session_recap"
10727
+ ).argument("<title>", "Short title or phrase to slugify").action((title, opts) => {
10728
+ const parsed = MemoryTypeSchema2.safeParse(opts.type);
10729
+ if (!parsed.success) {
10730
+ ui.error(`Invalid type: ${opts.type}`);
10731
+ process.exit(1);
10732
+ }
10733
+ const suggestion = suggestTopicKey2(parsed.data, title);
10734
+ console.log(JSON.stringify({ type: parsed.data, ...suggestion }, null, 2));
10735
+ });
10736
+ }
10737
+
10738
+ // src/commands/resolve-project.ts
10739
+ import path40 from "path";
10740
+ import "commander";
10741
+ import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
10742
+ function registerResolveProject(program2) {
10743
+ program2.command("resolve-project").description(
10744
+ "Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
10745
+ ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
10746
+ const info = resolveProjectInfo2({ cwd: path40.resolve(opts.dir) });
10747
+ console.log(JSON.stringify({ ok: true, info }, null, 2));
10748
+ });
10749
+ }
10750
+
10475
10751
  // src/index.ts
10476
- var program = new Command42();
10477
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.3");
10752
+ var program = new Command44();
10753
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.5");
10478
10754
  registerInit(program);
10479
10755
  registerWelcome(program);
10756
+ registerResolveProject(program);
10480
10757
  registerMcp(program);
10481
10758
  registerBriefing(program);
10482
10759
  registerTui(program);
@@ -10507,6 +10784,7 @@ registerMemoryImport(memory);
10507
10784
  registerMemoryImportChangelog(memory);
10508
10785
  registerMemoryDigest(memory);
10509
10786
  registerMemorySuggest(memory);
10787
+ registerMemorySuggestTopic(memory);
10510
10788
  registerMemoryArchive(memory);
10511
10789
  registerMemoryLint(memory);
10512
10790
  var session = program.command("session").description("Manage session lifecycle");