@hiveai/cli 0.2.2 → 0.2.4

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,13 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command23 } from "commander";
4
+ import { Command as Command24 } from "commander";
5
5
 
6
- // src/commands/embeddings.ts
6
+ // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
8
- import path from "path";
8
+ import { readFile } from "fs/promises";
9
9
  import "commander";
10
- import { findProjectRoot, resolveHaivePaths } from "@hiveai/core";
10
+ import {
11
+ findProjectRoot,
12
+ literalMatchesAllTokens,
13
+ loadMemoriesFromDir,
14
+ memoryMatchesAnchorPaths,
15
+ resolveHaivePaths,
16
+ tokenizeQuery
17
+ } from "@hiveai/core";
11
18
 
12
19
  // src/utils/ui.ts
13
20
  import pc from "picocolors";
@@ -17,16 +24,115 @@ var ui = {
17
24
  warn: (msg) => console.log(pc.yellow("\u26A0"), msg),
18
25
  error: (msg) => console.error(pc.red("\u2717"), msg),
19
26
  dim: (msg) => pc.dim(msg),
20
- bold: (msg) => pc.bold(msg)
27
+ bold: (msg) => pc.bold(msg),
28
+ green: (msg) => pc.green(msg),
29
+ yellow: (msg) => pc.yellow(msg),
30
+ red: (msg) => pc.red(msg),
31
+ statusBadge: (status) => {
32
+ switch (status) {
33
+ case "validated":
34
+ return pc.green(status);
35
+ case "proposed":
36
+ return pc.yellow(status);
37
+ case "stale":
38
+ return pc.yellow(status);
39
+ case "rejected":
40
+ return pc.red(status);
41
+ case "deprecated":
42
+ return pc.dim(status);
43
+ default:
44
+ return pc.dim(status);
45
+ }
46
+ }
21
47
  };
22
48
 
49
+ // src/commands/briefing.ts
50
+ function registerBriefing(program2) {
51
+ program2.command("briefing").description(
52
+ "Print project context + relevant memories in one shot \u2014 ideal for agent onboarding"
53
+ ).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
54
+ "--scope <scope>",
55
+ "personal | team | module | all (default: team)",
56
+ "team"
57
+ ).option("--include-draft", "include draft memories (excluded by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
58
+ const root = findProjectRoot(opts.dir);
59
+ const paths = resolveHaivePaths(root);
60
+ if (existsSync(paths.projectContext)) {
61
+ const ctx = await readFile(paths.projectContext, "utf8");
62
+ console.log(`${ui.bold("=== Project Context ===")}
63
+ `);
64
+ console.log(ctx.trim());
65
+ console.log();
66
+ } else {
67
+ ui.warn(
68
+ "No project-context.md found. Run `haive init` and the `bootstrap_project` MCP prompt to set it up."
69
+ );
70
+ }
71
+ if (!existsSync(paths.memoriesDir)) return;
72
+ const all = await loadMemoriesFromDir(paths.memoriesDir);
73
+ const filePaths = parseCsv(opts.files);
74
+ const tokens = opts.task ? tokenizeQuery(opts.task) : null;
75
+ const maxMemories = Math.max(1, Number(opts.maxMemories ?? 10));
76
+ const scopeFilter = opts.scope ?? "team";
77
+ const candidates = all.filter(({ memory: mem }) => {
78
+ const fm = mem.frontmatter;
79
+ if (fm.status === "rejected" || fm.status === "deprecated") return false;
80
+ if (!opts.includeDraft && fm.status === "draft") return false;
81
+ if (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
82
+ return true;
83
+ });
84
+ const scored = candidates.map(({ memory: mem, filePath }) => {
85
+ const fm = mem.frontmatter;
86
+ let score = 0;
87
+ if (fm.status === "validated") score += 3;
88
+ else if (fm.status === "proposed") score += 1;
89
+ if (filePaths.length > 0 && memoryMatchesAnchorPaths(mem, filePaths)) score += 4;
90
+ if (tokens && literalMatchesAllTokens(mem, tokens)) score += 3;
91
+ return { memory: mem, filePath, score };
92
+ });
93
+ scored.sort((a, b) => b.score - a.score);
94
+ const top = scored.slice(0, maxMemories);
95
+ if (top.length === 0) {
96
+ ui.info("No relevant memories found.");
97
+ const draftCount = all.filter(
98
+ (m) => m.memory.frontmatter.status === "draft" && (scopeFilter === "all" || m.memory.frontmatter.scope === scopeFilter)
99
+ ).length;
100
+ if (draftCount > 0) {
101
+ ui.info(`(${draftCount} draft memories excluded \u2014 use --include-draft to show)`);
102
+ }
103
+ return;
104
+ }
105
+ console.log(`${ui.bold("=== Relevant Memories ===")}
106
+ `);
107
+ for (const { memory: mem } of top) {
108
+ const fm = mem.frontmatter;
109
+ const badge = ui.statusBadge(fm.status);
110
+ const draftMarker = fm.status === "draft" ? ui.yellow(" [DRAFT]") : "";
111
+ console.log(
112
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}`
113
+ );
114
+ console.log(mem.body.trim());
115
+ console.log();
116
+ }
117
+ console.log(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
118
+ });
119
+ }
120
+ function parseCsv(value) {
121
+ if (!value) return [];
122
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
123
+ }
124
+
23
125
  // src/commands/embeddings.ts
126
+ import { existsSync as existsSync2 } from "fs";
127
+ import path from "path";
128
+ import "commander";
129
+ import { findProjectRoot as findProjectRoot2, resolveHaivePaths as resolveHaivePaths2 } from "@hiveai/core";
24
130
  function registerEmbeddings(program2) {
25
131
  const embeddings = program2.command("embeddings").description("Manage local embeddings index for semantic search");
26
132
  embeddings.command("index").description("Generate or refresh the embeddings index for all memories").option("-d, --dir <dir>", "project root").action(async (opts) => {
27
- const root = findProjectRoot(opts.dir);
28
- const paths = resolveHaivePaths(root);
29
- if (!existsSync(paths.memoriesDir)) {
133
+ const root = findProjectRoot2(opts.dir);
134
+ const paths = resolveHaivePaths2(root);
135
+ if (!existsSync2(paths.memoriesDir)) {
30
136
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
31
137
  process.exitCode = 1;
32
138
  return;
@@ -41,8 +147,8 @@ function registerEmbeddings(program2) {
41
147
  );
42
148
  });
43
149
  embeddings.command("query <text>").description("Run a semantic search against the local embeddings index").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "10").option("--min-score <n>", "minimum cosine similarity (0-1)", "0").action(async (text, opts) => {
44
- const root = findProjectRoot(opts.dir);
45
- const paths = resolveHaivePaths(root);
150
+ const root = findProjectRoot2(opts.dir);
151
+ const paths = resolveHaivePaths2(root);
46
152
  const { semanticSearch } = await loadEmbeddings();
47
153
  const result = await semanticSearch(paths, text, {
48
154
  limit: Number(opts.limit ?? 10),
@@ -64,8 +170,8 @@ function registerEmbeddings(program2) {
64
170
  }
65
171
  });
66
172
  embeddings.command("status").description("Show the embeddings index status").option("-d, --dir <dir>", "project root").action(async (opts) => {
67
- const root = findProjectRoot(opts.dir);
68
- const paths = resolveHaivePaths(root);
173
+ const root = findProjectRoot2(opts.dir);
174
+ const paths = resolveHaivePaths2(root);
69
175
  const { indexStat } = await loadEmbeddings();
70
176
  const stat = await indexStat(paths);
71
177
  if (!stat.exists) {
@@ -95,8 +201,8 @@ import "commander";
95
201
  import {
96
202
  buildCodeMap,
97
203
  codeMapPath,
98
- findProjectRoot as findProjectRoot2,
99
- resolveHaivePaths as resolveHaivePaths2,
204
+ findProjectRoot as findProjectRoot3,
205
+ resolveHaivePaths as resolveHaivePaths3,
100
206
  saveCodeMap
101
207
  } from "@hiveai/core";
102
208
  function registerIndexCode(program2) {
@@ -107,8 +213,8 @@ function registerIndexCode(program2) {
107
213
  "extra directory names to skip (comma-separated)",
108
214
  ""
109
215
  ).action(async (opts) => {
110
- const root = findProjectRoot2(opts.dir);
111
- const paths = resolveHaivePaths2(root);
216
+ const root = findProjectRoot3(opts.dir);
217
+ const paths = resolveHaivePaths3(root);
112
218
  const extraExcludes = (opts.exclude ?? "").split(",").map((s) => s.trim()).filter(Boolean);
113
219
  ui.info(`Indexing source files in ${root}\u2026`);
114
220
  const map = await buildCodeMap(root, {
@@ -136,10 +242,10 @@ function registerIndexCode(program2) {
136
242
 
137
243
  // src/commands/init.ts
138
244
  import { mkdir, writeFile } from "fs/promises";
139
- import { existsSync as existsSync2 } from "fs";
245
+ import { existsSync as existsSync3 } from "fs";
140
246
  import path3 from "path";
141
247
  import "commander";
142
- import { resolveHaivePaths as resolveHaivePaths3 } from "@hiveai/core";
248
+ import { resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
143
249
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
144
250
 
145
251
  > Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
@@ -168,15 +274,15 @@ Memories live under \`.ai/memories/\` (personal/team/module).
168
274
  function registerInit(program2) {
169
275
  program2.command("init").description("Initialize a hAIve project (.ai/ structure + bridge files)").option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate CLAUDE.md / .cursorrules / copilot-instructions.md").action(async (opts) => {
170
276
  const root = path3.resolve(opts.dir);
171
- const paths = resolveHaivePaths3(root);
172
- if (existsSync2(paths.haiveDir)) {
277
+ const paths = resolveHaivePaths4(root);
278
+ if (existsSync3(paths.haiveDir)) {
173
279
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
174
280
  }
175
281
  await mkdir(paths.personalDir, { recursive: true });
176
282
  await mkdir(paths.teamDir, { recursive: true });
177
283
  await mkdir(paths.moduleDir, { recursive: true });
178
284
  await mkdir(paths.modulesContextDir, { recursive: true });
179
- if (!existsSync2(paths.projectContext)) {
285
+ if (!existsSync3(paths.projectContext)) {
180
286
  await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
181
287
  ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
182
288
  }
@@ -191,7 +297,7 @@ function registerInit(program2) {
191
297
  }
192
298
  async function writeBridge(root, relPath) {
193
299
  const target = path3.join(root, relPath);
194
- if (existsSync2(target)) {
300
+ if (existsSync3(target)) {
195
301
  ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
196
302
  return;
197
303
  }
@@ -201,11 +307,11 @@ async function writeBridge(root, relPath) {
201
307
  }
202
308
 
203
309
  // src/commands/install-hooks.ts
204
- import { mkdir as mkdir2, writeFile as writeFile2, chmod, readFile } from "fs/promises";
205
- import { existsSync as existsSync3 } from "fs";
310
+ import { mkdir as mkdir2, writeFile as writeFile2, chmod, readFile as readFile2 } from "fs/promises";
311
+ import { existsSync as existsSync4 } from "fs";
206
312
  import path4 from "path";
207
313
  import "commander";
208
- import { findProjectRoot as findProjectRoot4 } from "@hiveai/core";
314
+ import { findProjectRoot as findProjectRoot5 } from "@hiveai/core";
209
315
  var HOOK_MARKER = "# hAIve auto-generated";
210
316
  var HOOK_BODY = `#!/bin/sh
211
317
  ${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
@@ -221,9 +327,9 @@ fi
221
327
  var HOOKS = ["post-merge", "post-rewrite"];
222
328
  function registerInstallHooks(program2) {
223
329
  program2.command("install-hooks").description("Install git hooks that run `haive sync` after pull/merge").option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
224
- const root = findProjectRoot4(opts.dir);
330
+ const root = findProjectRoot5(opts.dir);
225
331
  const gitDir = path4.join(root, ".git");
226
- if (!existsSync3(gitDir)) {
332
+ if (!existsSync4(gitDir)) {
227
333
  ui.error(`No .git directory at ${root}.`);
228
334
  process.exitCode = 1;
229
335
  return;
@@ -234,8 +340,8 @@ function registerInstallHooks(program2) {
234
340
  let skipped = 0;
235
341
  for (const name of HOOKS) {
236
342
  const file = path4.join(hooksDir, name);
237
- if (existsSync3(file) && !opts.force) {
238
- const existing = await readFile(file, "utf8");
343
+ if (existsSync4(file) && !opts.force) {
344
+ const existing = await readFile2(file, "utf8");
239
345
  if (!existing.includes(HOOK_MARKER)) {
240
346
  ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
241
347
  skipped++;
@@ -253,16 +359,16 @@ function registerInstallHooks(program2) {
253
359
 
254
360
  // src/commands/mcp.ts
255
361
  import { spawn } from "child_process";
256
- import { existsSync as existsSync4 } from "fs";
362
+ import { existsSync as existsSync5 } from "fs";
257
363
  import { createRequire } from "module";
258
364
  import path5 from "path";
259
365
  import { fileURLToPath } from "url";
260
366
  import "commander";
261
- import { findProjectRoot as findProjectRoot5 } from "@hiveai/core";
367
+ import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
262
368
  var require2 = createRequire(import.meta.url);
263
369
  function registerMcp(program2) {
264
370
  program2.command("mcp").description("Start the hAIve MCP server (stdio transport)").option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
265
- const root = findProjectRoot5(opts.dir);
371
+ const root = findProjectRoot6(opts.dir);
266
372
  const bin = locateMcpBin();
267
373
  if (!bin) {
268
374
  ui.error(
@@ -282,29 +388,29 @@ function locateMcpBin() {
282
388
  const pkgPath = require2.resolve("@hiveai/mcp/package.json");
283
389
  const pkgDir = path5.dirname(pkgPath);
284
390
  const candidate = path5.join(pkgDir, "dist", "index.js");
285
- if (existsSync4(candidate)) return candidate;
391
+ if (existsSync5(candidate)) return candidate;
286
392
  } catch {
287
393
  }
288
394
  const here = path5.dirname(fileURLToPath(import.meta.url));
289
395
  const sibling = path5.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
290
- if (existsSync4(sibling)) return sibling;
396
+ if (existsSync5(sibling)) return sibling;
291
397
  return null;
292
398
  }
293
399
 
294
400
  // src/commands/sync.ts
295
401
  import { spawnSync } from "child_process";
296
402
  import { writeFile as writeFile3 } from "fs/promises";
297
- import { existsSync as existsSync5 } from "fs";
403
+ import { existsSync as existsSync6 } from "fs";
298
404
  import "path";
299
405
  import "commander";
300
406
  import {
301
407
  DEFAULT_AUTO_PROMOTE_RULE,
302
- findProjectRoot as findProjectRoot6,
408
+ findProjectRoot as findProjectRoot7,
303
409
  getUsage,
304
410
  isAutoPromoteEligible,
305
- loadMemoriesFromDir,
411
+ loadMemoriesFromDir as loadMemoriesFromDir2,
306
412
  loadUsageIndex,
307
- resolveHaivePaths as resolveHaivePaths4,
413
+ resolveHaivePaths as resolveHaivePaths5,
308
414
  serializeMemory,
309
415
  verifyAnchor
310
416
  } from "@hiveai/core";
@@ -313,9 +419,9 @@ function registerSync(program2) {
313
419
  "--since <ref>",
314
420
  "git ref/commit to compare against; report memories added/modified/removed since"
315
421
  ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").action(async (opts) => {
316
- const root = findProjectRoot6(opts.dir);
317
- const paths = resolveHaivePaths4(root);
318
- if (!existsSync5(paths.memoriesDir)) {
422
+ const root = findProjectRoot7(opts.dir);
423
+ const paths = resolveHaivePaths5(root);
424
+ if (!existsSync6(paths.memoriesDir)) {
319
425
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
320
426
  process.exitCode = 1;
321
427
  return;
@@ -327,7 +433,7 @@ function registerSync(program2) {
327
433
  let revalidated = 0;
328
434
  let promoted = 0;
329
435
  if (opts.verify !== false) {
330
- const memories = await loadMemoriesFromDir(paths.memoriesDir);
436
+ const memories = await loadMemoriesFromDir2(paths.memoriesDir);
331
437
  for (const { memory: memory2, filePath } of memories) {
332
438
  const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
333
439
  if (!isAnchored) continue;
@@ -369,7 +475,7 @@ function registerSync(program2) {
369
475
  }
370
476
  }
371
477
  if (opts.promote !== false) {
372
- const memories = await loadMemoriesFromDir(paths.memoriesDir);
478
+ const memories = await loadMemoriesFromDir2(paths.memoriesDir);
373
479
  const usage = await loadUsageIndex(paths);
374
480
  for (const { memory: memory2, filePath } of memories) {
375
481
  if (isAutoPromoteEligible(
@@ -390,9 +496,20 @@ function registerSync(program2) {
390
496
  }
391
497
  }
392
498
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
499
+ const draftMemories = (await loadMemoriesFromDir2(paths.memoriesDir)).filter(
500
+ (m) => m.memory.frontmatter.status === "draft"
501
+ );
502
+ const draftCount = draftMemories.length;
393
503
  log(
394
504
  `${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
395
505
  );
506
+ if (!opts.quiet && draftCount > 0) {
507
+ log(
508
+ ui.dim(
509
+ `\u2139 ${draftCount} memor${draftCount === 1 ? "y" : "ies"} in draft \u2014 run \`haive memory approve <id>\` to activate or \`haive memory list --status draft\` to review`
510
+ )
511
+ );
512
+ }
396
513
  if (sinceReport && !opts.quiet) {
397
514
  if (sinceReport.added.length > 0) {
398
515
  log(ui.bold("\nNew memories:"));
@@ -430,28 +547,28 @@ function collectSinceChanges(root, ref) {
430
547
 
431
548
  // src/commands/memory-add.ts
432
549
  import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
433
- import { existsSync as existsSync6 } from "fs";
550
+ import { existsSync as existsSync7 } from "fs";
434
551
  import path7 from "path";
435
552
  import "commander";
436
553
  import {
437
554
  buildFrontmatter,
438
- findProjectRoot as findProjectRoot7,
555
+ findProjectRoot as findProjectRoot8,
439
556
  inferModulesFromPaths,
440
557
  memoryFilePath,
441
- resolveHaivePaths as resolveHaivePaths5,
558
+ resolveHaivePaths as resolveHaivePaths6,
442
559
  serializeMemory as serializeMemory2
443
560
  } from "@hiveai/core";
444
561
  function registerMemoryAdd(memory2) {
445
- memory2.command("add").description("Add a new memory (defaults to personal scope \u2014 v0.1 approach B)").requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary").requiredOption("--slug <slug>", "short identifier used in the file name").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor paths, comma-separated").option("--symbols <csv>", "anchor symbols, comma-separated").option("--commit <sha>", "anchor commit SHA").option("--body <text>", "memory body content (Markdown)").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("-d, --dir <dir>", "project root").action(async (opts) => {
446
- const root = findProjectRoot7(opts.dir);
447
- const paths = resolveHaivePaths5(root);
448
- if (!existsSync6(paths.haiveDir)) {
562
+ memory2.command("add").description("Add a new memory (defaults to personal scope)").requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary").requiredOption("--slug <slug>", "short 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", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor paths, comma-separated").option("--symbols <csv>", "anchor symbols, comma-separated").option("--commit <sha>", "anchor commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("-d, --dir <dir>", "project root").action(async (opts) => {
563
+ const root = findProjectRoot8(opts.dir);
564
+ const paths = resolveHaivePaths6(root);
565
+ if (!existsSync7(paths.haiveDir)) {
449
566
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
450
567
  process.exitCode = 1;
451
568
  return;
452
569
  }
453
- const userTags = parseCsv(opts.tags);
454
- const anchorPaths = parseCsv(opts.paths);
570
+ const userTags = parseCsv2(opts.tags);
571
+ const anchorPaths = parseCsv2(opts.paths);
455
572
  const autoTagsEnabled = opts.autoTag !== false;
456
573
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
457
574
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
@@ -464,73 +581,125 @@ function registerMemoryAdd(memory2) {
464
581
  domain: opts.domain,
465
582
  author: opts.author,
466
583
  paths: anchorPaths,
467
- symbols: parseCsv(opts.symbols),
584
+ symbols: parseCsv2(opts.symbols),
468
585
  commit: opts.commit
469
586
  });
470
- const body = opts.body ?? `# ${opts.slug}
587
+ const title = opts.title ?? opts.slug;
588
+ let body;
589
+ if (opts.body !== void 0) {
590
+ body = opts.title ? `# ${opts.title}
591
+
592
+ ${opts.body}` : opts.body;
593
+ } else {
594
+ body = `# ${title}
471
595
 
472
596
  TODO \u2014 write the memory body.
473
597
  `;
598
+ }
474
599
  const file = memoryFilePath(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
475
600
  await mkdir3(path7.dirname(file), { recursive: true });
476
- if (existsSync6(file)) {
601
+ if (existsSync7(file)) {
477
602
  ui.error(`Memory already exists at ${file}`);
478
603
  process.exitCode = 1;
479
604
  return;
480
605
  }
481
606
  await writeFile4(file, serializeMemory2({ frontmatter, body }), "utf8");
482
607
  ui.success(`Created ${path7.relative(root, file)}`);
483
- ui.info(`scope=${frontmatter.scope} status=${frontmatter.status} id=${frontmatter.id}`);
608
+ ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
484
609
  if (inferredTags.length > 0) {
485
610
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
486
611
  }
612
+ if (frontmatter.scope === "personal") {
613
+ console.log(
614
+ ui.dim(
615
+ `\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
616
+ )
617
+ );
618
+ } else {
619
+ console.log(
620
+ ui.dim(`\u2192 next: haive memory approve ${frontmatter.id} (mark as validated)`)
621
+ );
622
+ }
487
623
  });
488
624
  }
489
- function parseCsv(value) {
625
+ function parseCsv2(value) {
490
626
  if (!value) return [];
491
627
  return value.split(",").map((s) => s.trim()).filter(Boolean);
492
628
  }
493
629
 
494
630
  // src/commands/memory-list.ts
495
- import { existsSync as existsSync7 } from "fs";
631
+ import { existsSync as existsSync8 } from "fs";
496
632
  import path8 from "path";
497
633
  import "commander";
498
- import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
634
+ import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
499
635
 
500
636
  // src/utils/fs.ts
501
637
  import {
502
- loadMemoriesFromDir as loadMemoriesFromDir2,
638
+ loadMemoriesFromDir as loadMemoriesFromDir3,
503
639
  loadMemory,
504
640
  listMarkdownFilesRecursive
505
641
  } from "@hiveai/core";
506
642
 
507
643
  // src/commands/memory-list.ts
508
644
  function registerMemoryList(memory2) {
509
- 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("-d, --dir <dir>", "project root").action(async (opts) => {
510
- const root = findProjectRoot8(opts.dir);
511
- const paths = resolveHaivePaths6(root);
512
- if (!existsSync7(paths.memoriesDir)) {
645
+ 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) => {
646
+ const root = findProjectRoot9(opts.dir);
647
+ const paths = resolveHaivePaths7(root);
648
+ if (!existsSync8(paths.memoriesDir)) {
513
649
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
514
650
  process.exitCode = 1;
515
651
  return;
516
652
  }
517
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
518
- const filtered = all.filter((m) => matchesFilters(m, opts));
653
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
654
+ const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
655
+ const filtered = all.filter((m) => {
656
+ if (!matchesFilters(m, opts)) return false;
657
+ const status = m.memory.frontmatter.status;
658
+ if (!opts.showRejected && !statusFilter && status === "rejected") return false;
659
+ if (statusFilter && !statusFilter.includes(status)) return false;
660
+ return true;
661
+ });
662
+ const hiddenRejectedCount = !opts.showRejected && !statusFilter ? all.filter(
663
+ (m) => matchesFilters(m, opts) && m.memory.frontmatter.status === "rejected"
664
+ ).length : 0;
519
665
  if (filtered.length === 0) {
520
666
  ui.info("No memories match the filters.");
667
+ if (hiddenRejectedCount > 0) {
668
+ ui.info(`(${hiddenRejectedCount} rejected hidden \u2014 use --show-rejected to include)`);
669
+ }
521
670
  return;
522
671
  }
523
672
  for (const { memory: mem, filePath } of filtered) {
524
673
  const fm = mem.frontmatter;
525
674
  const tagStr = fm.tags.length ? ui.dim(` [${fm.tags.join(", ")}]`) : "";
526
675
  const moduleStr = fm.module ? ui.dim(` (${fm.module})`) : "";
676
+ const statusBadge = ui.statusBadge(fm.status);
527
677
  console.log(
528
- `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)}${moduleStr}${tagStr}`
678
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
529
679
  );
530
680
  console.log(` ${ui.dim(path8.relative(root, filePath))}`);
531
681
  }
532
682
  console.log(ui.dim(`
533
683
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
684
+ if (hiddenRejectedCount > 0) {
685
+ console.log(
686
+ ui.dim(`(${hiddenRejectedCount} rejected hidden \u2014 use --show-rejected to include)`)
687
+ );
688
+ }
689
+ const draftItems = filtered.filter((m) => m.memory.frontmatter.status === "draft");
690
+ if (draftItems.length > 0) {
691
+ const hasPersonalDrafts = draftItems.some(
692
+ (m) => m.memory.frontmatter.scope === "personal"
693
+ );
694
+ const hasTeamDrafts = draftItems.some(
695
+ (m) => m.memory.frontmatter.scope !== "personal"
696
+ );
697
+ let hint = `\u2139 ${draftItems.length} in draft \u2014 use \`haive memory approve <id>\` to activate`;
698
+ if (hasPersonalDrafts && !hasTeamDrafts) {
699
+ hint += " or `haive memory promote <id>` to share with team";
700
+ }
701
+ console.log(ui.dim(hint));
702
+ }
534
703
  });
535
704
  }
536
705
  function matchesFilters(loaded, opts) {
@@ -544,25 +713,39 @@ function matchesFilters(loaded, opts) {
544
713
 
545
714
  // src/commands/memory-promote.ts
546
715
  import { mkdir as mkdir4, unlink, writeFile as writeFile5 } from "fs/promises";
547
- import { existsSync as existsSync8 } from "fs";
716
+ import { existsSync as existsSync9 } from "fs";
548
717
  import path9 from "path";
549
718
  import "commander";
550
719
  import {
551
- findProjectRoot as findProjectRoot9,
720
+ findProjectRoot as findProjectRoot10,
552
721
  memoryFilePath as memoryFilePath2,
553
- resolveHaivePaths as resolveHaivePaths7,
722
+ resolveHaivePaths as resolveHaivePaths8,
554
723
  serializeMemory as serializeMemory3
555
724
  } from "@hiveai/core";
556
725
  function registerMemoryPromote(memory2) {
557
726
  memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
558
- const root = findProjectRoot9(opts.dir);
559
- const paths = resolveHaivePaths7(root);
560
- if (!existsSync8(paths.memoriesDir)) {
727
+ const root = findProjectRoot10(opts.dir);
728
+ const paths = resolveHaivePaths8(root);
729
+ if (!existsSync9(paths.memoriesDir)) {
561
730
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
562
731
  process.exitCode = 1;
563
732
  return;
564
733
  }
565
- const all = await loadMemoriesFromDir2(paths.personalDir);
734
+ const teamAndModule = await loadMemoriesFromDir3(paths.memoriesDir);
735
+ const alreadyShared = teamAndModule.find(
736
+ (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
737
+ );
738
+ if (alreadyShared) {
739
+ const fm = alreadyShared.memory.frontmatter;
740
+ ui.warn(
741
+ `"${id}" is already in ${fm.scope} scope (status=${fm.status}).`
742
+ );
743
+ if (fm.status !== "validated") {
744
+ ui.info(`\u2192 run \`haive memory approve ${id}\` to validate it`);
745
+ }
746
+ return;
747
+ }
748
+ const all = await loadMemoriesFromDir3(paths.personalDir);
566
749
  const found = all.find((m) => m.memory.frontmatter.id === id);
567
750
  if (!found) {
568
751
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -583,29 +766,57 @@ function registerMemoryPromote(memory2) {
583
766
  await unlink(found.filePath);
584
767
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
585
768
  ui.info(`Now at ${path9.relative(root, newPath)}`);
769
+ console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
586
770
  });
587
771
  }
588
772
 
589
773
  // src/commands/memory-approve.ts
590
- import { existsSync as existsSync9 } from "fs";
774
+ import { existsSync as existsSync10 } from "fs";
591
775
  import { writeFile as writeFile6 } from "fs/promises";
592
776
  import path10 from "path";
593
777
  import "commander";
594
778
  import {
595
- findProjectRoot as findProjectRoot10,
596
- resolveHaivePaths as resolveHaivePaths8,
779
+ findProjectRoot as findProjectRoot11,
780
+ resolveHaivePaths as resolveHaivePaths9,
597
781
  serializeMemory as serializeMemory4
598
782
  } from "@hiveai/core";
599
783
  function registerMemoryApprove(memory2) {
600
- memory2.command("approve <id>").description("Mark a 'proposed' memory as 'validated' immediately (explicit review)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
601
- const root = findProjectRoot10(opts.dir);
602
- const paths = resolveHaivePaths8(root);
603
- if (!existsSync9(paths.memoriesDir)) {
784
+ 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) => {
785
+ const root = findProjectRoot11(opts.dir);
786
+ const paths = resolveHaivePaths9(root);
787
+ if (!existsSync10(paths.memoriesDir)) {
604
788
  ui.error(`No .ai/memories at ${root}.`);
605
789
  process.exitCode = 1;
606
790
  return;
607
791
  }
608
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
792
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
793
+ if (opts.all || opts.pending) {
794
+ const candidates = all.filter((m) => {
795
+ const s = m.memory.frontmatter.status;
796
+ if (opts.all) return s === "proposed" || s === "draft";
797
+ return s === "proposed";
798
+ });
799
+ if (candidates.length === 0) {
800
+ ui.info(opts.all ? "No draft or proposed memories to approve." : "No proposed memories to approve.");
801
+ return;
802
+ }
803
+ let count = 0;
804
+ for (const found2 of candidates) {
805
+ const next2 = {
806
+ frontmatter: { ...found2.memory.frontmatter, status: "validated" },
807
+ body: found2.memory.body
808
+ };
809
+ await writeFile6(found2.filePath, serializeMemory4(next2), "utf8");
810
+ count++;
811
+ }
812
+ ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
813
+ return;
814
+ }
815
+ if (!id) {
816
+ ui.error("Provide a memory id or use --all / --pending for bulk approval.");
817
+ process.exitCode = 1;
818
+ return;
819
+ }
609
820
  const found = all.find((m) => m.memory.frontmatter.id === id);
610
821
  if (!found) {
611
822
  ui.error(`No memory with id "${id}".`);
@@ -632,24 +843,24 @@ function registerMemoryApprove(memory2) {
632
843
 
633
844
  // src/commands/memory-update.ts
634
845
  import { writeFile as writeFile7 } from "fs/promises";
635
- import { existsSync as existsSync10 } from "fs";
846
+ import { existsSync as existsSync11 } from "fs";
636
847
  import path11 from "path";
637
848
  import "commander";
638
849
  import {
639
- findProjectRoot as findProjectRoot11,
640
- resolveHaivePaths as resolveHaivePaths9,
850
+ findProjectRoot as findProjectRoot12,
851
+ resolveHaivePaths as resolveHaivePaths10,
641
852
  serializeMemory as serializeMemory5
642
853
  } from "@hiveai/core";
643
854
  function registerMemoryUpdate(memory2) {
644
- memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").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) => {
645
- const root = findProjectRoot11(opts.dir);
646
- const paths = resolveHaivePaths9(root);
647
- if (!existsSync10(paths.memoriesDir)) {
855
+ 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) => {
856
+ const root = findProjectRoot12(opts.dir);
857
+ const paths = resolveHaivePaths10(root);
858
+ if (!existsSync11(paths.memoriesDir)) {
648
859
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
649
860
  process.exitCode = 1;
650
861
  return;
651
862
  }
652
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
863
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
653
864
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
654
865
  if (!loaded) {
655
866
  ui.error(`No memory with id "${id}".`);
@@ -660,11 +871,11 @@ function registerMemoryUpdate(memory2) {
660
871
  const { frontmatter, body } = loaded.memory;
661
872
  const newAnchor = { ...frontmatter.anchor };
662
873
  if (opts.paths !== void 0) {
663
- newAnchor.paths = parseCsv2(opts.paths);
874
+ newAnchor.paths = parseCsv3(opts.paths);
664
875
  updated.push("anchor.paths");
665
876
  }
666
877
  if (opts.symbols !== void 0) {
667
- newAnchor.symbols = parseCsv2(opts.symbols);
878
+ newAnchor.symbols = parseCsv3(opts.symbols);
668
879
  updated.push("anchor.symbols");
669
880
  }
670
881
  if (opts.commit !== void 0) {
@@ -674,14 +885,18 @@ function registerMemoryUpdate(memory2) {
674
885
  const newFrontmatter = {
675
886
  ...frontmatter,
676
887
  anchor: newAnchor,
677
- ...opts.tags !== void 0 ? { tags: parseCsv2(opts.tags) } : {},
888
+ ...opts.tags !== void 0 ? { tags: parseCsv3(opts.tags) } : {},
678
889
  ...opts.domain !== void 0 ? { domain: opts.domain } : {},
679
890
  ...opts.author !== void 0 ? { author: opts.author } : {}
680
891
  };
681
892
  if (opts.tags !== void 0) updated.push("tags");
682
893
  if (opts.domain !== void 0) updated.push("domain");
683
894
  if (opts.author !== void 0) updated.push("author");
684
- const newBody = opts.body !== void 0 ? opts.body : body;
895
+ let newBody = opts.body !== void 0 ? opts.body : body;
896
+ if (opts.title !== void 0) {
897
+ newBody = replaceFirstHeading(newBody, opts.title);
898
+ updated.push("title");
899
+ }
685
900
  if (opts.body !== void 0) updated.push("body");
686
901
  if (updated.length === 0) {
687
902
  ui.warn("Nothing to update \u2014 provide at least one option.");
@@ -696,22 +911,32 @@ function registerMemoryUpdate(memory2) {
696
911
  ui.info(`fields: ${updated.join(", ")}`);
697
912
  });
698
913
  }
699
- function parseCsv2(value) {
914
+ function replaceFirstHeading(body, title) {
915
+ const headingRe = /^#\s+.+$/m;
916
+ const replacement = `# ${title}`;
917
+ if (headingRe.test(body)) {
918
+ return body.replace(headingRe, replacement);
919
+ }
920
+ return `${replacement}
921
+
922
+ ${body}`;
923
+ }
924
+ function parseCsv3(value) {
700
925
  return value.split(",").map((s) => s.trim()).filter(Boolean);
701
926
  }
702
927
 
703
928
  // src/commands/memory-auto-promote.ts
704
929
  import { writeFile as writeFile8 } from "fs/promises";
705
- import { existsSync as existsSync11 } from "fs";
930
+ import { existsSync as existsSync12 } from "fs";
706
931
  import path12 from "path";
707
932
  import "commander";
708
933
  import {
709
934
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
710
- findProjectRoot as findProjectRoot12,
935
+ findProjectRoot as findProjectRoot13,
711
936
  getUsage as getUsage2,
712
937
  isAutoPromoteEligible as isAutoPromoteEligible2,
713
938
  loadUsageIndex as loadUsageIndex2,
714
- resolveHaivePaths as resolveHaivePaths10,
939
+ resolveHaivePaths as resolveHaivePaths11,
715
940
  serializeMemory as serializeMemory6
716
941
  } from "@hiveai/core";
717
942
  function registerMemoryAutoPromote(memory2) {
@@ -720,9 +945,9 @@ function registerMemoryAutoPromote(memory2) {
720
945
  "memories with more rejections than this are skipped",
721
946
  String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
722
947
  ).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
723
- const root = findProjectRoot12(opts.dir);
724
- const paths = resolveHaivePaths10(root);
725
- if (!existsSync11(paths.memoriesDir)) {
948
+ const root = findProjectRoot13(opts.dir);
949
+ const paths = resolveHaivePaths11(root);
950
+ if (!existsSync12(paths.memoriesDir)) {
726
951
  ui.error(`No .ai/memories at ${root}.`);
727
952
  process.exitCode = 1;
728
953
  return;
@@ -731,7 +956,7 @@ function registerMemoryAutoPromote(memory2) {
731
956
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads),
732
957
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
733
958
  };
734
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
959
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
735
960
  const usage = await loadUsageIndex2(paths);
736
961
  const eligible = memories.filter(
737
962
  ({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
@@ -765,25 +990,25 @@ function registerMemoryAutoPromote(memory2) {
765
990
 
766
991
  // src/commands/memory-edit.ts
767
992
  import { spawn as spawn2 } from "child_process";
768
- import { existsSync as existsSync12 } from "fs";
769
- import { readFile as readFile2 } from "fs/promises";
993
+ import { existsSync as existsSync13 } from "fs";
994
+ import { readFile as readFile3 } from "fs/promises";
770
995
  import path13 from "path";
771
996
  import "commander";
772
997
  import {
773
- findProjectRoot as findProjectRoot13,
998
+ findProjectRoot as findProjectRoot14,
774
999
  parseMemory,
775
- resolveHaivePaths as resolveHaivePaths11
1000
+ resolveHaivePaths as resolveHaivePaths12
776
1001
  } from "@hiveai/core";
777
1002
  function registerMemoryEdit(memory2) {
778
1003
  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) => {
779
- const root = findProjectRoot13(opts.dir);
780
- const paths = resolveHaivePaths11(root);
781
- if (!existsSync12(paths.memoriesDir)) {
1004
+ const root = findProjectRoot14(opts.dir);
1005
+ const paths = resolveHaivePaths12(root);
1006
+ if (!existsSync13(paths.memoriesDir)) {
782
1007
  ui.error(`No .ai/memories at ${root}.`);
783
1008
  process.exitCode = 1;
784
1009
  return;
785
1010
  }
786
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1011
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
787
1012
  const found = all.find((m) => m.memory.frontmatter.id === id);
788
1013
  if (!found) {
789
1014
  ui.error(`No memory with id "${id}".`);
@@ -797,7 +1022,7 @@ function registerMemoryEdit(memory2) {
797
1022
  ui.warn(`Editor exited with status ${code}.`);
798
1023
  }
799
1024
  try {
800
- const fresh = await readFile2(found.filePath, "utf8");
1025
+ const fresh = await readFile3(found.filePath, "utf8");
801
1026
  parseMemory(fresh);
802
1027
  ui.success("Memory still parses cleanly.");
803
1028
  } catch (err) {
@@ -818,28 +1043,28 @@ function runEditor(editor, file) {
818
1043
  }
819
1044
 
820
1045
  // src/commands/memory-for-files.ts
821
- import { existsSync as existsSync13 } from "fs";
1046
+ import { existsSync as existsSync14 } from "fs";
822
1047
  import path14 from "path";
823
1048
  import "commander";
824
1049
  import {
825
1050
  deriveConfidence,
826
- findProjectRoot as findProjectRoot14,
1051
+ findProjectRoot as findProjectRoot15,
827
1052
  getUsage as getUsage3,
828
1053
  inferModulesFromPaths as inferModulesFromPaths2,
829
1054
  loadUsageIndex as loadUsageIndex3,
830
- memoryMatchesAnchorPaths,
831
- resolveHaivePaths as resolveHaivePaths12
1055
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
1056
+ resolveHaivePaths as resolveHaivePaths13
832
1057
  } from "@hiveai/core";
833
1058
  function registerMemoryForFiles(memory2) {
834
1059
  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) => {
835
- const root = findProjectRoot14(opts.dir);
836
- const paths = resolveHaivePaths12(root);
837
- if (!existsSync13(paths.memoriesDir)) {
1060
+ const root = findProjectRoot15(opts.dir);
1061
+ const paths = resolveHaivePaths13(root);
1062
+ if (!existsSync14(paths.memoriesDir)) {
838
1063
  ui.error(`No .ai/memories at ${root}.`);
839
1064
  process.exitCode = 1;
840
1065
  return;
841
1066
  }
842
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1067
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
843
1068
  const usage = await loadUsageIndex3(paths);
844
1069
  const inferred = inferModulesFromPaths2(files);
845
1070
  const byAnchor = [];
@@ -847,7 +1072,7 @@ function registerMemoryForFiles(memory2) {
847
1072
  const byDomain = [];
848
1073
  const seen = /* @__PURE__ */ new Set();
849
1074
  for (const loaded of all) {
850
- if (memoryMatchesAnchorPaths(loaded.memory, files)) {
1075
+ if (memoryMatchesAnchorPaths2(loaded.memory, files)) {
851
1076
  byAnchor.push(loaded);
852
1077
  seen.add(loaded.memory.frontmatter.id);
853
1078
  }
@@ -892,26 +1117,26 @@ function printGroup(root, label, loaded, usage) {
892
1117
  }
893
1118
 
894
1119
  // src/commands/memory-hot.ts
895
- import { existsSync as existsSync14 } from "fs";
1120
+ import { existsSync as existsSync15 } from "fs";
896
1121
  import path15 from "path";
897
1122
  import "commander";
898
1123
  import {
899
- findProjectRoot as findProjectRoot15,
1124
+ findProjectRoot as findProjectRoot16,
900
1125
  getUsage as getUsage4,
901
1126
  loadUsageIndex as loadUsageIndex4,
902
- resolveHaivePaths as resolveHaivePaths13
1127
+ resolveHaivePaths as resolveHaivePaths14
903
1128
  } from "@hiveai/core";
904
1129
  function registerMemoryHot(memory2) {
905
1130
  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) => {
906
- const root = findProjectRoot15(opts.dir);
907
- const paths = resolveHaivePaths13(root);
908
- if (!existsSync14(paths.memoriesDir)) {
1131
+ const root = findProjectRoot16(opts.dir);
1132
+ const paths = resolveHaivePaths14(root);
1133
+ if (!existsSync15(paths.memoriesDir)) {
909
1134
  ui.error(`No .ai/memories at ${root}.`);
910
1135
  process.exitCode = 1;
911
1136
  return;
912
1137
  }
913
1138
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
914
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1139
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
915
1140
  const usage = await loadUsageIndex4(paths);
916
1141
  const candidates = all.filter(({ memory: mem }) => {
917
1142
  const fm = mem.frontmatter;
@@ -942,25 +1167,25 @@ function registerMemoryHot(memory2) {
942
1167
  }
943
1168
 
944
1169
  // src/commands/memory-pending.ts
945
- import { existsSync as existsSync15 } from "fs";
1170
+ import { existsSync as existsSync16 } from "fs";
946
1171
  import path16 from "path";
947
1172
  import "commander";
948
1173
  import {
949
- findProjectRoot as findProjectRoot16,
1174
+ findProjectRoot as findProjectRoot17,
950
1175
  getUsage as getUsage5,
951
1176
  loadUsageIndex as loadUsageIndex5,
952
- resolveHaivePaths as resolveHaivePaths14
1177
+ resolveHaivePaths as resolveHaivePaths15
953
1178
  } from "@hiveai/core";
954
1179
  function registerMemoryPending(memory2) {
955
1180
  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) => {
956
- const root = findProjectRoot16(opts.dir);
957
- const paths = resolveHaivePaths14(root);
958
- if (!existsSync15(paths.memoriesDir)) {
1181
+ const root = findProjectRoot17(opts.dir);
1182
+ const paths = resolveHaivePaths15(root);
1183
+ if (!existsSync16(paths.memoriesDir)) {
959
1184
  ui.error(`No .ai/memories at ${root}.`);
960
1185
  process.exitCode = 1;
961
1186
  return;
962
1187
  }
963
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1188
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
964
1189
  const usage = await loadUsageIndex5(paths);
965
1190
  const proposed = all.filter(({ memory: mem }) => {
966
1191
  if (mem.frontmatter.status !== "proposed") return false;
@@ -990,29 +1215,36 @@ function registerMemoryPending(memory2) {
990
1215
  }
991
1216
 
992
1217
  // src/commands/memory-query.ts
993
- import { existsSync as existsSync16 } from "fs";
1218
+ import { existsSync as existsSync17 } from "fs";
994
1219
  import path17 from "path";
995
1220
  import "commander";
996
1221
  import {
997
1222
  extractSnippet,
998
- findProjectRoot as findProjectRoot17,
999
- literalMatchesAllTokens,
1223
+ findProjectRoot as findProjectRoot18,
1224
+ literalMatchesAllTokens as literalMatchesAllTokens2,
1000
1225
  pickSnippetNeedle,
1001
- resolveHaivePaths as resolveHaivePaths15,
1002
- tokenizeQuery
1226
+ resolveHaivePaths as resolveHaivePaths16,
1227
+ tokenizeQuery as tokenizeQuery2
1003
1228
  } from "@hiveai/core";
1004
1229
  function registerMemoryQuery(memory2) {
1005
- memory2.command("query <text>").description("Search memories by id, tag, or substring (multi-word AND)").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").action(async (text, opts) => {
1006
- const root = findProjectRoot17(opts.dir);
1007
- const paths = resolveHaivePaths15(root);
1008
- if (!existsSync16(paths.memoriesDir)) {
1230
+ memory2.command("query <text>").description("Search memories by id, tag, or substring (multi-word AND)").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) => {
1231
+ const root = findProjectRoot18(opts.dir);
1232
+ const paths = resolveHaivePaths16(root);
1233
+ if (!existsSync17(paths.memoriesDir)) {
1009
1234
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
1010
1235
  process.exitCode = 1;
1011
1236
  return;
1012
1237
  }
1013
- const tokens = tokenizeQuery(text);
1014
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1015
- const matches = all.filter(({ memory: mem }) => literalMatchesAllTokens(mem, tokens));
1238
+ const tokens = tokenizeQuery2(text);
1239
+ const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
1240
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1241
+ const matches = all.filter(({ memory: mem }) => {
1242
+ const fm = mem.frontmatter;
1243
+ if (opts.scope && fm.scope !== opts.scope) return false;
1244
+ if (!opts.showRejected && !statusFilter && fm.status === "rejected") return false;
1245
+ if (statusFilter && !statusFilter.includes(fm.status)) return false;
1246
+ return literalMatchesAllTokens2(mem, tokens);
1247
+ });
1016
1248
  const limit = Math.max(1, Number(opts.limit ?? 20));
1017
1249
  const top = matches.slice(0, limit);
1018
1250
  if (top.length === 0) {
@@ -1021,9 +1253,11 @@ function registerMemoryQuery(memory2) {
1021
1253
  }
1022
1254
  const snippetNeedle = pickSnippetNeedle(text);
1023
1255
  for (const { memory: mem, filePath } of top) {
1024
- const snippet = extractSnippet(mem.body, snippetNeedle);
1025
- console.log(`${ui.bold(mem.frontmatter.id)} ${ui.dim(mem.frontmatter.scope)}`);
1256
+ const fm = mem.frontmatter;
1257
+ const statusBadge = ui.statusBadge(fm.status);
1258
+ console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
1026
1259
  console.log(` ${ui.dim(path17.relative(root, filePath))}`);
1260
+ const snippet = extractSnippet(mem.body, snippetNeedle);
1027
1261
  if (snippet) console.log(` ${snippet}`);
1028
1262
  }
1029
1263
  console.log(
@@ -1035,26 +1269,26 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
1035
1269
 
1036
1270
  // src/commands/memory-reject.ts
1037
1271
  import { writeFile as writeFile9 } from "fs/promises";
1038
- import { existsSync as existsSync17 } from "fs";
1272
+ import { existsSync as existsSync18 } from "fs";
1039
1273
  import "commander";
1040
1274
  import {
1041
- findProjectRoot as findProjectRoot18,
1275
+ findProjectRoot as findProjectRoot19,
1042
1276
  loadUsageIndex as loadUsageIndex6,
1043
1277
  recordRejection,
1044
- resolveHaivePaths as resolveHaivePaths16,
1278
+ resolveHaivePaths as resolveHaivePaths17,
1045
1279
  saveUsageIndex,
1046
1280
  serializeMemory as serializeMemory7
1047
1281
  } from "@hiveai/core";
1048
1282
  function registerMemoryReject(memory2) {
1049
1283
  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) => {
1050
- const root = findProjectRoot18(opts.dir);
1051
- const paths = resolveHaivePaths16(root);
1052
- if (!existsSync17(paths.memoriesDir)) {
1284
+ const root = findProjectRoot19(opts.dir);
1285
+ const paths = resolveHaivePaths17(root);
1286
+ if (!existsSync18(paths.memoriesDir)) {
1053
1287
  ui.error(`No .ai/memories at ${root}.`);
1054
1288
  process.exitCode = 1;
1055
1289
  return;
1056
1290
  }
1057
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
1291
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1058
1292
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
1059
1293
  if (!loaded) {
1060
1294
  ui.error(`No memory with id "${id}".`);
@@ -1064,7 +1298,11 @@ function registerMemoryReject(memory2) {
1064
1298
  await writeFile9(
1065
1299
  loaded.filePath,
1066
1300
  serializeMemory7({
1067
- frontmatter: { ...loaded.memory.frontmatter, status: "rejected" },
1301
+ frontmatter: {
1302
+ ...loaded.memory.frontmatter,
1303
+ status: "rejected",
1304
+ stale_reason: opts.reason ?? loaded.memory.frontmatter.stale_reason ?? null
1305
+ },
1068
1306
  body: loaded.memory.body
1069
1307
  }),
1070
1308
  "utf8"
@@ -1081,27 +1319,27 @@ function registerMemoryReject(memory2) {
1081
1319
  }
1082
1320
 
1083
1321
  // src/commands/memory-rm.ts
1084
- import { existsSync as existsSync18 } from "fs";
1322
+ import { existsSync as existsSync19 } from "fs";
1085
1323
  import { unlink as unlink2 } from "fs/promises";
1086
1324
  import path18 from "path";
1087
1325
  import { createInterface } from "readline/promises";
1088
1326
  import "commander";
1089
1327
  import {
1090
- findProjectRoot as findProjectRoot19,
1328
+ findProjectRoot as findProjectRoot20,
1091
1329
  loadUsageIndex as loadUsageIndex7,
1092
- resolveHaivePaths as resolveHaivePaths17,
1330
+ resolveHaivePaths as resolveHaivePaths18,
1093
1331
  saveUsageIndex as saveUsageIndex2
1094
1332
  } from "@hiveai/core";
1095
1333
  function registerMemoryRm(memory2) {
1096
1334
  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) => {
1097
- const root = findProjectRoot19(opts.dir);
1098
- const paths = resolveHaivePaths17(root);
1099
- if (!existsSync18(paths.memoriesDir)) {
1335
+ const root = findProjectRoot20(opts.dir);
1336
+ const paths = resolveHaivePaths18(root);
1337
+ if (!existsSync19(paths.memoriesDir)) {
1100
1338
  ui.error(`No .ai/memories at ${root}.`);
1101
1339
  process.exitCode = 1;
1102
1340
  return;
1103
1341
  }
1104
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1342
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1105
1343
  const found = all.find((m) => m.memory.frontmatter.id === id);
1106
1344
  if (!found) {
1107
1345
  ui.error(`No memory with id "${id}".`);
@@ -1132,27 +1370,27 @@ function registerMemoryRm(memory2) {
1132
1370
  }
1133
1371
 
1134
1372
  // src/commands/memory-show.ts
1135
- import { existsSync as existsSync19 } from "fs";
1136
- import { readFile as readFile3 } from "fs/promises";
1373
+ import { existsSync as existsSync20 } from "fs";
1374
+ import { readFile as readFile4 } from "fs/promises";
1137
1375
  import path19 from "path";
1138
1376
  import "commander";
1139
1377
  import {
1140
1378
  deriveConfidence as deriveConfidence2,
1141
- findProjectRoot as findProjectRoot20,
1379
+ findProjectRoot as findProjectRoot21,
1142
1380
  getUsage as getUsage6,
1143
1381
  loadUsageIndex as loadUsageIndex8,
1144
- resolveHaivePaths as resolveHaivePaths18
1382
+ resolveHaivePaths as resolveHaivePaths19
1145
1383
  } from "@hiveai/core";
1146
1384
  function registerMemoryShow(memory2) {
1147
1385
  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) => {
1148
- const root = findProjectRoot20(opts.dir);
1149
- const paths = resolveHaivePaths18(root);
1150
- if (!existsSync19(paths.memoriesDir)) {
1386
+ const root = findProjectRoot21(opts.dir);
1387
+ const paths = resolveHaivePaths19(root);
1388
+ if (!existsSync20(paths.memoriesDir)) {
1151
1389
  ui.error(`No .ai/memories at ${root}.`);
1152
1390
  process.exitCode = 1;
1153
1391
  return;
1154
1392
  }
1155
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1393
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1156
1394
  const found = all.find((m) => m.memory.frontmatter.id === id);
1157
1395
  if (!found) {
1158
1396
  ui.error(`No memory with id "${id}".`);
@@ -1160,7 +1398,7 @@ function registerMemoryShow(memory2) {
1160
1398
  return;
1161
1399
  }
1162
1400
  if (opts.raw) {
1163
- console.log(await readFile3(found.filePath, "utf8"));
1401
+ console.log(await readFile4(found.filePath, "utf8"));
1164
1402
  return;
1165
1403
  }
1166
1404
  const fm = found.memory.frontmatter;
@@ -1191,26 +1429,26 @@ function registerMemoryShow(memory2) {
1191
1429
  }
1192
1430
 
1193
1431
  // src/commands/memory-stats.ts
1194
- import { existsSync as existsSync20 } from "fs";
1432
+ import { existsSync as existsSync21 } from "fs";
1195
1433
  import path20 from "path";
1196
1434
  import "commander";
1197
1435
  import {
1198
1436
  deriveConfidence as deriveConfidence3,
1199
- findProjectRoot as findProjectRoot21,
1437
+ findProjectRoot as findProjectRoot22,
1200
1438
  getUsage as getUsage7,
1201
1439
  loadUsageIndex as loadUsageIndex9,
1202
- resolveHaivePaths as resolveHaivePaths19
1440
+ resolveHaivePaths as resolveHaivePaths20
1203
1441
  } from "@hiveai/core";
1204
1442
  function registerMemoryStats(memory2) {
1205
1443
  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) => {
1206
- const root = findProjectRoot21(opts.dir);
1207
- const paths = resolveHaivePaths19(root);
1208
- if (!existsSync20(paths.memoriesDir)) {
1444
+ const root = findProjectRoot22(opts.dir);
1445
+ const paths = resolveHaivePaths20(root);
1446
+ if (!existsSync21(paths.memoriesDir)) {
1209
1447
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1210
1448
  process.exitCode = 1;
1211
1449
  return;
1212
1450
  }
1213
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1451
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1214
1452
  const usage = await loadUsageIndex9(paths);
1215
1453
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1216
1454
  if (target.length === 0) {
@@ -1237,25 +1475,25 @@ function registerMemoryStats(memory2) {
1237
1475
 
1238
1476
  // src/commands/memory-verify.ts
1239
1477
  import { writeFile as writeFile10 } from "fs/promises";
1240
- import { existsSync as existsSync21 } from "fs";
1478
+ import { existsSync as existsSync22 } from "fs";
1241
1479
  import path21 from "path";
1242
1480
  import "commander";
1243
1481
  import {
1244
- findProjectRoot as findProjectRoot22,
1245
- resolveHaivePaths as resolveHaivePaths20,
1482
+ findProjectRoot as findProjectRoot23,
1483
+ resolveHaivePaths as resolveHaivePaths21,
1246
1484
  serializeMemory as serializeMemory8,
1247
1485
  verifyAnchor as verifyAnchor2
1248
1486
  } from "@hiveai/core";
1249
1487
  function registerMemoryVerify(memory2) {
1250
1488
  memory2.command("verify").description("Check memory anchors against current code, optionally marking stale ones").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 for re-freshed) back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
1251
- const root = findProjectRoot22(opts.dir);
1252
- const paths = resolveHaivePaths20(root);
1253
- if (!existsSync21(paths.memoriesDir)) {
1489
+ const root = findProjectRoot23(opts.dir);
1490
+ const paths = resolveHaivePaths21(root);
1491
+ if (!existsSync22(paths.memoriesDir)) {
1254
1492
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1255
1493
  process.exitCode = 1;
1256
1494
  return;
1257
1495
  }
1258
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1496
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1259
1497
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1260
1498
  if (opts.id && targets.length === 0) {
1261
1499
  ui.error(`No memory with id "${opts.id}".`);
@@ -1324,10 +1562,11 @@ function applyVerification(mem, result) {
1324
1562
  }
1325
1563
 
1326
1564
  // src/index.ts
1327
- var program = new Command23();
1328
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.2");
1565
+ var program = new Command24();
1566
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.4");
1329
1567
  registerInit(program);
1330
1568
  registerMcp(program);
1569
+ registerBriefing(program);
1331
1570
  registerEmbeddings(program);
1332
1571
  registerSync(program);
1333
1572
  registerInstallHooks(program);