@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 +216 -33
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`${
|
|
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
|
|
1706
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.
|
|
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) {
|