@hiveai/cli 0.2.2 → 0.2.3

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,107 @@ 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 (default: team + validated only)",
56
+ "team"
57
+ ).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 (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
81
+ return true;
82
+ });
83
+ const scored = candidates.map(({ memory: mem, filePath }) => {
84
+ const fm = mem.frontmatter;
85
+ let score = 0;
86
+ if (fm.status === "validated") score += 3;
87
+ else if (fm.status === "proposed") score += 1;
88
+ if (filePaths.length > 0 && memoryMatchesAnchorPaths(mem, filePaths)) score += 4;
89
+ if (tokens && literalMatchesAllTokens(mem, tokens)) score += 3;
90
+ return { memory: mem, filePath, score };
91
+ });
92
+ scored.sort((a, b) => b.score - a.score);
93
+ const top = scored.slice(0, maxMemories);
94
+ if (top.length === 0) {
95
+ ui.info("No relevant memories found.");
96
+ return;
97
+ }
98
+ console.log(`${ui.bold("=== Relevant Memories ===")}
99
+ `);
100
+ for (const { memory: mem } of top) {
101
+ const fm = mem.frontmatter;
102
+ const badge = ui.statusBadge(fm.status);
103
+ console.log(
104
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}`
105
+ );
106
+ console.log(mem.body.trim());
107
+ console.log();
108
+ }
109
+ console.log(ui.dim(`${top.length} memor${top.length === 1 ? "y" : "ies"} surfaced`));
110
+ });
111
+ }
112
+ function parseCsv(value) {
113
+ if (!value) return [];
114
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
115
+ }
116
+
23
117
  // src/commands/embeddings.ts
118
+ import { existsSync as existsSync2 } from "fs";
119
+ import path from "path";
120
+ import "commander";
121
+ import { findProjectRoot as findProjectRoot2, resolveHaivePaths as resolveHaivePaths2 } from "@hiveai/core";
24
122
  function registerEmbeddings(program2) {
25
123
  const embeddings = program2.command("embeddings").description("Manage local embeddings index for semantic search");
26
124
  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)) {
125
+ const root = findProjectRoot2(opts.dir);
126
+ const paths = resolveHaivePaths2(root);
127
+ if (!existsSync2(paths.memoriesDir)) {
30
128
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
31
129
  process.exitCode = 1;
32
130
  return;
@@ -41,8 +139,8 @@ function registerEmbeddings(program2) {
41
139
  );
42
140
  });
43
141
  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);
142
+ const root = findProjectRoot2(opts.dir);
143
+ const paths = resolveHaivePaths2(root);
46
144
  const { semanticSearch } = await loadEmbeddings();
47
145
  const result = await semanticSearch(paths, text, {
48
146
  limit: Number(opts.limit ?? 10),
@@ -64,8 +162,8 @@ function registerEmbeddings(program2) {
64
162
  }
65
163
  });
66
164
  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);
165
+ const root = findProjectRoot2(opts.dir);
166
+ const paths = resolveHaivePaths2(root);
69
167
  const { indexStat } = await loadEmbeddings();
70
168
  const stat = await indexStat(paths);
71
169
  if (!stat.exists) {
@@ -95,8 +193,8 @@ import "commander";
95
193
  import {
96
194
  buildCodeMap,
97
195
  codeMapPath,
98
- findProjectRoot as findProjectRoot2,
99
- resolveHaivePaths as resolveHaivePaths2,
196
+ findProjectRoot as findProjectRoot3,
197
+ resolveHaivePaths as resolveHaivePaths3,
100
198
  saveCodeMap
101
199
  } from "@hiveai/core";
102
200
  function registerIndexCode(program2) {
@@ -107,8 +205,8 @@ function registerIndexCode(program2) {
107
205
  "extra directory names to skip (comma-separated)",
108
206
  ""
109
207
  ).action(async (opts) => {
110
- const root = findProjectRoot2(opts.dir);
111
- const paths = resolveHaivePaths2(root);
208
+ const root = findProjectRoot3(opts.dir);
209
+ const paths = resolveHaivePaths3(root);
112
210
  const extraExcludes = (opts.exclude ?? "").split(",").map((s) => s.trim()).filter(Boolean);
113
211
  ui.info(`Indexing source files in ${root}\u2026`);
114
212
  const map = await buildCodeMap(root, {
@@ -136,10 +234,10 @@ function registerIndexCode(program2) {
136
234
 
137
235
  // src/commands/init.ts
138
236
  import { mkdir, writeFile } from "fs/promises";
139
- import { existsSync as existsSync2 } from "fs";
237
+ import { existsSync as existsSync3 } from "fs";
140
238
  import path3 from "path";
141
239
  import "commander";
142
- import { resolveHaivePaths as resolveHaivePaths3 } from "@hiveai/core";
240
+ import { resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
143
241
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
144
242
 
145
243
  > Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
@@ -168,15 +266,15 @@ Memories live under \`.ai/memories/\` (personal/team/module).
168
266
  function registerInit(program2) {
169
267
  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
268
  const root = path3.resolve(opts.dir);
171
- const paths = resolveHaivePaths3(root);
172
- if (existsSync2(paths.haiveDir)) {
269
+ const paths = resolveHaivePaths4(root);
270
+ if (existsSync3(paths.haiveDir)) {
173
271
  ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
174
272
  }
175
273
  await mkdir(paths.personalDir, { recursive: true });
176
274
  await mkdir(paths.teamDir, { recursive: true });
177
275
  await mkdir(paths.moduleDir, { recursive: true });
178
276
  await mkdir(paths.modulesContextDir, { recursive: true });
179
- if (!existsSync2(paths.projectContext)) {
277
+ if (!existsSync3(paths.projectContext)) {
180
278
  await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
181
279
  ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
182
280
  }
@@ -191,7 +289,7 @@ function registerInit(program2) {
191
289
  }
192
290
  async function writeBridge(root, relPath) {
193
291
  const target = path3.join(root, relPath);
194
- if (existsSync2(target)) {
292
+ if (existsSync3(target)) {
195
293
  ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
196
294
  return;
197
295
  }
@@ -201,11 +299,11 @@ async function writeBridge(root, relPath) {
201
299
  }
202
300
 
203
301
  // 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";
302
+ import { mkdir as mkdir2, writeFile as writeFile2, chmod, readFile as readFile2 } from "fs/promises";
303
+ import { existsSync as existsSync4 } from "fs";
206
304
  import path4 from "path";
207
305
  import "commander";
208
- import { findProjectRoot as findProjectRoot4 } from "@hiveai/core";
306
+ import { findProjectRoot as findProjectRoot5 } from "@hiveai/core";
209
307
  var HOOK_MARKER = "# hAIve auto-generated";
210
308
  var HOOK_BODY = `#!/bin/sh
211
309
  ${HOOK_MARKER} \u2014 keep this block to allow upgrades. Hand-edit anything outside it.
@@ -221,9 +319,9 @@ fi
221
319
  var HOOKS = ["post-merge", "post-rewrite"];
222
320
  function registerInstallHooks(program2) {
223
321
  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);
322
+ const root = findProjectRoot5(opts.dir);
225
323
  const gitDir = path4.join(root, ".git");
226
- if (!existsSync3(gitDir)) {
324
+ if (!existsSync4(gitDir)) {
227
325
  ui.error(`No .git directory at ${root}.`);
228
326
  process.exitCode = 1;
229
327
  return;
@@ -234,8 +332,8 @@ function registerInstallHooks(program2) {
234
332
  let skipped = 0;
235
333
  for (const name of HOOKS) {
236
334
  const file = path4.join(hooksDir, name);
237
- if (existsSync3(file) && !opts.force) {
238
- const existing = await readFile(file, "utf8");
335
+ if (existsSync4(file) && !opts.force) {
336
+ const existing = await readFile2(file, "utf8");
239
337
  if (!existing.includes(HOOK_MARKER)) {
240
338
  ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
241
339
  skipped++;
@@ -253,16 +351,16 @@ function registerInstallHooks(program2) {
253
351
 
254
352
  // src/commands/mcp.ts
255
353
  import { spawn } from "child_process";
256
- import { existsSync as existsSync4 } from "fs";
354
+ import { existsSync as existsSync5 } from "fs";
257
355
  import { createRequire } from "module";
258
356
  import path5 from "path";
259
357
  import { fileURLToPath } from "url";
260
358
  import "commander";
261
- import { findProjectRoot as findProjectRoot5 } from "@hiveai/core";
359
+ import { findProjectRoot as findProjectRoot6 } from "@hiveai/core";
262
360
  var require2 = createRequire(import.meta.url);
263
361
  function registerMcp(program2) {
264
362
  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);
363
+ const root = findProjectRoot6(opts.dir);
266
364
  const bin = locateMcpBin();
267
365
  if (!bin) {
268
366
  ui.error(
@@ -282,29 +380,29 @@ function locateMcpBin() {
282
380
  const pkgPath = require2.resolve("@hiveai/mcp/package.json");
283
381
  const pkgDir = path5.dirname(pkgPath);
284
382
  const candidate = path5.join(pkgDir, "dist", "index.js");
285
- if (existsSync4(candidate)) return candidate;
383
+ if (existsSync5(candidate)) return candidate;
286
384
  } catch {
287
385
  }
288
386
  const here = path5.dirname(fileURLToPath(import.meta.url));
289
387
  const sibling = path5.resolve(here, "..", "..", "..", "mcp", "dist", "index.js");
290
- if (existsSync4(sibling)) return sibling;
388
+ if (existsSync5(sibling)) return sibling;
291
389
  return null;
292
390
  }
293
391
 
294
392
  // src/commands/sync.ts
295
393
  import { spawnSync } from "child_process";
296
394
  import { writeFile as writeFile3 } from "fs/promises";
297
- import { existsSync as existsSync5 } from "fs";
395
+ import { existsSync as existsSync6 } from "fs";
298
396
  import "path";
299
397
  import "commander";
300
398
  import {
301
399
  DEFAULT_AUTO_PROMOTE_RULE,
302
- findProjectRoot as findProjectRoot6,
400
+ findProjectRoot as findProjectRoot7,
303
401
  getUsage,
304
402
  isAutoPromoteEligible,
305
- loadMemoriesFromDir,
403
+ loadMemoriesFromDir as loadMemoriesFromDir2,
306
404
  loadUsageIndex,
307
- resolveHaivePaths as resolveHaivePaths4,
405
+ resolveHaivePaths as resolveHaivePaths5,
308
406
  serializeMemory,
309
407
  verifyAnchor
310
408
  } from "@hiveai/core";
@@ -313,9 +411,9 @@ function registerSync(program2) {
313
411
  "--since <ref>",
314
412
  "git ref/commit to compare against; report memories added/modified/removed since"
315
413
  ).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)) {
414
+ const root = findProjectRoot7(opts.dir);
415
+ const paths = resolveHaivePaths5(root);
416
+ if (!existsSync6(paths.memoriesDir)) {
319
417
  if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
320
418
  process.exitCode = 1;
321
419
  return;
@@ -327,7 +425,7 @@ function registerSync(program2) {
327
425
  let revalidated = 0;
328
426
  let promoted = 0;
329
427
  if (opts.verify !== false) {
330
- const memories = await loadMemoriesFromDir(paths.memoriesDir);
428
+ const memories = await loadMemoriesFromDir2(paths.memoriesDir);
331
429
  for (const { memory: memory2, filePath } of memories) {
332
430
  const isAnchored = memory2.frontmatter.anchor.paths.length > 0 || memory2.frontmatter.anchor.symbols.length > 0;
333
431
  if (!isAnchored) continue;
@@ -369,7 +467,7 @@ function registerSync(program2) {
369
467
  }
370
468
  }
371
469
  if (opts.promote !== false) {
372
- const memories = await loadMemoriesFromDir(paths.memoriesDir);
470
+ const memories = await loadMemoriesFromDir2(paths.memoriesDir);
373
471
  const usage = await loadUsageIndex(paths);
374
472
  for (const { memory: memory2, filePath } of memories) {
375
473
  if (isAutoPromoteEligible(
@@ -390,9 +488,20 @@ function registerSync(program2) {
390
488
  }
391
489
  }
392
490
  const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
491
+ const draftMemories = (await loadMemoriesFromDir2(paths.memoriesDir)).filter(
492
+ (m) => m.memory.frontmatter.status === "draft"
493
+ );
494
+ const draftCount = draftMemories.length;
393
495
  log(
394
496
  `${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
497
  );
498
+ if (!opts.quiet && draftCount > 0) {
499
+ log(
500
+ ui.dim(
501
+ `\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`
502
+ )
503
+ );
504
+ }
396
505
  if (sinceReport && !opts.quiet) {
397
506
  if (sinceReport.added.length > 0) {
398
507
  log(ui.bold("\nNew memories:"));
@@ -430,28 +539,28 @@ function collectSinceChanges(root, ref) {
430
539
 
431
540
  // src/commands/memory-add.ts
432
541
  import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
433
- import { existsSync as existsSync6 } from "fs";
542
+ import { existsSync as existsSync7 } from "fs";
434
543
  import path7 from "path";
435
544
  import "commander";
436
545
  import {
437
546
  buildFrontmatter,
438
- findProjectRoot as findProjectRoot7,
547
+ findProjectRoot as findProjectRoot8,
439
548
  inferModulesFromPaths,
440
549
  memoryFilePath,
441
- resolveHaivePaths as resolveHaivePaths5,
550
+ resolveHaivePaths as resolveHaivePaths6,
442
551
  serializeMemory as serializeMemory2
443
552
  } from "@hiveai/core";
444
553
  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)) {
554
+ 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) => {
555
+ const root = findProjectRoot8(opts.dir);
556
+ const paths = resolveHaivePaths6(root);
557
+ if (!existsSync7(paths.haiveDir)) {
449
558
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
450
559
  process.exitCode = 1;
451
560
  return;
452
561
  }
453
- const userTags = parseCsv(opts.tags);
454
- const anchorPaths = parseCsv(opts.paths);
562
+ const userTags = parseCsv2(opts.tags);
563
+ const anchorPaths = parseCsv2(opts.paths);
455
564
  const autoTagsEnabled = opts.autoTag !== false;
456
565
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths(anchorPaths) : [];
457
566
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
@@ -464,73 +573,112 @@ function registerMemoryAdd(memory2) {
464
573
  domain: opts.domain,
465
574
  author: opts.author,
466
575
  paths: anchorPaths,
467
- symbols: parseCsv(opts.symbols),
576
+ symbols: parseCsv2(opts.symbols),
468
577
  commit: opts.commit
469
578
  });
470
- const body = opts.body ?? `# ${opts.slug}
579
+ const title = opts.title ?? opts.slug;
580
+ let body;
581
+ if (opts.body !== void 0) {
582
+ body = opts.title ? `# ${opts.title}
583
+
584
+ ${opts.body}` : opts.body;
585
+ } else {
586
+ body = `# ${title}
471
587
 
472
588
  TODO \u2014 write the memory body.
473
589
  `;
590
+ }
474
591
  const file = memoryFilePath(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
475
592
  await mkdir3(path7.dirname(file), { recursive: true });
476
- if (existsSync6(file)) {
593
+ if (existsSync7(file)) {
477
594
  ui.error(`Memory already exists at ${file}`);
478
595
  process.exitCode = 1;
479
596
  return;
480
597
  }
481
598
  await writeFile4(file, serializeMemory2({ frontmatter, body }), "utf8");
482
599
  ui.success(`Created ${path7.relative(root, file)}`);
483
- ui.info(`scope=${frontmatter.scope} status=${frontmatter.status} id=${frontmatter.id}`);
600
+ ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
484
601
  if (inferredTags.length > 0) {
485
602
  ui.info(`auto-tagged: ${inferredTags.join(", ")} (use --no-auto-tag to disable)`);
486
603
  }
604
+ if (frontmatter.scope === "personal") {
605
+ console.log(
606
+ ui.dim(
607
+ `\u2192 next: haive memory approve ${frontmatter.id} (activate) | haive memory promote ${frontmatter.id} (share with team)`
608
+ )
609
+ );
610
+ } else {
611
+ console.log(
612
+ ui.dim(`\u2192 next: haive memory approve ${frontmatter.id} (mark as validated)`)
613
+ );
614
+ }
487
615
  });
488
616
  }
489
- function parseCsv(value) {
617
+ function parseCsv2(value) {
490
618
  if (!value) return [];
491
619
  return value.split(",").map((s) => s.trim()).filter(Boolean);
492
620
  }
493
621
 
494
622
  // src/commands/memory-list.ts
495
- import { existsSync as existsSync7 } from "fs";
623
+ import { existsSync as existsSync8 } from "fs";
496
624
  import path8 from "path";
497
625
  import "commander";
498
- import { findProjectRoot as findProjectRoot8, resolveHaivePaths as resolveHaivePaths6 } from "@hiveai/core";
626
+ import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
499
627
 
500
628
  // src/utils/fs.ts
501
629
  import {
502
- loadMemoriesFromDir as loadMemoriesFromDir2,
630
+ loadMemoriesFromDir as loadMemoriesFromDir3,
503
631
  loadMemory,
504
632
  listMarkdownFilesRecursive
505
633
  } from "@hiveai/core";
506
634
 
507
635
  // src/commands/memory-list.ts
508
636
  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)) {
637
+ 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) => {
638
+ const root = findProjectRoot9(opts.dir);
639
+ const paths = resolveHaivePaths7(root);
640
+ if (!existsSync8(paths.memoriesDir)) {
513
641
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
514
642
  process.exitCode = 1;
515
643
  return;
516
644
  }
517
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
518
- const filtered = all.filter((m) => matchesFilters(m, opts));
645
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
646
+ const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
647
+ const filtered = all.filter((m) => {
648
+ if (!matchesFilters(m, opts)) return false;
649
+ const status = m.memory.frontmatter.status;
650
+ if (!opts.showRejected && status === "rejected") return false;
651
+ if (statusFilter && !statusFilter.includes(status)) return false;
652
+ return true;
653
+ });
519
654
  if (filtered.length === 0) {
520
655
  ui.info("No memories match the filters.");
656
+ const rejectedCount = all.filter((m) => m.memory.frontmatter.status === "rejected").length;
657
+ if (rejectedCount > 0 && !opts.showRejected) {
658
+ ui.info(`(${rejectedCount} rejected hidden \u2014 use --show-rejected to include)`);
659
+ }
521
660
  return;
522
661
  }
523
662
  for (const { memory: mem, filePath } of filtered) {
524
663
  const fm = mem.frontmatter;
525
664
  const tagStr = fm.tags.length ? ui.dim(` [${fm.tags.join(", ")}]`) : "";
526
665
  const moduleStr = fm.module ? ui.dim(` (${fm.module})`) : "";
666
+ const statusBadge = ui.statusBadge(fm.status);
527
667
  console.log(
528
- `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)}${moduleStr}${tagStr}`
668
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope)}/${ui.dim(fm.type)} ${statusBadge}${moduleStr}${tagStr}`
529
669
  );
530
670
  console.log(` ${ui.dim(path8.relative(root, filePath))}`);
531
671
  }
532
672
  console.log(ui.dim(`
533
673
  ${filtered.length} memor${filtered.length === 1 ? "y" : "ies"}`));
674
+ const draftCount = filtered.filter((m) => m.memory.frontmatter.status === "draft").length;
675
+ if (draftCount > 0) {
676
+ console.log(
677
+ ui.dim(
678
+ `\u2139 ${draftCount} in draft \u2014 use \`haive memory approve <id>\` to activate or \`haive memory promote <id>\` to share with team`
679
+ )
680
+ );
681
+ }
534
682
  });
535
683
  }
536
684
  function matchesFilters(loaded, opts) {
@@ -544,25 +692,39 @@ function matchesFilters(loaded, opts) {
544
692
 
545
693
  // src/commands/memory-promote.ts
546
694
  import { mkdir as mkdir4, unlink, writeFile as writeFile5 } from "fs/promises";
547
- import { existsSync as existsSync8 } from "fs";
695
+ import { existsSync as existsSync9 } from "fs";
548
696
  import path9 from "path";
549
697
  import "commander";
550
698
  import {
551
- findProjectRoot as findProjectRoot9,
699
+ findProjectRoot as findProjectRoot10,
552
700
  memoryFilePath as memoryFilePath2,
553
- resolveHaivePaths as resolveHaivePaths7,
701
+ resolveHaivePaths as resolveHaivePaths8,
554
702
  serializeMemory as serializeMemory3
555
703
  } from "@hiveai/core";
556
704
  function registerMemoryPromote(memory2) {
557
705
  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)) {
706
+ const root = findProjectRoot10(opts.dir);
707
+ const paths = resolveHaivePaths8(root);
708
+ if (!existsSync9(paths.memoriesDir)) {
561
709
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
562
710
  process.exitCode = 1;
563
711
  return;
564
712
  }
565
- const all = await loadMemoriesFromDir2(paths.personalDir);
713
+ const teamAndModule = await loadMemoriesFromDir3(paths.memoriesDir);
714
+ const alreadyShared = teamAndModule.find(
715
+ (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
716
+ );
717
+ if (alreadyShared) {
718
+ const fm = alreadyShared.memory.frontmatter;
719
+ ui.warn(
720
+ `"${id}" is already in ${fm.scope} scope (status=${fm.status}).`
721
+ );
722
+ if (fm.status !== "validated") {
723
+ ui.info(`\u2192 run \`haive memory approve ${id}\` to validate it`);
724
+ }
725
+ return;
726
+ }
727
+ const all = await loadMemoriesFromDir3(paths.personalDir);
566
728
  const found = all.find((m) => m.memory.frontmatter.id === id);
567
729
  if (!found) {
568
730
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -583,29 +745,57 @@ function registerMemoryPromote(memory2) {
583
745
  await unlink(found.filePath);
584
746
  ui.success(`Promoted ${id} to team scope (status=proposed)`);
585
747
  ui.info(`Now at ${path9.relative(root, newPath)}`);
748
+ console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
586
749
  });
587
750
  }
588
751
 
589
752
  // src/commands/memory-approve.ts
590
- import { existsSync as existsSync9 } from "fs";
753
+ import { existsSync as existsSync10 } from "fs";
591
754
  import { writeFile as writeFile6 } from "fs/promises";
592
755
  import path10 from "path";
593
756
  import "commander";
594
757
  import {
595
- findProjectRoot as findProjectRoot10,
596
- resolveHaivePaths as resolveHaivePaths8,
758
+ findProjectRoot as findProjectRoot11,
759
+ resolveHaivePaths as resolveHaivePaths9,
597
760
  serializeMemory as serializeMemory4
598
761
  } from "@hiveai/core";
599
762
  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)) {
763
+ 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) => {
764
+ const root = findProjectRoot11(opts.dir);
765
+ const paths = resolveHaivePaths9(root);
766
+ if (!existsSync10(paths.memoriesDir)) {
604
767
  ui.error(`No .ai/memories at ${root}.`);
605
768
  process.exitCode = 1;
606
769
  return;
607
770
  }
608
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
771
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
772
+ if (opts.all || opts.pending) {
773
+ const candidates = all.filter((m) => {
774
+ const s = m.memory.frontmatter.status;
775
+ if (opts.all) return s === "proposed" || s === "draft";
776
+ return s === "proposed";
777
+ });
778
+ if (candidates.length === 0) {
779
+ ui.info(opts.all ? "No draft or proposed memories to approve." : "No proposed memories to approve.");
780
+ return;
781
+ }
782
+ let count = 0;
783
+ for (const found2 of candidates) {
784
+ const next2 = {
785
+ frontmatter: { ...found2.memory.frontmatter, status: "validated" },
786
+ body: found2.memory.body
787
+ };
788
+ await writeFile6(found2.filePath, serializeMemory4(next2), "utf8");
789
+ count++;
790
+ }
791
+ ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
792
+ return;
793
+ }
794
+ if (!id) {
795
+ ui.error("Provide a memory id or use --all / --pending for bulk approval.");
796
+ process.exitCode = 1;
797
+ return;
798
+ }
609
799
  const found = all.find((m) => m.memory.frontmatter.id === id);
610
800
  if (!found) {
611
801
  ui.error(`No memory with id "${id}".`);
@@ -632,24 +822,24 @@ function registerMemoryApprove(memory2) {
632
822
 
633
823
  // src/commands/memory-update.ts
634
824
  import { writeFile as writeFile7 } from "fs/promises";
635
- import { existsSync as existsSync10 } from "fs";
825
+ import { existsSync as existsSync11 } from "fs";
636
826
  import path11 from "path";
637
827
  import "commander";
638
828
  import {
639
- findProjectRoot as findProjectRoot11,
640
- resolveHaivePaths as resolveHaivePaths9,
829
+ findProjectRoot as findProjectRoot12,
830
+ resolveHaivePaths as resolveHaivePaths10,
641
831
  serializeMemory as serializeMemory5
642
832
  } from "@hiveai/core";
643
833
  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)) {
834
+ 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) => {
835
+ const root = findProjectRoot12(opts.dir);
836
+ const paths = resolveHaivePaths10(root);
837
+ if (!existsSync11(paths.memoriesDir)) {
648
838
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
649
839
  process.exitCode = 1;
650
840
  return;
651
841
  }
652
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
842
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
653
843
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
654
844
  if (!loaded) {
655
845
  ui.error(`No memory with id "${id}".`);
@@ -660,11 +850,11 @@ function registerMemoryUpdate(memory2) {
660
850
  const { frontmatter, body } = loaded.memory;
661
851
  const newAnchor = { ...frontmatter.anchor };
662
852
  if (opts.paths !== void 0) {
663
- newAnchor.paths = parseCsv2(opts.paths);
853
+ newAnchor.paths = parseCsv3(opts.paths);
664
854
  updated.push("anchor.paths");
665
855
  }
666
856
  if (opts.symbols !== void 0) {
667
- newAnchor.symbols = parseCsv2(opts.symbols);
857
+ newAnchor.symbols = parseCsv3(opts.symbols);
668
858
  updated.push("anchor.symbols");
669
859
  }
670
860
  if (opts.commit !== void 0) {
@@ -674,14 +864,18 @@ function registerMemoryUpdate(memory2) {
674
864
  const newFrontmatter = {
675
865
  ...frontmatter,
676
866
  anchor: newAnchor,
677
- ...opts.tags !== void 0 ? { tags: parseCsv2(opts.tags) } : {},
867
+ ...opts.tags !== void 0 ? { tags: parseCsv3(opts.tags) } : {},
678
868
  ...opts.domain !== void 0 ? { domain: opts.domain } : {},
679
869
  ...opts.author !== void 0 ? { author: opts.author } : {}
680
870
  };
681
871
  if (opts.tags !== void 0) updated.push("tags");
682
872
  if (opts.domain !== void 0) updated.push("domain");
683
873
  if (opts.author !== void 0) updated.push("author");
684
- const newBody = opts.body !== void 0 ? opts.body : body;
874
+ let newBody = opts.body !== void 0 ? opts.body : body;
875
+ if (opts.title !== void 0) {
876
+ newBody = replaceFirstHeading(newBody, opts.title);
877
+ updated.push("title");
878
+ }
685
879
  if (opts.body !== void 0) updated.push("body");
686
880
  if (updated.length === 0) {
687
881
  ui.warn("Nothing to update \u2014 provide at least one option.");
@@ -696,22 +890,32 @@ function registerMemoryUpdate(memory2) {
696
890
  ui.info(`fields: ${updated.join(", ")}`);
697
891
  });
698
892
  }
699
- function parseCsv2(value) {
893
+ function replaceFirstHeading(body, title) {
894
+ const headingRe = /^#\s+.+$/m;
895
+ const replacement = `# ${title}`;
896
+ if (headingRe.test(body)) {
897
+ return body.replace(headingRe, replacement);
898
+ }
899
+ return `${replacement}
900
+
901
+ ${body}`;
902
+ }
903
+ function parseCsv3(value) {
700
904
  return value.split(",").map((s) => s.trim()).filter(Boolean);
701
905
  }
702
906
 
703
907
  // src/commands/memory-auto-promote.ts
704
908
  import { writeFile as writeFile8 } from "fs/promises";
705
- import { existsSync as existsSync11 } from "fs";
909
+ import { existsSync as existsSync12 } from "fs";
706
910
  import path12 from "path";
707
911
  import "commander";
708
912
  import {
709
913
  DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
710
- findProjectRoot as findProjectRoot12,
914
+ findProjectRoot as findProjectRoot13,
711
915
  getUsage as getUsage2,
712
916
  isAutoPromoteEligible as isAutoPromoteEligible2,
713
917
  loadUsageIndex as loadUsageIndex2,
714
- resolveHaivePaths as resolveHaivePaths10,
918
+ resolveHaivePaths as resolveHaivePaths11,
715
919
  serializeMemory as serializeMemory6
716
920
  } from "@hiveai/core";
717
921
  function registerMemoryAutoPromote(memory2) {
@@ -720,9 +924,9 @@ function registerMemoryAutoPromote(memory2) {
720
924
  "memories with more rejections than this are skipped",
721
925
  String(DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
722
926
  ).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)) {
927
+ const root = findProjectRoot13(opts.dir);
928
+ const paths = resolveHaivePaths11(root);
929
+ if (!existsSync12(paths.memoriesDir)) {
726
930
  ui.error(`No .ai/memories at ${root}.`);
727
931
  process.exitCode = 1;
728
932
  return;
@@ -731,7 +935,7 @@ function registerMemoryAutoPromote(memory2) {
731
935
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads),
732
936
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
733
937
  };
734
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
938
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
735
939
  const usage = await loadUsageIndex2(paths);
736
940
  const eligible = memories.filter(
737
941
  ({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
@@ -765,25 +969,25 @@ function registerMemoryAutoPromote(memory2) {
765
969
 
766
970
  // src/commands/memory-edit.ts
767
971
  import { spawn as spawn2 } from "child_process";
768
- import { existsSync as existsSync12 } from "fs";
769
- import { readFile as readFile2 } from "fs/promises";
972
+ import { existsSync as existsSync13 } from "fs";
973
+ import { readFile as readFile3 } from "fs/promises";
770
974
  import path13 from "path";
771
975
  import "commander";
772
976
  import {
773
- findProjectRoot as findProjectRoot13,
977
+ findProjectRoot as findProjectRoot14,
774
978
  parseMemory,
775
- resolveHaivePaths as resolveHaivePaths11
979
+ resolveHaivePaths as resolveHaivePaths12
776
980
  } from "@hiveai/core";
777
981
  function registerMemoryEdit(memory2) {
778
982
  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)) {
983
+ const root = findProjectRoot14(opts.dir);
984
+ const paths = resolveHaivePaths12(root);
985
+ if (!existsSync13(paths.memoriesDir)) {
782
986
  ui.error(`No .ai/memories at ${root}.`);
783
987
  process.exitCode = 1;
784
988
  return;
785
989
  }
786
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
990
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
787
991
  const found = all.find((m) => m.memory.frontmatter.id === id);
788
992
  if (!found) {
789
993
  ui.error(`No memory with id "${id}".`);
@@ -797,7 +1001,7 @@ function registerMemoryEdit(memory2) {
797
1001
  ui.warn(`Editor exited with status ${code}.`);
798
1002
  }
799
1003
  try {
800
- const fresh = await readFile2(found.filePath, "utf8");
1004
+ const fresh = await readFile3(found.filePath, "utf8");
801
1005
  parseMemory(fresh);
802
1006
  ui.success("Memory still parses cleanly.");
803
1007
  } catch (err) {
@@ -818,28 +1022,28 @@ function runEditor(editor, file) {
818
1022
  }
819
1023
 
820
1024
  // src/commands/memory-for-files.ts
821
- import { existsSync as existsSync13 } from "fs";
1025
+ import { existsSync as existsSync14 } from "fs";
822
1026
  import path14 from "path";
823
1027
  import "commander";
824
1028
  import {
825
1029
  deriveConfidence,
826
- findProjectRoot as findProjectRoot14,
1030
+ findProjectRoot as findProjectRoot15,
827
1031
  getUsage as getUsage3,
828
1032
  inferModulesFromPaths as inferModulesFromPaths2,
829
1033
  loadUsageIndex as loadUsageIndex3,
830
- memoryMatchesAnchorPaths,
831
- resolveHaivePaths as resolveHaivePaths12
1034
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
1035
+ resolveHaivePaths as resolveHaivePaths13
832
1036
  } from "@hiveai/core";
833
1037
  function registerMemoryForFiles(memory2) {
834
1038
  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)) {
1039
+ const root = findProjectRoot15(opts.dir);
1040
+ const paths = resolveHaivePaths13(root);
1041
+ if (!existsSync14(paths.memoriesDir)) {
838
1042
  ui.error(`No .ai/memories at ${root}.`);
839
1043
  process.exitCode = 1;
840
1044
  return;
841
1045
  }
842
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1046
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
843
1047
  const usage = await loadUsageIndex3(paths);
844
1048
  const inferred = inferModulesFromPaths2(files);
845
1049
  const byAnchor = [];
@@ -847,7 +1051,7 @@ function registerMemoryForFiles(memory2) {
847
1051
  const byDomain = [];
848
1052
  const seen = /* @__PURE__ */ new Set();
849
1053
  for (const loaded of all) {
850
- if (memoryMatchesAnchorPaths(loaded.memory, files)) {
1054
+ if (memoryMatchesAnchorPaths2(loaded.memory, files)) {
851
1055
  byAnchor.push(loaded);
852
1056
  seen.add(loaded.memory.frontmatter.id);
853
1057
  }
@@ -892,26 +1096,26 @@ function printGroup(root, label, loaded, usage) {
892
1096
  }
893
1097
 
894
1098
  // src/commands/memory-hot.ts
895
- import { existsSync as existsSync14 } from "fs";
1099
+ import { existsSync as existsSync15 } from "fs";
896
1100
  import path15 from "path";
897
1101
  import "commander";
898
1102
  import {
899
- findProjectRoot as findProjectRoot15,
1103
+ findProjectRoot as findProjectRoot16,
900
1104
  getUsage as getUsage4,
901
1105
  loadUsageIndex as loadUsageIndex4,
902
- resolveHaivePaths as resolveHaivePaths13
1106
+ resolveHaivePaths as resolveHaivePaths14
903
1107
  } from "@hiveai/core";
904
1108
  function registerMemoryHot(memory2) {
905
1109
  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)) {
1110
+ const root = findProjectRoot16(opts.dir);
1111
+ const paths = resolveHaivePaths14(root);
1112
+ if (!existsSync15(paths.memoriesDir)) {
909
1113
  ui.error(`No .ai/memories at ${root}.`);
910
1114
  process.exitCode = 1;
911
1115
  return;
912
1116
  }
913
1117
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
914
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1118
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
915
1119
  const usage = await loadUsageIndex4(paths);
916
1120
  const candidates = all.filter(({ memory: mem }) => {
917
1121
  const fm = mem.frontmatter;
@@ -942,25 +1146,25 @@ function registerMemoryHot(memory2) {
942
1146
  }
943
1147
 
944
1148
  // src/commands/memory-pending.ts
945
- import { existsSync as existsSync15 } from "fs";
1149
+ import { existsSync as existsSync16 } from "fs";
946
1150
  import path16 from "path";
947
1151
  import "commander";
948
1152
  import {
949
- findProjectRoot as findProjectRoot16,
1153
+ findProjectRoot as findProjectRoot17,
950
1154
  getUsage as getUsage5,
951
1155
  loadUsageIndex as loadUsageIndex5,
952
- resolveHaivePaths as resolveHaivePaths14
1156
+ resolveHaivePaths as resolveHaivePaths15
953
1157
  } from "@hiveai/core";
954
1158
  function registerMemoryPending(memory2) {
955
1159
  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)) {
1160
+ const root = findProjectRoot17(opts.dir);
1161
+ const paths = resolveHaivePaths15(root);
1162
+ if (!existsSync16(paths.memoriesDir)) {
959
1163
  ui.error(`No .ai/memories at ${root}.`);
960
1164
  process.exitCode = 1;
961
1165
  return;
962
1166
  }
963
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1167
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
964
1168
  const usage = await loadUsageIndex5(paths);
965
1169
  const proposed = all.filter(({ memory: mem }) => {
966
1170
  if (mem.frontmatter.status !== "proposed") return false;
@@ -990,29 +1194,35 @@ function registerMemoryPending(memory2) {
990
1194
  }
991
1195
 
992
1196
  // src/commands/memory-query.ts
993
- import { existsSync as existsSync16 } from "fs";
1197
+ import { existsSync as existsSync17 } from "fs";
994
1198
  import path17 from "path";
995
1199
  import "commander";
996
1200
  import {
997
1201
  extractSnippet,
998
- findProjectRoot as findProjectRoot17,
999
- literalMatchesAllTokens,
1202
+ findProjectRoot as findProjectRoot18,
1203
+ literalMatchesAllTokens as literalMatchesAllTokens2,
1000
1204
  pickSnippetNeedle,
1001
- resolveHaivePaths as resolveHaivePaths15,
1002
- tokenizeQuery
1205
+ resolveHaivePaths as resolveHaivePaths16,
1206
+ tokenizeQuery as tokenizeQuery2
1003
1207
  } from "@hiveai/core";
1004
1208
  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)) {
1209
+ 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)").action(async (text, opts) => {
1210
+ const root = findProjectRoot18(opts.dir);
1211
+ const paths = resolveHaivePaths16(root);
1212
+ if (!existsSync17(paths.memoriesDir)) {
1009
1213
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
1010
1214
  process.exitCode = 1;
1011
1215
  return;
1012
1216
  }
1013
- const tokens = tokenizeQuery(text);
1014
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1015
- const matches = all.filter(({ memory: mem }) => literalMatchesAllTokens(mem, tokens));
1217
+ const tokens = tokenizeQuery2(text);
1218
+ const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
1219
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1220
+ const matches = all.filter(({ memory: mem }) => {
1221
+ const fm = mem.frontmatter;
1222
+ if (opts.scope && fm.scope !== opts.scope) return false;
1223
+ if (statusFilter && !statusFilter.includes(fm.status)) return false;
1224
+ return literalMatchesAllTokens2(mem, tokens);
1225
+ });
1016
1226
  const limit = Math.max(1, Number(opts.limit ?? 20));
1017
1227
  const top = matches.slice(0, limit);
1018
1228
  if (top.length === 0) {
@@ -1021,9 +1231,11 @@ function registerMemoryQuery(memory2) {
1021
1231
  }
1022
1232
  const snippetNeedle = pickSnippetNeedle(text);
1023
1233
  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)}`);
1234
+ const fm = mem.frontmatter;
1235
+ const statusBadge = ui.statusBadge(fm.status);
1236
+ console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
1026
1237
  console.log(` ${ui.dim(path17.relative(root, filePath))}`);
1238
+ const snippet = extractSnippet(mem.body, snippetNeedle);
1027
1239
  if (snippet) console.log(` ${snippet}`);
1028
1240
  }
1029
1241
  console.log(
@@ -1035,26 +1247,26 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
1035
1247
 
1036
1248
  // src/commands/memory-reject.ts
1037
1249
  import { writeFile as writeFile9 } from "fs/promises";
1038
- import { existsSync as existsSync17 } from "fs";
1250
+ import { existsSync as existsSync18 } from "fs";
1039
1251
  import "commander";
1040
1252
  import {
1041
- findProjectRoot as findProjectRoot18,
1253
+ findProjectRoot as findProjectRoot19,
1042
1254
  loadUsageIndex as loadUsageIndex6,
1043
1255
  recordRejection,
1044
- resolveHaivePaths as resolveHaivePaths16,
1256
+ resolveHaivePaths as resolveHaivePaths17,
1045
1257
  saveUsageIndex,
1046
1258
  serializeMemory as serializeMemory7
1047
1259
  } from "@hiveai/core";
1048
1260
  function registerMemoryReject(memory2) {
1049
1261
  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)) {
1262
+ const root = findProjectRoot19(opts.dir);
1263
+ const paths = resolveHaivePaths17(root);
1264
+ if (!existsSync18(paths.memoriesDir)) {
1053
1265
  ui.error(`No .ai/memories at ${root}.`);
1054
1266
  process.exitCode = 1;
1055
1267
  return;
1056
1268
  }
1057
- const memories = await loadMemoriesFromDir2(paths.memoriesDir);
1269
+ const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1058
1270
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
1059
1271
  if (!loaded) {
1060
1272
  ui.error(`No memory with id "${id}".`);
@@ -1081,27 +1293,27 @@ function registerMemoryReject(memory2) {
1081
1293
  }
1082
1294
 
1083
1295
  // src/commands/memory-rm.ts
1084
- import { existsSync as existsSync18 } from "fs";
1296
+ import { existsSync as existsSync19 } from "fs";
1085
1297
  import { unlink as unlink2 } from "fs/promises";
1086
1298
  import path18 from "path";
1087
1299
  import { createInterface } from "readline/promises";
1088
1300
  import "commander";
1089
1301
  import {
1090
- findProjectRoot as findProjectRoot19,
1302
+ findProjectRoot as findProjectRoot20,
1091
1303
  loadUsageIndex as loadUsageIndex7,
1092
- resolveHaivePaths as resolveHaivePaths17,
1304
+ resolveHaivePaths as resolveHaivePaths18,
1093
1305
  saveUsageIndex as saveUsageIndex2
1094
1306
  } from "@hiveai/core";
1095
1307
  function registerMemoryRm(memory2) {
1096
1308
  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)) {
1309
+ const root = findProjectRoot20(opts.dir);
1310
+ const paths = resolveHaivePaths18(root);
1311
+ if (!existsSync19(paths.memoriesDir)) {
1100
1312
  ui.error(`No .ai/memories at ${root}.`);
1101
1313
  process.exitCode = 1;
1102
1314
  return;
1103
1315
  }
1104
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1316
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1105
1317
  const found = all.find((m) => m.memory.frontmatter.id === id);
1106
1318
  if (!found) {
1107
1319
  ui.error(`No memory with id "${id}".`);
@@ -1132,27 +1344,27 @@ function registerMemoryRm(memory2) {
1132
1344
  }
1133
1345
 
1134
1346
  // src/commands/memory-show.ts
1135
- import { existsSync as existsSync19 } from "fs";
1136
- import { readFile as readFile3 } from "fs/promises";
1347
+ import { existsSync as existsSync20 } from "fs";
1348
+ import { readFile as readFile4 } from "fs/promises";
1137
1349
  import path19 from "path";
1138
1350
  import "commander";
1139
1351
  import {
1140
1352
  deriveConfidence as deriveConfidence2,
1141
- findProjectRoot as findProjectRoot20,
1353
+ findProjectRoot as findProjectRoot21,
1142
1354
  getUsage as getUsage6,
1143
1355
  loadUsageIndex as loadUsageIndex8,
1144
- resolveHaivePaths as resolveHaivePaths18
1356
+ resolveHaivePaths as resolveHaivePaths19
1145
1357
  } from "@hiveai/core";
1146
1358
  function registerMemoryShow(memory2) {
1147
1359
  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)) {
1360
+ const root = findProjectRoot21(opts.dir);
1361
+ const paths = resolveHaivePaths19(root);
1362
+ if (!existsSync20(paths.memoriesDir)) {
1151
1363
  ui.error(`No .ai/memories at ${root}.`);
1152
1364
  process.exitCode = 1;
1153
1365
  return;
1154
1366
  }
1155
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1367
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1156
1368
  const found = all.find((m) => m.memory.frontmatter.id === id);
1157
1369
  if (!found) {
1158
1370
  ui.error(`No memory with id "${id}".`);
@@ -1160,7 +1372,7 @@ function registerMemoryShow(memory2) {
1160
1372
  return;
1161
1373
  }
1162
1374
  if (opts.raw) {
1163
- console.log(await readFile3(found.filePath, "utf8"));
1375
+ console.log(await readFile4(found.filePath, "utf8"));
1164
1376
  return;
1165
1377
  }
1166
1378
  const fm = found.memory.frontmatter;
@@ -1191,26 +1403,26 @@ function registerMemoryShow(memory2) {
1191
1403
  }
1192
1404
 
1193
1405
  // src/commands/memory-stats.ts
1194
- import { existsSync as existsSync20 } from "fs";
1406
+ import { existsSync as existsSync21 } from "fs";
1195
1407
  import path20 from "path";
1196
1408
  import "commander";
1197
1409
  import {
1198
1410
  deriveConfidence as deriveConfidence3,
1199
- findProjectRoot as findProjectRoot21,
1411
+ findProjectRoot as findProjectRoot22,
1200
1412
  getUsage as getUsage7,
1201
1413
  loadUsageIndex as loadUsageIndex9,
1202
- resolveHaivePaths as resolveHaivePaths19
1414
+ resolveHaivePaths as resolveHaivePaths20
1203
1415
  } from "@hiveai/core";
1204
1416
  function registerMemoryStats(memory2) {
1205
1417
  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)) {
1418
+ const root = findProjectRoot22(opts.dir);
1419
+ const paths = resolveHaivePaths20(root);
1420
+ if (!existsSync21(paths.memoriesDir)) {
1209
1421
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1210
1422
  process.exitCode = 1;
1211
1423
  return;
1212
1424
  }
1213
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1425
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1214
1426
  const usage = await loadUsageIndex9(paths);
1215
1427
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1216
1428
  if (target.length === 0) {
@@ -1237,25 +1449,25 @@ function registerMemoryStats(memory2) {
1237
1449
 
1238
1450
  // src/commands/memory-verify.ts
1239
1451
  import { writeFile as writeFile10 } from "fs/promises";
1240
- import { existsSync as existsSync21 } from "fs";
1452
+ import { existsSync as existsSync22 } from "fs";
1241
1453
  import path21 from "path";
1242
1454
  import "commander";
1243
1455
  import {
1244
- findProjectRoot as findProjectRoot22,
1245
- resolveHaivePaths as resolveHaivePaths20,
1456
+ findProjectRoot as findProjectRoot23,
1457
+ resolveHaivePaths as resolveHaivePaths21,
1246
1458
  serializeMemory as serializeMemory8,
1247
1459
  verifyAnchor as verifyAnchor2
1248
1460
  } from "@hiveai/core";
1249
1461
  function registerMemoryVerify(memory2) {
1250
1462
  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)) {
1463
+ const root = findProjectRoot23(opts.dir);
1464
+ const paths = resolveHaivePaths21(root);
1465
+ if (!existsSync22(paths.memoriesDir)) {
1254
1466
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1255
1467
  process.exitCode = 1;
1256
1468
  return;
1257
1469
  }
1258
- const all = await loadMemoriesFromDir2(paths.memoriesDir);
1470
+ const all = await loadMemoriesFromDir3(paths.memoriesDir);
1259
1471
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1260
1472
  if (opts.id && targets.length === 0) {
1261
1473
  ui.error(`No memory with id "${opts.id}".`);
@@ -1324,10 +1536,11 @@ function applyVerification(mem, result) {
1324
1536
  }
1325
1537
 
1326
1538
  // 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");
1539
+ var program = new Command24();
1540
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.3");
1329
1541
  registerInit(program);
1330
1542
  registerMcp(program);
1543
+ registerBriefing(program);
1331
1544
  registerEmbeddings(program);
1332
1545
  registerSync(program);
1333
1546
  registerInstallHooks(program);