@hiveai/cli 0.7.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command39 } from "commander";
4
+ import { Command as Command40 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -14,6 +14,7 @@ import {
14
14
  literalMatchesAnyToken,
15
15
  loadCodeMap,
16
16
  loadMemoriesFromDir,
17
+ loadUsageIndex,
17
18
  memoryMatchesAnchorPaths,
18
19
  queryCodeMap,
19
20
  resolveHaivePaths,
@@ -51,11 +52,240 @@ var ui = {
51
52
  }
52
53
  };
53
54
 
55
+ // src/utils/briefing-radar.ts
56
+ import { execFile } from "child_process";
57
+ import { promisify } from "util";
58
+ var exec = promisify(execFile);
59
+ var DEFAULT_DAYS_BACK = 14;
60
+ var DEFAULT_MAX_COMMITS = 5;
61
+ var DEFAULT_MAX_TODOS = 8;
62
+ var DEFAULT_MAX_HOT_FILES = 5;
63
+ var TODO_RE = /\b(?:TODO|FIXME|HACK|XXX)\b[: ]?(.{0,120})/i;
64
+ var SOURCE_GLOBS = [
65
+ "*.ts",
66
+ "*.tsx",
67
+ "*.js",
68
+ "*.jsx",
69
+ "*.py",
70
+ "*.go",
71
+ "*.rs",
72
+ "*.java",
73
+ "*.kt",
74
+ "*.swift",
75
+ "*.rb",
76
+ "*.php",
77
+ "*.cs",
78
+ "*.cpp",
79
+ "*.c",
80
+ "*.h"
81
+ ];
82
+ async function isGitRepo(root) {
83
+ try {
84
+ await exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd: root });
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ async function getRecentCommits(root, daysBack, maxCommits, taskTokens, filePaths) {
91
+ try {
92
+ const { stdout } = await exec(
93
+ "git",
94
+ [
95
+ "log",
96
+ `--since=${daysBack}.days.ago`,
97
+ "--name-only",
98
+ "--pretty=format:%x1f%h%x1f%ad%x1f%s",
99
+ "--date=short",
100
+ "-n",
101
+ "60"
102
+ ],
103
+ { cwd: root, maxBuffer: 4 * 1024 * 1024 }
104
+ );
105
+ const blocks = stdout.split("").filter((b) => b.trim().length > 0);
106
+ const commits = [];
107
+ for (let i = 0; i + 2 < blocks.length; i += 3) {
108
+ const sha = blocks[i].trim();
109
+ const date = blocks[i + 1].trim();
110
+ const tail = blocks[i + 2];
111
+ const lines = tail.split("\n").map((l) => l.trim()).filter(Boolean);
112
+ const subject = lines.shift() ?? "";
113
+ const files = lines;
114
+ commits.push({ sha, date, subject, files });
115
+ }
116
+ const lowerTokens = taskTokens?.map((t) => t.toLowerCase()) ?? [];
117
+ const lowerPaths = filePaths.map((p) => p.toLowerCase());
118
+ const scored = commits.map((c) => {
119
+ let score = 0;
120
+ const haystack = (c.subject + " " + c.files.join(" ")).toLowerCase();
121
+ for (const t of lowerTokens) if (haystack.includes(t)) score += 2;
122
+ for (const p of lowerPaths) if (c.files.some((f) => f.toLowerCase().includes(p))) score += 3;
123
+ return { c, score };
124
+ });
125
+ if (lowerTokens.length === 0 && lowerPaths.length === 0) {
126
+ return commits.slice(0, maxCommits);
127
+ }
128
+ return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, maxCommits).map((s) => s.c);
129
+ } catch {
130
+ return [];
131
+ }
132
+ }
133
+ async function getOpenTodos(root, maxTodos, taskTokens, filePaths) {
134
+ try {
135
+ const includeArgs = SOURCE_GLOBS.flatMap((g) => ["--include", g]);
136
+ const { stdout } = await exec(
137
+ "grep",
138
+ [
139
+ "-rnE",
140
+ "--exclude-dir=node_modules",
141
+ "--exclude-dir=.git",
142
+ "--exclude-dir=dist",
143
+ "--exclude-dir=build",
144
+ "--exclude-dir=.next",
145
+ "--exclude-dir=coverage",
146
+ ...includeArgs,
147
+ "\\b(TODO|FIXME|HACK|XXX)\\b",
148
+ "."
149
+ ],
150
+ { cwd: root, maxBuffer: 4 * 1024 * 1024 }
151
+ ).catch((err) => ({ stdout: err.stdout ?? "" }));
152
+ const lines = stdout.split("\n").filter(Boolean);
153
+ const parsed = [];
154
+ for (const line of lines) {
155
+ const m = line.match(/^([^:]+):(\d+):(.*)$/);
156
+ if (!m) continue;
157
+ const [, file, lineNoStr, rest] = m;
158
+ const todoMatch = rest.match(TODO_RE);
159
+ if (!todoMatch) continue;
160
+ const text = (todoMatch[1] ?? "").trim() || rest.trim().slice(0, 120);
161
+ parsed.push({ file: file.replace(/^\.\//, ""), line: Number(lineNoStr), text });
162
+ }
163
+ const lowerTokens = taskTokens?.map((t) => t.toLowerCase()) ?? [];
164
+ const lowerPaths = filePaths.map((p) => p.toLowerCase());
165
+ if (lowerTokens.length === 0 && lowerPaths.length === 0) {
166
+ return parsed.slice(0, maxTodos);
167
+ }
168
+ const scored = parsed.map((t) => {
169
+ let score = 0;
170
+ const hay = (t.file + " " + t.text).toLowerCase();
171
+ for (const tok of lowerTokens) if (hay.includes(tok)) score += 1;
172
+ for (const p of lowerPaths) if (t.file.toLowerCase().includes(p)) score += 2;
173
+ return { t, score };
174
+ });
175
+ return scored.filter((s) => s.score > 0).sort((a, b) => b.score - a.score).slice(0, maxTodos).map((s) => s.t);
176
+ } catch {
177
+ return [];
178
+ }
179
+ }
180
+ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
181
+ try {
182
+ const { stdout } = await exec(
183
+ "git",
184
+ [
185
+ "log",
186
+ `--since=${daysBack * 6}.days.ago`,
187
+ "--name-only",
188
+ "--pretty=format:"
189
+ ],
190
+ { cwd: root, maxBuffer: 4 * 1024 * 1024 }
191
+ );
192
+ const counts = /* @__PURE__ */ new Map();
193
+ for (const raw of stdout.split("\n")) {
194
+ const f = raw.trim();
195
+ if (!f) continue;
196
+ counts.set(f, (counts.get(f) ?? 0) + 1);
197
+ }
198
+ let entries = [...counts.entries()].map(([path37, changes]) => ({ path: path37, changes }));
199
+ const lowerPaths = filePaths.map((p) => p.toLowerCase());
200
+ if (lowerPaths.length > 0) {
201
+ entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
202
+ }
203
+ return entries.sort((a, b) => b.changes - a.changes).slice(0, maxHotFiles);
204
+ } catch {
205
+ return [];
206
+ }
207
+ }
208
+ async function buildRadar(opts) {
209
+ const inside = await isGitRepo(opts.root);
210
+ if (!inside) {
211
+ return { recentCommits: [], openTodos: [], hotFiles: [], insideGitRepo: false };
212
+ }
213
+ const daysBack = opts.daysBack ?? DEFAULT_DAYS_BACK;
214
+ const [recentCommits, openTodos, hotFiles] = await Promise.all([
215
+ getRecentCommits(opts.root, daysBack, opts.maxCommits ?? DEFAULT_MAX_COMMITS, opts.taskTokens, opts.filePaths),
216
+ getOpenTodos(opts.root, opts.maxTodos ?? DEFAULT_MAX_TODOS, opts.taskTokens, opts.filePaths),
217
+ getHotFiles(opts.root, daysBack, opts.maxHotFiles ?? DEFAULT_MAX_HOT_FILES, opts.filePaths)
218
+ ]);
219
+ return { recentCommits, openTodos, hotFiles, insideGitRepo: true };
220
+ }
221
+ function radarHasContent(r) {
222
+ return r.recentCommits.length > 0 || r.openTodos.length > 0 || r.hotFiles.length > 0;
223
+ }
224
+
54
225
  // src/commands/briefing.ts
226
+ var RADAR_AUTO_THRESHOLD = 3;
227
+ var CHARS_PER_TOKEN = 4;
228
+ function printRadar(radar, out, reason) {
229
+ if (!radar.insideGitRepo) return;
230
+ if (!radarHasContent(radar)) return;
231
+ const header = reason === "low-memory-signal" ? "=== Project Radar (few relevant memories \u2014 surfacing live signals) ===" : "=== Project Radar ===";
232
+ out(`${ui.bold(header)}
233
+ `);
234
+ if (radar.recentCommits.length > 0) {
235
+ out(ui.bold("Recent commits:"));
236
+ for (const c of radar.recentCommits) {
237
+ const filesBlurb = c.files.slice(0, 3).join(", ");
238
+ const more = c.files.length > 3 ? ` (+${c.files.length - 3})` : "";
239
+ out(` ${ui.dim(c.date)} ${c.sha} ${c.subject}`);
240
+ if (filesBlurb) out(ui.dim(` ${filesBlurb}${more}`));
241
+ }
242
+ out("");
243
+ }
244
+ if (radar.openTodos.length > 0) {
245
+ out(ui.bold("Open TODOs/FIXMEs:"));
246
+ for (const t of radar.openTodos) {
247
+ out(` ${ui.dim(t.file + ":" + t.line)} ${t.text}`);
248
+ }
249
+ out("");
250
+ }
251
+ if (radar.hotFiles.length > 0) {
252
+ out(ui.bold("Hot files (most modified recently):"));
253
+ for (const f of radar.hotFiles) {
254
+ out(` ${f.changes}\xD7 ${ui.dim(f.path)}`);
255
+ }
256
+ out("");
257
+ }
258
+ }
259
+ var TokenBudgetWriter = class {
260
+ constructor(budgetChars) {
261
+ this.budgetChars = budgetChars;
262
+ }
263
+ budgetChars;
264
+ used = 0;
265
+ truncated = false;
266
+ write(text) {
267
+ if (this.truncated) return false;
268
+ const next = this.used + text.length + 1;
269
+ if (next > this.budgetChars) {
270
+ console.log(ui.dim(`... [briefing truncated to fit --max-tokens budget \xB7 ${Math.round(this.used / CHARS_PER_TOKEN)} tokens used]`));
271
+ this.truncated = true;
272
+ return false;
273
+ }
274
+ console.log(text);
275
+ this.used = next;
276
+ return true;
277
+ }
278
+ isTruncated() {
279
+ return this.truncated;
280
+ }
281
+ remainingChars() {
282
+ return Math.max(0, this.budgetChars - this.used);
283
+ }
284
+ };
55
285
  function registerBriefing(program2) {
56
286
  program2.command("briefing").description(
57
287
  'Print the full project briefing: last session recap + project context + relevant memories.\n Equivalent to calling get_briefing via MCP. Run before starting any task.\n\n Examples:\n haive briefing\n haive briefing --task "add Stripe payment" --files src/payments/PaymentService.ts\n haive briefing --symbols PaymentService,TenantFilter # look up where symbols live\n'
58
- ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option(
288
+ ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option("--max-tokens <n>", "approximate token budget for the entire briefing (truncates if exceeded)").option("--explain-source", "annotate each memory with [source: <relative-path> \xB7 anchors: <files>] for traceable citations").option("--radar", "force project radar (recent commits, open TODOs, hot files) even when memories are plentiful").option("--no-radar", "disable the project radar even when memories are scarce").option(
59
289
  "--scope <scope>",
60
290
  "personal | team | shared | all (default: all \u2014 includes team + shared cross-repo memories)",
61
291
  "all"
@@ -67,14 +297,29 @@ function registerBriefing(program2) {
67
297
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
68
298
  const root = findProjectRoot(opts.dir);
69
299
  const paths = resolveHaivePaths(root);
300
+ const budgetTokens = opts.maxTokens ? Math.max(100, Number(opts.maxTokens)) : null;
301
+ const writer = budgetTokens ? new TokenBudgetWriter(budgetTokens * CHARS_PER_TOKEN) : null;
302
+ const out = (text) => {
303
+ if (writer) return writer.write(text);
304
+ console.log(text);
305
+ return true;
306
+ };
307
+ const stopped = () => writer?.isTruncated() ?? false;
70
308
  if (!existsSync(paths.memoriesDir)) {
71
309
  if (existsSync(paths.projectContext)) {
72
- console.log(`${ui.bold("=== Project Context ===")}
310
+ out(`${ui.bold("=== Project Context ===")}
73
311
  `);
74
- console.log((await readFile(paths.projectContext, "utf8")).trim());
312
+ out((await readFile(paths.projectContext, "utf8")).trim());
313
+ out("");
75
314
  } else {
76
315
  ui.warn("No project-context.md found. Run `haive init` and the `bootstrap_project` MCP prompt to set it up.");
77
316
  }
317
+ if (opts.radar !== false && !stopped()) {
318
+ const filePathsEarly = parseCsv(opts.files);
319
+ const tokensEarly = opts.task ? tokenizeQuery(opts.task) : null;
320
+ const radar = await buildRadar({ root, taskTokens: tokensEarly, filePaths: filePathsEarly });
321
+ printRadar(radar, out, "low-memory-signal");
322
+ }
78
323
  return;
79
324
  }
80
325
  const ownMemories = await loadMemoriesFromDir(paths.memoriesDir);
@@ -112,17 +357,17 @@ function registerBriefing(program2) {
112
357
  const recaps = all.filter(({ memory: mem }) => mem.frontmatter.type === "session_recap").sort(
113
358
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
114
359
  );
115
- if (recaps.length > 0) {
360
+ if (recaps.length > 0 && !stopped()) {
116
361
  const recap = recaps[0];
117
362
  const fm = recap.memory.frontmatter;
118
363
  const rev = fm.revision_count ? ` \xB7 revision #${fm.revision_count}` : "";
119
- console.log(`${ui.bold("=== Last Session Recap ===")}
364
+ out(`${ui.bold("=== Last Session Recap ===")}
120
365
  `);
121
- console.log(ui.dim(`${fm.id} (${fm.scope}${rev})`));
122
- console.log(recap.memory.body.trim());
123
- console.log();
366
+ out(ui.dim(`${fm.id} (${fm.scope}${rev})`));
367
+ out(recap.memory.body.trim());
368
+ out("");
124
369
  }
125
- if (existsSync(paths.projectContext)) {
370
+ if (existsSync(paths.projectContext) && !stopped()) {
126
371
  const ctx = await readFile(paths.projectContext, "utf8");
127
372
  const isTemplate = ctx.includes("TODO \u2014 high-level overview") || ctx.includes("Generated by `haive init`");
128
373
  if (isTemplate) {
@@ -132,14 +377,14 @@ function registerBriefing(program2) {
132
377
  ui.warn(
133
378
  "Fix: in your AI client, invoke the MCP prompt bootstrap_project to auto-fill it from your codebase."
134
379
  );
135
- console.log();
380
+ out("");
136
381
  } else {
137
- console.log(`${ui.bold("=== Project Context ===")}
382
+ out(`${ui.bold("=== Project Context ===")}
138
383
  `);
139
- console.log(ctx.trim());
140
- console.log();
384
+ out(ctx.trim());
385
+ out("");
141
386
  }
142
- } else {
387
+ } else if (!existsSync(paths.projectContext)) {
143
388
  ui.warn(
144
389
  "No project-context.md found. Run `haive init` then invoke the bootstrap_project MCP prompt."
145
390
  );
@@ -177,54 +422,85 @@ function registerBriefing(program2) {
177
422
  if (draftCount > 0) {
178
423
  ui.info(`(${draftCount} draft memories excluded \u2014 use --include-draft to show)`);
179
424
  }
425
+ if (opts.radar !== false && !stopped()) {
426
+ const radar = await buildRadar({ root, taskTokens: tokens, filePaths });
427
+ out("");
428
+ printRadar(radar, out, "low-memory-signal");
429
+ }
180
430
  return;
181
431
  }
182
- console.log(`${ui.bold("=== Relevant Memories ===")}
432
+ if (stopped()) return;
433
+ const usageIndex = await loadUsageIndex(paths).catch(() => null);
434
+ out(`${ui.bold("=== Relevant Memories ===")}
183
435
  `);
184
436
  for (const item of top) {
437
+ if (stopped()) break;
185
438
  const fm = item.memory.frontmatter;
186
439
  const badge = ui.statusBadge(fm.status);
187
440
  const draftMarker = fm.status === "draft" ? ui.yellow(" [DRAFT]") : "";
188
441
  const unverifiedMarker = fm.status === "proposed" ? ui.yellow(" [UNVERIFIED]") : "";
189
442
  const originMarker = item.origin ? ` ${ui.yellow("[from " + item.origin + "]")}` : "";
190
- console.log(
191
- `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}`
443
+ const reads = usageIndex?.by_id[fm.id]?.read_count ?? 0;
444
+ const hitMarker = reads > 0 ? ` ${ui.dim("\xB7 " + reads + "\xD7 read")}` : "";
445
+ out(
446
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
192
447
  );
193
- console.log(item.memory.body.trim());
194
- console.log();
448
+ if (opts.explainSource) {
449
+ const relPath = path.relative(root, item.filePath);
450
+ const anchorPaths = fm.anchor?.paths ?? [];
451
+ const anchorSymbols = fm.anchor?.symbols ?? [];
452
+ const parts = [`source: ${relPath}`];
453
+ if (anchorPaths.length > 0) parts.push(`paths: ${anchorPaths.join(", ")}`);
454
+ if (anchorSymbols.length > 0) parts.push(`symbols: ${anchorSymbols.join(", ")}`);
455
+ out(ui.dim(` [${parts.join(" \xB7 ")}]`));
456
+ }
457
+ out(item.memory.body.trim());
458
+ out("");
195
459
  }
196
- console.log(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
460
+ if (!stopped()) out(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
197
461
  const ids = top.map(({ memory: mem }) => mem.frontmatter.id);
198
462
  if (ids.length > 0) {
199
463
  await trackReads(paths, ids).catch(() => {
200
464
  });
201
465
  }
466
+ const radarForced = opts.radar === true;
467
+ const radarAuto = opts.radar !== false && top.length < RADAR_AUTO_THRESHOLD;
468
+ if ((radarForced || radarAuto) && !stopped()) {
469
+ const radar = await buildRadar({ root, taskTokens: tokens, filePaths });
470
+ if (radarHasContent(radar)) {
471
+ out("");
472
+ printRadar(radar, out, radarForced ? "forced" : "low-memory-signal");
473
+ }
474
+ }
202
475
  const requestedSymbols = (opts.symbols ?? "").split(",").map((s) => s.trim()).filter(Boolean);
203
- if (requestedSymbols.length > 0) {
476
+ if (requestedSymbols.length > 0 && !stopped()) {
204
477
  const codeMap = await loadCodeMap(paths);
205
478
  if (!codeMap) {
206
479
  ui.warn("No code-map found. Run `haive index code` first to enable symbol lookup.");
207
480
  } else {
208
- console.log(`
481
+ out(`
209
482
  ${ui.bold("=== Symbol Locations ===")}
210
483
  `);
211
484
  for (const sym of requestedSymbols) {
485
+ if (stopped()) break;
212
486
  const { files } = queryCodeMap(codeMap, { symbol: sym });
213
487
  if (files.length === 0) {
214
- console.log(`${ui.dim(sym)} (not found in code-map)`);
488
+ out(`${ui.dim(sym)} (not found in code-map)`);
215
489
  } else {
216
490
  for (const f of files) {
491
+ if (stopped()) break;
217
492
  const exports = f.entry.exports.filter(
218
493
  (e) => e.name.toLowerCase().includes(sym.toLowerCase())
219
494
  );
220
495
  for (const e of exports) {
496
+ if (stopped()) break;
221
497
  const desc = e.description ? ` \u2014 ${e.description}` : "";
222
- console.log(`${ui.bold(e.name)} ${ui.dim(f.path + ":" + e.line)} [${e.kind}]${desc}`);
498
+ out(`${ui.bold(e.name)} ${ui.dim(f.path + ":" + e.line)} [${e.kind}]${desc}`);
223
499
  }
224
500
  }
225
501
  }
226
502
  }
227
- console.log();
503
+ out("");
228
504
  }
229
505
  }
230
506
  });
@@ -1671,18 +1947,21 @@ jobs:
1671
1947
  `;
1672
1948
  function registerInit(program2) {
1673
1949
  program2.command("init").description(
1674
- "Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Add --manual if you want to control memory approval and session recaps yourself."
1950
+ "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."
1675
1951
  ).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(
1676
1952
  "--manual",
1677
1953
  "opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
1678
1954
  ).option(
1679
1955
  "--bootstrap",
1680
- "auto-generate .ai/project-context.md from package.json, README, and directory structure (no AI needed)"
1956
+ "auto-generate .ai/project-context.md from package.json, README, and directory structure (ON by default in autopilot)"
1957
+ ).option(
1958
+ "--no-bootstrap",
1959
+ "skip the project-context auto-generation (only the default template is written)"
1681
1960
  ).option(
1682
1961
  "--stack <stacks>",
1683
1962
  `pre-seed validated memory packs for the given stacks (comma-separated).
1684
1963
  Supported: ${SUPPORTED_STACKS.join(", ")}.
1685
- Use 'auto' to detect from package.json automatically.`
1964
+ Defaults to 'auto' in autopilot mode (detects from package.json). Pass 'none' to disable.`
1686
1965
  ).option(
1687
1966
  "--no-mcp-setup",
1688
1967
  "skip auto-configuring haive-mcp in Cursor / VS Code / Claude Code"
@@ -1690,6 +1969,8 @@ function registerInit(program2) {
1690
1969
  const root = path7.resolve(opts.dir);
1691
1970
  const paths = resolveHaivePaths4(root);
1692
1971
  const autopilot = opts.manual !== true;
1972
+ const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
1973
+ const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
1693
1974
  if (existsSync6(paths.haiveDir)) {
1694
1975
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
1695
1976
  }
@@ -1698,7 +1979,7 @@ function registerInit(program2) {
1698
1979
  await mkdir3(paths.moduleDir, { recursive: true });
1699
1980
  await mkdir3(paths.modulesContextDir, { recursive: true });
1700
1981
  if (!existsSync6(paths.projectContext)) {
1701
- if (opts.bootstrap) {
1982
+ if (wantBootstrap) {
1702
1983
  ui.info("Bootstrapping project context from local files\u2026");
1703
1984
  try {
1704
1985
  const context = await generateBootstrapContext(root);
@@ -1727,7 +2008,7 @@ function registerInit(program2) {
1727
2008
  await writeBridge(root, ".cursorrules");
1728
2009
  await writeBridge(root, path7.join(".github", "copilot-instructions.md"));
1729
2010
  }
1730
- const stacksToSeed = await resolveStacksToSeed(root, opts.stack);
2011
+ const stacksToSeed = await resolveStacksToSeed(root, wantStack);
1731
2012
  if (stacksToSeed.length > 0) {
1732
2013
  let totalSeeded = 0;
1733
2014
  for (const stack of stacksToSeed) {
@@ -1869,11 +2150,106 @@ async function writeBridge(root, relPath) {
1869
2150
  }
1870
2151
 
1871
2152
  // src/commands/install-hooks.ts
1872
- import { mkdir as mkdir4, writeFile as writeFile4, chmod, readFile as readFile5 } from "fs/promises";
1873
- import { existsSync as existsSync7 } from "fs";
1874
- import path8 from "path";
2153
+ import { mkdir as mkdir5, writeFile as writeFile5, chmod, readFile as readFile6 } from "fs/promises";
2154
+ import { existsSync as existsSync8 } from "fs";
2155
+ import path9 from "path";
1875
2156
  import "commander";
1876
2157
  import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
2158
+
2159
+ // src/utils/claude-hooks.ts
2160
+ import { existsSync as existsSync7 } from "fs";
2161
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
2162
+ import path8 from "path";
2163
+ var HAIVE_HOOK_TAG = "haive-passive-capture";
2164
+ var POST_TOOL_USE_GROUP = {
2165
+ matcher: "Edit|Write|Bash",
2166
+ hooks: [
2167
+ {
2168
+ type: "command",
2169
+ command: "haive observe",
2170
+ haive_tag: HAIVE_HOOK_TAG
2171
+ }
2172
+ ]
2173
+ };
2174
+ var SESSION_END_GROUP = {
2175
+ hooks: [
2176
+ {
2177
+ type: "command",
2178
+ command: "haive session end --quiet --auto",
2179
+ haive_tag: HAIVE_HOOK_TAG
2180
+ }
2181
+ ]
2182
+ };
2183
+ function dropHaiveGroups(groups) {
2184
+ return groups.filter(
2185
+ (g) => !g.hooks.some((h) => h.haive_tag === HAIVE_HOOK_TAG)
2186
+ );
2187
+ }
2188
+ function patchClaudeSettings(input) {
2189
+ const settings = input ? { ...input } : {};
2190
+ const hooks = settings.hooks ? { ...settings.hooks } : {};
2191
+ hooks.PostToolUse = [
2192
+ ...dropHaiveGroups(hooks.PostToolUse ?? []),
2193
+ POST_TOOL_USE_GROUP
2194
+ ];
2195
+ hooks.SessionEnd = [
2196
+ ...dropHaiveGroups(hooks.SessionEnd ?? []),
2197
+ SESSION_END_GROUP
2198
+ ];
2199
+ settings.hooks = hooks;
2200
+ return settings;
2201
+ }
2202
+ function unpatchClaudeSettings(input) {
2203
+ const settings = input ? { ...input } : {};
2204
+ if (!settings.hooks) return settings;
2205
+ const hooks = { ...settings.hooks };
2206
+ for (const [event, groups] of Object.entries(hooks)) {
2207
+ const cleaned = dropHaiveGroups(groups);
2208
+ if (cleaned.length === 0) {
2209
+ delete hooks[event];
2210
+ } else {
2211
+ hooks[event] = cleaned;
2212
+ }
2213
+ }
2214
+ settings.hooks = hooks;
2215
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
2216
+ return settings;
2217
+ }
2218
+ async function installClaudeHooksAtPath(settingsPath) {
2219
+ let raw = null;
2220
+ let created = false;
2221
+ if (existsSync7(settingsPath)) {
2222
+ try {
2223
+ raw = JSON.parse(await readFile5(settingsPath, "utf8"));
2224
+ } catch {
2225
+ throw new Error(`${settingsPath} exists but is not valid JSON. Fix it manually first.`);
2226
+ }
2227
+ } else {
2228
+ created = true;
2229
+ }
2230
+ const patched = patchClaudeSettings(raw);
2231
+ await mkdir4(path8.dirname(settingsPath), { recursive: true });
2232
+ await writeFile4(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
2233
+ return { settingsPath, created };
2234
+ }
2235
+ async function uninstallClaudeHooksAtPath(settingsPath) {
2236
+ if (!existsSync7(settingsPath)) {
2237
+ return { settingsPath, created: false };
2238
+ }
2239
+ const raw = JSON.parse(await readFile5(settingsPath, "utf8"));
2240
+ const cleaned = unpatchClaudeSettings(raw);
2241
+ await writeFile4(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
2242
+ return { settingsPath, created: false };
2243
+ }
2244
+ function defaultClaudeSettingsPath(scope, projectRoot) {
2245
+ if (scope === "user") {
2246
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2247
+ return path8.join(home, ".claude", "settings.json");
2248
+ }
2249
+ return path8.join(projectRoot, ".claude", "settings.local.json");
2250
+ }
2251
+
2252
+ // src/commands/install-hooks.ts
1877
2253
  var HOOK_MARKER = "# hAIve auto-generated";
1878
2254
  var POST_MERGE_BODY = `#!/bin/sh
1879
2255
  ${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
@@ -1922,50 +2298,192 @@ var HOOKS = [
1922
2298
  { name: "post-rewrite", body: POST_MERGE_BODY },
1923
2299
  { name: "pre-push", body: PRE_PUSH_BODY }
1924
2300
  ];
2301
+ async function installGitHooks(opts) {
2302
+ const root = findProjectRoot6(opts.dir);
2303
+ const gitDir = path9.join(root, ".git");
2304
+ if (!existsSync8(gitDir)) {
2305
+ ui.error(`No .git directory at ${root}.`);
2306
+ process.exitCode = 1;
2307
+ return;
2308
+ }
2309
+ const hooksDir = path9.join(gitDir, "hooks");
2310
+ await mkdir5(hooksDir, { recursive: true });
2311
+ let installed = 0;
2312
+ let skipped = 0;
2313
+ for (const { name, body } of HOOKS) {
2314
+ const file = path9.join(hooksDir, name);
2315
+ if (existsSync8(file) && !opts.force) {
2316
+ const existing = await readFile6(file, "utf8");
2317
+ if (!existing.includes(HOOK_MARKER)) {
2318
+ ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
2319
+ skipped++;
2320
+ continue;
2321
+ }
2322
+ }
2323
+ await writeFile5(file, body, "utf8");
2324
+ await chmod(file, 493);
2325
+ installed++;
2326
+ }
2327
+ ui.success(`Installed ${installed} git hook(s) in .git/hooks/${skipped ? `, skipped ${skipped}` : ""}`);
2328
+ ui.info("post-merge: haive sync runs after every pull/merge.");
2329
+ ui.info("pre-push: haive precommit runs before every push (advisory, never blocks).");
2330
+ ui.info(" Set HAIVE_BLOCK=1 in your shell to make pre-push blocking.");
2331
+ }
2332
+ async function installClaudeHooks(opts) {
2333
+ const root = findProjectRoot6(opts.dir);
2334
+ const scope = opts.scope ?? "user";
2335
+ const settingsPath = opts.settings ?? defaultClaudeSettingsPath(scope, root);
2336
+ if (opts.uninstall) {
2337
+ const result = await uninstallClaudeHooksAtPath(settingsPath);
2338
+ ui.success(`Removed hAIve hooks from ${result.settingsPath}`);
2339
+ return;
2340
+ }
2341
+ try {
2342
+ const result = await installClaudeHooksAtPath(settingsPath);
2343
+ if (result.created) {
2344
+ ui.success(`Created ${result.settingsPath} with hAIve passive-capture hooks`);
2345
+ } else {
2346
+ ui.success(`Patched ${result.settingsPath} (existing user hooks preserved)`);
2347
+ }
2348
+ } catch (err) {
2349
+ ui.error(err instanceof Error ? err.message : String(err));
2350
+ process.exitCode = 1;
2351
+ return;
2352
+ }
2353
+ ui.info("PostToolUse hook: `haive observe` runs after every Edit/Write/Bash");
2354
+ ui.info(" (appends a JSON line to .ai/.cache/observations.jsonl)");
2355
+ ui.info("SessionEnd hook: `haive session end --auto --quiet` distills observations");
2356
+ ui.info(" into a session_recap memory at session close");
2357
+ ui.info("Restart Claude Code (or open a new conversation) for the hooks to take effect.");
2358
+ ui.info(`Run \`haive install-hooks claude --uninstall\` to remove.`);
2359
+ }
1925
2360
  function registerInstallHooks(program2) {
1926
- program2.command("install-hooks").description(
1927
- "Install git hooks so haive sync runs automatically after every pull or merge.\n\n Installs:\n post-merge / post-rewrite \u2014 runs haive sync after every pull/merge\n pre-push \u2014 runs haive precommit before every push (advisory)\n\n Installed automatically by haive init (autopilot mode).\n Use --force to overwrite existing hooks.\n"
1928
- ).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
1929
- const root = findProjectRoot6(opts.dir);
1930
- const gitDir = path8.join(root, ".git");
1931
- if (!existsSync7(gitDir)) {
1932
- ui.error(`No .git directory at ${root}.`);
2361
+ program2.command("install-hooks [target]").description(
2362
+ "Install hAIve hooks. Targets:\n\n git (default) post-merge / post-rewrite / pre-push for haive sync + precommit\n claude PostToolUse + SessionEnd hooks in ~/.claude/settings.json\n for passive observation capture (Claude Code only)\n\n Examples:\n haive install-hooks # git hooks (legacy default)\n haive install-hooks git\n haive install-hooks claude\n haive install-hooks claude --scope project\n haive install-hooks claude --uninstall\n"
2363
+ ).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks (git target only)").option("--scope <scope>", "claude target: 'user' (~/.claude) or 'project' (.claude/)", "user").option("--uninstall", "remove previously installed hAIve hooks (claude target only)").option("--settings <path>", "explicit path to settings.json (claude target only)").action(async (target, opts) => {
2364
+ const t = (target ?? "git").toLowerCase();
2365
+ if (t === "git") {
2366
+ await installGitHooks(opts);
2367
+ } else if (t === "claude") {
2368
+ await installClaudeHooks(opts);
2369
+ } else {
2370
+ ui.error(`Unknown target: ${target}. Available: git, claude`);
1933
2371
  process.exitCode = 1;
1934
- return;
1935
2372
  }
1936
- const hooksDir = path8.join(gitDir, "hooks");
1937
- await mkdir4(hooksDir, { recursive: true });
1938
- let installed = 0;
1939
- let skipped = 0;
1940
- for (const { name, body } of HOOKS) {
1941
- const file = path8.join(hooksDir, name);
1942
- if (existsSync7(file) && !opts.force) {
1943
- const existing = await readFile5(file, "utf8");
1944
- if (!existing.includes(HOOK_MARKER)) {
1945
- ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
1946
- skipped++;
1947
- continue;
1948
- }
2373
+ });
2374
+ }
2375
+
2376
+ // src/commands/observe.ts
2377
+ import { appendFile, mkdir as mkdir6 } from "fs/promises";
2378
+ import { existsSync as existsSync9 } from "fs";
2379
+ import path10 from "path";
2380
+ import "commander";
2381
+ import { findProjectRoot as findProjectRoot7, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
2382
+ var MAX_STDIN_BYTES = 256 * 1024;
2383
+ var TRUNCATE_FIELD = 800;
2384
+ function truncate(s, max = TRUNCATE_FIELD) {
2385
+ if (s == null) return "";
2386
+ const str = typeof s === "string" ? s : JSON.stringify(s);
2387
+ return str.length > max ? str.slice(0, max) + "\u2026" : str;
2388
+ }
2389
+ function extractFiles(payload) {
2390
+ const files = /* @__PURE__ */ new Set();
2391
+ const input = payload.tool_input ?? {};
2392
+ for (const k of ["file_path", "path", "notebook_path"]) {
2393
+ const v = input[k];
2394
+ if (typeof v === "string") files.add(v);
2395
+ }
2396
+ const cmd = input["command"];
2397
+ if (typeof cmd === "string") {
2398
+ const matches = cmd.match(/[\w./-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|kt|swift|rb|php|cs|cpp|c|h|md|json|yml|yaml)\b/g);
2399
+ if (matches) for (const m of matches) files.add(m);
2400
+ }
2401
+ return [...files].slice(0, 8);
2402
+ }
2403
+ function buildSummary(payload) {
2404
+ const tool = payload.tool_name ?? "?";
2405
+ const input = payload.tool_input ?? {};
2406
+ if (tool === "Bash") return `Bash: ${truncate(input["command"], 200)}`;
2407
+ if (tool === "Edit") return `Edit ${truncate(input["file_path"], 200)}`;
2408
+ if (tool === "Write") return `Write ${truncate(input["file_path"], 200)}`;
2409
+ return `${tool}: ${truncate(input, 200)}`;
2410
+ }
2411
+ async function readStdin(maxBytes) {
2412
+ if (process.stdin.isTTY) return "";
2413
+ return await new Promise((resolve) => {
2414
+ const chunks = [];
2415
+ let total = 0;
2416
+ let done = false;
2417
+ const finish = () => {
2418
+ if (done) return;
2419
+ done = true;
2420
+ resolve(Buffer.concat(chunks).toString("utf8"));
2421
+ };
2422
+ process.stdin.on("data", (c) => {
2423
+ total += c.length;
2424
+ if (total > maxBytes) {
2425
+ process.stdin.destroy();
2426
+ finish();
2427
+ return;
2428
+ }
2429
+ chunks.push(c);
2430
+ });
2431
+ process.stdin.on("end", finish);
2432
+ process.stdin.on("error", finish);
2433
+ setTimeout(finish, 2e3);
2434
+ });
2435
+ }
2436
+ function registerObserve(program2) {
2437
+ program2.command("observe").description(
2438
+ "Passive-capture endpoint for Claude Code PostToolUse hooks.\n\n Reads a JSON payload on stdin and appends an observation record to\n .ai/.cache/observations.jsonl. Always exits 0; never blocks the agent.\n Wired up automatically by `haive install-hooks claude`."
2439
+ ).option("-d, --dir <dir>", "project root").action(async (opts) => {
2440
+ try {
2441
+ const raw = await readStdin(MAX_STDIN_BYTES);
2442
+ if (!raw.trim()) return;
2443
+ let payload;
2444
+ try {
2445
+ payload = JSON.parse(raw);
2446
+ } catch {
2447
+ return;
1949
2448
  }
1950
- await writeFile4(file, body, "utf8");
1951
- await chmod(file, 493);
1952
- installed++;
1953
- }
1954
- ui.success(`Installed ${installed} hook(s) in .git/hooks/${skipped ? `, skipped ${skipped}` : ""}`);
1955
- ui.info("post-merge: haive sync runs after every pull/merge.");
1956
- ui.info("pre-push: haive precommit runs before every push (advisory, never blocks).");
1957
- ui.info(" Set HAIVE_BLOCK=1 in your shell to make pre-push blocking.");
2449
+ const root = (() => {
2450
+ try {
2451
+ return findProjectRoot7(opts.dir ?? payload.cwd);
2452
+ } catch {
2453
+ return null;
2454
+ }
2455
+ })();
2456
+ if (!root) return;
2457
+ const paths = resolveHaivePaths5(root);
2458
+ if (!existsSync9(paths.haiveDir)) return;
2459
+ const observation = {
2460
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2461
+ session_id: payload.session_id,
2462
+ cwd: payload.cwd,
2463
+ tool: payload.tool_name ?? "?",
2464
+ summary: buildSummary(payload),
2465
+ files: extractFiles(payload)
2466
+ };
2467
+ const cacheDir = path10.join(paths.haiveDir, ".cache");
2468
+ await mkdir6(cacheDir, { recursive: true });
2469
+ await appendFile(
2470
+ path10.join(cacheDir, "observations.jsonl"),
2471
+ JSON.stringify(observation) + "\n",
2472
+ "utf8"
2473
+ );
2474
+ } catch {
2475
+ }
1958
2476
  });
1959
2477
  }
1960
2478
 
1961
2479
  // src/commands/mcp.ts
1962
2480
  import { spawn } from "child_process";
1963
- import { existsSync as existsSync8 } from "fs";
2481
+ import { existsSync as existsSync10 } from "fs";
1964
2482
  import { createRequire } from "module";
1965
- import path9 from "path";
2483
+ import path11 from "path";
1966
2484
  import { fileURLToPath } from "url";
1967
2485
  import "commander";
1968
- import { findProjectRoot as findProjectRoot7 } from "@hiveai/core";
2486
+ import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
1969
2487
  var require2 = createRequire(import.meta.url);
1970
2488
  function registerMcp(program2) {
1971
2489
  program2.command("mcp").description(
@@ -1982,7 +2500,7 @@ function registerMcp(program2) {
1982
2500
  VS Code: code --add-mcp '{"name":"haive","command":"haive-mcp",...}'
1983
2501
  `
1984
2502
  ).option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
1985
- const root = findProjectRoot7(opts.dir);
2503
+ const root = findProjectRoot8(opts.dir);
1986
2504
  const bin = locateMcpBin();
1987
2505
  if (!bin) {
1988
2506
  ui.error(
@@ -2000,36 +2518,36 @@ function registerMcp(program2) {
2000
2518
  function locateMcpBin() {
2001
2519
  try {
2002
2520
  const pkgPath = require2.resolve("@hiveai/mcp/package.json");
2003
- const pkgDir = path9.dirname(pkgPath);
2004
- const candidate = path9.join(pkgDir, "dist", "index.js");
2005
- if (existsSync8(candidate)) return candidate;
2521
+ const pkgDir = path11.dirname(pkgPath);
2522
+ const candidate = path11.join(pkgDir, "dist", "index.js");
2523
+ if (existsSync10(candidate)) return candidate;
2006
2524
  } catch {
2007
2525
  }
2008
- const here = path9.dirname(fileURLToPath(import.meta.url));
2009
- const sibling = path9.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
2010
- if (existsSync8(sibling)) return sibling;
2526
+ const here = path11.dirname(fileURLToPath(import.meta.url));
2527
+ const sibling = path11.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
2528
+ if (existsSync10(sibling)) return sibling;
2011
2529
  return null;
2012
2530
  }
2013
2531
 
2014
2532
  // src/commands/sync.ts
2015
2533
  import { spawnSync as spawnSync2 } from "child_process";
2016
- import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
2017
- import { existsSync as existsSync9 } from "fs";
2018
- import path10 from "path";
2534
+ import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir7 } from "fs/promises";
2535
+ import { existsSync as existsSync11 } from "fs";
2536
+ import path12 from "path";
2019
2537
  import "commander";
2020
2538
  import {
2021
2539
  DEFAULT_AUTO_PROMOTE_RULE,
2022
2540
  buildFrontmatter as buildFrontmatter2,
2023
- findProjectRoot as findProjectRoot8,
2541
+ findProjectRoot as findProjectRoot9,
2024
2542
  getUsage,
2025
2543
  isAutoPromoteEligible,
2026
2544
  isDecaying,
2027
2545
  loadCodeMap as loadCodeMap2,
2028
2546
  loadConfig,
2029
2547
  loadMemoriesFromDir as loadMemoriesFromDir2,
2030
- loadUsageIndex,
2548
+ loadUsageIndex as loadUsageIndex2,
2031
2549
  pullCrossRepoSources,
2032
- resolveHaivePaths as resolveHaivePaths5,
2550
+ resolveHaivePaths as resolveHaivePaths6,
2033
2551
  resolveManifestFiles,
2034
2552
  serializeMemory as serializeMemory2,
2035
2553
  trackDependencies,
@@ -2048,9 +2566,9 @@ function registerSync(program2) {
2048
2566
  "--inject-bridge",
2049
2567
  "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
2050
2568
  ).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) => {
2051
- const root = findProjectRoot8(opts.dir);
2052
- const paths = resolveHaivePaths5(root);
2053
- if (!existsSync9(paths.memoriesDir)) {
2569
+ const root = findProjectRoot9(opts.dir);
2570
+ const paths = resolveHaivePaths6(root);
2571
+ if (!existsSync11(paths.memoriesDir)) {
2054
2572
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
2055
2573
  process.exitCode = 1;
2056
2574
  return;
@@ -2070,7 +2588,7 @@ function registerSync(program2) {
2070
2588
  for (const { memory: memory2, filePath } of memories) {
2071
2589
  if (memory2.frontmatter.type === "session_recap") {
2072
2590
  if (memory2.frontmatter.status === "stale") {
2073
- await writeFile5(
2591
+ await writeFile6(
2074
2592
  filePath,
2075
2593
  serializeMemory2({
2076
2594
  frontmatter: {
@@ -2093,7 +2611,7 @@ function registerSync(program2) {
2093
2611
  const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
2094
2612
  if (result.stale) {
2095
2613
  if (memory2.frontmatter.status !== "stale") {
2096
- await writeFile5(
2614
+ await writeFile6(
2097
2615
  filePath,
2098
2616
  serializeMemory2({
2099
2617
  frontmatter: {
@@ -2109,7 +2627,7 @@ function registerSync(program2) {
2109
2627
  staleMarked++;
2110
2628
  }
2111
2629
  } else if (memory2.frontmatter.status === "stale") {
2112
- await writeFile5(
2630
+ await writeFile6(
2113
2631
  filePath,
2114
2632
  serializeMemory2({
2115
2633
  frontmatter: {
@@ -2128,7 +2646,7 @@ function registerSync(program2) {
2128
2646
  }
2129
2647
  if (opts.promote !== false) {
2130
2648
  const memories = await loadMemoriesFromDir2(paths.memoriesDir);
2131
- const usage = await loadUsageIndex(paths);
2649
+ const usage = await loadUsageIndex2(paths);
2132
2650
  const nowMs = Date.now();
2133
2651
  for (const { memory: memory2, filePath } of memories) {
2134
2652
  const fm = memory2.frontmatter;
@@ -2137,7 +2655,7 @@ function registerSync(program2) {
2137
2655
  minReads: autoPromoteMinReads,
2138
2656
  maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
2139
2657
  })) {
2140
- await writeFile5(
2658
+ await writeFile6(
2141
2659
  filePath,
2142
2660
  serializeMemory2({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
2143
2661
  "utf8"
@@ -2148,7 +2666,7 @@ function registerSync(program2) {
2148
2666
  if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
2149
2667
  const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
2150
2668
  if (ageHours >= autoApproveDelayHours) {
2151
- await writeFile5(
2669
+ await writeFile6(
2152
2670
  filePath,
2153
2671
  serializeMemory2({
2154
2672
  frontmatter: {
@@ -2182,7 +2700,7 @@ function registerSync(program2) {
2182
2700
  );
2183
2701
  }
2184
2702
  if (opts.injectBridge) {
2185
- const bridgeFile = opts.bridgeFile ? path10.resolve(opts.bridgeFile) : path10.join(root, "CLAUDE.md");
2703
+ const bridgeFile = opts.bridgeFile ? path12.resolve(opts.bridgeFile) : path12.join(root, "CLAUDE.md");
2186
2704
  const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
2187
2705
  await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
2188
2706
  }
@@ -2202,7 +2720,7 @@ function registerSync(program2) {
2202
2720
  }
2203
2721
  if (!opts.quiet) {
2204
2722
  const allForDecay = await loadMemoriesFromDir2(paths.memoriesDir);
2205
- const usageForDecay = await loadUsageIndex(paths);
2723
+ const usageForDecay = await loadUsageIndex2(paths);
2206
2724
  const decaying = allForDecay.filter(({ memory: memory2 }) => {
2207
2725
  const fm = memory2.frontmatter;
2208
2726
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -2288,10 +2806,10 @@ Attends une **confirmation explicite** avant d'agir.
2288
2806
  paths: [result.file],
2289
2807
  topic: `dep-bump-${slugParts}`
2290
2808
  });
2291
- const teamDir = path10.join(paths.memoriesDir, "team");
2292
- await mkdir5(teamDir, { recursive: true });
2293
- await writeFile5(
2294
- path10.join(teamDir, `${fm.id}.md`),
2809
+ const teamDir = path12.join(paths.memoriesDir, "team");
2810
+ await mkdir7(teamDir, { recursive: true });
2811
+ await writeFile6(
2812
+ path12.join(teamDir, `${fm.id}.md`),
2295
2813
  serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
2296
2814
  "utf8"
2297
2815
  );
@@ -2355,10 +2873,10 @@ Attends une **confirmation explicite** avant d'agir.
2355
2873
  paths: [diff.file],
2356
2874
  topic: `contract-breaking-${diff.contract}`
2357
2875
  });
2358
- const teamDir = path10.join(paths.memoriesDir, "team");
2359
- await mkdir5(teamDir, { recursive: true });
2360
- await writeFile5(
2361
- path10.join(teamDir, `${fm.id}.md`),
2876
+ const teamDir = path12.join(paths.memoriesDir, "team");
2877
+ await mkdir7(teamDir, { recursive: true });
2878
+ await writeFile6(
2879
+ path12.join(teamDir, `${fm.id}.md`),
2362
2880
  serializeMemory2({ frontmatter: { ...fm, requires_human_approval: true }, body }),
2363
2881
  "utf8"
2364
2882
  );
@@ -2419,7 +2937,7 @@ Attends une **confirmation explicite** avant d'agir.
2419
2937
  });
2420
2938
  }
2421
2939
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
2422
- if (!existsSync9(memoriesDir)) return;
2940
+ if (!existsSync11(memoriesDir)) return;
2423
2941
  const all = await loadMemoriesFromDir2(memoriesDir);
2424
2942
  const top = all.filter(({ memory: memory2 }) => {
2425
2943
  const s = memory2.frontmatter.status;
@@ -2444,17 +2962,17 @@ ${m.memory.body.trim()}`;
2444
2962
  ` + block + `
2445
2963
 
2446
2964
  ${BRIDGE_END}`;
2447
- const fileExists = existsSync9(bridgeFile);
2448
- let existing = fileExists ? await readFile6(bridgeFile, "utf8") : "";
2965
+ const fileExists = existsSync11(bridgeFile);
2966
+ let existing = fileExists ? await readFile7(bridgeFile, "utf8") : "";
2449
2967
  existing = existing.replace(/\r\n/g, "\n");
2450
2968
  const startIdx = existing.indexOf(BRIDGE_START);
2451
2969
  const endIdx = existing.indexOf(BRIDGE_END);
2452
2970
  if (startIdx !== -1 && endIdx === -1) {
2453
- ui.warn(`${path10.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
2971
+ ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
2454
2972
  return;
2455
2973
  }
2456
2974
  if (startIdx === -1 && endIdx !== -1) {
2457
- ui.warn(`${path10.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
2975
+ ui.warn(`${path12.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
2458
2976
  return;
2459
2977
  }
2460
2978
  let updated;
@@ -2462,14 +2980,14 @@ ${BRIDGE_END}`;
2462
2980
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
2463
2981
  } else {
2464
2982
  if (!fileExists && !quiet) {
2465
- ui.info(`Creating ${path10.relative(root, bridgeFile)} with haive memory block.`);
2983
+ ui.info(`Creating ${path12.relative(root, bridgeFile)} with haive memory block.`);
2466
2984
  }
2467
2985
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
2468
2986
  }
2469
- await writeFile5(bridgeFile, updated, "utf8");
2987
+ await writeFile6(bridgeFile, updated, "utf8");
2470
2988
  if (!quiet) {
2471
2989
  console.log(
2472
- ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path10.relative(root, bridgeFile)}`)
2990
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path12.relative(root, bridgeFile)}`)
2473
2991
  );
2474
2992
  }
2475
2993
  }
@@ -2494,17 +3012,17 @@ function collectSinceChanges(root, ref) {
2494
3012
 
2495
3013
  // src/commands/memory-add.ts
2496
3014
  import { createHash } from "crypto";
2497
- import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
2498
- import { existsSync as existsSync10 } from "fs";
2499
- import path11 from "path";
3015
+ import { mkdir as mkdir8, readFile as readFile8, writeFile as writeFile7 } from "fs/promises";
3016
+ import { existsSync as existsSync12 } from "fs";
3017
+ import path13 from "path";
2500
3018
  import "commander";
2501
3019
  import {
2502
3020
  buildFrontmatter as buildFrontmatter3,
2503
- findProjectRoot as findProjectRoot9,
3021
+ findProjectRoot as findProjectRoot10,
2504
3022
  inferModulesFromPaths,
2505
3023
  loadMemoriesFromDir as loadMemoriesFromDir3,
2506
3024
  memoryFilePath as memoryFilePath2,
2507
- resolveHaivePaths as resolveHaivePaths6,
3025
+ resolveHaivePaths as resolveHaivePaths7,
2508
3026
  serializeMemory as serializeMemory3
2509
3027
  } from "@hiveai/core";
2510
3028
  function registerMemoryAdd(memory2) {
@@ -2532,9 +3050,9 @@ function registerMemoryAdd(memory2) {
2532
3050
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
2533
3051
  `
2534
3052
  ).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) => {
2535
- const root = findProjectRoot9(opts.dir);
2536
- const paths = resolveHaivePaths6(root);
2537
- if (!existsSync10(paths.haiveDir)) {
3053
+ const root = findProjectRoot10(opts.dir);
3054
+ const paths = resolveHaivePaths7(root);
3055
+ if (!existsSync12(paths.haiveDir)) {
2538
3056
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
2539
3057
  process.exitCode = 1;
2540
3058
  return;
@@ -2545,7 +3063,7 @@ function registerMemoryAdd(memory2) {
2545
3063
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
2546
3064
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
2547
3065
  if (anchorPaths.length > 0) {
2548
- const missing = anchorPaths.filter((p) => !existsSync10(path11.resolve(root, p)));
3066
+ const missing = anchorPaths.filter((p) => !existsSync12(path13.resolve(root, p)));
2549
3067
  if (missing.length > 0) {
2550
3068
  ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
2551
3069
  for (const p of missing) ui.warn(` \u2717 ${p}`);
@@ -2557,12 +3075,12 @@ function registerMemoryAdd(memory2) {
2557
3075
  const title = opts.title ?? opts.slug;
2558
3076
  let body;
2559
3077
  if (opts.bodyFile !== void 0) {
2560
- if (!existsSync10(opts.bodyFile)) {
3078
+ if (!existsSync12(opts.bodyFile)) {
2561
3079
  ui.error(`--body-file not found: ${opts.bodyFile}`);
2562
3080
  process.exitCode = 1;
2563
3081
  return;
2564
3082
  }
2565
- const fileContent = await readFile7(opts.bodyFile, "utf8");
3083
+ const fileContent = await readFile8(opts.bodyFile, "utf8");
2566
3084
  body = opts.title ? `# ${opts.title}
2567
3085
 
2568
3086
  ${fileContent.trim()}
@@ -2578,7 +3096,7 @@ TODO \u2014 write the memory body.
2578
3096
  `;
2579
3097
  }
2580
3098
  const scope = opts.scope ?? "personal";
2581
- if (existsSync10(paths.memoriesDir)) {
3099
+ if (existsSync12(paths.memoriesDir)) {
2582
3100
  const incomingHash = createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
2583
3101
  const allForHash = await loadMemoriesFromDir3(paths.memoriesDir);
2584
3102
  const hashDup = allForHash.find(
@@ -2591,7 +3109,7 @@ TODO \u2014 write the memory body.
2591
3109
  return;
2592
3110
  }
2593
3111
  }
2594
- if (opts.topic && existsSync10(paths.memoriesDir)) {
3112
+ if (opts.topic && existsSync12(paths.memoriesDir)) {
2595
3113
  const existing = await loadMemoriesFromDir3(paths.memoriesDir);
2596
3114
  const topicMatch = existing.find(
2597
3115
  ({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
@@ -2609,8 +3127,8 @@ TODO \u2014 write the memory body.
2609
3127
  symbols: parseCsv2(opts.symbols).length ? parseCsv2(opts.symbols) : fm.anchor.symbols
2610
3128
  }
2611
3129
  };
2612
- await writeFile6(topicMatch.filePath, serializeMemory3({ frontmatter: newFrontmatter, body }), "utf8");
2613
- ui.success(`Updated (topic upsert) ${path11.relative(root, topicMatch.filePath)}`);
3130
+ await writeFile7(topicMatch.filePath, serializeMemory3({ frontmatter: newFrontmatter, body }), "utf8");
3131
+ ui.success(`Updated (topic upsert) ${path13.relative(root, topicMatch.filePath)}`);
2614
3132
  ui.info(`id=${fm.id} revision=${revisionCount}`);
2615
3133
  return;
2616
3134
  }
@@ -2629,13 +3147,13 @@ TODO \u2014 write the memory body.
2629
3147
  topic: opts.topic
2630
3148
  });
2631
3149
  const file = memoryFilePath2(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
2632
- await mkdir6(path11.dirname(file), { recursive: true });
2633
- if (existsSync10(file)) {
3150
+ await mkdir8(path13.dirname(file), { recursive: true });
3151
+ if (existsSync12(file)) {
2634
3152
  ui.error(`Memory already exists at ${file}`);
2635
3153
  process.exitCode = 1;
2636
3154
  return;
2637
3155
  }
2638
- if (existsSync10(paths.memoriesDir)) {
3156
+ if (existsSync12(paths.memoriesDir)) {
2639
3157
  const existing = await loadMemoriesFromDir3(paths.memoriesDir);
2640
3158
  const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
2641
3159
  const similar = existing.filter(({ memory: memory3 }) => {
@@ -2647,8 +3165,8 @@ TODO \u2014 write the memory body.
2647
3165
  ui.warn("Consider updating one of these with `haive memory update` instead.");
2648
3166
  }
2649
3167
  }
2650
- await writeFile6(file, serializeMemory3({ frontmatter, body }), "utf8");
2651
- ui.success(`Created ${path11.relative(root, file)}`);
3168
+ await writeFile7(file, serializeMemory3({ frontmatter, body }), "utf8");
3169
+ ui.success(`Created ${path13.relative(root, file)}`);
2652
3170
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
2653
3171
  if (inferredTags.length > 0) {
2654
3172
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
@@ -2678,10 +3196,10 @@ function parseCsv2(value) {
2678
3196
  }
2679
3197
 
2680
3198
  // src/commands/memory-list.ts
2681
- import { existsSync as existsSync11 } from "fs";
2682
- import path12 from "path";
3199
+ import { existsSync as existsSync13 } from "fs";
3200
+ import path14 from "path";
2683
3201
  import "commander";
2684
- import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
3202
+ import { findProjectRoot as findProjectRoot11, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
2685
3203
 
2686
3204
  // src/utils/fs.ts
2687
3205
  import {
@@ -2693,9 +3211,9 @@ import {
2693
3211
  // src/commands/memory-list.ts
2694
3212
  function registerMemoryList(memory2) {
2695
3213
  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) => {
2696
- const root = findProjectRoot10(opts.dir);
2697
- const paths = resolveHaivePaths7(root);
2698
- if (!existsSync11(paths.memoriesDir)) {
3214
+ const root = findProjectRoot11(opts.dir);
3215
+ const paths = resolveHaivePaths8(root);
3216
+ if (!existsSync13(paths.memoriesDir)) {
2699
3217
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
2700
3218
  process.exitCode = 1;
2701
3219
  return;
@@ -2727,7 +3245,7 @@ function registerMemoryList(memory2) {
2727
3245
  console.log(
2728
3246
  `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
2729
3247
  );
2730
- console.log(` ${ui.dim(path12.relative(root, filePath))}`);
3248
+ console.log(` ${ui.dim(path14.relative(root, filePath))}`);
2731
3249
  }
2732
3250
  console.log(ui.dim(`
2733
3251
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
@@ -2762,21 +3280,21 @@ function matchesFilters(loaded, opts) {
2762
3280
  }
2763
3281
 
2764
3282
  // src/commands/memory-promote.ts
2765
- import { mkdir as mkdir7, unlink, writeFile as writeFile7 } from "fs/promises";
2766
- import { existsSync as existsSync12 } from "fs";
2767
- import path13 from "path";
3283
+ import { mkdir as mkdir9, unlink, writeFile as writeFile8 } from "fs/promises";
3284
+ import { existsSync as existsSync14 } from "fs";
3285
+ import path15 from "path";
2768
3286
  import "commander";
2769
3287
  import {
2770
- findProjectRoot as findProjectRoot11,
3288
+ findProjectRoot as findProjectRoot12,
2771
3289
  memoryFilePath as memoryFilePath3,
2772
- resolveHaivePaths as resolveHaivePaths8,
3290
+ resolveHaivePaths as resolveHaivePaths9,
2773
3291
  serializeMemory as serializeMemory4
2774
3292
  } from "@hiveai/core";
2775
3293
  function registerMemoryPromote(memory2) {
2776
3294
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
2777
- const root = findProjectRoot11(opts.dir);
2778
- const paths = resolveHaivePaths8(root);
2779
- if (!existsSync12(paths.memoriesDir)) {
3295
+ const root = findProjectRoot12(opts.dir);
3296
+ const paths = resolveHaivePaths9(root);
3297
+ if (!existsSync14(paths.memoriesDir)) {
2780
3298
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
2781
3299
  process.exitCode = 1;
2782
3300
  return;
@@ -2811,30 +3329,30 @@ function registerMemoryPromote(memory2) {
2811
3329
  body: found.memory.body
2812
3330
  };
2813
3331
  const newPath = memoryFilePath3(paths, "team", updated.frontmatter.id);
2814
- await mkdir7(path13.dirname(newPath), { recursive: true });
2815
- await writeFile7(newPath, serializeMemory4(updated), "utf8");
3332
+ await mkdir9(path15.dirname(newPath), { recursive: true });
3333
+ await writeFile8(newPath, serializeMemory4(updated), "utf8");
2816
3334
  await unlink(found.filePath);
2817
3335
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
2818
- ui.info(`Now at ${path13.relative(root, newPath)}`);
3336
+ ui.info(`Now at ${path15.relative(root, newPath)}`);
2819
3337
  console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
2820
3338
  });
2821
3339
  }
2822
3340
 
2823
3341
  // src/commands/memory-approve.ts
2824
- import { existsSync as existsSync13 } from "fs";
2825
- import { writeFile as writeFile8 } from "fs/promises";
2826
- import path14 from "path";
3342
+ import { existsSync as existsSync15 } from "fs";
3343
+ import { writeFile as writeFile9 } from "fs/promises";
3344
+ import path16 from "path";
2827
3345
  import "commander";
2828
3346
  import {
2829
- findProjectRoot as findProjectRoot12,
2830
- resolveHaivePaths as resolveHaivePaths9,
3347
+ findProjectRoot as findProjectRoot13,
3348
+ resolveHaivePaths as resolveHaivePaths10,
2831
3349
  serializeMemory as serializeMemory5
2832
3350
  } from "@hiveai/core";
2833
3351
  function registerMemoryApprove(memory2) {
2834
3352
  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) => {
2835
- const root = findProjectRoot12(opts.dir);
2836
- const paths = resolveHaivePaths9(root);
2837
- if (!existsSync13(paths.memoriesDir)) {
3353
+ const root = findProjectRoot13(opts.dir);
3354
+ const paths = resolveHaivePaths10(root);
3355
+ if (!existsSync15(paths.memoriesDir)) {
2838
3356
  ui.error(`No .ai/memories at ${root}.`);
2839
3357
  process.exitCode = 1;
2840
3358
  return;
@@ -2856,7 +3374,7 @@ function registerMemoryApprove(memory2) {
2856
3374
  frontmatter: { ...found2.memory.frontmatter, status: "validated" },
2857
3375
  body: found2.memory.body
2858
3376
  };
2859
- await writeFile8(found2.filePath, serializeMemory5(next2), "utf8");
3377
+ await writeFile9(found2.filePath, serializeMemory5(next2), "utf8");
2860
3378
  count++;
2861
3379
  }
2862
3380
  ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
@@ -2885,27 +3403,27 @@ function registerMemoryApprove(memory2) {
2885
3403
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
2886
3404
  body: found.memory.body
2887
3405
  };
2888
- await writeFile8(found.filePath, serializeMemory5(next), "utf8");
3406
+ await writeFile9(found.filePath, serializeMemory5(next), "utf8");
2889
3407
  ui.success(`Approved ${id} (status=validated)`);
2890
- ui.info(path14.relative(root, found.filePath));
3408
+ ui.info(path16.relative(root, found.filePath));
2891
3409
  });
2892
3410
  }
2893
3411
 
2894
3412
  // src/commands/memory-update.ts
2895
- import { writeFile as writeFile9 } from "fs/promises";
2896
- import { existsSync as existsSync14 } from "fs";
2897
- import path15 from "path";
3413
+ import { writeFile as writeFile10 } from "fs/promises";
3414
+ import { existsSync as existsSync16 } from "fs";
3415
+ import path17 from "path";
2898
3416
  import "commander";
2899
3417
  import {
2900
- findProjectRoot as findProjectRoot13,
2901
- resolveHaivePaths as resolveHaivePaths10,
3418
+ findProjectRoot as findProjectRoot14,
3419
+ resolveHaivePaths as resolveHaivePaths11,
2902
3420
  serializeMemory as serializeMemory6
2903
3421
  } from "@hiveai/core";
2904
3422
  function registerMemoryUpdate(memory2) {
2905
3423
  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) => {
2906
- const root = findProjectRoot13(opts.dir);
2907
- const paths = resolveHaivePaths10(root);
2908
- if (!existsSync14(paths.memoriesDir)) {
3424
+ const root = findProjectRoot14(opts.dir);
3425
+ const paths = resolveHaivePaths11(root);
3426
+ if (!existsSync16(paths.memoriesDir)) {
2909
3427
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
2910
3428
  process.exitCode = 1;
2911
3429
  return;
@@ -2952,12 +3470,12 @@ function registerMemoryUpdate(memory2) {
2952
3470
  ui.warn("Nothing to update \u2014 provide at least one option.");
2953
3471
  return;
2954
3472
  }
2955
- await writeFile9(
3473
+ await writeFile10(
2956
3474
  loaded.filePath,
2957
3475
  serializeMemory6({ frontmatter: newFrontmatter, body: newBody }),
2958
3476
  "utf8"
2959
3477
  );
2960
- ui.success(`Updated ${path15.relative(root, loaded.filePath)}`);
3478
+ ui.success(`Updated ${path17.relative(root, loaded.filePath)}`);
2961
3479
  ui.info(`fields: ${updated.join(", ")}`);
2962
3480
  });
2963
3481
  }
@@ -2976,17 +3494,17 @@ function parseCsv3(value) {
2976
3494
  }
2977
3495
 
2978
3496
  // src/commands/memory-auto-promote.ts
2979
- import { writeFile as writeFile10 } from "fs/promises";
2980
- import { existsSync as existsSync15 } from "fs";
2981
- import path16 from "path";
3497
+ import { writeFile as writeFile11 } from "fs/promises";
3498
+ import { existsSync as existsSync17 } from "fs";
3499
+ import path18 from "path";
2982
3500
  import "commander";
2983
3501
  import {
2984
3502
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
2985
- findProjectRoot as findProjectRoot14,
3503
+ findProjectRoot as findProjectRoot15,
2986
3504
  getUsage as getUsage2,
2987
3505
  isAutoPromoteEligible as isAutoPromoteEligible2,
2988
- loadUsageIndex as loadUsageIndex2,
2989
- resolveHaivePaths as resolveHaivePaths11,
3506
+ loadUsageIndex as loadUsageIndex3,
3507
+ resolveHaivePaths as resolveHaivePaths12,
2990
3508
  serializeMemory as serializeMemory7
2991
3509
  } from "@hiveai/core";
2992
3510
  function registerMemoryAutoPromote(memory2) {
@@ -2995,9 +3513,9 @@ function registerMemoryAutoPromote(memory2) {
2995
3513
  "memories with more rejections than this are skipped",
2996
3514
  String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
2997
3515
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
2998
- const root = findProjectRoot14(opts.dir);
2999
- const paths = resolveHaivePaths11(root);
3000
- if (!existsSync15(paths.memoriesDir)) {
3516
+ const root = findProjectRoot15(opts.dir);
3517
+ const paths = resolveHaivePaths12(root);
3518
+ if (!existsSync17(paths.memoriesDir)) {
3001
3519
  ui.error(`No .ai/memories at ${root}.`);
3002
3520
  process.exitCode = 1;
3003
3521
  return;
@@ -3007,7 +3525,7 @@ function registerMemoryAutoPromote(memory2) {
3007
3525
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
3008
3526
  };
3009
3527
  const memories = await loadMemoriesFromDir4(paths.memoriesDir);
3010
- const usage = await loadUsageIndex2(paths);
3528
+ const usage = await loadUsageIndex3(paths);
3011
3529
  const eligible = memories.filter(
3012
3530
  ({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
3013
3531
  );
@@ -3023,13 +3541,13 @@ function registerMemoryAutoPromote(memory2) {
3023
3541
  console.log(
3024
3542
  `${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
3025
3543
  );
3026
- console.log(` ${ui.dim(path16.relative(root, filePath))}`);
3544
+ console.log(` ${ui.dim(path18.relative(root, filePath))}`);
3027
3545
  if (opts.apply) {
3028
3546
  const next = {
3029
3547
  frontmatter: { ...mem.frontmatter, status: "validated" },
3030
3548
  body: mem.body
3031
3549
  };
3032
- await writeFile10(filePath, serializeMemory7(next), "utf8");
3550
+ await writeFile11(filePath, serializeMemory7(next), "utf8");
3033
3551
  written++;
3034
3552
  }
3035
3553
  }
@@ -3040,20 +3558,20 @@ function registerMemoryAutoPromote(memory2) {
3040
3558
 
3041
3559
  // src/commands/memory-edit.ts
3042
3560
  import { spawn as spawn2 } from "child_process";
3043
- import { existsSync as existsSync16 } from "fs";
3044
- import { readFile as readFile8 } from "fs/promises";
3045
- import path17 from "path";
3561
+ import { existsSync as existsSync18 } from "fs";
3562
+ import { readFile as readFile9 } from "fs/promises";
3563
+ import path19 from "path";
3046
3564
  import "commander";
3047
3565
  import {
3048
- findProjectRoot as findProjectRoot15,
3566
+ findProjectRoot as findProjectRoot16,
3049
3567
  parseMemory,
3050
- resolveHaivePaths as resolveHaivePaths12
3568
+ resolveHaivePaths as resolveHaivePaths13
3051
3569
  } from "@hiveai/core";
3052
3570
  function registerMemoryEdit(memory2) {
3053
3571
  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) => {
3054
- const root = findProjectRoot15(opts.dir);
3055
- const paths = resolveHaivePaths12(root);
3056
- if (!existsSync16(paths.memoriesDir)) {
3572
+ const root = findProjectRoot16(opts.dir);
3573
+ const paths = resolveHaivePaths13(root);
3574
+ if (!existsSync18(paths.memoriesDir)) {
3057
3575
  ui.error(`No .ai/memories at ${root}.`);
3058
3576
  process.exitCode = 1;
3059
3577
  return;
@@ -3066,13 +3584,13 @@ function registerMemoryEdit(memory2) {
3066
3584
  return;
3067
3585
  }
3068
3586
  const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
3069
- ui.info(`Opening ${path17.relative(root, found.filePath)} with ${editor}\u2026`);
3587
+ ui.info(`Opening ${path19.relative(root, found.filePath)} with ${editor}\u2026`);
3070
3588
  const code = await runEditor(editor, found.filePath);
3071
3589
  if (code !== 0) {
3072
3590
  ui.warn(`Editor exited with status ${code}.`);
3073
3591
  }
3074
3592
  try {
3075
- const fresh = await readFile8(found.filePath, "utf8");
3593
+ const fresh = await readFile9(found.filePath, "utf8");
3076
3594
  parseMemory(fresh);
3077
3595
  ui.success("Memory still parses cleanly.");
3078
3596
  } catch (err) {
@@ -3093,29 +3611,29 @@ function runEditor(editor, file) {
3093
3611
  }
3094
3612
 
3095
3613
  // src/commands/memory-for-files.ts
3096
- import { existsSync as existsSync17 } from "fs";
3097
- import path18 from "path";
3098
- import "commander";
3614
+ import { existsSync as existsSync19 } from "fs";
3615
+ import path20 from "path";
3616
+ import "commander";
3099
3617
  import {
3100
3618
  deriveConfidence,
3101
- findProjectRoot as findProjectRoot16,
3619
+ findProjectRoot as findProjectRoot17,
3102
3620
  getUsage as getUsage3,
3103
3621
  inferModulesFromPaths as inferModulesFromPaths2,
3104
- loadUsageIndex as loadUsageIndex3,
3622
+ loadUsageIndex as loadUsageIndex4,
3105
3623
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
3106
- resolveHaivePaths as resolveHaivePaths13
3624
+ resolveHaivePaths as resolveHaivePaths14
3107
3625
  } from "@hiveai/core";
3108
3626
  function registerMemoryForFiles(memory2) {
3109
3627
  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) => {
3110
- const root = findProjectRoot16(opts.dir);
3111
- const paths = resolveHaivePaths13(root);
3112
- if (!existsSync17(paths.memoriesDir)) {
3628
+ const root = findProjectRoot17(opts.dir);
3629
+ const paths = resolveHaivePaths14(root);
3630
+ if (!existsSync19(paths.memoriesDir)) {
3113
3631
  ui.error(`No .ai/memories at ${root}.`);
3114
3632
  process.exitCode = 1;
3115
3633
  return;
3116
3634
  }
3117
3635
  const all = await loadMemoriesFromDir4(paths.memoriesDir);
3118
- const usage = await loadUsageIndex3(paths);
3636
+ const usage = await loadUsageIndex4(paths);
3119
3637
  const inferred = inferModulesFromPaths2(files);
3120
3638
  const byAnchor = [];
3121
3639
  const byModule = [];
@@ -3216,32 +3734,32 @@ function printGroup(root, label, loaded, usage) {
3216
3734
  const u = getUsage3(usage, fm.id);
3217
3735
  const conf = deriveConfidence(fm, u);
3218
3736
  console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
3219
- console.log(` ${ui.dim(path18.relative(root, filePath))}`);
3737
+ console.log(` ${ui.dim(path20.relative(root, filePath))}`);
3220
3738
  }
3221
3739
  }
3222
3740
 
3223
3741
  // src/commands/memory-hot.ts
3224
- import { existsSync as existsSync18 } from "fs";
3225
- import path19 from "path";
3742
+ import { existsSync as existsSync20 } from "fs";
3743
+ import path21 from "path";
3226
3744
  import "commander";
3227
3745
  import {
3228
- findProjectRoot as findProjectRoot17,
3746
+ findProjectRoot as findProjectRoot18,
3229
3747
  getUsage as getUsage4,
3230
- loadUsageIndex as loadUsageIndex4,
3231
- resolveHaivePaths as resolveHaivePaths14
3748
+ loadUsageIndex as loadUsageIndex5,
3749
+ resolveHaivePaths as resolveHaivePaths15
3232
3750
  } from "@hiveai/core";
3233
3751
  function registerMemoryHot(memory2) {
3234
3752
  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) => {
3235
- const root = findProjectRoot17(opts.dir);
3236
- const paths = resolveHaivePaths14(root);
3237
- if (!existsSync18(paths.memoriesDir)) {
3753
+ const root = findProjectRoot18(opts.dir);
3754
+ const paths = resolveHaivePaths15(root);
3755
+ if (!existsSync20(paths.memoriesDir)) {
3238
3756
  ui.error(`No .ai/memories at ${root}.`);
3239
3757
  process.exitCode = 1;
3240
3758
  return;
3241
3759
  }
3242
3760
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
3243
3761
  const all = await loadMemoriesFromDir4(paths.memoriesDir);
3244
- const usage = await loadUsageIndex4(paths);
3762
+ const usage = await loadUsageIndex5(paths);
3245
3763
  const candidates = all.filter(({ memory: mem }) => {
3246
3764
  const fm = mem.frontmatter;
3247
3765
  if (opts.status && fm.status !== opts.status) return false;
@@ -3262,7 +3780,7 @@ function registerMemoryHot(memory2) {
3262
3780
  console.log(
3263
3781
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
3264
3782
  );
3265
- console.log(` ${ui.dim(path19.relative(root, filePath))}`);
3783
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
3266
3784
  }
3267
3785
  ui.info(
3268
3786
  `${candidates.length} hot \u2014 promote drafts with \`haive memory promote <id>\`, then \`haive memory auto-promote --apply\`.`
@@ -3271,15 +3789,15 @@ function registerMemoryHot(memory2) {
3271
3789
  }
3272
3790
 
3273
3791
  // src/commands/memory-tried.ts
3274
- import { mkdir as mkdir8, writeFile as writeFile11 } from "fs/promises";
3275
- import { existsSync as existsSync19 } from "fs";
3276
- import path20 from "path";
3792
+ import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
3793
+ import { existsSync as existsSync21 } from "fs";
3794
+ import path22 from "path";
3277
3795
  import "commander";
3278
3796
  import {
3279
3797
  buildFrontmatter as buildFrontmatter4,
3280
- findProjectRoot as findProjectRoot18,
3798
+ findProjectRoot as findProjectRoot19,
3281
3799
  memoryFilePath as memoryFilePath4,
3282
- resolveHaivePaths as resolveHaivePaths15,
3800
+ resolveHaivePaths as resolveHaivePaths16,
3283
3801
  serializeMemory as serializeMemory8
3284
3802
  } from "@hiveai/core";
3285
3803
  function registerMemoryTried(memory2) {
@@ -3299,9 +3817,9 @@ function registerMemoryTried(memory2) {
3299
3817
  --paths packages/cli/src/index.ts
3300
3818
  `
3301
3819
  ).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) => {
3302
- const root = findProjectRoot18(opts.dir);
3303
- const paths = resolveHaivePaths15(root);
3304
- if (!existsSync19(paths.haiveDir)) {
3820
+ const root = findProjectRoot19(opts.dir);
3821
+ const paths = resolveHaivePaths16(root);
3822
+ if (!existsSync21(paths.haiveDir)) {
3305
3823
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
3306
3824
  process.exitCode = 1;
3307
3825
  return;
@@ -3324,14 +3842,14 @@ function registerMemoryTried(memory2) {
3324
3842
  }
3325
3843
  const body = lines.join("\n") + "\n";
3326
3844
  const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
3327
- await mkdir8(path20.dirname(file), { recursive: true });
3328
- if (existsSync19(file)) {
3845
+ await mkdir10(path22.dirname(file), { recursive: true });
3846
+ if (existsSync21(file)) {
3329
3847
  ui.error(`Memory already exists at ${file}`);
3330
3848
  process.exitCode = 1;
3331
3849
  return;
3332
3850
  }
3333
- await writeFile11(file, serializeMemory8({ frontmatter, body }), "utf8");
3334
- ui.success(`Recorded: ${path20.relative(root, file)}`);
3851
+ await writeFile12(file, serializeMemory8({ frontmatter, body }), "utf8");
3852
+ ui.success(`Recorded: ${path22.relative(root, file)}`);
3335
3853
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
3336
3854
  });
3337
3855
  }
@@ -3341,26 +3859,26 @@ function parseCsv4(value) {
3341
3859
  }
3342
3860
 
3343
3861
  // src/commands/memory-pending.ts
3344
- import { existsSync as existsSync20 } from "fs";
3345
- import path21 from "path";
3862
+ import { existsSync as existsSync22 } from "fs";
3863
+ import path23 from "path";
3346
3864
  import "commander";
3347
3865
  import {
3348
- findProjectRoot as findProjectRoot19,
3866
+ findProjectRoot as findProjectRoot20,
3349
3867
  getUsage as getUsage5,
3350
- loadUsageIndex as loadUsageIndex5,
3351
- resolveHaivePaths as resolveHaivePaths16
3868
+ loadUsageIndex as loadUsageIndex6,
3869
+ resolveHaivePaths as resolveHaivePaths17
3352
3870
  } from "@hiveai/core";
3353
3871
  function registerMemoryPending(memory2) {
3354
3872
  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) => {
3355
- const root = findProjectRoot19(opts.dir);
3356
- const paths = resolveHaivePaths16(root);
3357
- if (!existsSync20(paths.memoriesDir)) {
3873
+ const root = findProjectRoot20(opts.dir);
3874
+ const paths = resolveHaivePaths17(root);
3875
+ if (!existsSync22(paths.memoriesDir)) {
3358
3876
  ui.error(`No .ai/memories at ${root}.`);
3359
3877
  process.exitCode = 1;
3360
3878
  return;
3361
3879
  }
3362
3880
  const all = await loadMemoriesFromDir4(paths.memoriesDir);
3363
- const usage = await loadUsageIndex5(paths);
3881
+ const usage = await loadUsageIndex6(paths);
3364
3882
  const proposed = all.filter(({ memory: mem }) => {
3365
3883
  if (mem.frontmatter.status !== "proposed") return false;
3366
3884
  if (opts.scope && mem.frontmatter.scope !== opts.scope) return false;
@@ -3382,31 +3900,31 @@ function registerMemoryPending(memory2) {
3382
3900
  console.log(
3383
3901
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
3384
3902
  );
3385
- console.log(` ${ui.dim(path21.relative(root, filePath))}`);
3903
+ console.log(` ${ui.dim(path23.relative(root, filePath))}`);
3386
3904
  }
3387
3905
  ui.info(`${proposed.length} pending`);
3388
3906
  });
3389
3907
  }
3390
3908
 
3391
3909
  // src/commands/memory-query.ts
3392
- import { existsSync as existsSync21 } from "fs";
3393
- import path22 from "path";
3910
+ import { existsSync as existsSync23 } from "fs";
3911
+ import path24 from "path";
3394
3912
  import "commander";
3395
3913
  import {
3396
3914
  extractSnippet,
3397
- findProjectRoot as findProjectRoot20,
3915
+ findProjectRoot as findProjectRoot21,
3398
3916
  literalMatchesAllTokens as literalMatchesAllTokens2,
3399
3917
  literalMatchesAnyToken as literalMatchesAnyToken2,
3400
3918
  pickSnippetNeedle,
3401
- resolveHaivePaths as resolveHaivePaths17,
3919
+ resolveHaivePaths as resolveHaivePaths18,
3402
3920
  tokenizeQuery as tokenizeQuery2,
3403
3921
  trackReads as trackReads2
3404
3922
  } from "@hiveai/core";
3405
3923
  function registerMemoryQuery(memory2) {
3406
- memory2.command("query <text>").description("Search memories by id, tag, or substring (AND, OR fallback)").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) => {
3407
- const root = findProjectRoot20(opts.dir);
3408
- const paths = resolveHaivePaths17(root);
3409
- if (!existsSync21(paths.memoriesDir)) {
3924
+ 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) => {
3925
+ const root = findProjectRoot21(opts.dir);
3926
+ const paths = resolveHaivePaths18(root);
3927
+ if (!existsSync23(paths.memoriesDir)) {
3410
3928
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
3411
3929
  process.exitCode = 1;
3412
3930
  return;
@@ -3447,7 +3965,7 @@ function registerMemoryQuery(memory2) {
3447
3965
  const fm = mem.frontmatter;
3448
3966
  const statusBadge = ui.statusBadge(fm.status);
3449
3967
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
3450
- console.log(` ${ui.dim(path22.relative(root, filePath))}`);
3968
+ console.log(` ${ui.dim(path24.relative(root, filePath))}`);
3451
3969
  const snippet = extractSnippet(mem.body, snippetNeedle);
3452
3970
  if (snippet) console.log(` ${snippet}`);
3453
3971
  }
@@ -3464,22 +3982,22 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
3464
3982
  }
3465
3983
 
3466
3984
  // src/commands/memory-reject.ts
3467
- import { writeFile as writeFile12 } from "fs/promises";
3468
- import { existsSync as existsSync22 } from "fs";
3985
+ import { writeFile as writeFile13 } from "fs/promises";
3986
+ import { existsSync as existsSync24 } from "fs";
3469
3987
  import "commander";
3470
3988
  import {
3471
- findProjectRoot as findProjectRoot21,
3472
- loadUsageIndex as loadUsageIndex6,
3989
+ findProjectRoot as findProjectRoot22,
3990
+ loadUsageIndex as loadUsageIndex7,
3473
3991
  recordRejection,
3474
- resolveHaivePaths as resolveHaivePaths18,
3992
+ resolveHaivePaths as resolveHaivePaths19,
3475
3993
  saveUsageIndex,
3476
3994
  serializeMemory as serializeMemory9
3477
3995
  } from "@hiveai/core";
3478
3996
  function registerMemoryReject(memory2) {
3479
3997
  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) => {
3480
- const root = findProjectRoot21(opts.dir);
3481
- const paths = resolveHaivePaths18(root);
3482
- if (!existsSync22(paths.memoriesDir)) {
3998
+ const root = findProjectRoot22(opts.dir);
3999
+ const paths = resolveHaivePaths19(root);
4000
+ if (!existsSync24(paths.memoriesDir)) {
3483
4001
  ui.error(`No .ai/memories at ${root}.`);
3484
4002
  process.exitCode = 1;
3485
4003
  return;
@@ -3491,7 +4009,7 @@ function registerMemoryReject(memory2) {
3491
4009
  process.exitCode = 1;
3492
4010
  return;
3493
4011
  }
3494
- await writeFile12(
4012
+ await writeFile13(
3495
4013
  loaded.filePath,
3496
4014
  serializeMemory9({
3497
4015
  frontmatter: {
@@ -3503,7 +4021,7 @@ function registerMemoryReject(memory2) {
3503
4021
  }),
3504
4022
  "utf8"
3505
4023
  );
3506
- const idx = await loadUsageIndex6(paths);
4024
+ const idx = await loadUsageIndex7(paths);
3507
4025
  recordRejection(idx, id, opts.reason ?? null);
3508
4026
  await saveUsageIndex(paths, idx);
3509
4027
  const u = idx.by_id[id];
@@ -3515,22 +4033,22 @@ function registerMemoryReject(memory2) {
3515
4033
  }
3516
4034
 
3517
4035
  // src/commands/memory-rm.ts
3518
- import { existsSync as existsSync23 } from "fs";
4036
+ import { existsSync as existsSync25 } from "fs";
3519
4037
  import { unlink as unlink2 } from "fs/promises";
3520
- import path23 from "path";
4038
+ import path25 from "path";
3521
4039
  import { createInterface } from "readline/promises";
3522
4040
  import "commander";
3523
4041
  import {
3524
- findProjectRoot as findProjectRoot22,
3525
- loadUsageIndex as loadUsageIndex7,
3526
- resolveHaivePaths as resolveHaivePaths19,
4042
+ findProjectRoot as findProjectRoot23,
4043
+ loadUsageIndex as loadUsageIndex8,
4044
+ resolveHaivePaths as resolveHaivePaths20,
3527
4045
  saveUsageIndex as saveUsageIndex2
3528
4046
  } from "@hiveai/core";
3529
4047
  function registerMemoryRm(memory2) {
3530
4048
  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) => {
3531
- const root = findProjectRoot22(opts.dir);
3532
- const paths = resolveHaivePaths19(root);
3533
- if (!existsSync23(paths.memoriesDir)) {
4049
+ const root = findProjectRoot23(opts.dir);
4050
+ const paths = resolveHaivePaths20(root);
4051
+ if (!existsSync25(paths.memoriesDir)) {
3534
4052
  ui.error(`No .ai/memories at ${root}.`);
3535
4053
  process.exitCode = 1;
3536
4054
  return;
@@ -3542,7 +4060,7 @@ function registerMemoryRm(memory2) {
3542
4060
  process.exitCode = 1;
3543
4061
  return;
3544
4062
  }
3545
- const rel = path23.relative(root, found.filePath);
4063
+ const rel = path25.relative(root, found.filePath);
3546
4064
  if (!opts.yes) {
3547
4065
  const rl = createInterface({ input: process.stdin, output: process.stdout });
3548
4066
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -3555,7 +4073,7 @@ function registerMemoryRm(memory2) {
3555
4073
  await unlink2(found.filePath);
3556
4074
  ui.success(`Deleted ${rel}`);
3557
4075
  if (!opts.keepUsage) {
3558
- const idx = await loadUsageIndex7(paths);
4076
+ const idx = await loadUsageIndex8(paths);
3559
4077
  if (idx.by_id[id]) {
3560
4078
  delete idx.by_id[id];
3561
4079
  await saveUsageIndex2(paths, idx);
@@ -3566,22 +4084,22 @@ function registerMemoryRm(memory2) {
3566
4084
  }
3567
4085
 
3568
4086
  // src/commands/memory-show.ts
3569
- import { existsSync as existsSync24 } from "fs";
3570
- import { readFile as readFile9 } from "fs/promises";
3571
- import path24 from "path";
4087
+ import { existsSync as existsSync26 } from "fs";
4088
+ import { readFile as readFile10 } from "fs/promises";
4089
+ import path26 from "path";
3572
4090
  import "commander";
3573
4091
  import {
3574
4092
  deriveConfidence as deriveConfidence2,
3575
- findProjectRoot as findProjectRoot23,
4093
+ findProjectRoot as findProjectRoot24,
3576
4094
  getUsage as getUsage6,
3577
- loadUsageIndex as loadUsageIndex8,
3578
- resolveHaivePaths as resolveHaivePaths20
4095
+ loadUsageIndex as loadUsageIndex9,
4096
+ resolveHaivePaths as resolveHaivePaths21
3579
4097
  } from "@hiveai/core";
3580
4098
  function registerMemoryShow(memory2) {
3581
4099
  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) => {
3582
- const root = findProjectRoot23(opts.dir);
3583
- const paths = resolveHaivePaths20(root);
3584
- if (!existsSync24(paths.memoriesDir)) {
4100
+ const root = findProjectRoot24(opts.dir);
4101
+ const paths = resolveHaivePaths21(root);
4102
+ if (!existsSync26(paths.memoriesDir)) {
3585
4103
  ui.error(`No .ai/memories at ${root}.`);
3586
4104
  process.exitCode = 1;
3587
4105
  return;
@@ -3594,11 +4112,11 @@ function registerMemoryShow(memory2) {
3594
4112
  return;
3595
4113
  }
3596
4114
  if (opts.raw) {
3597
- console.log(await readFile9(found.filePath, "utf8"));
4115
+ console.log(await readFile10(found.filePath, "utf8"));
3598
4116
  return;
3599
4117
  }
3600
4118
  const fm = found.memory.frontmatter;
3601
- const usage = await loadUsageIndex8(paths);
4119
+ const usage = await loadUsageIndex9(paths);
3602
4120
  const u = getUsage6(usage, fm.id);
3603
4121
  const conf = deriveConfidence2(fm, u);
3604
4122
  console.log(ui.bold(fm.id));
@@ -3610,7 +4128,7 @@ function registerMemoryShow(memory2) {
3610
4128
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
3611
4129
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
3612
4130
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
3613
- console.log(`${ui.dim("file:")} ${path24.relative(root, found.filePath)}`);
4131
+ console.log(`${ui.dim("file:")} ${path26.relative(root, found.filePath)}`);
3614
4132
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
3615
4133
  console.log(ui.dim("anchor:"));
3616
4134
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -3625,27 +4143,27 @@ function registerMemoryShow(memory2) {
3625
4143
  }
3626
4144
 
3627
4145
  // src/commands/memory-stats.ts
3628
- import { existsSync as existsSync25 } from "fs";
3629
- import path25 from "path";
4146
+ import { existsSync as existsSync27 } from "fs";
4147
+ import path27 from "path";
3630
4148
  import "commander";
3631
4149
  import {
3632
4150
  deriveConfidence as deriveConfidence3,
3633
- findProjectRoot as findProjectRoot24,
4151
+ findProjectRoot as findProjectRoot25,
3634
4152
  getUsage as getUsage7,
3635
- loadUsageIndex as loadUsageIndex9,
3636
- resolveHaivePaths as resolveHaivePaths21
4153
+ loadUsageIndex as loadUsageIndex10,
4154
+ resolveHaivePaths as resolveHaivePaths22
3637
4155
  } from "@hiveai/core";
3638
4156
  function registerMemoryStats(memory2) {
3639
4157
  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) => {
3640
- const root = findProjectRoot24(opts.dir);
3641
- const paths = resolveHaivePaths21(root);
3642
- if (!existsSync25(paths.memoriesDir)) {
4158
+ const root = findProjectRoot25(opts.dir);
4159
+ const paths = resolveHaivePaths22(root);
4160
+ if (!existsSync27(paths.memoriesDir)) {
3643
4161
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
3644
4162
  process.exitCode = 1;
3645
4163
  return;
3646
4164
  }
3647
4165
  const all = await loadMemoriesFromDir4(paths.memoriesDir);
3648
- const usage = await loadUsageIndex9(paths);
4166
+ const usage = await loadUsageIndex10(paths);
3649
4167
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
3650
4168
  if (target.length === 0) {
3651
4169
  ui.info(opts.id ? `No memory with id "${opts.id}".` : "No memories.");
@@ -3664,19 +4182,19 @@ function registerMemoryStats(memory2) {
3664
4182
  console.log(
3665
4183
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
3666
4184
  );
3667
- console.log(` ${ui.dim(path25.relative(root, filePath))}`);
4185
+ console.log(` ${ui.dim(path27.relative(root, filePath))}`);
3668
4186
  }
3669
4187
  });
3670
4188
  }
3671
4189
 
3672
4190
  // src/commands/memory-verify.ts
3673
- import { writeFile as writeFile13 } from "fs/promises";
3674
- import { existsSync as existsSync26 } from "fs";
3675
- import path26 from "path";
4191
+ import { writeFile as writeFile14 } from "fs/promises";
4192
+ import { existsSync as existsSync28 } from "fs";
4193
+ import path28 from "path";
3676
4194
  import "commander";
3677
4195
  import {
3678
- findProjectRoot as findProjectRoot25,
3679
- resolveHaivePaths as resolveHaivePaths22,
4196
+ findProjectRoot as findProjectRoot26,
4197
+ resolveHaivePaths as resolveHaivePaths23,
3680
4198
  serializeMemory as serializeMemory10,
3681
4199
  verifyAnchor as verifyAnchor2
3682
4200
  } from "@hiveai/core";
@@ -3684,9 +4202,9 @@ function registerMemoryVerify(memory2) {
3684
4202
  memory2.command("verify").description(
3685
4203
  "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"
3686
4204
  ).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) => {
3687
- const root = findProjectRoot25(opts.dir);
3688
- const paths = resolveHaivePaths22(root);
3689
- if (!existsSync26(paths.memoriesDir)) {
4205
+ const root = findProjectRoot26(opts.dir);
4206
+ const paths = resolveHaivePaths23(root);
4207
+ if (!existsSync28(paths.memoriesDir)) {
3690
4208
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
3691
4209
  process.exitCode = 1;
3692
4210
  return;
@@ -3709,7 +4227,7 @@ function registerMemoryVerify(memory2) {
3709
4227
  anchorlessIds.push(mem.frontmatter.id);
3710
4228
  continue;
3711
4229
  }
3712
- const rel = path26.relative(root, filePath);
4230
+ const rel = path28.relative(root, filePath);
3713
4231
  if (result.stale) {
3714
4232
  staleCount++;
3715
4233
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -3724,7 +4242,7 @@ function registerMemoryVerify(memory2) {
3724
4242
  }
3725
4243
  if (opts.update) {
3726
4244
  const next = applyVerification(mem, result);
3727
- await writeFile13(filePath, serializeMemory10(next), "utf8");
4245
+ await writeFile14(filePath, serializeMemory10(next), "utf8");
3728
4246
  updated++;
3729
4247
  }
3730
4248
  }
@@ -3772,30 +4290,30 @@ function applyVerification(mem, result) {
3772
4290
  }
3773
4291
 
3774
4292
  // src/commands/memory-import.ts
3775
- import { readFile as readFile10 } from "fs/promises";
3776
- import { existsSync as existsSync27 } from "fs";
4293
+ import { readFile as readFile11 } from "fs/promises";
4294
+ import { existsSync as existsSync29 } from "fs";
3777
4295
  import "commander";
3778
4296
  import {
3779
- findProjectRoot as findProjectRoot26,
3780
- resolveHaivePaths as resolveHaivePaths23
4297
+ findProjectRoot as findProjectRoot27,
4298
+ resolveHaivePaths as resolveHaivePaths24
3781
4299
  } from "@hiveai/core";
3782
4300
  function registerMemoryImport(memory2) {
3783
4301
  memory2.command("import").description(
3784
4302
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
3785
4303
  ).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) => {
3786
- const root = findProjectRoot26(opts.dir);
3787
- const paths = resolveHaivePaths23(root);
3788
- if (!existsSync27(paths.haiveDir)) {
4304
+ const root = findProjectRoot27(opts.dir);
4305
+ const paths = resolveHaivePaths24(root);
4306
+ if (!existsSync29(paths.haiveDir)) {
3789
4307
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
3790
4308
  process.exitCode = 1;
3791
4309
  return;
3792
4310
  }
3793
- if (!existsSync27(opts.from)) {
4311
+ if (!existsSync29(opts.from)) {
3794
4312
  ui.error(`File not found: ${opts.from}`);
3795
4313
  process.exitCode = 1;
3796
4314
  return;
3797
4315
  }
3798
- const content = await readFile10(opts.from, "utf8");
4316
+ const content = await readFile11(opts.from, "utf8");
3799
4317
  const scope = opts.scope ?? "team";
3800
4318
  ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
3801
4319
  ui.info(`Content length: ${content.length} chars`);
@@ -3823,14 +4341,14 @@ function registerMemoryImport(memory2) {
3823
4341
  }
3824
4342
 
3825
4343
  // src/commands/memory-import-changelog.ts
3826
- import { existsSync as existsSync28 } from "fs";
3827
- import { readFile as readFile11, mkdir as mkdir9, writeFile as writeFile14 } from "fs/promises";
3828
- import path27 from "path";
4344
+ import { existsSync as existsSync30 } from "fs";
4345
+ import { readFile as readFile12, mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
4346
+ import path29 from "path";
3829
4347
  import "commander";
3830
4348
  import {
3831
4349
  buildFrontmatter as buildFrontmatter5,
3832
- findProjectRoot as findProjectRoot27,
3833
- resolveHaivePaths as resolveHaivePaths24,
4350
+ findProjectRoot as findProjectRoot28,
4351
+ resolveHaivePaths as resolveHaivePaths25,
3834
4352
  serializeMemory as serializeMemory11
3835
4353
  } from "@hiveai/core";
3836
4354
  function parseChangelog(content) {
@@ -3894,15 +4412,15 @@ function registerMemoryImportChangelog(memory2) {
3894
4412
  "--versions <csv>",
3895
4413
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
3896
4414
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
3897
- const root = findProjectRoot27(opts.dir);
3898
- const paths = resolveHaivePaths24(root);
3899
- const changelogPath = path27.resolve(root, opts.fromChangelog);
3900
- if (!existsSync28(changelogPath)) {
4415
+ const root = findProjectRoot28(opts.dir);
4416
+ const paths = resolveHaivePaths25(root);
4417
+ const changelogPath = path29.resolve(root, opts.fromChangelog);
4418
+ if (!existsSync30(changelogPath)) {
3901
4419
  ui.error(`CHANGELOG not found: ${changelogPath}`);
3902
4420
  process.exitCode = 1;
3903
4421
  return;
3904
4422
  }
3905
- const content = await readFile11(changelogPath, "utf8");
4423
+ const content = await readFile12(changelogPath, "utf8");
3906
4424
  let entries = parseChangelog(content);
3907
4425
  if (entries.length === 0) {
3908
4426
  ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
@@ -3916,10 +4434,10 @@ function registerMemoryImportChangelog(memory2) {
3916
4434
  entries = entries.filter((e) => requested.includes(e.version));
3917
4435
  }
3918
4436
  }
3919
- const pkgName = opts.package ?? path27.basename(path27.dirname(changelogPath));
4437
+ const pkgName = opts.package ?? path29.basename(path29.dirname(changelogPath));
3920
4438
  const scope = opts.scope ?? "team";
3921
- const teamDir = path27.join(paths.memoriesDir, scope);
3922
- await mkdir9(teamDir, { recursive: true });
4439
+ const teamDir = path29.join(paths.memoriesDir, scope);
4440
+ await mkdir11(teamDir, { recursive: true });
3923
4441
  let saved = 0;
3924
4442
  for (const entry of entries) {
3925
4443
  const lines = [];
@@ -3941,7 +4459,7 @@ function registerMemoryImportChangelog(memory2) {
3941
4459
  lines.push("");
3942
4460
  }
3943
4461
  lines.push(
3944
- `**Source:** \`${path27.relative(root, changelogPath)}\`
4462
+ `**Source:** \`${path29.relative(root, changelogPath)}\`
3945
4463
  **Action:** Update all usages of ${pkgName} if they rely on any of the above.`
3946
4464
  );
3947
4465
  const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
@@ -3956,11 +4474,11 @@ function registerMemoryImportChangelog(memory2) {
3956
4474
  pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
3957
4475
  `v${entry.version}`
3958
4476
  ],
3959
- paths: [path27.relative(root, changelogPath)],
4477
+ paths: [path29.relative(root, changelogPath)],
3960
4478
  topic: `changelog-${pkgName}-${entry.version}`
3961
4479
  });
3962
- await writeFile14(
3963
- path27.join(teamDir, `${fm.id}.md`),
4480
+ await writeFile15(
4481
+ path29.join(teamDir, `${fm.id}.md`),
3964
4482
  serializeMemory11({ frontmatter: fm, body: lines.join("\n") }),
3965
4483
  "utf8"
3966
4484
  );
@@ -3983,17 +4501,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
3983
4501
  }
3984
4502
 
3985
4503
  // src/commands/memory-digest.ts
3986
- import { existsSync as existsSync29 } from "fs";
3987
- import { writeFile as writeFile15 } from "fs/promises";
3988
- import path28 from "path";
4504
+ import { existsSync as existsSync31 } from "fs";
4505
+ import { writeFile as writeFile16 } from "fs/promises";
4506
+ import path30 from "path";
3989
4507
  import "commander";
3990
4508
  import {
3991
4509
  deriveConfidence as deriveConfidence4,
3992
- findProjectRoot as findProjectRoot28,
4510
+ findProjectRoot as findProjectRoot29,
3993
4511
  getUsage as getUsage8,
3994
4512
  loadMemoriesFromDir as loadMemoriesFromDir5,
3995
- loadUsageIndex as loadUsageIndex10,
3996
- resolveHaivePaths as resolveHaivePaths25
4513
+ loadUsageIndex as loadUsageIndex11,
4514
+ resolveHaivePaths as resolveHaivePaths26
3997
4515
  } from "@hiveai/core";
3998
4516
  var CONFIDENCE_EMOJI = {
3999
4517
  unverified: "\u2B1C",
@@ -4006,9 +4524,9 @@ function registerMemoryDigest(program2) {
4006
4524
  program2.command("digest").description(
4007
4525
  "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"
4008
4526
  ).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) => {
4009
- const root = findProjectRoot28(opts.dir);
4010
- const paths = resolveHaivePaths25(root);
4011
- if (!existsSync29(paths.memoriesDir)) {
4527
+ const root = findProjectRoot29(opts.dir);
4528
+ const paths = resolveHaivePaths26(root);
4529
+ if (!existsSync31(paths.memoriesDir)) {
4012
4530
  ui.error("No .ai/memories found. Run `haive init` first.");
4013
4531
  process.exitCode = 1;
4014
4532
  return;
@@ -4017,7 +4535,7 @@ function registerMemoryDigest(program2) {
4017
4535
  const scopeFilter = opts.scope ?? "team";
4018
4536
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
4019
4537
  const all = await loadMemoriesFromDir5(paths.memoriesDir);
4020
- const usage = await loadUsageIndex10(paths);
4538
+ const usage = await loadUsageIndex11(paths);
4021
4539
  const recent = all.filter(({ memory: mem }) => {
4022
4540
  const fm = mem.frontmatter;
4023
4541
  if (fm.type === "session_recap") return false;
@@ -4080,8 +4598,8 @@ function registerMemoryDigest(program2) {
4080
4598
  );
4081
4599
  const digest = lines.join("\n");
4082
4600
  if (opts.out) {
4083
- const outPath = path28.resolve(process.cwd(), opts.out);
4084
- await writeFile15(outPath, digest, "utf8");
4601
+ const outPath = path30.resolve(process.cwd(), opts.out);
4602
+ await writeFile16(outPath, digest, "utf8");
4085
4603
  ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
4086
4604
  } else {
4087
4605
  console.log(digest);
@@ -4090,18 +4608,52 @@ function registerMemoryDigest(program2) {
4090
4608
  }
4091
4609
 
4092
4610
  // src/commands/session-end.ts
4093
- import { writeFile as writeFile16, mkdir as mkdir10 } from "fs/promises";
4094
- import { existsSync as existsSync30 } from "fs";
4095
- import path29 from "path";
4611
+ import { writeFile as writeFile17, mkdir as mkdir12, readFile as readFile13, rm } from "fs/promises";
4612
+ import { existsSync as existsSync32 } from "fs";
4613
+ import path31 from "path";
4096
4614
  import "commander";
4097
4615
  import {
4098
4616
  buildFrontmatter as buildFrontmatter6,
4099
- findProjectRoot as findProjectRoot29,
4617
+ findProjectRoot as findProjectRoot30,
4100
4618
  loadMemoriesFromDir as loadMemoriesFromDir6,
4101
4619
  memoryFilePath as memoryFilePath5,
4102
- resolveHaivePaths as resolveHaivePaths26,
4620
+ resolveHaivePaths as resolveHaivePaths27,
4103
4621
  serializeMemory as serializeMemory12
4104
4622
  } from "@hiveai/core";
4623
+ async function buildAutoRecap(paths) {
4624
+ const obsFile = path31.join(paths.haiveDir, ".cache", "observations.jsonl");
4625
+ if (!existsSync32(obsFile)) return null;
4626
+ const raw = await readFile13(obsFile, "utf8").catch(() => "");
4627
+ if (!raw.trim()) return null;
4628
+ const lines = raw.split("\n").filter(Boolean);
4629
+ const obs = [];
4630
+ for (const line of lines) {
4631
+ try {
4632
+ obs.push(JSON.parse(line));
4633
+ } catch {
4634
+ }
4635
+ }
4636
+ if (obs.length === 0) return null;
4637
+ const toolCounts = /* @__PURE__ */ new Map();
4638
+ const fileCounts = /* @__PURE__ */ new Map();
4639
+ const summaries = [];
4640
+ for (const o of obs) {
4641
+ toolCounts.set(o.tool, (toolCounts.get(o.tool) ?? 0) + 1);
4642
+ for (const f of o.files ?? []) fileCounts.set(f, (fileCounts.get(f) ?? 0) + 1);
4643
+ if (summaries.length < 10) summaries.push(`- ${o.summary}`);
4644
+ }
4645
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5).map(([t, c]) => `${t} \xD7${c}`).join(", ");
4646
+ const topFiles = [...fileCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8);
4647
+ const goal = `Auto-captured session \u2014 ${obs.length} tool calls (${topTools})`;
4648
+ const accomplished = summaries.length ? `Recent activity:
4649
+ ${summaries.join("\n")}` : `Activity captured but no parseable summaries.`;
4650
+ return {
4651
+ goal,
4652
+ accomplished,
4653
+ files: topFiles.map(([f]) => f),
4654
+ rawCount: obs.length
4655
+ };
4656
+ }
4105
4657
  function buildRecapBody(opts) {
4106
4658
  const lines = [];
4107
4659
  lines.push(`## Goal
@@ -4148,24 +4700,53 @@ function registerSessionEnd(session2) {
4148
4700
  --files src/payments/WebhookController.ts,src/payments/WebhookService.ts \\\\
4149
4701
  --next "Add integration tests for webhook signature validation"
4150
4702
  `
4151
- ).requiredOption("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").requiredOption("--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("-d, --dir <dir>", "project root").action(async (opts) => {
4152
- const root = findProjectRoot29(opts.dir);
4153
- const paths = resolveHaivePaths26(root);
4154
- if (!existsSync30(paths.haiveDir)) {
4703
+ ).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) => {
4704
+ const root = findProjectRoot30(opts.dir);
4705
+ const paths = resolveHaivePaths27(root);
4706
+ if (!existsSync32(paths.haiveDir)) {
4707
+ if (opts.auto || opts.quiet) return;
4155
4708
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
4156
4709
  process.exitCode = 1;
4157
4710
  return;
4158
4711
  }
4712
+ let resolvedFiles = opts.files;
4713
+ let goal = opts.goal;
4714
+ let accomplished = opts.accomplished;
4715
+ if (opts.auto) {
4716
+ const synth = await buildAutoRecap(paths);
4717
+ if (!synth) return;
4718
+ goal = goal ?? synth.goal;
4719
+ accomplished = accomplished ?? synth.accomplished;
4720
+ if (!resolvedFiles && synth.files.length) resolvedFiles = synth.files.join(",");
4721
+ }
4722
+ if (!goal || !accomplished) {
4723
+ if (opts.quiet) return;
4724
+ ui.error("session-end requires --goal and --accomplished (or pass --auto with captured observations).");
4725
+ process.exitCode = 1;
4726
+ return;
4727
+ }
4159
4728
  const scope = opts.scope ?? "personal";
4160
- const body = buildRecapBody(opts);
4729
+ const body = buildRecapBody({
4730
+ goal,
4731
+ accomplished,
4732
+ discoveries: opts.discoveries,
4733
+ files: resolvedFiles,
4734
+ next: opts.next
4735
+ });
4161
4736
  const topic = recapTopic(scope, opts.module);
4162
- const filesTouched = parseCsv5(opts.files);
4163
- const missingPaths = filesTouched.filter((p) => !existsSync30(path29.resolve(root, p)));
4164
- if (missingPaths.length > 0) {
4737
+ const filesTouched = parseCsv5(resolvedFiles);
4738
+ const missingPaths = filesTouched.filter((p) => !existsSync32(path31.resolve(root, p)));
4739
+ if (missingPaths.length > 0 && !opts.quiet) {
4165
4740
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
4166
4741
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
4167
4742
  }
4168
- if (existsSync30(paths.memoriesDir)) {
4743
+ const cleanupObservations = async () => {
4744
+ if (!opts.auto) return;
4745
+ const obsFile = path31.join(paths.haiveDir, ".cache", "observations.jsonl");
4746
+ if (existsSync32(obsFile)) await rm(obsFile).catch(() => {
4747
+ });
4748
+ };
4749
+ if (existsSync32(paths.memoriesDir)) {
4169
4750
  const existing = await loadMemoriesFromDir6(paths.memoriesDir);
4170
4751
  const topicMatch = existing.find(
4171
4752
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
@@ -4181,9 +4762,12 @@ function registerSessionEnd(session2) {
4181
4762
  paths: filesTouched.length ? filesTouched : fm.anchor.paths
4182
4763
  }
4183
4764
  };
4184
- await writeFile16(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
4185
- ui.success(`Session recap updated (revision #${revisionCount})`);
4186
- ui.info(`id=${fm.id} file=${path29.relative(root, topicMatch.filePath)}`);
4765
+ await writeFile17(topicMatch.filePath, serializeMemory12({ frontmatter: newFrontmatter, body }), "utf8");
4766
+ await cleanupObservations();
4767
+ if (!opts.quiet) {
4768
+ ui.success(`Session recap updated (revision #${revisionCount})`);
4769
+ ui.info(`id=${fm.id} file=${path31.relative(root, topicMatch.filePath)}`);
4770
+ }
4187
4771
  return;
4188
4772
  }
4189
4773
  }
@@ -4198,11 +4782,14 @@ function registerSessionEnd(session2) {
4198
4782
  status: "validated"
4199
4783
  });
4200
4784
  const file = memoryFilePath5(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
4201
- await mkdir10(path29.dirname(file), { recursive: true });
4202
- await writeFile16(file, serializeMemory12({ frontmatter, body }), "utf8");
4203
- ui.success(`Session recap created`);
4204
- ui.info(`id=${frontmatter.id} scope=${scope} file=${path29.relative(root, file)}`);
4205
- ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
4785
+ await mkdir12(path31.dirname(file), { recursive: true });
4786
+ await writeFile17(file, serializeMemory12({ frontmatter, body }), "utf8");
4787
+ await cleanupObservations();
4788
+ if (!opts.quiet) {
4789
+ ui.success(`Session recap created`);
4790
+ ui.info(`id=${frontmatter.id} scope=${scope} file=${path31.relative(root, file)}`);
4791
+ ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
4792
+ }
4206
4793
  });
4207
4794
  }
4208
4795
  function parseCsv5(value) {
@@ -4211,15 +4798,15 @@ function parseCsv5(value) {
4211
4798
  }
4212
4799
 
4213
4800
  // src/commands/snapshot.ts
4214
- import { existsSync as existsSync31 } from "fs";
4801
+ import { existsSync as existsSync33 } from "fs";
4215
4802
  import { readdir as readdir2 } from "fs/promises";
4216
- import path30 from "path";
4803
+ import path32 from "path";
4217
4804
  import "commander";
4218
4805
  import {
4219
4806
  diffContract,
4220
- findProjectRoot as findProjectRoot30,
4807
+ findProjectRoot as findProjectRoot31,
4221
4808
  loadConfig as loadConfig2,
4222
- resolveHaivePaths as resolveHaivePaths27,
4809
+ resolveHaivePaths as resolveHaivePaths28,
4223
4810
  snapshotContract
4224
4811
  } from "@hiveai/core";
4225
4812
  function registerSnapshot(program2) {
@@ -4244,16 +4831,16 @@ function registerSnapshot(program2) {
4244
4831
  "--format <format>",
4245
4832
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
4246
4833
  ).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) => {
4247
- const root = findProjectRoot30(opts.dir);
4248
- const paths = resolveHaivePaths27(root);
4249
- if (!existsSync31(paths.haiveDir)) {
4834
+ const root = findProjectRoot31(opts.dir);
4835
+ const paths = resolveHaivePaths28(root);
4836
+ if (!existsSync33(paths.haiveDir)) {
4250
4837
  ui.error("No .ai/ found. Run `haive init` first.");
4251
4838
  process.exitCode = 1;
4252
4839
  return;
4253
4840
  }
4254
4841
  if (opts.list) {
4255
- const contractsDir = path30.join(paths.haiveDir, "contracts");
4256
- if (!existsSync31(contractsDir)) {
4842
+ const contractsDir = path32.join(paths.haiveDir, "contracts");
4843
+ if (!existsSync33(contractsDir)) {
4257
4844
  console.log(ui.dim("No contract snapshots found."));
4258
4845
  return;
4259
4846
  }
@@ -4308,7 +4895,7 @@ function registerSnapshot(program2) {
4308
4895
  return;
4309
4896
  }
4310
4897
  const contractPath = opts.contract;
4311
- const name = opts.name ?? path30.basename(contractPath, path30.extname(contractPath));
4898
+ const name = opts.name ?? path32.basename(contractPath, path32.extname(contractPath));
4312
4899
  const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
4313
4900
  const contract = { name, path: contractPath, format };
4314
4901
  try {
@@ -4363,8 +4950,8 @@ async function runDiff(root, haiveDir, contract) {
4363
4950
  }
4364
4951
  }
4365
4952
  function detectFormat(filePath) {
4366
- const ext = path30.extname(filePath).toLowerCase();
4367
- const base = path30.basename(filePath).toLowerCase();
4953
+ const ext = path32.extname(filePath).toLowerCase();
4954
+ const base = path32.basename(filePath).toLowerCase();
4368
4955
  if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
4369
4956
  if (base.includes("openapi") || base.includes("swagger")) return "openapi";
4370
4957
  if (base.includes("schema") || base.includes("graphql")) return "graphql";
@@ -4377,16 +4964,16 @@ function detectFormat(filePath) {
4377
4964
  }
4378
4965
 
4379
4966
  // src/commands/hub.ts
4380
- import { existsSync as existsSync32 } from "fs";
4381
- import { mkdir as mkdir11, readFile as readFile12, writeFile as writeFile17, copyFile } from "fs/promises";
4382
- import path31 from "path";
4967
+ import { existsSync as existsSync34 } from "fs";
4968
+ import { mkdir as mkdir13, readFile as readFile14, writeFile as writeFile18, copyFile } from "fs/promises";
4969
+ import path33 from "path";
4383
4970
  import { spawnSync as spawnSync3 } from "child_process";
4384
4971
  import "commander";
4385
4972
  import {
4386
- findProjectRoot as findProjectRoot31,
4973
+ findProjectRoot as findProjectRoot32,
4387
4974
  loadConfig as loadConfig3,
4388
4975
  loadMemoriesFromDir as loadMemoriesFromDir7,
4389
- resolveHaivePaths as resolveHaivePaths28,
4976
+ resolveHaivePaths as resolveHaivePaths29,
4390
4977
  saveConfig as saveConfig2,
4391
4978
  serializeMemory as serializeMemory13
4392
4979
  } from "@hiveai/core";
@@ -4398,8 +4985,8 @@ function registerHub(program2) {
4398
4985
  hub.command("init <hubPath>").description(
4399
4986
  "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"
4400
4987
  ).action(async (hubPath) => {
4401
- const absPath = path31.resolve(hubPath);
4402
- await mkdir11(absPath, { recursive: true });
4988
+ const absPath = path33.resolve(hubPath);
4989
+ await mkdir13(absPath, { recursive: true });
4403
4990
  const gitCheck = spawnSync3("git", ["rev-parse", "--git-dir"], { cwd: absPath });
4404
4991
  if (gitCheck.status !== 0) {
4405
4992
  const init = spawnSync3("git", ["init"], { cwd: absPath, encoding: "utf8" });
@@ -4409,10 +4996,10 @@ function registerHub(program2) {
4409
4996
  return;
4410
4997
  }
4411
4998
  }
4412
- const sharedDir = path31.join(absPath, ".ai", "memories", "shared");
4413
- await mkdir11(sharedDir, { recursive: true });
4414
- await writeFile17(
4415
- path31.join(absPath, ".ai", "README.md"),
4999
+ const sharedDir = path33.join(absPath, ".ai", "memories", "shared");
5000
+ await mkdir13(sharedDir, { recursive: true });
5001
+ await writeFile18(
5002
+ path33.join(absPath, ".ai", "README.md"),
4416
5003
  `# hAIve Team Knowledge Hub
4417
5004
 
4418
5005
  This repo is a shared knowledge hub for hAIve.
@@ -4433,8 +5020,8 @@ haive hub pull # import into a project
4433
5020
  `,
4434
5021
  "utf8"
4435
5022
  );
4436
- await writeFile17(
4437
- path31.join(absPath, ".gitignore"),
5023
+ await writeFile18(
5024
+ path33.join(absPath, ".gitignore"),
4438
5025
  ".ai/.cache/\n.ai/memories/personal/\n",
4439
5026
  "utf8"
4440
5027
  );
@@ -4449,7 +5036,7 @@ haive hub pull # import into a project
4449
5036
  `
4450
5037
  Next steps:
4451
5038
  1. Add hubPath to your project's .ai/haive.config.json:
4452
- { "hubPath": "${path31.relative(process.cwd(), absPath)}" }
5039
+ { "hubPath": "${path33.relative(process.cwd(), absPath)}" }
4453
5040
  2. Run \`haive hub push\` to publish your shared memories
4454
5041
  3. Share ${absPath} with teammates (git remote, NFS, etc.)
4455
5042
  `
@@ -4468,8 +5055,8 @@ Next steps:
4468
5055
  haive hub push --commit --message "feat: add payment API contract memories"
4469
5056
  `
4470
5057
  ).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) => {
4471
- const root = findProjectRoot31(opts.dir);
4472
- const paths = resolveHaivePaths28(root);
5058
+ const root = findProjectRoot32(opts.dir);
5059
+ const paths = resolveHaivePaths29(root);
4473
5060
  const config = await loadConfig3(paths);
4474
5061
  if (!config.hubPath) {
4475
5062
  ui.error(
@@ -4478,15 +5065,15 @@ Next steps:
4478
5065
  process.exitCode = 1;
4479
5066
  return;
4480
5067
  }
4481
- const hubRoot = path31.resolve(root, config.hubPath);
4482
- if (!existsSync32(hubRoot)) {
5068
+ const hubRoot = path33.resolve(root, config.hubPath);
5069
+ if (!existsSync34(hubRoot)) {
4483
5070
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
4484
5071
  process.exitCode = 1;
4485
5072
  return;
4486
5073
  }
4487
- const projectName = path31.basename(root);
4488
- const destDir = path31.join(hubRoot, ".ai", "memories", "shared", projectName);
4489
- await mkdir11(destDir, { recursive: true });
5074
+ const projectName = path33.basename(root);
5075
+ const destDir = path33.join(hubRoot, ".ai", "memories", "shared", projectName);
5076
+ await mkdir13(destDir, { recursive: true });
4490
5077
  const all = await loadMemoriesFromDir7(paths.memoriesDir);
4491
5078
  const shared = all.filter(
4492
5079
  ({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
@@ -4504,15 +5091,15 @@ Next steps:
4504
5091
  for (const { memory: memory2 } of shared) {
4505
5092
  const fm = memory2.frontmatter;
4506
5093
  const fileName = `${fm.id}.md`;
4507
- const destPath = path31.join(destDir, fileName);
4508
- await writeFile17(destPath, serializeMemory13(memory2), "utf8");
5094
+ const destPath = path33.join(destDir, fileName);
5095
+ await writeFile18(destPath, serializeMemory13(memory2), "utf8");
4509
5096
  pushed++;
4510
5097
  }
4511
5098
  console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
4512
5099
  console.log(ui.dim(` Location: ${destDir}`));
4513
5100
  if (opts.commit) {
4514
5101
  const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
4515
- spawnSync3("git", ["add", path31.join(".ai", "memories", "shared", projectName)], {
5102
+ spawnSync3("git", ["add", path33.join(".ai", "memories", "shared", projectName)], {
4516
5103
  cwd: hubRoot
4517
5104
  });
4518
5105
  const commit = spawnSync3("git", ["commit", "-m", message], {
@@ -4537,8 +5124,8 @@ Next steps:
4537
5124
  hub.command("pull").description(
4538
5125
  "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"
4539
5126
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
4540
- const root = findProjectRoot31(opts.dir);
4541
- const paths = resolveHaivePaths28(root);
5127
+ const root = findProjectRoot32(opts.dir);
5128
+ const paths = resolveHaivePaths29(root);
4542
5129
  const config = await loadConfig3(paths);
4543
5130
  if (!config.hubPath) {
4544
5131
  ui.error(
@@ -4547,13 +5134,13 @@ Next steps:
4547
5134
  process.exitCode = 1;
4548
5135
  return;
4549
5136
  }
4550
- const hubRoot = path31.resolve(root, config.hubPath);
4551
- const hubSharedDir = path31.join(hubRoot, ".ai", "memories", "shared");
4552
- if (!existsSync32(hubSharedDir)) {
5137
+ const hubRoot = path33.resolve(root, config.hubPath);
5138
+ const hubSharedDir = path33.join(hubRoot, ".ai", "memories", "shared");
5139
+ if (!existsSync34(hubSharedDir)) {
4553
5140
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
4554
5141
  return;
4555
5142
  }
4556
- const projectName = path31.basename(root);
5143
+ const projectName = path33.basename(root);
4557
5144
  const { readdir: readdir3 } = await import("fs/promises");
4558
5145
  const projectDirs = (await readdir3(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
4559
5146
  if (projectDirs.length === 0) {
@@ -4563,17 +5150,17 @@ Next steps:
4563
5150
  let totalImported = 0;
4564
5151
  let totalUpdated = 0;
4565
5152
  for (const sourceName of projectDirs) {
4566
- const sourceDir = path31.join(hubSharedDir, sourceName);
4567
- const destDir = path31.join(paths.memoriesDir, "shared", sourceName);
4568
- await mkdir11(destDir, { recursive: true });
5153
+ const sourceDir = path33.join(hubSharedDir, sourceName);
5154
+ const destDir = path33.join(paths.memoriesDir, "shared", sourceName);
5155
+ await mkdir13(destDir, { recursive: true });
4569
5156
  const sourceFiles = (await readdir3(sourceDir)).filter((f) => f.endsWith(".md"));
4570
5157
  const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
4571
5158
  const existingInDest = await loadDir(destDir);
4572
5159
  const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
4573
5160
  for (const file of sourceFiles) {
4574
- const srcPath = path31.join(sourceDir, file);
4575
- const destPath = path31.join(destDir, file);
4576
- const fileContent = await readFile12(srcPath, "utf8");
5161
+ const srcPath = path33.join(sourceDir, file);
5162
+ const destPath = path33.join(destDir, file);
5163
+ const fileContent = await readFile14(srcPath, "utf8");
4577
5164
  const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
4578
5165
  if (!alreadyTagged) {
4579
5166
  await copyFile(srcPath, destPath);
@@ -4596,21 +5183,21 @@ Next steps:
4596
5183
  );
4597
5184
  });
4598
5185
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
4599
- const root = findProjectRoot31(opts.dir);
4600
- const paths = resolveHaivePaths28(root);
5186
+ const root = findProjectRoot32(opts.dir);
5187
+ const paths = resolveHaivePaths29(root);
4601
5188
  const config = await loadConfig3(paths);
4602
5189
  console.log(ui.bold("Hub status"));
4603
5190
  console.log(
4604
5191
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
4605
5192
  );
4606
- const sharedDir = path31.join(paths.memoriesDir, "shared");
4607
- if (existsSync32(sharedDir)) {
5193
+ const sharedDir = path33.join(paths.memoriesDir, "shared");
5194
+ if (existsSync34(sharedDir)) {
4608
5195
  const { readdir: readdir3 } = await import("fs/promises");
4609
5196
  const sources = (await readdir3(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
4610
5197
  console.log(`
4611
5198
  Imported from ${sources.length} source(s):`);
4612
5199
  for (const src of sources) {
4613
- const files = (await readdir3(path31.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
5200
+ const files = (await readdir3(path33.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
4614
5201
  console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
4615
5202
  }
4616
5203
  } else {
@@ -4625,8 +5212,8 @@ Next steps:
4625
5212
  if (outgoing.length > 0) {
4626
5213
  console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
4627
5214
  }
4628
- void readFile12;
4629
- void writeFile17;
5215
+ void readFile14;
5216
+ void writeFile18;
4630
5217
  void saveConfig2;
4631
5218
  });
4632
5219
  }
@@ -4635,16 +5222,21 @@ Next steps:
4635
5222
  import "commander";
4636
5223
  import {
4637
5224
  aggregateUsage,
4638
- findProjectRoot as findProjectRoot32,
5225
+ findProjectRoot as findProjectRoot33,
5226
+ loadUsageIndex as loadUsageIndex12,
4639
5227
  parseSince,
4640
5228
  readUsageEvents,
4641
- resolveHaivePaths as resolveHaivePaths29,
5229
+ resolveHaivePaths as resolveHaivePaths30,
4642
5230
  usageLogSize
4643
5231
  } from "@hiveai/core";
4644
5232
  function registerStats(program2) {
4645
- program2.command("stats").description("Show MCP tool-usage stats over a window (e.g. --since 7d).").option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
4646
- const root = findProjectRoot32(opts.dir);
4647
- const paths = resolveHaivePaths29(root);
5233
+ program2.command("stats").description("Show MCP tool-usage stats over a window (e.g. --since 7d).").option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("--memory-hits", "show top-read memories (which mems are actually being used)", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
5234
+ const root = findProjectRoot33(opts.dir);
5235
+ const paths = resolveHaivePaths30(root);
5236
+ if (opts.memoryHits) {
5237
+ await renderMemoryHits(paths, opts);
5238
+ return;
5239
+ }
4648
5240
  const size = await usageLogSize(paths);
4649
5241
  if (!size.exists) {
4650
5242
  if (opts.json) {
@@ -4689,14 +5281,57 @@ function registerStats(program2) {
4689
5281
  }
4690
5282
  });
4691
5283
  }
5284
+ async function renderMemoryHits(paths, opts) {
5285
+ const index = await loadUsageIndex12(paths);
5286
+ const since = parseSince(opts.since ?? "30d");
5287
+ const sinceMs = since ? new Date(since).getTime() : null;
5288
+ const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
5289
+ if (!sinceMs || !e.last_read_at) return !sinceMs;
5290
+ return new Date(e.last_read_at).getTime() >= sinceMs;
5291
+ }).sort((a, b) => b.read_count - a.read_count);
5292
+ if (opts.json) {
5293
+ console.log(JSON.stringify({
5294
+ window: opts.since ?? "30d",
5295
+ total_mems_with_hits: entries.length,
5296
+ top: entries.slice(0, 50)
5297
+ }, null, 2));
5298
+ return;
5299
+ }
5300
+ const window = opts.since ?? "30d";
5301
+ console.log(ui.bold(`Memory hits (${window})`));
5302
+ if (entries.length === 0) {
5303
+ ui.info(
5304
+ `No memory reads recorded in window. Reads are logged when \`haive briefing\` or \`haive memory query\` surface a memory.`
5305
+ );
5306
+ return;
5307
+ }
5308
+ console.log(
5309
+ ` ${ui.dim("memories with hits:")} ${entries.length} ${ui.dim("total reads:")} ${entries.reduce((a, e) => a + e.read_count, 0)}`
5310
+ );
5311
+ console.log();
5312
+ console.log(ui.bold("Top read memories:"));
5313
+ const maxCount = entries[0].read_count;
5314
+ for (const e of entries.slice(0, 25)) {
5315
+ const bar = "\u2588".repeat(Math.max(1, Math.round(e.read_count / maxCount * 20)));
5316
+ const lastRead = e.last_read_at?.slice(0, 10) ?? "?";
5317
+ console.log(
5318
+ ` ${ui.bold(String(e.read_count).padStart(4))} ${ui.green(bar.padEnd(20))} ${e.id} ${ui.dim(`last: ${lastRead}`)}`
5319
+ );
5320
+ }
5321
+ const dead = Object.keys(index.by_id).length - entries.length;
5322
+ if (dead > 0) {
5323
+ console.log();
5324
+ ui.info(`${dead} memor${dead === 1 ? "y" : "ies"} never read in window \u2014 candidates for cleanup (run \`haive doctor\`).`);
5325
+ }
5326
+ }
4692
5327
 
4693
5328
  // src/commands/bench.ts
4694
5329
  import { performance } from "perf_hooks";
4695
5330
  import "commander";
4696
5331
  import {
4697
5332
  estimateTokens,
4698
- findProjectRoot as findProjectRoot33,
4699
- resolveHaivePaths as resolveHaivePaths30
5333
+ findProjectRoot as findProjectRoot34,
5334
+ resolveHaivePaths as resolveHaivePaths31
4700
5335
  } from "@hiveai/core";
4701
5336
  import {
4702
5337
  antiPatternsCheck,
@@ -4708,8 +5343,8 @@ import {
4708
5343
  } from "@hiveai/mcp";
4709
5344
  function registerBench(program2) {
4710
5345
  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) => {
4711
- const root = findProjectRoot33(opts.dir);
4712
- const paths = resolveHaivePaths30(root);
5346
+ const root = findProjectRoot34(opts.dir);
5347
+ const paths = resolveHaivePaths31(root);
4713
5348
  const ctx = { paths };
4714
5349
  const task = opts.task ?? "audit dependencies for security risks";
4715
5350
  const scenarios = [
@@ -4828,19 +5463,19 @@ function summarize(name, t0, payload, notes) {
4828
5463
  }
4829
5464
 
4830
5465
  // src/commands/memory-suggest.ts
4831
- import { mkdir as mkdir12, writeFile as writeFile18 } from "fs/promises";
4832
- import { existsSync as existsSync33 } from "fs";
4833
- import path32 from "path";
5466
+ import { mkdir as mkdir14, writeFile as writeFile19 } from "fs/promises";
5467
+ import { existsSync as existsSync35 } from "fs";
5468
+ import path34 from "path";
4834
5469
  import "commander";
4835
5470
  import {
4836
5471
  aggregateUsage as aggregateUsage2,
4837
5472
  buildFrontmatter as buildFrontmatter7,
4838
- findProjectRoot as findProjectRoot34,
5473
+ findProjectRoot as findProjectRoot35,
4839
5474
  loadMemoriesFromDir as loadMemoriesFromDir8,
4840
5475
  memoryFilePath as memoryFilePath6,
4841
5476
  parseSince as parseSince2,
4842
5477
  readUsageEvents as readUsageEvents2,
4843
- resolveHaivePaths as resolveHaivePaths31,
5478
+ resolveHaivePaths as resolveHaivePaths32,
4844
5479
  serializeMemory as serializeMemory14
4845
5480
  } from "@hiveai/core";
4846
5481
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -4853,8 +5488,8 @@ function registerMemorySuggest(memory2) {
4853
5488
  memory2.command("suggest").description(
4854
5489
  "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."
4855
5490
  ).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) => {
4856
- const root = findProjectRoot34(opts.dir);
4857
- const paths = resolveHaivePaths31(root);
5491
+ const root = findProjectRoot35(opts.dir);
5492
+ const paths = resolveHaivePaths32(root);
4858
5493
  const events = await readUsageEvents2(paths);
4859
5494
  if (events.length === 0) {
4860
5495
  if (opts.json) {
@@ -4900,7 +5535,7 @@ function registerMemorySuggest(memory2) {
4900
5535
  }
4901
5536
  const created = [];
4902
5537
  const skipped = [];
4903
- const existing = existsSync33(paths.memoriesDir) ? await loadMemoriesFromDir8(paths.memoriesDir) : [];
5538
+ const existing = existsSync35(paths.memoriesDir) ? await loadMemoriesFromDir8(paths.memoriesDir) : [];
4904
5539
  for (const s of top) {
4905
5540
  const slug = slugify(s.query);
4906
5541
  if (!slug) {
@@ -4923,13 +5558,13 @@ function registerMemorySuggest(memory2) {
4923
5558
  fm.status = "draft";
4924
5559
  const body = renderTemplate(s);
4925
5560
  const file = memoryFilePath6(paths, fm.scope, fm.id, fm.module);
4926
- await mkdir12(path32.dirname(file), { recursive: true });
4927
- if (existsSync33(file)) {
4928
- skipped.push({ query: s.query, reason: `file already exists at ${path32.relative(root, file)}` });
5561
+ await mkdir14(path34.dirname(file), { recursive: true });
5562
+ if (existsSync35(file)) {
5563
+ skipped.push({ query: s.query, reason: `file already exists at ${path34.relative(root, file)}` });
4929
5564
  continue;
4930
5565
  }
4931
- await writeFile18(file, serializeMemory14({ frontmatter: fm, body }), "utf8");
4932
- created.push({ id: fm.id, file: path32.relative(root, file), query: s.query });
5566
+ await writeFile19(file, serializeMemory14({ frontmatter: fm, body }), "utf8");
5567
+ created.push({ id: fm.id, file: path34.relative(root, file), query: s.query });
4933
5568
  }
4934
5569
  if (opts.json) {
4935
5570
  console.log(JSON.stringify({ created, skipped }, null, 2));
@@ -4937,10 +5572,10 @@ function registerMemorySuggest(memory2) {
4937
5572
  }
4938
5573
  for (const c of created) {
4939
5574
  ui.success(`Drafted ${c.id} \u2192 ${c.file}`);
4940
- console.log(` ${ui.dim("from query:")} ${truncate(c.query, 60)}`);
5575
+ console.log(` ${ui.dim("from query:")} ${truncate2(c.query, 60)}`);
4941
5576
  }
4942
5577
  for (const s of skipped) {
4943
- ui.warn(`Skipped: ${truncate(s.query, 50)} \u2014 ${s.reason}`);
5578
+ ui.warn(`Skipped: ${truncate2(s.query, 50)} \u2014 ${s.reason}`);
4944
5579
  }
4945
5580
  if (created.length > 0) {
4946
5581
  console.log();
@@ -4964,7 +5599,7 @@ function registerMemorySuggest(memory2) {
4964
5599
  }
4965
5600
  for (const s of suggestions.slice(0, 30)) {
4966
5601
  console.log(
4967
- ` ${ui.bold(`\xD7${s.count}`)} ${ui.dim(`[${s.tools.join(",")}]`)} ${truncate(s.query, 70)}`
5602
+ ` ${ui.bold(`\xD7${s.count}`)} ${ui.dim(`[${s.tools.join(",")}]`)} ${truncate2(s.query, 70)}`
4968
5603
  );
4969
5604
  console.log(` ${ui.dim("\u2192")} ${s.reason}`);
4970
5605
  }
@@ -5010,28 +5645,28 @@ function renderTemplate(s) {
5010
5645
  `- **Why** \u2014 the rationale or root cause`,
5011
5646
  `- **How to apply** \u2014 what an agent should do when this comes up again`,
5012
5647
  ``,
5013
- `Then run \`haive memory promote ${truncate(s.query, 30)}\` to mark it validated.`
5648
+ `Then run \`haive memory promote ${truncate2(s.query, 30)}\` to mark it validated.`
5014
5649
  ].join("\n");
5015
5650
  }
5016
5651
  function slugify(s) {
5017
5652
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
5018
5653
  }
5019
- function truncate(text, max) {
5654
+ function truncate2(text, max) {
5020
5655
  if (text.length <= max) return text;
5021
5656
  return text.slice(0, max - 1) + "\u2026";
5022
5657
  }
5023
5658
 
5024
5659
  // src/commands/memory-archive.ts
5025
- import { existsSync as existsSync34 } from "fs";
5026
- import { writeFile as writeFile19 } from "fs/promises";
5027
- import path33 from "path";
5660
+ import { existsSync as existsSync36 } from "fs";
5661
+ import { writeFile as writeFile20 } from "fs/promises";
5662
+ import path35 from "path";
5028
5663
  import "commander";
5029
5664
  import {
5030
- findProjectRoot as findProjectRoot35,
5665
+ findProjectRoot as findProjectRoot36,
5031
5666
  getUsage as getUsage9,
5032
5667
  loadMemoriesFromDir as loadMemoriesFromDir9,
5033
- loadUsageIndex as loadUsageIndex11,
5034
- resolveHaivePaths as resolveHaivePaths32,
5668
+ loadUsageIndex as loadUsageIndex13,
5669
+ resolveHaivePaths as resolveHaivePaths33,
5035
5670
  serializeMemory as serializeMemory15
5036
5671
  } from "@hiveai/core";
5037
5672
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
@@ -5039,9 +5674,9 @@ function registerMemoryArchive(memory2) {
5039
5674
  memory2.command("archive").description(
5040
5675
  "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."
5041
5676
  ).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) => {
5042
- const root = findProjectRoot35(opts.dir);
5043
- const paths = resolveHaivePaths32(root);
5044
- if (!existsSync34(paths.memoriesDir)) {
5677
+ const root = findProjectRoot36(opts.dir);
5678
+ const paths = resolveHaivePaths33(root);
5679
+ if (!existsSync36(paths.memoriesDir)) {
5045
5680
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
5046
5681
  process.exitCode = 1;
5047
5682
  return;
@@ -5054,7 +5689,7 @@ function registerMemoryArchive(memory2) {
5054
5689
  }
5055
5690
  const cutoff = Date.now() - minDays * MS_PER_DAY;
5056
5691
  const all = await loadMemoriesFromDir9(paths.memoriesDir);
5057
- const usage = await loadUsageIndex11(paths);
5692
+ const usage = await loadUsageIndex13(paths);
5058
5693
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
5059
5694
  const candidates = [];
5060
5695
  for (const { memory: mem, filePath } of all) {
@@ -5062,7 +5697,7 @@ function registerMemoryArchive(memory2) {
5062
5697
  if (typeFilter && fm.type !== typeFilter) continue;
5063
5698
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
5064
5699
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
5065
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync34(path33.join(paths.root, p)));
5700
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync36(path35.join(paths.root, p)));
5066
5701
  const isAnchorless = !hasAnyAnchor;
5067
5702
  if (!isAnchorless && !allPathsGone) continue;
5068
5703
  const u = getUsage9(usage, fm.id);
@@ -5110,7 +5745,7 @@ function registerMemoryArchive(memory2) {
5110
5745
  if (!found) continue;
5111
5746
  const fm = { ...found.memory.frontmatter, status: "deprecated" };
5112
5747
  try {
5113
- await writeFile19(c.filePath, serializeMemory15({ frontmatter: fm, body: found.memory.body }), "utf8");
5748
+ await writeFile20(c.filePath, serializeMemory15({ frontmatter: fm, body: found.memory.body }), "utf8");
5114
5749
  archived++;
5115
5750
  } catch (err) {
5116
5751
  if (!opts.json) {
@@ -5136,30 +5771,30 @@ function parseDays(input) {
5136
5771
  }
5137
5772
 
5138
5773
  // src/commands/doctor.ts
5139
- import { existsSync as existsSync35 } from "fs";
5774
+ import { existsSync as existsSync37 } from "fs";
5140
5775
  import { stat } from "fs/promises";
5141
5776
  import "path";
5142
5777
  import "commander";
5143
5778
  import {
5144
5779
  codeMapPath as codeMapPath2,
5145
- findProjectRoot as findProjectRoot36,
5780
+ findProjectRoot as findProjectRoot37,
5146
5781
  getUsage as getUsage10,
5147
5782
  loadCodeMap as loadCodeMap3,
5148
5783
  loadConfig as loadConfig4,
5149
5784
  loadMemoriesFromDir as loadMemoriesFromDir10,
5150
- loadUsageIndex as loadUsageIndex12,
5785
+ loadUsageIndex as loadUsageIndex14,
5151
5786
  readUsageEvents as readUsageEvents3,
5152
- resolveHaivePaths as resolveHaivePaths33
5787
+ resolveHaivePaths as resolveHaivePaths34
5153
5788
  } from "@hiveai/core";
5154
5789
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
5155
5790
  function registerDoctor(program2) {
5156
5791
  program2.command("doctor").description(
5157
5792
  "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."
5158
5793
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
5159
- const root = findProjectRoot36(opts.dir);
5160
- const paths = resolveHaivePaths33(root);
5794
+ const root = findProjectRoot37(opts.dir);
5795
+ const paths = resolveHaivePaths34(root);
5161
5796
  const findings = [];
5162
- if (!existsSync35(paths.haiveDir)) {
5797
+ if (!existsSync37(paths.haiveDir)) {
5163
5798
  findings.push({
5164
5799
  severity: "error",
5165
5800
  code: "not-initialized",
@@ -5168,7 +5803,7 @@ function registerDoctor(program2) {
5168
5803
  });
5169
5804
  return emit(findings, opts);
5170
5805
  }
5171
- if (!existsSync35(paths.projectContext)) {
5806
+ if (!existsSync37(paths.projectContext)) {
5172
5807
  findings.push({
5173
5808
  severity: "warn",
5174
5809
  code: "no-project-context",
@@ -5176,8 +5811,8 @@ function registerDoctor(program2) {
5176
5811
  fix: "haive init"
5177
5812
  });
5178
5813
  } else {
5179
- const { readFile: readFile13 } = await import("fs/promises");
5180
- const content = await readFile13(paths.projectContext, "utf8");
5814
+ const { readFile: readFile15 } = await import("fs/promises");
5815
+ const content = await readFile15(paths.projectContext, "utf8");
5181
5816
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
5182
5817
  if (isTemplate) {
5183
5818
  findings.push({
@@ -5188,7 +5823,7 @@ function registerDoctor(program2) {
5188
5823
  });
5189
5824
  }
5190
5825
  }
5191
- const memories = existsSync35(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
5826
+ const memories = existsSync37(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
5192
5827
  const now = Date.now();
5193
5828
  if (memories.length === 0) {
5194
5829
  findings.push({
@@ -5197,7 +5832,7 @@ function registerDoctor(program2) {
5197
5832
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
5198
5833
  });
5199
5834
  } else {
5200
- const usage = await loadUsageIndex12(paths);
5835
+ const usage = await loadUsageIndex14(paths);
5201
5836
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
5202
5837
  if (stale.length > 0) {
5203
5838
  findings.push({
@@ -5342,22 +5977,22 @@ function isSearchTool(name) {
5342
5977
  }
5343
5978
 
5344
5979
  // src/commands/playback.ts
5345
- import { existsSync as existsSync36 } from "fs";
5980
+ import { existsSync as existsSync38 } from "fs";
5346
5981
  import "commander";
5347
5982
  import {
5348
- findProjectRoot as findProjectRoot37,
5983
+ findProjectRoot as findProjectRoot38,
5349
5984
  loadMemoriesFromDir as loadMemoriesFromDir11,
5350
5985
  parseSince as parseSince3,
5351
5986
  readUsageEvents as readUsageEvents4,
5352
- resolveHaivePaths as resolveHaivePaths34
5987
+ resolveHaivePaths as resolveHaivePaths35
5353
5988
  } from "@hiveai/core";
5354
5989
  var MS_PER_MINUTE = 6e4;
5355
5990
  function registerPlayback(program2) {
5356
5991
  program2.command("playback").description(
5357
5992
  "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?'"
5358
5993
  ).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) => {
5359
- const root = findProjectRoot37(opts.dir);
5360
- const paths = resolveHaivePaths34(root);
5994
+ const root = findProjectRoot38(opts.dir);
5995
+ const paths = resolveHaivePaths35(root);
5361
5996
  const events = await readUsageEvents4(paths);
5362
5997
  if (events.length === 0) {
5363
5998
  if (opts.json) {
@@ -5372,7 +6007,7 @@ function registerPlayback(program2) {
5372
6007
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
5373
6008
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
5374
6009
  const sessions = bucketSessions(filtered, gapMs);
5375
- const all = existsSync36(paths.memoriesDir) ? await loadMemoriesFromDir11(paths.memoriesDir) : [];
6010
+ const all = existsSync38(paths.memoriesDir) ? await loadMemoriesFromDir11(paths.memoriesDir) : [];
5376
6011
  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);
5377
6012
  const enriched = sessions.map((s, i) => {
5378
6013
  const startMs = Date.parse(s.start);
@@ -5412,7 +6047,7 @@ function registerPlayback(program2) {
5412
6047
  if (s.briefing_tasks.length > 0) {
5413
6048
  console.log(` ${ui.dim("briefings asked:")}`);
5414
6049
  for (const t of s.briefing_tasks) {
5415
- console.log(` \u2022 ${truncate2(t, 80)}`);
6050
+ console.log(` \u2022 ${truncate3(t, 80)}`);
5416
6051
  }
5417
6052
  }
5418
6053
  if (s.memories_created_since > 0) {
@@ -5453,7 +6088,7 @@ function countTools(events) {
5453
6088
  for (const e of events) out[e.tool] = (out[e.tool] ?? 0) + 1;
5454
6089
  return out;
5455
6090
  }
5456
- function truncate2(text, max) {
6091
+ function truncate3(text, max) {
5457
6092
  if (text.length <= max) return text;
5458
6093
  return text.slice(0, max - 1) + "\u2026";
5459
6094
  }
@@ -5462,8 +6097,8 @@ function truncate2(text, max) {
5462
6097
  import { spawn as spawn3 } from "child_process";
5463
6098
  import "commander";
5464
6099
  import {
5465
- findProjectRoot as findProjectRoot38,
5466
- resolveHaivePaths as resolveHaivePaths35
6100
+ findProjectRoot as findProjectRoot39,
6101
+ resolveHaivePaths as resolveHaivePaths36
5467
6102
  } from "@hiveai/core";
5468
6103
  import { preCommitCheck } from "@hiveai/mcp";
5469
6104
  function registerPrecommit(program2) {
@@ -5474,8 +6109,8 @@ function registerPrecommit(program2) {
5474
6109
  "'any' | 'high-confidence' (default) | 'never' (report only)",
5475
6110
  "high-confidence"
5476
6111
  ).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) => {
5477
- const root = findProjectRoot38(opts.dir);
5478
- const paths = resolveHaivePaths35(root);
6112
+ const root = findProjectRoot39(opts.dir);
6113
+ const paths = resolveHaivePaths36(root);
5479
6114
  const ctx = { paths };
5480
6115
  let diff = "";
5481
6116
  let touchedPaths = opts.paths ?? [];
@@ -5567,8 +6202,8 @@ function runCommand(cmd, args, cwd) {
5567
6202
  }
5568
6203
 
5569
6204
  // src/index.ts
5570
- var program = new Command39();
5571
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.7.2");
6205
+ var program = new Command40();
6206
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.8.0");
5572
6207
  registerInit(program);
5573
6208
  registerMcp(program);
5574
6209
  registerBriefing(program);
@@ -5576,6 +6211,7 @@ registerTui(program);
5576
6211
  registerEmbeddings(program);
5577
6212
  registerSync(program);
5578
6213
  registerInstallHooks(program);
6214
+ registerObserve(program);
5579
6215
  registerIndexCode(program);
5580
6216
  var memory = program.command("memory").description("Manage memory entries");
5581
6217
  registerMemoryAdd(memory);