@hiveai/cli 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command25 } from "commander";
4
+ import { Command as Command26 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -54,7 +54,7 @@ function registerBriefing(program2) {
54
54
  "--scope <scope>",
55
55
  "personal | team | module | all (default: team)",
56
56
  "team"
57
- ).option("--include-draft", "include draft memories (excluded by default)").option("-d, --dir <dir>", "project root").action(async (opts) => {
57
+ ).option("--include-draft", "include draft memories (excluded by default)").option("--include-stale", "include stale memories (excluded by default \u2014 may be outdated)").option("-d, --dir <dir>", "project root").action(async (opts) => {
58
58
  const root = findProjectRoot(opts.dir);
59
59
  const paths = resolveHaivePaths(root);
60
60
  if (existsSync(paths.projectContext)) {
@@ -78,6 +78,7 @@ function registerBriefing(program2) {
78
78
  const fm = mem.frontmatter;
79
79
  if (fm.status === "rejected" || fm.status === "deprecated") return false;
80
80
  if (!opts.includeDraft && fm.status === "draft") return false;
81
+ if (!opts.includeStale && fm.status === "stale") return false;
81
82
  if (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
82
83
  return true;
83
84
  });
@@ -108,8 +109,9 @@ function registerBriefing(program2) {
108
109
  const fm = mem.frontmatter;
109
110
  const badge = ui.statusBadge(fm.status);
110
111
  const draftMarker = fm.status === "draft" ? ui.yellow(" [DRAFT]") : "";
112
+ const unverifiedMarker = fm.status === "proposed" ? ui.yellow(" [UNVERIFIED]") : "";
111
113
  console.log(
112
- `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}`
114
+ `${ui.bold(fm.id)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}`
113
115
  );
114
116
  console.log(mem.body.trim());
115
117
  console.log();
@@ -418,9 +420,9 @@ function locateMcpBin() {
418
420
 
419
421
  // src/commands/sync.ts
420
422
  import { spawnSync } from "child_process";
421
- import { writeFile as writeFile3 } from "fs/promises";
423
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
422
424
  import { existsSync as existsSync6 } from "fs";
423
- import "path";
425
+ import path6 from "path";
424
426
  import "commander";
425
427
  import {
426
428
  DEFAULT_AUTO_PROMOTE_RULE,
@@ -433,11 +435,16 @@ import {
433
435
  serializeMemory,
434
436
  verifyAnchor
435
437
  } from "@hiveai/core";
438
+ var BRIDGE_START = "<!-- haive:memories-start -->";
439
+ var BRIDGE_END = "<!-- haive:memories-end -->";
436
440
  function registerSync(program2) {
437
441
  program2.command("sync").description("Refresh memory state after a pull/merge: verify anchors, auto-promote, report changes").option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
438
442
  "--since <ref>",
439
443
  "git ref/commit to compare against; report memories added/modified/removed since"
440
- ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").action(async (opts) => {
444
+ ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
445
+ "--inject-bridge",
446
+ "inject top validated memories into CLAUDE.md (or --bridge-file) between <!-- haive:memories-start/end --> markers"
447
+ ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").action(async (opts) => {
441
448
  const root = findProjectRoot8(opts.dir);
442
449
  const paths = resolveHaivePaths5(root);
443
450
  if (!existsSync6(paths.memoriesDir)) {
@@ -529,6 +536,11 @@ function registerSync(program2) {
529
536
  )
530
537
  );
531
538
  }
539
+ if (opts.injectBridge) {
540
+ const bridgeFile = opts.bridgeFile ? path6.resolve(opts.bridgeFile) : path6.join(root, "CLAUDE.md");
541
+ const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
542
+ await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
543
+ }
532
544
  if (sinceReport && !opts.quiet) {
533
545
  if (sinceReport.added.length > 0) {
534
546
  log(ui.bold("\nNew memories:"));
@@ -545,6 +557,60 @@ function registerSync(program2) {
545
557
  }
546
558
  });
547
559
  }
560
+ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
561
+ if (!existsSync6(memoriesDir)) return;
562
+ const all = await loadMemoriesFromDir2(memoriesDir);
563
+ const top = all.filter(({ memory: memory2 }) => {
564
+ const s = memory2.frontmatter.status;
565
+ return s === "validated" || s === "proposed";
566
+ }).sort((a, b) => {
567
+ const score = (m) => {
568
+ const s = m.memory.frontmatter.status;
569
+ return s === "validated" ? 2 : 1;
570
+ };
571
+ return score(b) - score(a);
572
+ }).slice(0, maxMemories);
573
+ const block = top.map((m) => {
574
+ const fm = m.memory.frontmatter;
575
+ const unverified = fm.status === "proposed" ? " [UNVERIFIED]" : "";
576
+ return `### ${fm.id} (${fm.scope}/${fm.type})${unverified}
577
+ ${m.memory.body.trim()}`;
578
+ }).join("\n\n---\n\n");
579
+ const injected = `${BRIDGE_START}
580
+ <!-- AUTO-GENERATED by haive sync --inject-bridge \u2014 do not edit between these markers -->
581
+
582
+ ` + block + `
583
+
584
+ ${BRIDGE_END}`;
585
+ const fileExists = existsSync6(bridgeFile);
586
+ let existing = fileExists ? await readFile3(bridgeFile, "utf8") : "";
587
+ existing = existing.replace(/\r\n/g, "\n");
588
+ const startIdx = existing.indexOf(BRIDGE_START);
589
+ const endIdx = existing.indexOf(BRIDGE_END);
590
+ if (startIdx !== -1 && endIdx === -1) {
591
+ ui.warn(`${path6.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
592
+ return;
593
+ }
594
+ if (startIdx === -1 && endIdx !== -1) {
595
+ ui.warn(`${path6.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
596
+ return;
597
+ }
598
+ let updated;
599
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
600
+ updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
601
+ } else {
602
+ if (!fileExists && !quiet) {
603
+ ui.info(`Creating ${path6.relative(root, bridgeFile)} with haive memory block.`);
604
+ }
605
+ updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
606
+ }
607
+ await writeFile3(bridgeFile, updated, "utf8");
608
+ if (!quiet) {
609
+ console.log(
610
+ ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path6.relative(root, bridgeFile)}`)
611
+ );
612
+ }
613
+ }
548
614
  function collectSinceChanges(root, ref) {
549
615
  const result = spawnSync(
550
616
  "git",
@@ -565,7 +631,7 @@ function collectSinceChanges(root, ref) {
565
631
  }
566
632
 
567
633
  // src/commands/memory-add.ts
568
- import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
634
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
569
635
  import { existsSync as existsSync7 } from "fs";
570
636
  import path7 from "path";
571
637
  import "commander";
@@ -573,12 +639,13 @@ import {
573
639
  buildFrontmatter,
574
640
  findProjectRoot as findProjectRoot9,
575
641
  inferModulesFromPaths,
642
+ loadMemoriesFromDir as loadMemoriesFromDir3,
576
643
  memoryFilePath,
577
644
  resolveHaivePaths as resolveHaivePaths6,
578
645
  serializeMemory as serializeMemory2
579
646
  } from "@hiveai/core";
580
647
  function registerMemoryAdd(memory2) {
581
- 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) => {
648
+ memory2.command("add").description("Add a new memory (defaults to personal scope)").requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").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("--body-file <path>", "read memory body from a Markdown file \u2014 alternative to --body for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("-d, --dir <dir>", "project root").action(async (opts) => {
582
649
  const root = findProjectRoot9(opts.dir);
583
650
  const paths = resolveHaivePaths6(root);
584
651
  if (!existsSync7(paths.haiveDir)) {
@@ -605,7 +672,18 @@ function registerMemoryAdd(memory2) {
605
672
  });
606
673
  const title = opts.title ?? opts.slug;
607
674
  let body;
608
- if (opts.body !== void 0) {
675
+ if (opts.bodyFile !== void 0) {
676
+ if (!existsSync7(opts.bodyFile)) {
677
+ ui.error(`--body-file not found: ${opts.bodyFile}`);
678
+ process.exitCode = 1;
679
+ return;
680
+ }
681
+ const fileContent = await readFile4(opts.bodyFile, "utf8");
682
+ body = opts.title ? `# ${opts.title}
683
+
684
+ ${fileContent.trim()}
685
+ ` : fileContent;
686
+ } else if (opts.body !== void 0) {
609
687
  body = opts.title ? `# ${opts.title}
610
688
 
611
689
  ${opts.body}` : opts.body;
@@ -622,6 +700,18 @@ TODO \u2014 write the memory body.
622
700
  process.exitCode = 1;
623
701
  return;
624
702
  }
703
+ if (existsSync7(paths.memoriesDir)) {
704
+ const existing = await loadMemoriesFromDir3(paths.memoriesDir);
705
+ const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
706
+ const similar = existing.filter(({ memory: memory3 }) => {
707
+ const id = memory3.frontmatter.id.toLowerCase();
708
+ return slugTokens.length >= 2 && slugTokens.filter((t) => id.includes(t)).length >= Math.ceil(slugTokens.length * 0.6);
709
+ });
710
+ if (similar.length > 0) {
711
+ ui.warn(`Possible duplicate \u2014 similar memories exist: ${similar.map((m) => m.memory.frontmatter.id).join(", ")}`);
712
+ ui.warn("Consider updating one of these with `haive memory update` instead.");
713
+ }
714
+ }
625
715
  await writeFile4(file, serializeMemory2({ frontmatter, body }), "utf8");
626
716
  ui.success(`Created ${path7.relative(root, file)}`);
627
717
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
@@ -654,7 +744,7 @@ import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaive
654
744
 
655
745
  // src/utils/fs.ts
656
746
  import {
657
- loadMemoriesFromDir as loadMemoriesFromDir3,
747
+ loadMemoriesFromDir as loadMemoriesFromDir4,
658
748
  loadMemory,
659
749
  listMarkdownFilesRecursive
660
750
  } from "@hiveai/core";
@@ -669,7 +759,7 @@ function registerMemoryList(memory2) {
669
759
  process.exitCode = 1;
670
760
  return;
671
761
  }
672
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
762
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
673
763
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
674
764
  const filtered = all.filter((m) => {
675
765
  if (!matchesFilters(m, opts)) return false;
@@ -750,7 +840,7 @@ function registerMemoryPromote(memory2) {
750
840
  process.exitCode = 1;
751
841
  return;
752
842
  }
753
- const teamAndModule = await loadMemoriesFromDir3(paths.memoriesDir);
843
+ const teamAndModule = await loadMemoriesFromDir4(paths.memoriesDir);
754
844
  const alreadyShared = teamAndModule.find(
755
845
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
756
846
  );
@@ -764,7 +854,7 @@ function registerMemoryPromote(memory2) {
764
854
  }
765
855
  return;
766
856
  }
767
- const all = await loadMemoriesFromDir3(paths.personalDir);
857
+ const all = await loadMemoriesFromDir4(paths.personalDir);
768
858
  const found = all.find((m) => m.memory.frontmatter.id === id);
769
859
  if (!found) {
770
860
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -808,7 +898,7 @@ function registerMemoryApprove(memory2) {
808
898
  process.exitCode = 1;
809
899
  return;
810
900
  }
811
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
901
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
812
902
  if (opts.all || opts.pending) {
813
903
  const candidates = all.filter((m) => {
814
904
  const s = m.memory.frontmatter.status;
@@ -879,7 +969,7 @@ function registerMemoryUpdate(memory2) {
879
969
  process.exitCode = 1;
880
970
  return;
881
971
  }
882
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
972
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
883
973
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
884
974
  if (!loaded) {
885
975
  ui.error(`No memory with id "${id}".`);
@@ -975,7 +1065,7 @@ function registerMemoryAutoPromote(memory2) {
975
1065
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads),
976
1066
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
977
1067
  };
978
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1068
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
979
1069
  const usage = await loadUsageIndex2(paths);
980
1070
  const eligible = memories.filter(
981
1071
  ({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
@@ -1010,7 +1100,7 @@ function registerMemoryAutoPromote(memory2) {
1010
1100
  // src/commands/memory-edit.ts
1011
1101
  import { spawn as spawn2 } from "child_process";
1012
1102
  import { existsSync as existsSync13 } from "fs";
1013
- import { readFile as readFile3 } from "fs/promises";
1103
+ import { readFile as readFile5 } from "fs/promises";
1014
1104
  import path13 from "path";
1015
1105
  import "commander";
1016
1106
  import {
@@ -1027,7 +1117,7 @@ function registerMemoryEdit(memory2) {
1027
1117
  process.exitCode = 1;
1028
1118
  return;
1029
1119
  }
1030
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1120
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1031
1121
  const found = all.find((m) => m.memory.frontmatter.id === id);
1032
1122
  if (!found) {
1033
1123
  ui.error(`No memory with id "${id}".`);
@@ -1041,7 +1131,7 @@ function registerMemoryEdit(memory2) {
1041
1131
  ui.warn(`Editor exited with status ${code}.`);
1042
1132
  }
1043
1133
  try {
1044
- const fresh = await readFile3(found.filePath, "utf8");
1134
+ const fresh = await readFile5(found.filePath, "utf8");
1045
1135
  parseMemory(fresh);
1046
1136
  ui.success("Memory still parses cleanly.");
1047
1137
  } catch (err) {
@@ -1083,7 +1173,7 @@ function registerMemoryForFiles(memory2) {
1083
1173
  process.exitCode = 1;
1084
1174
  return;
1085
1175
  }
1086
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1176
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1087
1177
  const usage = await loadUsageIndex3(paths);
1088
1178
  const inferred = inferModulesFromPaths2(files);
1089
1179
  const byAnchor = [];
@@ -1155,7 +1245,7 @@ function registerMemoryHot(memory2) {
1155
1245
  return;
1156
1246
  }
1157
1247
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
1158
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1248
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1159
1249
  const usage = await loadUsageIndex4(paths);
1160
1250
  const candidates = all.filter(({ memory: mem }) => {
1161
1251
  const fm = mem.frontmatter;
@@ -1185,26 +1275,83 @@ function registerMemoryHot(memory2) {
1185
1275
  });
1186
1276
  }
1187
1277
 
1188
- // src/commands/memory-pending.ts
1278
+ // src/commands/memory-tried.ts
1279
+ import { mkdir as mkdir5, writeFile as writeFile9 } from "fs/promises";
1189
1280
  import { existsSync as existsSync16 } from "fs";
1190
1281
  import path16 from "path";
1191
1282
  import "commander";
1192
1283
  import {
1284
+ buildFrontmatter as buildFrontmatter2,
1193
1285
  findProjectRoot as findProjectRoot18,
1286
+ memoryFilePath as memoryFilePath3,
1287
+ resolveHaivePaths as resolveHaivePaths15,
1288
+ serializeMemory as serializeMemory7
1289
+ } from "@hiveai/core";
1290
+ function registerMemoryTried(memory2) {
1291
+ memory2.command("tried").description(
1292
+ "Record a failed approach \u2014 negative knowledge to prevent repeated AI mistakes"
1293
+ ).requiredOption("--what <text>", "what approach was tried").requiredOption("--why-failed <text>", "why it failed or should NOT be used").option("--instead <text>", "recommended alternative").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
1294
+ const root = findProjectRoot18(opts.dir);
1295
+ const paths = resolveHaivePaths15(root);
1296
+ if (!existsSync16(paths.haiveDir)) {
1297
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
1298
+ process.exitCode = 1;
1299
+ return;
1300
+ }
1301
+ const slug = opts.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1302
+ const baseFm = buildFrontmatter2({
1303
+ type: "attempt",
1304
+ slug,
1305
+ scope: opts.scope,
1306
+ module: opts.module,
1307
+ tags: parseCsv4(opts.tags),
1308
+ paths: parseCsv4(opts.paths),
1309
+ author: opts.author
1310
+ });
1311
+ const frontmatter = { ...baseFm, status: "validated" };
1312
+ const lines = [`# ${opts.what}`, ""];
1313
+ lines.push(`**Why it failed / do NOT use:** ${opts.whyFailed}`);
1314
+ if (opts.instead) {
1315
+ lines.push("", `**Instead, use:** ${opts.instead}`);
1316
+ }
1317
+ const body = lines.join("\n") + "\n";
1318
+ const file = memoryFilePath3(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1319
+ await mkdir5(path16.dirname(file), { recursive: true });
1320
+ if (existsSync16(file)) {
1321
+ ui.error(`Memory already exists at ${file}`);
1322
+ process.exitCode = 1;
1323
+ return;
1324
+ }
1325
+ await writeFile9(file, serializeMemory7({ frontmatter, body }), "utf8");
1326
+ ui.success(`Recorded: ${path16.relative(root, file)}`);
1327
+ ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
1328
+ });
1329
+ }
1330
+ function parseCsv4(value) {
1331
+ if (!value) return [];
1332
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
1333
+ }
1334
+
1335
+ // src/commands/memory-pending.ts
1336
+ import { existsSync as existsSync17 } from "fs";
1337
+ import path17 from "path";
1338
+ import "commander";
1339
+ import {
1340
+ findProjectRoot as findProjectRoot19,
1194
1341
  getUsage as getUsage5,
1195
1342
  loadUsageIndex as loadUsageIndex5,
1196
- resolveHaivePaths as resolveHaivePaths15
1343
+ resolveHaivePaths as resolveHaivePaths16
1197
1344
  } from "@hiveai/core";
1198
1345
  function registerMemoryPending(memory2) {
1199
1346
  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) => {
1200
- const root = findProjectRoot18(opts.dir);
1201
- const paths = resolveHaivePaths15(root);
1202
- if (!existsSync16(paths.memoriesDir)) {
1347
+ const root = findProjectRoot19(opts.dir);
1348
+ const paths = resolveHaivePaths16(root);
1349
+ if (!existsSync17(paths.memoriesDir)) {
1203
1350
  ui.error(`No .ai/memories at ${root}.`);
1204
1351
  process.exitCode = 1;
1205
1352
  return;
1206
1353
  }
1207
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1354
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1208
1355
  const usage = await loadUsageIndex5(paths);
1209
1356
  const proposed = all.filter(({ memory: mem }) => {
1210
1357
  if (mem.frontmatter.status !== "proposed") return false;
@@ -1227,55 +1374,70 @@ function registerMemoryPending(memory2) {
1227
1374
  console.log(
1228
1375
  `${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count} rejections=${u.rejected_count}`)}`
1229
1376
  );
1230
- console.log(` ${ui.dim(path16.relative(root, filePath))}`);
1377
+ console.log(` ${ui.dim(path17.relative(root, filePath))}`);
1231
1378
  }
1232
1379
  ui.info(`${proposed.length} pending`);
1233
1380
  });
1234
1381
  }
1235
1382
 
1236
1383
  // src/commands/memory-query.ts
1237
- import { existsSync as existsSync17 } from "fs";
1238
- import path17 from "path";
1384
+ import { existsSync as existsSync18 } from "fs";
1385
+ import path18 from "path";
1239
1386
  import "commander";
1240
1387
  import {
1241
1388
  extractSnippet,
1242
- findProjectRoot as findProjectRoot19,
1389
+ findProjectRoot as findProjectRoot20,
1243
1390
  literalMatchesAllTokens as literalMatchesAllTokens2,
1391
+ literalMatchesAnyToken,
1244
1392
  pickSnippetNeedle,
1245
- resolveHaivePaths as resolveHaivePaths16,
1393
+ resolveHaivePaths as resolveHaivePaths17,
1246
1394
  tokenizeQuery as tokenizeQuery2
1247
1395
  } from "@hiveai/core";
1248
1396
  function registerMemoryQuery(memory2) {
1249
- 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) => {
1250
- const root = findProjectRoot19(opts.dir);
1251
- const paths = resolveHaivePaths16(root);
1252
- if (!existsSync17(paths.memoriesDir)) {
1397
+ memory2.command("query <text>").description("Search memories by id, tag, or substring (AND, OR fallback)").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
1398
+ const root = findProjectRoot20(opts.dir);
1399
+ const paths = resolveHaivePaths17(root);
1400
+ if (!existsSync18(paths.memoriesDir)) {
1253
1401
  ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
1254
1402
  process.exitCode = 1;
1255
1403
  return;
1256
1404
  }
1257
1405
  const tokens = tokenizeQuery2(text);
1406
+ if (tokens.length === 0) {
1407
+ ui.warn("Empty query \u2014 use `haive memory list` to list all memories.");
1408
+ return;
1409
+ }
1258
1410
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
1259
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1260
- const matches = all.filter(({ memory: mem }) => {
1411
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1412
+ const passesFilters = (mem) => {
1261
1413
  const fm = mem.frontmatter;
1262
1414
  if (opts.scope && fm.scope !== opts.scope) return false;
1263
1415
  if (!opts.showRejected && !statusFilter && fm.status === "rejected") return false;
1264
1416
  if (statusFilter && !statusFilter.includes(fm.status)) return false;
1265
- return literalMatchesAllTokens2(mem, tokens);
1266
- });
1417
+ return true;
1418
+ };
1419
+ const eligible = all.filter(({ memory: mem }) => passesFilters(mem));
1420
+ let matches = eligible.filter(({ memory: mem }) => literalMatchesAllTokens2(mem, tokens));
1421
+ let fallback = false;
1422
+ if (matches.length === 0 && tokens.length > 1) {
1423
+ matches = eligible.filter(({ memory: mem }) => literalMatchesAnyToken(mem, tokens));
1424
+ fallback = true;
1425
+ }
1267
1426
  const limit = Math.max(1, Number(opts.limit ?? 20));
1268
1427
  const top = matches.slice(0, limit);
1269
1428
  if (top.length === 0) {
1270
1429
  ui.info(`No matches for "${text}".`);
1271
1430
  return;
1272
1431
  }
1432
+ if (fallback) {
1433
+ ui.info(`No exact match \u2014 showing partial results (OR fallback):`);
1434
+ }
1273
1435
  const snippetNeedle = pickSnippetNeedle(text);
1274
1436
  for (const { memory: mem, filePath } of top) {
1275
1437
  const fm = mem.frontmatter;
1276
1438
  const statusBadge = ui.statusBadge(fm.status);
1277
1439
  console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
1278
- console.log(` ${ui.dim(path17.relative(root, filePath))}`);
1440
+ console.log(` ${ui.dim(path18.relative(root, filePath))}`);
1279
1441
  const snippet = extractSnippet(mem.body, snippetNeedle);
1280
1442
  if (snippet) console.log(` ${snippet}`);
1281
1443
  }
@@ -1287,36 +1449,36 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
1287
1449
  }
1288
1450
 
1289
1451
  // src/commands/memory-reject.ts
1290
- import { writeFile as writeFile9 } from "fs/promises";
1291
- import { existsSync as existsSync18 } from "fs";
1452
+ import { writeFile as writeFile10 } from "fs/promises";
1453
+ import { existsSync as existsSync19 } from "fs";
1292
1454
  import "commander";
1293
1455
  import {
1294
- findProjectRoot as findProjectRoot20,
1456
+ findProjectRoot as findProjectRoot21,
1295
1457
  loadUsageIndex as loadUsageIndex6,
1296
1458
  recordRejection,
1297
- resolveHaivePaths as resolveHaivePaths17,
1459
+ resolveHaivePaths as resolveHaivePaths18,
1298
1460
  saveUsageIndex,
1299
- serializeMemory as serializeMemory7
1461
+ serializeMemory as serializeMemory8
1300
1462
  } from "@hiveai/core";
1301
1463
  function registerMemoryReject(memory2) {
1302
1464
  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) => {
1303
- const root = findProjectRoot20(opts.dir);
1304
- const paths = resolveHaivePaths17(root);
1305
- if (!existsSync18(paths.memoriesDir)) {
1465
+ const root = findProjectRoot21(opts.dir);
1466
+ const paths = resolveHaivePaths18(root);
1467
+ if (!existsSync19(paths.memoriesDir)) {
1306
1468
  ui.error(`No .ai/memories at ${root}.`);
1307
1469
  process.exitCode = 1;
1308
1470
  return;
1309
1471
  }
1310
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1472
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
1311
1473
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
1312
1474
  if (!loaded) {
1313
1475
  ui.error(`No memory with id "${id}".`);
1314
1476
  process.exitCode = 1;
1315
1477
  return;
1316
1478
  }
1317
- await writeFile9(
1479
+ await writeFile10(
1318
1480
  loaded.filePath,
1319
- serializeMemory7({
1481
+ serializeMemory8({
1320
1482
  frontmatter: {
1321
1483
  ...loaded.memory.frontmatter,
1322
1484
  status: "rejected",
@@ -1338,34 +1500,34 @@ function registerMemoryReject(memory2) {
1338
1500
  }
1339
1501
 
1340
1502
  // src/commands/memory-rm.ts
1341
- import { existsSync as existsSync19 } from "fs";
1503
+ import { existsSync as existsSync20 } from "fs";
1342
1504
  import { unlink as unlink2 } from "fs/promises";
1343
- import path18 from "path";
1505
+ import path19 from "path";
1344
1506
  import { createInterface } from "readline/promises";
1345
1507
  import "commander";
1346
1508
  import {
1347
- findProjectRoot as findProjectRoot21,
1509
+ findProjectRoot as findProjectRoot22,
1348
1510
  loadUsageIndex as loadUsageIndex7,
1349
- resolveHaivePaths as resolveHaivePaths18,
1511
+ resolveHaivePaths as resolveHaivePaths19,
1350
1512
  saveUsageIndex as saveUsageIndex2
1351
1513
  } from "@hiveai/core";
1352
1514
  function registerMemoryRm(memory2) {
1353
1515
  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) => {
1354
- const root = findProjectRoot21(opts.dir);
1355
- const paths = resolveHaivePaths18(root);
1356
- if (!existsSync19(paths.memoriesDir)) {
1516
+ const root = findProjectRoot22(opts.dir);
1517
+ const paths = resolveHaivePaths19(root);
1518
+ if (!existsSync20(paths.memoriesDir)) {
1357
1519
  ui.error(`No .ai/memories at ${root}.`);
1358
1520
  process.exitCode = 1;
1359
1521
  return;
1360
1522
  }
1361
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1523
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1362
1524
  const found = all.find((m) => m.memory.frontmatter.id === id);
1363
1525
  if (!found) {
1364
1526
  ui.error(`No memory with id "${id}".`);
1365
1527
  process.exitCode = 1;
1366
1528
  return;
1367
1529
  }
1368
- const rel = path18.relative(root, found.filePath);
1530
+ const rel = path19.relative(root, found.filePath);
1369
1531
  if (!opts.yes) {
1370
1532
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1371
1533
  const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
@@ -1389,27 +1551,27 @@ function registerMemoryRm(memory2) {
1389
1551
  }
1390
1552
 
1391
1553
  // src/commands/memory-show.ts
1392
- import { existsSync as existsSync20 } from "fs";
1393
- import { readFile as readFile4 } from "fs/promises";
1394
- import path19 from "path";
1554
+ import { existsSync as existsSync21 } from "fs";
1555
+ import { readFile as readFile6 } from "fs/promises";
1556
+ import path20 from "path";
1395
1557
  import "commander";
1396
1558
  import {
1397
1559
  deriveConfidence as deriveConfidence2,
1398
- findProjectRoot as findProjectRoot22,
1560
+ findProjectRoot as findProjectRoot23,
1399
1561
  getUsage as getUsage6,
1400
1562
  loadUsageIndex as loadUsageIndex8,
1401
- resolveHaivePaths as resolveHaivePaths19
1563
+ resolveHaivePaths as resolveHaivePaths20
1402
1564
  } from "@hiveai/core";
1403
1565
  function registerMemoryShow(memory2) {
1404
1566
  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) => {
1405
- const root = findProjectRoot22(opts.dir);
1406
- const paths = resolveHaivePaths19(root);
1407
- if (!existsSync20(paths.memoriesDir)) {
1567
+ const root = findProjectRoot23(opts.dir);
1568
+ const paths = resolveHaivePaths20(root);
1569
+ if (!existsSync21(paths.memoriesDir)) {
1408
1570
  ui.error(`No .ai/memories at ${root}.`);
1409
1571
  process.exitCode = 1;
1410
1572
  return;
1411
1573
  }
1412
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1574
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1413
1575
  const found = all.find((m) => m.memory.frontmatter.id === id);
1414
1576
  if (!found) {
1415
1577
  ui.error(`No memory with id "${id}".`);
@@ -1417,7 +1579,7 @@ function registerMemoryShow(memory2) {
1417
1579
  return;
1418
1580
  }
1419
1581
  if (opts.raw) {
1420
- console.log(await readFile4(found.filePath, "utf8"));
1582
+ console.log(await readFile6(found.filePath, "utf8"));
1421
1583
  return;
1422
1584
  }
1423
1585
  const fm = found.memory.frontmatter;
@@ -1433,7 +1595,7 @@ function registerMemoryShow(memory2) {
1433
1595
  if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
1434
1596
  if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
1435
1597
  console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
1436
- console.log(`${ui.dim("file:")} ${path19.relative(root, found.filePath)}`);
1598
+ console.log(`${ui.dim("file:")} ${path20.relative(root, found.filePath)}`);
1437
1599
  if (fm.anchor.paths.length || fm.anchor.symbols.length) {
1438
1600
  console.log(ui.dim("anchor:"));
1439
1601
  if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
@@ -1448,26 +1610,26 @@ function registerMemoryShow(memory2) {
1448
1610
  }
1449
1611
 
1450
1612
  // src/commands/memory-stats.ts
1451
- import { existsSync as existsSync21 } from "fs";
1452
- import path20 from "path";
1613
+ import { existsSync as existsSync22 } from "fs";
1614
+ import path21 from "path";
1453
1615
  import "commander";
1454
1616
  import {
1455
1617
  deriveConfidence as deriveConfidence3,
1456
- findProjectRoot as findProjectRoot23,
1618
+ findProjectRoot as findProjectRoot24,
1457
1619
  getUsage as getUsage7,
1458
1620
  loadUsageIndex as loadUsageIndex9,
1459
- resolveHaivePaths as resolveHaivePaths20
1621
+ resolveHaivePaths as resolveHaivePaths21
1460
1622
  } from "@hiveai/core";
1461
1623
  function registerMemoryStats(memory2) {
1462
1624
  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) => {
1463
- const root = findProjectRoot23(opts.dir);
1464
- const paths = resolveHaivePaths20(root);
1465
- if (!existsSync21(paths.memoriesDir)) {
1625
+ const root = findProjectRoot24(opts.dir);
1626
+ const paths = resolveHaivePaths21(root);
1627
+ if (!existsSync22(paths.memoriesDir)) {
1466
1628
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1467
1629
  process.exitCode = 1;
1468
1630
  return;
1469
1631
  }
1470
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1632
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1471
1633
  const usage = await loadUsageIndex9(paths);
1472
1634
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1473
1635
  if (target.length === 0) {
@@ -1487,32 +1649,32 @@ function registerMemoryStats(memory2) {
1487
1649
  console.log(
1488
1650
  ` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
1489
1651
  );
1490
- console.log(` ${ui.dim(path20.relative(root, filePath))}`);
1652
+ console.log(` ${ui.dim(path21.relative(root, filePath))}`);
1491
1653
  }
1492
1654
  });
1493
1655
  }
1494
1656
 
1495
1657
  // src/commands/memory-verify.ts
1496
- import { writeFile as writeFile10 } from "fs/promises";
1497
- import { existsSync as existsSync22 } from "fs";
1498
- import path21 from "path";
1658
+ import { writeFile as writeFile11 } from "fs/promises";
1659
+ import { existsSync as existsSync23 } from "fs";
1660
+ import path22 from "path";
1499
1661
  import "commander";
1500
1662
  import {
1501
- findProjectRoot as findProjectRoot24,
1502
- resolveHaivePaths as resolveHaivePaths21,
1503
- serializeMemory as serializeMemory8,
1663
+ findProjectRoot as findProjectRoot25,
1664
+ resolveHaivePaths as resolveHaivePaths22,
1665
+ serializeMemory as serializeMemory9,
1504
1666
  verifyAnchor as verifyAnchor2
1505
1667
  } from "@hiveai/core";
1506
1668
  function registerMemoryVerify(memory2) {
1507
1669
  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) => {
1508
- const root = findProjectRoot24(opts.dir);
1509
- const paths = resolveHaivePaths21(root);
1510
- if (!existsSync22(paths.memoriesDir)) {
1670
+ const root = findProjectRoot25(opts.dir);
1671
+ const paths = resolveHaivePaths22(root);
1672
+ if (!existsSync23(paths.memoriesDir)) {
1511
1673
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
1512
1674
  process.exitCode = 1;
1513
1675
  return;
1514
1676
  }
1515
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1677
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1516
1678
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1517
1679
  if (opts.id && targets.length === 0) {
1518
1680
  ui.error(`No memory with id "${opts.id}".`);
@@ -1521,16 +1683,16 @@ function registerMemoryVerify(memory2) {
1521
1683
  }
1522
1684
  let staleCount = 0;
1523
1685
  let freshCount = 0;
1524
- let anchorless = 0;
1686
+ const anchorlessIds = [];
1525
1687
  let updated = 0;
1526
1688
  for (const { memory: mem, filePath } of targets) {
1527
1689
  const result = await verifyAnchor2(mem, { projectRoot: root });
1528
1690
  const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
1529
1691
  if (!isAnchored) {
1530
- anchorless++;
1692
+ anchorlessIds.push(mem.frontmatter.id);
1531
1693
  continue;
1532
1694
  }
1533
- const rel = path21.relative(root, filePath);
1695
+ const rel = path22.relative(root, filePath);
1534
1696
  if (result.stale) {
1535
1697
  staleCount++;
1536
1698
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
@@ -1542,17 +1704,26 @@ function registerMemoryVerify(memory2) {
1542
1704
  }
1543
1705
  if (opts.update) {
1544
1706
  const next = applyVerification(mem, result);
1545
- await writeFile10(filePath, serializeMemory8(next), "utf8");
1707
+ await writeFile11(filePath, serializeMemory9(next), "utf8");
1546
1708
  updated++;
1547
1709
  }
1548
1710
  }
1549
1711
  const summary = [
1550
1712
  `${freshCount} fresh`,
1551
1713
  `${staleCount} stale`,
1552
- `${anchorless} anchorless (skipped)`
1714
+ `${anchorlessIds.length} anchorless (skipped)`
1553
1715
  ];
1554
1716
  if (opts.update) summary.push(`${updated} updated on disk`);
1555
1717
  ui.info(summary.join(" \xB7 "));
1718
+ if (anchorlessIds.length > 0) {
1719
+ console.log(
1720
+ ui.dim(
1721
+ `Anchorless memories (no paths/symbols \u2014 staleness cannot be detected):
1722
+ ` + anchorlessIds.map((id) => ` ${id}`).join("\n") + `
1723
+ Tip: use \`haive memory update <id> --paths <files>\` to add anchors.`
1724
+ )
1725
+ );
1726
+ }
1556
1727
  });
1557
1728
  }
1558
1729
  function applyVerification(mem, result) {
@@ -1581,8 +1752,8 @@ function applyVerification(mem, result) {
1581
1752
  }
1582
1753
 
1583
1754
  // src/index.ts
1584
- var program = new Command25();
1585
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.5");
1755
+ var program = new Command26();
1756
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.7");
1586
1757
  registerInit(program);
1587
1758
  registerMcp(program);
1588
1759
  registerBriefing(program);
@@ -1608,6 +1779,7 @@ registerMemoryPending(memory);
1608
1779
  registerMemoryApprove(memory);
1609
1780
  registerMemoryUpdate(memory);
1610
1781
  registerMemoryHot(memory);
1782
+ registerMemoryTried(memory);
1611
1783
  program.parseAsync(process.argv).catch((err) => {
1612
1784
  if (isZodError(err)) {
1613
1785
  for (const issue of err.issues) {