@hiveai/cli 0.2.16 → 0.3.2
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/README.md +62 -8
- package/dist/Dashboard-Y2AIWFZK.js +361 -0
- package/dist/Dashboard-Y2AIWFZK.js.map +1 -0
- package/dist/index.js +304 -57
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/Dashboard-SRPCHP7Z.js +0 -194
- package/dist/Dashboard-SRPCHP7Z.js.map +0 -1
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 Command29 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync } from "fs";
|
|
@@ -11,8 +11,10 @@ import {
|
|
|
11
11
|
findProjectRoot,
|
|
12
12
|
literalMatchesAllTokens,
|
|
13
13
|
literalMatchesAnyToken,
|
|
14
|
+
loadCodeMap,
|
|
14
15
|
loadMemoriesFromDir,
|
|
15
16
|
memoryMatchesAnchorPaths,
|
|
17
|
+
queryCodeMap,
|
|
16
18
|
resolveHaivePaths,
|
|
17
19
|
tokenizeQuery,
|
|
18
20
|
trackReads
|
|
@@ -52,7 +54,7 @@ var ui = {
|
|
|
52
54
|
function registerBriefing(program2) {
|
|
53
55
|
program2.command("briefing").description(
|
|
54
56
|
"Print project context + relevant memories in one shot \u2014 ideal for agent onboarding"
|
|
55
|
-
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
|
|
57
|
+
).option("--task <text>", "what you are about to do \u2014 filters memories by relevance").option("--files <csv>", "comma-separated file paths being worked on (anchors memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter)").option("--max-memories <n>", "cap on memories surfaced", "10").option(
|
|
56
58
|
"--scope <scope>",
|
|
57
59
|
"personal | team | module | all (default: team)",
|
|
58
60
|
"team"
|
|
@@ -163,6 +165,34 @@ function registerBriefing(program2) {
|
|
|
163
165
|
await trackReads(paths, ids).catch(() => {
|
|
164
166
|
});
|
|
165
167
|
}
|
|
168
|
+
const requestedSymbols = (opts.symbols ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
169
|
+
if (requestedSymbols.length > 0) {
|
|
170
|
+
const codeMap = await loadCodeMap(paths);
|
|
171
|
+
if (!codeMap) {
|
|
172
|
+
ui.warn("No code-map found. Run `haive index code` first to enable symbol lookup.");
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`
|
|
175
|
+
${ui.bold("=== Symbol Locations ===")}
|
|
176
|
+
`);
|
|
177
|
+
for (const sym of requestedSymbols) {
|
|
178
|
+
const { files } = queryCodeMap(codeMap, { symbol: sym });
|
|
179
|
+
if (files.length === 0) {
|
|
180
|
+
console.log(`${ui.dim(sym)} (not found in code-map)`);
|
|
181
|
+
} else {
|
|
182
|
+
for (const f of files) {
|
|
183
|
+
const exports = f.entry.exports.filter(
|
|
184
|
+
(e) => e.name.toLowerCase().includes(sym.toLowerCase())
|
|
185
|
+
);
|
|
186
|
+
for (const e of exports) {
|
|
187
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
188
|
+
console.log(`${ui.bold(e.name)} ${ui.dim(f.path + ":" + e.line)} [${e.kind}]${desc}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
console.log();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
166
196
|
});
|
|
167
197
|
}
|
|
168
198
|
function parseCsv(value) {
|
|
@@ -183,7 +213,7 @@ function registerTui(program2) {
|
|
|
183
213
|
const root = findProjectRoot2(opts.dir);
|
|
184
214
|
const { render } = await import("ink");
|
|
185
215
|
const { createElement } = await import("react");
|
|
186
|
-
const { Dashboard } = await import("./Dashboard-
|
|
216
|
+
const { Dashboard } = await import("./Dashboard-Y2AIWFZK.js");
|
|
187
217
|
const { waitUntilExit } = render(createElement(Dashboard, { root }));
|
|
188
218
|
await waitUntilExit();
|
|
189
219
|
});
|
|
@@ -311,8 +341,15 @@ function registerIndexCode(program2) {
|
|
|
311
341
|
import { mkdir, writeFile } from "fs/promises";
|
|
312
342
|
import { existsSync as existsSync3 } from "fs";
|
|
313
343
|
import path3 from "path";
|
|
344
|
+
import { spawnSync } from "child_process";
|
|
314
345
|
import "commander";
|
|
315
|
-
import {
|
|
346
|
+
import {
|
|
347
|
+
AUTOPILOT_DEFAULTS,
|
|
348
|
+
buildCodeMap as buildCodeMap2,
|
|
349
|
+
resolveHaivePaths as resolveHaivePaths4,
|
|
350
|
+
saveCodeMap as saveCodeMap2,
|
|
351
|
+
saveConfig
|
|
352
|
+
} from "@hiveai/core";
|
|
316
353
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
317
354
|
|
|
318
355
|
> Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
|
|
@@ -421,9 +458,13 @@ jobs:
|
|
|
421
458
|
});
|
|
422
459
|
`;
|
|
423
460
|
function registerInit(program2) {
|
|
424
|
-
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)").
|
|
461
|
+
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)").option(
|
|
462
|
+
"--autopilot",
|
|
463
|
+
"zero-friction mode: memories \u2192 validated, auto-approve, auto-session, auto-context, git hooks + CI included"
|
|
464
|
+
).action(async (opts) => {
|
|
425
465
|
const root = path3.resolve(opts.dir);
|
|
426
466
|
const paths = resolveHaivePaths4(root);
|
|
467
|
+
const autopilot = opts.autopilot === true;
|
|
427
468
|
if (existsSync3(paths.haiveDir)) {
|
|
428
469
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
429
470
|
}
|
|
@@ -435,12 +476,22 @@ function registerInit(program2) {
|
|
|
435
476
|
await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
436
477
|
ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
|
|
437
478
|
}
|
|
479
|
+
const configExists = existsSync3(
|
|
480
|
+
path3.join(paths.haiveDir, "haive.config.json")
|
|
481
|
+
);
|
|
482
|
+
if (!configExists) {
|
|
483
|
+
await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
|
|
484
|
+
ui.success(
|
|
485
|
+
`Created .ai/haive.config.json (mode: ${autopilot ? "autopilot" : "standard"})`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
438
488
|
if (opts.bridges) {
|
|
439
489
|
await writeBridge(root, "CLAUDE.md");
|
|
440
490
|
await writeBridge(root, ".cursorrules");
|
|
441
491
|
await writeBridge(root, path3.join(".github", "copilot-instructions.md"));
|
|
442
492
|
}
|
|
443
|
-
|
|
493
|
+
const wantCi = opts.withCi || autopilot;
|
|
494
|
+
if (wantCi) {
|
|
444
495
|
const ciPath = path3.join(root, ".github", "workflows", "haive-sync.yml");
|
|
445
496
|
if (existsSync3(ciPath)) {
|
|
446
497
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
@@ -450,29 +501,55 @@ function registerInit(program2) {
|
|
|
450
501
|
ui.success(`Created ${path3.relative(root, ciPath)}`);
|
|
451
502
|
}
|
|
452
503
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
504
|
+
if (autopilot) {
|
|
505
|
+
const haiveBin = process.argv[1];
|
|
506
|
+
const hookResult = spawnSync(
|
|
507
|
+
process.execPath,
|
|
508
|
+
[haiveBin, "install-hooks", "--dir", root],
|
|
509
|
+
{ encoding: "utf8" }
|
|
510
|
+
);
|
|
511
|
+
if (hookResult.status === 0) {
|
|
512
|
+
ui.success("Git hooks installed (auto-sync after pull/merge)");
|
|
513
|
+
} else {
|
|
514
|
+
ui.warn("Git hooks not installed (not a git repo or no .git/ found) \u2014 run `haive install-hooks` manually");
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
ui.info("Building code-map\u2026");
|
|
518
|
+
const map = await buildCodeMap2(root);
|
|
519
|
+
await saveCodeMap2(paths, map);
|
|
520
|
+
ui.success(`Code-map built (${Object.keys(map.files).length} files)`);
|
|
521
|
+
} catch {
|
|
522
|
+
ui.warn("Code-map build failed \u2014 run `haive index code` manually");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
473
526
|
console.log();
|
|
474
|
-
|
|
475
|
-
|
|
527
|
+
if (autopilot) {
|
|
528
|
+
console.log(ui.bold("Autopilot mode is ON \u2014 hAIve runs itself:"));
|
|
529
|
+
console.log(ui.dim(" \u2713 Memories go directly to validated (no approval needed)"));
|
|
530
|
+
console.log(ui.dim(" \u2713 Proposed memories auto-approve after 72h without rejection"));
|
|
531
|
+
console.log(ui.dim(" \u2713 Session recap saved automatically when the AI session closes"));
|
|
532
|
+
console.log(ui.dim(" \u2713 Code-map refreshes automatically after every pull"));
|
|
533
|
+
console.log(ui.dim(" \u2713 Git hooks installed (auto-sync after pull/merge)"));
|
|
534
|
+
console.log(ui.dim(" \u2713 CI workflow created (pr-stale-check + sync-on-merge)"));
|
|
535
|
+
console.log();
|
|
536
|
+
console.log(ui.bold("One remaining step:"));
|
|
537
|
+
console.log(" In your AI client, invoke the MCP prompt: " + ui.bold("bootstrap_project"));
|
|
538
|
+
console.log(ui.dim(" This fills .ai/project-context.md \u2014 only needed once."));
|
|
539
|
+
} else {
|
|
540
|
+
console.log(ui.bold("Next steps:"));
|
|
541
|
+
console.log(ui.dim(" 1. Fill project context \u2014 let your AI agent do it:"));
|
|
542
|
+
console.log(" " + ui.bold("In your AI client (Claude, Cursor\u2026), invoke the MCP prompt: bootstrap_project"));
|
|
543
|
+
console.log(ui.dim(" This analyzes your codebase and writes .ai/project-context.md automatically."));
|
|
544
|
+
console.log();
|
|
545
|
+
console.log(ui.dim(" 2. Point your AI client at the MCP server:"));
|
|
546
|
+
console.log(` haive-mcp --root ${root}`);
|
|
547
|
+
console.log();
|
|
548
|
+
console.log(ui.dim(" 3. Start every AI session with:"));
|
|
549
|
+
console.log(" " + ui.bold("get_briefing({ task: '\u2026what you are about to do\u2026' })"));
|
|
550
|
+
console.log();
|
|
551
|
+
console.log(ui.dim(" Tip: run `haive init --autopilot` for zero-friction mode (no manual steps)."));
|
|
552
|
+
}
|
|
476
553
|
});
|
|
477
554
|
}
|
|
478
555
|
async function writeBridge(root, relPath) {
|
|
@@ -578,7 +655,7 @@ function locateMcpBin() {
|
|
|
578
655
|
}
|
|
579
656
|
|
|
580
657
|
// src/commands/sync.ts
|
|
581
|
-
import { spawnSync } from "child_process";
|
|
658
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
582
659
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
583
660
|
import { existsSync as existsSync6 } from "fs";
|
|
584
661
|
import path6 from "path";
|
|
@@ -589,6 +666,8 @@ import {
|
|
|
589
666
|
getUsage,
|
|
590
667
|
isAutoPromoteEligible,
|
|
591
668
|
isDecaying,
|
|
669
|
+
loadCodeMap as loadCodeMap2,
|
|
670
|
+
loadConfig,
|
|
592
671
|
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
593
672
|
loadUsageIndex,
|
|
594
673
|
resolveHaivePaths as resolveHaivePaths5,
|
|
@@ -615,9 +694,13 @@ function registerSync(program2) {
|
|
|
615
694
|
const log = (msg) => {
|
|
616
695
|
if (!opts.quiet) console.log(msg);
|
|
617
696
|
};
|
|
697
|
+
const config = await loadConfig(paths);
|
|
698
|
+
const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
|
|
699
|
+
const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads;
|
|
618
700
|
let staleMarked = 0;
|
|
619
701
|
let revalidated = 0;
|
|
620
702
|
let promoted = 0;
|
|
703
|
+
let autoApproved = 0;
|
|
621
704
|
if (opts.verify !== false) {
|
|
622
705
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
623
706
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -682,21 +765,39 @@ function registerSync(program2) {
|
|
|
682
765
|
if (opts.promote !== false) {
|
|
683
766
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
684
767
|
const usage = await loadUsageIndex(paths);
|
|
768
|
+
const nowMs = Date.now();
|
|
685
769
|
for (const { memory: memory2, filePath } of memories) {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
770
|
+
const fm = memory2.frontmatter;
|
|
771
|
+
if (fm.type === "session_recap") continue;
|
|
772
|
+
if (isAutoPromoteEligible(fm, getUsage(usage, fm.id), {
|
|
773
|
+
minReads: autoPromoteMinReads,
|
|
774
|
+
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
775
|
+
})) {
|
|
691
776
|
await writeFile3(
|
|
692
777
|
filePath,
|
|
693
|
-
serializeMemory({
|
|
694
|
-
frontmatter: { ...memory2.frontmatter, status: "validated" },
|
|
695
|
-
body: memory2.body
|
|
696
|
-
}),
|
|
778
|
+
serializeMemory({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
697
779
|
"utf8"
|
|
698
780
|
);
|
|
699
781
|
promoted++;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
785
|
+
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
786
|
+
if (ageHours >= autoApproveDelayHours) {
|
|
787
|
+
await writeFile3(
|
|
788
|
+
filePath,
|
|
789
|
+
serializeMemory({
|
|
790
|
+
frontmatter: {
|
|
791
|
+
...fm,
|
|
792
|
+
status: "validated",
|
|
793
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
794
|
+
},
|
|
795
|
+
body: memory2.body
|
|
796
|
+
}),
|
|
797
|
+
"utf8"
|
|
798
|
+
);
|
|
799
|
+
autoApproved++;
|
|
800
|
+
}
|
|
700
801
|
}
|
|
701
802
|
}
|
|
702
803
|
}
|
|
@@ -705,8 +806,9 @@ function registerSync(program2) {
|
|
|
705
806
|
(m) => m.memory.frontmatter.status === "draft"
|
|
706
807
|
);
|
|
707
808
|
const draftCount = draftMemories.length;
|
|
809
|
+
const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
|
|
708
810
|
log(
|
|
709
|
-
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
811
|
+
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
710
812
|
);
|
|
711
813
|
if (!opts.quiet && draftCount > 0) {
|
|
712
814
|
log(
|
|
@@ -751,6 +853,43 @@ function registerSync(program2) {
|
|
|
751
853
|
}
|
|
752
854
|
}
|
|
753
855
|
}
|
|
856
|
+
const existingMap = await loadCodeMap2(paths);
|
|
857
|
+
if (existingMap) {
|
|
858
|
+
const mapAge = new Date(existingMap.generated_at).getTime();
|
|
859
|
+
const gitResult = spawnSync2(
|
|
860
|
+
"git",
|
|
861
|
+
[
|
|
862
|
+
"diff",
|
|
863
|
+
"--name-only",
|
|
864
|
+
"--diff-filter=ACMR",
|
|
865
|
+
`@{${new Date(mapAge).toISOString()}}..HEAD`,
|
|
866
|
+
"--",
|
|
867
|
+
"*.ts",
|
|
868
|
+
"*.tsx",
|
|
869
|
+
"*.js",
|
|
870
|
+
"*.jsx",
|
|
871
|
+
"*.java",
|
|
872
|
+
"*.kt",
|
|
873
|
+
"*.py",
|
|
874
|
+
"*.go",
|
|
875
|
+
"*.rs",
|
|
876
|
+
"*.cs",
|
|
877
|
+
"*.php"
|
|
878
|
+
],
|
|
879
|
+
{ cwd: root, encoding: "utf8" }
|
|
880
|
+
);
|
|
881
|
+
const changedSourceFiles = (gitResult.stdout ?? "").trim();
|
|
882
|
+
if (changedSourceFiles.length > 0) {
|
|
883
|
+
try {
|
|
884
|
+
const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
|
|
885
|
+
log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
|
|
886
|
+
const newMap = await buildCodeMap3(root);
|
|
887
|
+
await saveCodeMap3(paths, newMap);
|
|
888
|
+
log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
754
893
|
if (opts.embed) {
|
|
755
894
|
try {
|
|
756
895
|
const emb = await import("@hiveai/embeddings");
|
|
@@ -819,7 +958,7 @@ ${BRIDGE_END}`;
|
|
|
819
958
|
}
|
|
820
959
|
}
|
|
821
960
|
function collectSinceChanges(root, ref) {
|
|
822
|
-
const result =
|
|
961
|
+
const result = spawnSync2(
|
|
823
962
|
"git",
|
|
824
963
|
["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
|
|
825
964
|
{ encoding: "utf8" }
|
|
@@ -2129,17 +2268,124 @@ function registerMemoryImport(memory2) {
|
|
|
2129
2268
|
});
|
|
2130
2269
|
}
|
|
2131
2270
|
|
|
2132
|
-
// src/commands/
|
|
2133
|
-
import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
|
|
2271
|
+
// src/commands/memory-digest.ts
|
|
2134
2272
|
import { existsSync as existsSync25 } from "fs";
|
|
2273
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
2135
2274
|
import path23 from "path";
|
|
2136
2275
|
import "commander";
|
|
2137
2276
|
import {
|
|
2138
|
-
|
|
2277
|
+
deriveConfidence as deriveConfidence4,
|
|
2139
2278
|
findProjectRoot as findProjectRoot27,
|
|
2279
|
+
getUsage as getUsage8,
|
|
2140
2280
|
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
2281
|
+
loadUsageIndex as loadUsageIndex10,
|
|
2282
|
+
resolveHaivePaths as resolveHaivePaths24
|
|
2283
|
+
} from "@hiveai/core";
|
|
2284
|
+
var CONFIDENCE_EMOJI = {
|
|
2285
|
+
unverified: "\u2B1C",
|
|
2286
|
+
low: "\u{1F7E1}",
|
|
2287
|
+
trusted: "\u{1F7E2}",
|
|
2288
|
+
authoritative: "\u2B50",
|
|
2289
|
+
stale: "\u{1F534}"
|
|
2290
|
+
};
|
|
2291
|
+
function registerMemoryDigest(program2) {
|
|
2292
|
+
program2.command("digest").description(
|
|
2293
|
+
"Generate a Markdown review digest of recently added/updated memories (default: last 7 days)"
|
|
2294
|
+
).option("--days <n>", "look-back window in days", "7").option("--scope <scope>", "personal | team | module | all", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2295
|
+
const root = findProjectRoot27(opts.dir);
|
|
2296
|
+
const paths = resolveHaivePaths24(root);
|
|
2297
|
+
if (!existsSync25(paths.memoriesDir)) {
|
|
2298
|
+
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
2299
|
+
process.exitCode = 1;
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const days = Math.max(1, Number(opts.days ?? 7));
|
|
2303
|
+
const scopeFilter = opts.scope ?? "team";
|
|
2304
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
2305
|
+
const all = await loadMemoriesFromDir5(paths.memoriesDir);
|
|
2306
|
+
const usage = await loadUsageIndex10(paths);
|
|
2307
|
+
const recent = all.filter(({ memory: mem }) => {
|
|
2308
|
+
const fm = mem.frontmatter;
|
|
2309
|
+
if (fm.type === "session_recap") return false;
|
|
2310
|
+
if (fm.status === "rejected" || fm.status === "deprecated") return false;
|
|
2311
|
+
if (scopeFilter !== "all" && fm.scope !== scopeFilter) return false;
|
|
2312
|
+
return new Date(fm.created_at) >= cutoff;
|
|
2313
|
+
});
|
|
2314
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2315
|
+
const lines = [
|
|
2316
|
+
`# hAIve Memory Digest \u2014 ${now}`,
|
|
2317
|
+
``,
|
|
2318
|
+
`> **Period:** last ${days} day${days > 1 ? "s" : ""} | **Scope:** ${scopeFilter} | **Total:** ${recent.length} memor${recent.length === 1 ? "y" : "ies"}`,
|
|
2319
|
+
``,
|
|
2320
|
+
`---`,
|
|
2321
|
+
``
|
|
2322
|
+
];
|
|
2323
|
+
if (recent.length === 0) {
|
|
2324
|
+
lines.push(`_No new memories in the last ${days} days._`);
|
|
2325
|
+
} else {
|
|
2326
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2327
|
+
for (const m of recent) {
|
|
2328
|
+
const t = m.memory.frontmatter.type;
|
|
2329
|
+
if (!byType.has(t)) byType.set(t, []);
|
|
2330
|
+
byType.get(t).push(m);
|
|
2331
|
+
}
|
|
2332
|
+
for (const [type, mems] of byType) {
|
|
2333
|
+
lines.push(`## ${type.charAt(0).toUpperCase() + type.slice(1)} (${mems.length})`);
|
|
2334
|
+
lines.push(``);
|
|
2335
|
+
for (const { memory: mem } of mems) {
|
|
2336
|
+
const fm = mem.frontmatter;
|
|
2337
|
+
const u = getUsage8(usage, fm.id);
|
|
2338
|
+
const confidence = deriveConfidence4(fm, u);
|
|
2339
|
+
const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
|
|
2340
|
+
const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
|
|
2341
|
+
lines.push(`### ${emoji} \`${fm.id}\``);
|
|
2342
|
+
lines.push(``);
|
|
2343
|
+
lines.push(`| Field | Value |`);
|
|
2344
|
+
lines.push(`|---|---|`);
|
|
2345
|
+
lines.push(`| **Status** | \`${fm.status}\` |`);
|
|
2346
|
+
lines.push(`| **Confidence** | ${confidence} |`);
|
|
2347
|
+
lines.push(`| **Scope** | ${fm.scope}${fm.module ? `/${fm.module}` : ""} |`);
|
|
2348
|
+
lines.push(`| **Tags** | ${fm.tags.length > 0 ? fm.tags.map((t) => `\`${t}\``).join(", ") : "_none_"} |`);
|
|
2349
|
+
lines.push(`| **Anchor** | ${anchor} |`);
|
|
2350
|
+
lines.push(`| **Reads** | ${u.read_count} |`);
|
|
2351
|
+
lines.push(`| **Created** | ${fm.created_at.slice(0, 10)} |`);
|
|
2352
|
+
lines.push(``);
|
|
2353
|
+
const bodyPreview = mem.body.split("\n").slice(0, 6).join("\n").trim();
|
|
2354
|
+
lines.push(bodyPreview);
|
|
2355
|
+
lines.push(``);
|
|
2356
|
+
lines.push(`**Action:** [ ] approve [ ] reject [ ] keep as-is`);
|
|
2357
|
+
lines.push(``);
|
|
2358
|
+
lines.push(`---`);
|
|
2359
|
+
lines.push(``);
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
lines.push(``);
|
|
2364
|
+
lines.push(
|
|
2365
|
+
`> _To take action: \`haive memory approve <id>\`, \`haive memory reject <id>\`, or open \`haive tui\` for interactive review._`
|
|
2366
|
+
);
|
|
2367
|
+
const digest = lines.join("\n");
|
|
2368
|
+
if (opts.out) {
|
|
2369
|
+
const outPath = path23.resolve(process.cwd(), opts.out);
|
|
2370
|
+
await writeFile12(outPath, digest, "utf8");
|
|
2371
|
+
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
2372
|
+
} else {
|
|
2373
|
+
console.log(digest);
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// src/commands/session-end.ts
|
|
2379
|
+
import { writeFile as writeFile13, mkdir as mkdir6 } from "fs/promises";
|
|
2380
|
+
import { existsSync as existsSync26 } from "fs";
|
|
2381
|
+
import path24 from "path";
|
|
2382
|
+
import "commander";
|
|
2383
|
+
import {
|
|
2384
|
+
buildFrontmatter as buildFrontmatter3,
|
|
2385
|
+
findProjectRoot as findProjectRoot28,
|
|
2386
|
+
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
2141
2387
|
memoryFilePath as memoryFilePath4,
|
|
2142
|
-
resolveHaivePaths as
|
|
2388
|
+
resolveHaivePaths as resolveHaivePaths25,
|
|
2143
2389
|
serializeMemory as serializeMemory10
|
|
2144
2390
|
} from "@hiveai/core";
|
|
2145
2391
|
function buildRecapBody(opts) {
|
|
@@ -2172,9 +2418,9 @@ function recapTopic(scope, module) {
|
|
|
2172
2418
|
}
|
|
2173
2419
|
function registerSessionEnd(session2) {
|
|
2174
2420
|
session2.command("end").description("Save a structured end-of-session recap (goal / accomplished / discoveries / next steps)").requiredOption("--goal <text>", "What you were trying to accomplish (1\u20132 sentences)").requiredOption("--accomplished <text>", "What was actually done (bullet list recommended)").option("--discoveries <text>", "Bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "Key files touched, comma-separated").option("--next <text>", "What should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2175
|
-
const root =
|
|
2176
|
-
const paths =
|
|
2177
|
-
if (!
|
|
2421
|
+
const root = findProjectRoot28(opts.dir);
|
|
2422
|
+
const paths = resolveHaivePaths25(root);
|
|
2423
|
+
if (!existsSync26(paths.haiveDir)) {
|
|
2178
2424
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
2179
2425
|
process.exitCode = 1;
|
|
2180
2426
|
return;
|
|
@@ -2183,13 +2429,13 @@ function registerSessionEnd(session2) {
|
|
|
2183
2429
|
const body = buildRecapBody(opts);
|
|
2184
2430
|
const topic = recapTopic(scope, opts.module);
|
|
2185
2431
|
const filesTouched = parseCsv5(opts.files);
|
|
2186
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
2432
|
+
const missingPaths = filesTouched.filter((p) => !existsSync26(path24.resolve(root, p)));
|
|
2187
2433
|
if (missingPaths.length > 0) {
|
|
2188
2434
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
2189
2435
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
2190
2436
|
}
|
|
2191
|
-
if (
|
|
2192
|
-
const existing = await
|
|
2437
|
+
if (existsSync26(paths.memoriesDir)) {
|
|
2438
|
+
const existing = await loadMemoriesFromDir6(paths.memoriesDir);
|
|
2193
2439
|
const topicMatch = existing.find(
|
|
2194
2440
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
2195
2441
|
);
|
|
@@ -2204,9 +2450,9 @@ function registerSessionEnd(session2) {
|
|
|
2204
2450
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
2205
2451
|
}
|
|
2206
2452
|
};
|
|
2207
|
-
await
|
|
2453
|
+
await writeFile13(topicMatch.filePath, serializeMemory10({ frontmatter: newFrontmatter, body }), "utf8");
|
|
2208
2454
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
2209
|
-
ui.info(`id=${fm.id} file=${
|
|
2455
|
+
ui.info(`id=${fm.id} file=${path24.relative(root, topicMatch.filePath)}`);
|
|
2210
2456
|
return;
|
|
2211
2457
|
}
|
|
2212
2458
|
}
|
|
@@ -2221,10 +2467,10 @@ function registerSessionEnd(session2) {
|
|
|
2221
2467
|
status: "validated"
|
|
2222
2468
|
});
|
|
2223
2469
|
const file = memoryFilePath4(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
2224
|
-
await mkdir6(
|
|
2225
|
-
await
|
|
2470
|
+
await mkdir6(path24.dirname(file), { recursive: true });
|
|
2471
|
+
await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
2226
2472
|
ui.success(`Session recap created`);
|
|
2227
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
2473
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path24.relative(root, file)}`);
|
|
2228
2474
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
2229
2475
|
});
|
|
2230
2476
|
}
|
|
@@ -2234,8 +2480,8 @@ function parseCsv5(value) {
|
|
|
2234
2480
|
}
|
|
2235
2481
|
|
|
2236
2482
|
// src/index.ts
|
|
2237
|
-
var program = new
|
|
2238
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2
|
|
2483
|
+
var program = new Command29();
|
|
2484
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.2");
|
|
2239
2485
|
registerInit(program);
|
|
2240
2486
|
registerMcp(program);
|
|
2241
2487
|
registerBriefing(program);
|
|
@@ -2263,6 +2509,7 @@ registerMemoryUpdate(memory);
|
|
|
2263
2509
|
registerMemoryHot(memory);
|
|
2264
2510
|
registerMemoryTried(memory);
|
|
2265
2511
|
registerMemoryImport(memory);
|
|
2512
|
+
registerMemoryDigest(memory);
|
|
2266
2513
|
var session = program.command("session").description("Manage session lifecycle");
|
|
2267
2514
|
registerSessionEnd(session);
|
|
2268
2515
|
program.parseAsync(process.argv).catch((err) => {
|