@hiveai/cli 0.2.6 → 0.2.8

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 Command26 } from "commander";
4
+ import { Command as Command27 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -291,9 +291,49 @@ var BRIDGE_BODY = `<!-- hAIve bridge file \u2014 do not edit by hand. -->
291
291
 
292
292
  See \`.ai/project-context.md\` for the full project context.
293
293
  Memories live under \`.ai/memories/\` (personal/team/module).
294
+
295
+ ## hAIve \u2014 mandatory rules for AI agents
296
+ - **Before** marking a task as done ("Done", "Finished", "C'est fait"), call the MCP prompt \`post_task\`.
297
+ - **Immediately** when an approach fails (wrong API, wrong pattern, wrong assumption), call \`mem_tried\` \u2014 do not wait until the end of the session.
298
+ - When starting a new task, call \`get_briefing\` with the task description to load relevant memories and avoid repeating past mistakes.
299
+ `;
300
+ var CI_WORKFLOW = `name: haive-sync
301
+
302
+ on:
303
+ push:
304
+ branches: [main, master]
305
+ pull_request:
306
+ paths:
307
+ - '.ai/**'
308
+
309
+ jobs:
310
+ sync:
311
+ runs-on: ubuntu-latest
312
+ steps:
313
+ - uses: actions/checkout@v4
314
+ with:
315
+ fetch-depth: 0
316
+
317
+ - uses: actions/setup-node@v4
318
+ with:
319
+ node-version: '20'
320
+
321
+ - name: Install hAIve CLI
322
+ run: npm install -g @haive/cli
323
+
324
+ - name: Sync memories (verify anchors + auto-promote)
325
+ run: haive sync --quiet
326
+
327
+ - name: Commit updated memories (if any)
328
+ run: |
329
+ git config user.name "github-actions[bot]"
330
+ git config user.email "github-actions[bot]@users.noreply.github.com"
331
+ git add .ai/
332
+ git diff --cached --quiet || git commit -m "chore: haive sync [skip ci]"
333
+ git push
294
334
  `;
295
335
  function registerInit(program2) {
296
- 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) => {
336
+ 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").option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml)").action(async (opts) => {
297
337
  const root = path3.resolve(opts.dir);
298
338
  const paths = resolveHaivePaths4(root);
299
339
  if (existsSync3(paths.haiveDir)) {
@@ -312,6 +352,16 @@ function registerInit(program2) {
312
352
  await writeBridge(root, ".cursorrules");
313
353
  await writeBridge(root, path3.join(".github", "copilot-instructions.md"));
314
354
  }
355
+ if (opts.withCi) {
356
+ const ciPath = path3.join(root, ".github", "workflows", "haive-sync.yml");
357
+ if (existsSync3(ciPath)) {
358
+ ui.info("CI workflow already exists \u2014 skipped");
359
+ } else {
360
+ await mkdir(path3.dirname(ciPath), { recursive: true });
361
+ await writeFile(ciPath, CI_WORKFLOW, "utf8");
362
+ ui.success(`Created ${path3.relative(root, ciPath)}`);
363
+ }
364
+ }
315
365
  ui.success(`hAIve initialized at ${root}`);
316
366
  ui.info("Next: " + ui.bold("haive memory add --type convention --slug <name>"));
317
367
  });
@@ -429,6 +479,7 @@ import {
429
479
  findProjectRoot as findProjectRoot8,
430
480
  getUsage,
431
481
  isAutoPromoteEligible,
482
+ isDecaying,
432
483
  loadMemoriesFromDir as loadMemoriesFromDir2,
433
484
  loadUsageIndex,
434
485
  resolveHaivePaths as resolveHaivePaths5,
@@ -444,7 +495,7 @@ function registerSync(program2) {
444
495
  ).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
445
496
  "--inject-bridge",
446
497
  "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) => {
498
+ ).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").action(async (opts) => {
448
499
  const root = findProjectRoot8(opts.dir);
449
500
  const paths = resolveHaivePaths5(root);
450
501
  if (!existsSync6(paths.memoriesDir)) {
@@ -555,6 +606,33 @@ function registerSync(program2) {
555
606
  for (const f of sinceReport.removed) log(` - ${f}`);
556
607
  }
557
608
  }
609
+ if (!opts.quiet) {
610
+ const allForDecay = await loadMemoriesFromDir2(paths.memoriesDir);
611
+ const usageForDecay = await loadUsageIndex(paths);
612
+ const decaying = allForDecay.filter(({ memory: memory2 }) => {
613
+ const fm = memory2.frontmatter;
614
+ if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
615
+ const u = getUsage(usageForDecay, fm.id);
616
+ return isDecaying(u, fm.created_at);
617
+ });
618
+ if (decaying.length > 0) {
619
+ log(ui.yellow(`
620
+ \u26A0 ${decaying.length} memor${decaying.length === 1 ? "y" : "ies"} not read in >90 days (consider reviewing or deprecating):`));
621
+ for (const { memory: memory2 } of decaying) {
622
+ log(ui.dim(` ${memory2.frontmatter.id}`));
623
+ }
624
+ }
625
+ }
626
+ if (opts.embed) {
627
+ try {
628
+ const emb = await import("@hiveai/embeddings");
629
+ log(ui.dim("embed: rebuilding index\u2026"));
630
+ const report = await emb.rebuildIndex(paths);
631
+ log(ui.dim(`embed: index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`));
632
+ } catch {
633
+ ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
634
+ }
635
+ }
558
636
  });
559
637
  }
560
638
  async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
@@ -582,13 +660,26 @@ ${m.memory.body.trim()}`;
582
660
  ` + block + `
583
661
 
584
662
  ${BRIDGE_END}`;
585
- let existing = existsSync6(bridgeFile) ? await readFile3(bridgeFile, "utf8") : "";
663
+ const fileExists = existsSync6(bridgeFile);
664
+ let existing = fileExists ? await readFile3(bridgeFile, "utf8") : "";
665
+ existing = existing.replace(/\r\n/g, "\n");
586
666
  const startIdx = existing.indexOf(BRIDGE_START);
587
667
  const endIdx = existing.indexOf(BRIDGE_END);
668
+ if (startIdx !== -1 && endIdx === -1) {
669
+ ui.warn(`${path6.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
670
+ return;
671
+ }
672
+ if (startIdx === -1 && endIdx !== -1) {
673
+ ui.warn(`${path6.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
674
+ return;
675
+ }
588
676
  let updated;
589
677
  if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
590
678
  updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
591
679
  } else {
680
+ if (!fileExists && !quiet) {
681
+ ui.info(`Creating ${path6.relative(root, bridgeFile)} with haive memory block.`);
682
+ }
592
683
  updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
593
684
  }
594
685
  await writeFile3(bridgeFile, updated, "utf8");
@@ -618,7 +709,7 @@ function collectSinceChanges(root, ref) {
618
709
  }
619
710
 
620
711
  // src/commands/memory-add.ts
621
- import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
712
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
622
713
  import { existsSync as existsSync7 } from "fs";
623
714
  import path7 from "path";
624
715
  import "commander";
@@ -626,12 +717,13 @@ import {
626
717
  buildFrontmatter,
627
718
  findProjectRoot as findProjectRoot9,
628
719
  inferModulesFromPaths,
720
+ loadMemoriesFromDir as loadMemoriesFromDir3,
629
721
  memoryFilePath,
630
722
  resolveHaivePaths as resolveHaivePaths6,
631
723
  serializeMemory as serializeMemory2
632
724
  } from "@hiveai/core";
633
725
  function registerMemoryAdd(memory2) {
634
- 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("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("-d, --dir <dir>", "project root").action(async (opts) => {
726
+ 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) => {
635
727
  const root = findProjectRoot9(opts.dir);
636
728
  const paths = resolveHaivePaths6(root);
637
729
  if (!existsSync7(paths.haiveDir)) {
@@ -658,7 +750,18 @@ function registerMemoryAdd(memory2) {
658
750
  });
659
751
  const title = opts.title ?? opts.slug;
660
752
  let body;
661
- if (opts.body !== void 0) {
753
+ if (opts.bodyFile !== void 0) {
754
+ if (!existsSync7(opts.bodyFile)) {
755
+ ui.error(`--body-file not found: ${opts.bodyFile}`);
756
+ process.exitCode = 1;
757
+ return;
758
+ }
759
+ const fileContent = await readFile4(opts.bodyFile, "utf8");
760
+ body = opts.title ? `# ${opts.title}
761
+
762
+ ${fileContent.trim()}
763
+ ` : fileContent;
764
+ } else if (opts.body !== void 0) {
662
765
  body = opts.title ? `# ${opts.title}
663
766
 
664
767
  ${opts.body}` : opts.body;
@@ -675,6 +778,18 @@ TODO \u2014 write the memory body.
675
778
  process.exitCode = 1;
676
779
  return;
677
780
  }
781
+ if (existsSync7(paths.memoriesDir)) {
782
+ const existing = await loadMemoriesFromDir3(paths.memoriesDir);
783
+ const slugTokens = opts.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
784
+ const similar = existing.filter(({ memory: memory3 }) => {
785
+ const id = memory3.frontmatter.id.toLowerCase();
786
+ return slugTokens.length >= 2 && slugTokens.filter((t) => id.includes(t)).length >= Math.ceil(slugTokens.length * 0.6);
787
+ });
788
+ if (similar.length > 0) {
789
+ ui.warn(`Possible duplicate \u2014 similar memories exist: ${similar.map((m) => m.memory.frontmatter.id).join(", ")}`);
790
+ ui.warn("Consider updating one of these with `haive memory update` instead.");
791
+ }
792
+ }
678
793
  await writeFile4(file, serializeMemory2({ frontmatter, body }), "utf8");
679
794
  ui.success(`Created ${path7.relative(root, file)}`);
680
795
  ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
@@ -707,7 +822,7 @@ import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaive
707
822
 
708
823
  // src/utils/fs.ts
709
824
  import {
710
- loadMemoriesFromDir as loadMemoriesFromDir3,
825
+ loadMemoriesFromDir as loadMemoriesFromDir4,
711
826
  loadMemory,
712
827
  listMarkdownFilesRecursive
713
828
  } from "@hiveai/core";
@@ -722,7 +837,7 @@ function registerMemoryList(memory2) {
722
837
  process.exitCode = 1;
723
838
  return;
724
839
  }
725
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
840
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
726
841
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
727
842
  const filtered = all.filter((m) => {
728
843
  if (!matchesFilters(m, opts)) return false;
@@ -803,7 +918,7 @@ function registerMemoryPromote(memory2) {
803
918
  process.exitCode = 1;
804
919
  return;
805
920
  }
806
- const teamAndModule = await loadMemoriesFromDir3(paths.memoriesDir);
921
+ const teamAndModule = await loadMemoriesFromDir4(paths.memoriesDir);
807
922
  const alreadyShared = teamAndModule.find(
808
923
  (m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
809
924
  );
@@ -817,7 +932,7 @@ function registerMemoryPromote(memory2) {
817
932
  }
818
933
  return;
819
934
  }
820
- const all = await loadMemoriesFromDir3(paths.personalDir);
935
+ const all = await loadMemoriesFromDir4(paths.personalDir);
821
936
  const found = all.find((m) => m.memory.frontmatter.id === id);
822
937
  if (!found) {
823
938
  ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
@@ -861,7 +976,7 @@ function registerMemoryApprove(memory2) {
861
976
  process.exitCode = 1;
862
977
  return;
863
978
  }
864
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
979
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
865
980
  if (opts.all || opts.pending) {
866
981
  const candidates = all.filter((m) => {
867
982
  const s = m.memory.frontmatter.status;
@@ -932,7 +1047,7 @@ function registerMemoryUpdate(memory2) {
932
1047
  process.exitCode = 1;
933
1048
  return;
934
1049
  }
935
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1050
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
936
1051
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
937
1052
  if (!loaded) {
938
1053
  ui.error(`No memory with id "${id}".`);
@@ -1028,7 +1143,7 @@ function registerMemoryAutoPromote(memory2) {
1028
1143
  minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE2.minReads),
1029
1144
  maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE2.maxRejections)
1030
1145
  };
1031
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1146
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
1032
1147
  const usage = await loadUsageIndex2(paths);
1033
1148
  const eligible = memories.filter(
1034
1149
  ({ memory: memory3 }) => isAutoPromoteEligible2(memory3.frontmatter, getUsage2(usage, memory3.frontmatter.id), rule)
@@ -1063,7 +1178,7 @@ function registerMemoryAutoPromote(memory2) {
1063
1178
  // src/commands/memory-edit.ts
1064
1179
  import { spawn as spawn2 } from "child_process";
1065
1180
  import { existsSync as existsSync13 } from "fs";
1066
- import { readFile as readFile4 } from "fs/promises";
1181
+ import { readFile as readFile5 } from "fs/promises";
1067
1182
  import path13 from "path";
1068
1183
  import "commander";
1069
1184
  import {
@@ -1080,7 +1195,7 @@ function registerMemoryEdit(memory2) {
1080
1195
  process.exitCode = 1;
1081
1196
  return;
1082
1197
  }
1083
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1198
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1084
1199
  const found = all.find((m) => m.memory.frontmatter.id === id);
1085
1200
  if (!found) {
1086
1201
  ui.error(`No memory with id "${id}".`);
@@ -1094,7 +1209,7 @@ function registerMemoryEdit(memory2) {
1094
1209
  ui.warn(`Editor exited with status ${code}.`);
1095
1210
  }
1096
1211
  try {
1097
- const fresh = await readFile4(found.filePath, "utf8");
1212
+ const fresh = await readFile5(found.filePath, "utf8");
1098
1213
  parseMemory(fresh);
1099
1214
  ui.success("Memory still parses cleanly.");
1100
1215
  } catch (err) {
@@ -1136,7 +1251,7 @@ function registerMemoryForFiles(memory2) {
1136
1251
  process.exitCode = 1;
1137
1252
  return;
1138
1253
  }
1139
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1254
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1140
1255
  const usage = await loadUsageIndex3(paths);
1141
1256
  const inferred = inferModulesFromPaths2(files);
1142
1257
  const byAnchor = [];
@@ -1208,7 +1323,7 @@ function registerMemoryHot(memory2) {
1208
1323
  return;
1209
1324
  }
1210
1325
  const threshold = Math.max(1, Number(opts.threshold ?? 3));
1211
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1326
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1212
1327
  const usage = await loadUsageIndex4(paths);
1213
1328
  const candidates = all.filter(({ memory: mem }) => {
1214
1329
  const fm = mem.frontmatter;
@@ -1314,7 +1429,7 @@ function registerMemoryPending(memory2) {
1314
1429
  process.exitCode = 1;
1315
1430
  return;
1316
1431
  }
1317
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1432
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1318
1433
  const usage = await loadUsageIndex5(paths);
1319
1434
  const proposed = all.filter(({ memory: mem }) => {
1320
1435
  if (mem.frontmatter.status !== "proposed") return false;
@@ -1366,8 +1481,12 @@ function registerMemoryQuery(memory2) {
1366
1481
  return;
1367
1482
  }
1368
1483
  const tokens = tokenizeQuery2(text);
1484
+ if (tokens.length === 0) {
1485
+ ui.warn("Empty query \u2014 use `haive memory list` to list all memories.");
1486
+ return;
1487
+ }
1369
1488
  const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
1370
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1489
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1371
1490
  const passesFilters = (mem) => {
1372
1491
  const fm = mem.frontmatter;
1373
1492
  if (opts.scope && fm.scope !== opts.scope) return false;
@@ -1428,7 +1547,7 @@ function registerMemoryReject(memory2) {
1428
1547
  process.exitCode = 1;
1429
1548
  return;
1430
1549
  }
1431
- const memories = await loadMemoriesFromDir3(paths.memoriesDir);
1550
+ const memories = await loadMemoriesFromDir4(paths.memoriesDir);
1432
1551
  const loaded = memories.find((m) => m.memory.frontmatter.id === id);
1433
1552
  if (!loaded) {
1434
1553
  ui.error(`No memory with id "${id}".`);
@@ -1479,7 +1598,7 @@ function registerMemoryRm(memory2) {
1479
1598
  process.exitCode = 1;
1480
1599
  return;
1481
1600
  }
1482
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1601
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1483
1602
  const found = all.find((m) => m.memory.frontmatter.id === id);
1484
1603
  if (!found) {
1485
1604
  ui.error(`No memory with id "${id}".`);
@@ -1511,7 +1630,7 @@ function registerMemoryRm(memory2) {
1511
1630
 
1512
1631
  // src/commands/memory-show.ts
1513
1632
  import { existsSync as existsSync21 } from "fs";
1514
- import { readFile as readFile5 } from "fs/promises";
1633
+ import { readFile as readFile6 } from "fs/promises";
1515
1634
  import path20 from "path";
1516
1635
  import "commander";
1517
1636
  import {
@@ -1530,7 +1649,7 @@ function registerMemoryShow(memory2) {
1530
1649
  process.exitCode = 1;
1531
1650
  return;
1532
1651
  }
1533
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1652
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1534
1653
  const found = all.find((m) => m.memory.frontmatter.id === id);
1535
1654
  if (!found) {
1536
1655
  ui.error(`No memory with id "${id}".`);
@@ -1538,7 +1657,7 @@ function registerMemoryShow(memory2) {
1538
1657
  return;
1539
1658
  }
1540
1659
  if (opts.raw) {
1541
- console.log(await readFile5(found.filePath, "utf8"));
1660
+ console.log(await readFile6(found.filePath, "utf8"));
1542
1661
  return;
1543
1662
  }
1544
1663
  const fm = found.memory.frontmatter;
@@ -1588,7 +1707,7 @@ function registerMemoryStats(memory2) {
1588
1707
  process.exitCode = 1;
1589
1708
  return;
1590
1709
  }
1591
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1710
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1592
1711
  const usage = await loadUsageIndex9(paths);
1593
1712
  const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1594
1713
  if (target.length === 0) {
@@ -1633,7 +1752,7 @@ function registerMemoryVerify(memory2) {
1633
1752
  process.exitCode = 1;
1634
1753
  return;
1635
1754
  }
1636
- const all = await loadMemoriesFromDir3(paths.memoriesDir);
1755
+ const all = await loadMemoriesFromDir4(paths.memoriesDir);
1637
1756
  const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
1638
1757
  if (opts.id && targets.length === 0) {
1639
1758
  ui.error(`No memory with id "${opts.id}".`);
@@ -1642,13 +1761,13 @@ function registerMemoryVerify(memory2) {
1642
1761
  }
1643
1762
  let staleCount = 0;
1644
1763
  let freshCount = 0;
1645
- let anchorless = 0;
1764
+ const anchorlessIds = [];
1646
1765
  let updated = 0;
1647
1766
  for (const { memory: mem, filePath } of targets) {
1648
1767
  const result = await verifyAnchor2(mem, { projectRoot: root });
1649
1768
  const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
1650
1769
  if (!isAnchored) {
1651
- anchorless++;
1770
+ anchorlessIds.push(mem.frontmatter.id);
1652
1771
  continue;
1653
1772
  }
1654
1773
  const rel = path22.relative(root, filePath);
@@ -1657,6 +1776,9 @@ function registerMemoryVerify(memory2) {
1657
1776
  console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
1658
1777
  console.log(` ${ui.dim(rel)}`);
1659
1778
  console.log(` ${result.reason}`);
1779
+ if (result.possibleRenames.length > 0) {
1780
+ console.log(` ${ui.yellow("Possible renames:")} ${result.possibleRenames.join(", ")}`);
1781
+ }
1660
1782
  } else {
1661
1783
  freshCount++;
1662
1784
  console.log(`${ui.dim("fresh")} ${mem.frontmatter.id}`);
@@ -1670,10 +1792,19 @@ function registerMemoryVerify(memory2) {
1670
1792
  const summary = [
1671
1793
  `${freshCount} fresh`,
1672
1794
  `${staleCount} stale`,
1673
- `${anchorless} anchorless (skipped)`
1795
+ `${anchorlessIds.length} anchorless (skipped)`
1674
1796
  ];
1675
1797
  if (opts.update) summary.push(`${updated} updated on disk`);
1676
1798
  ui.info(summary.join(" \xB7 "));
1799
+ if (anchorlessIds.length > 0) {
1800
+ console.log(
1801
+ ui.dim(
1802
+ `Anchorless memories (no paths/symbols \u2014 staleness cannot be detected):
1803
+ ` + anchorlessIds.map((id) => ` ${id}`).join("\n") + `
1804
+ Tip: use \`haive memory update <id> --paths <files>\` to add anchors.`
1805
+ )
1806
+ );
1807
+ }
1677
1808
  });
1678
1809
  }
1679
1810
  function applyVerification(mem, result) {
@@ -1701,9 +1832,60 @@ function applyVerification(mem, result) {
1701
1832
  };
1702
1833
  }
1703
1834
 
1835
+ // src/commands/memory-import.ts
1836
+ import { readFile as readFile7 } from "fs/promises";
1837
+ import { existsSync as existsSync24 } from "fs";
1838
+ import "commander";
1839
+ import {
1840
+ findProjectRoot as findProjectRoot26,
1841
+ resolveHaivePaths as resolveHaivePaths23
1842
+ } from "@hiveai/core";
1843
+ function registerMemoryImport(memory2) {
1844
+ memory2.command("import").description(
1845
+ "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
1846
+ ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
1847
+ const root = findProjectRoot26(opts.dir);
1848
+ const paths = resolveHaivePaths23(root);
1849
+ if (!existsSync24(paths.haiveDir)) {
1850
+ ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
1851
+ process.exitCode = 1;
1852
+ return;
1853
+ }
1854
+ if (!existsSync24(opts.from)) {
1855
+ ui.error(`File not found: ${opts.from}`);
1856
+ process.exitCode = 1;
1857
+ return;
1858
+ }
1859
+ const content = await readFile7(opts.from, "utf8");
1860
+ const scope = opts.scope ?? "team";
1861
+ ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
1862
+ ui.info(`Content length: ${content.length} chars`);
1863
+ console.log();
1864
+ console.log(ui.bold("To import via MCP, invoke the `import_docs` prompt with:"));
1865
+ console.log();
1866
+ console.log(
1867
+ ui.dim(
1868
+ JSON.stringify(
1869
+ {
1870
+ content: content.slice(0, 200) + (content.length > 200 ? "\u2026" : ""),
1871
+ source: opts.from,
1872
+ scope
1873
+ },
1874
+ null,
1875
+ 2
1876
+ )
1877
+ )
1878
+ );
1879
+ console.log();
1880
+ ui.info(
1881
+ 'Or use your AI client to call: import_docs({ content: <file contents>, source: "' + opts.from + '", scope: "' + scope + '" })'
1882
+ );
1883
+ });
1884
+ }
1885
+
1704
1886
  // src/index.ts
1705
- var program = new Command26();
1706
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.6");
1887
+ var program = new Command27();
1888
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.8");
1707
1889
  registerInit(program);
1708
1890
  registerMcp(program);
1709
1891
  registerBriefing(program);
@@ -1730,6 +1912,7 @@ registerMemoryApprove(memory);
1730
1912
  registerMemoryUpdate(memory);
1731
1913
  registerMemoryHot(memory);
1732
1914
  registerMemoryTried(memory);
1915
+ registerMemoryImport(memory);
1733
1916
  program.parseAsync(process.argv).catch((err) => {
1734
1917
  if (isZodError(err)) {
1735
1918
  for (const issue of err.issues) {