@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 +270 -98
- 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 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").
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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
|
|
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 =
|
|
1201
|
-
const paths =
|
|
1202
|
-
if (!
|
|
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
|
|
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(
|
|
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
|
|
1238
|
-
import
|
|
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
|
|
1389
|
+
findProjectRoot as findProjectRoot20,
|
|
1243
1390
|
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
1391
|
+
literalMatchesAnyToken,
|
|
1244
1392
|
pickSnippetNeedle,
|
|
1245
|
-
resolveHaivePaths as
|
|
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 (
|
|
1250
|
-
const root =
|
|
1251
|
-
const paths =
|
|
1252
|
-
if (!
|
|
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
|
|
1260
|
-
const
|
|
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
|
|
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(
|
|
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
|
|
1291
|
-
import { existsSync as
|
|
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
|
|
1456
|
+
findProjectRoot as findProjectRoot21,
|
|
1295
1457
|
loadUsageIndex as loadUsageIndex6,
|
|
1296
1458
|
recordRejection,
|
|
1297
|
-
resolveHaivePaths as
|
|
1459
|
+
resolveHaivePaths as resolveHaivePaths18,
|
|
1298
1460
|
saveUsageIndex,
|
|
1299
|
-
serializeMemory as
|
|
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 =
|
|
1304
|
-
const paths =
|
|
1305
|
-
if (!
|
|
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
|
|
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
|
|
1479
|
+
await writeFile10(
|
|
1318
1480
|
loaded.filePath,
|
|
1319
|
-
|
|
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
|
|
1503
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1342
1504
|
import { unlink as unlink2 } from "fs/promises";
|
|
1343
|
-
import
|
|
1505
|
+
import path19 from "path";
|
|
1344
1506
|
import { createInterface } from "readline/promises";
|
|
1345
1507
|
import "commander";
|
|
1346
1508
|
import {
|
|
1347
|
-
findProjectRoot as
|
|
1509
|
+
findProjectRoot as findProjectRoot22,
|
|
1348
1510
|
loadUsageIndex as loadUsageIndex7,
|
|
1349
|
-
resolveHaivePaths as
|
|
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 =
|
|
1355
|
-
const paths =
|
|
1356
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
|
1393
|
-
import { readFile as
|
|
1394
|
-
import
|
|
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
|
|
1560
|
+
findProjectRoot as findProjectRoot23,
|
|
1399
1561
|
getUsage as getUsage6,
|
|
1400
1562
|
loadUsageIndex as loadUsageIndex8,
|
|
1401
|
-
resolveHaivePaths as
|
|
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 =
|
|
1406
|
-
const paths =
|
|
1407
|
-
if (!
|
|
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
|
|
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
|
|
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:")} ${
|
|
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
|
|
1452
|
-
import
|
|
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
|
|
1618
|
+
findProjectRoot as findProjectRoot24,
|
|
1457
1619
|
getUsage as getUsage7,
|
|
1458
1620
|
loadUsageIndex as loadUsageIndex9,
|
|
1459
|
-
resolveHaivePaths as
|
|
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 =
|
|
1464
|
-
const paths =
|
|
1465
|
-
if (!
|
|
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
|
|
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(
|
|
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
|
|
1497
|
-
import { existsSync as
|
|
1498
|
-
import
|
|
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
|
|
1502
|
-
resolveHaivePaths as
|
|
1503
|
-
serializeMemory as
|
|
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 =
|
|
1509
|
-
const paths =
|
|
1510
|
-
if (!
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1692
|
+
anchorlessIds.push(mem.frontmatter.id);
|
|
1531
1693
|
continue;
|
|
1532
1694
|
}
|
|
1533
|
-
const rel =
|
|
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
|
|
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
|
-
`${
|
|
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
|
|
1585
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.
|
|
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) {
|