@hiveai/cli 0.3.0 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -53,8 +53,8 @@ var ui = {
|
|
|
53
53
|
// src/commands/briefing.ts
|
|
54
54
|
function registerBriefing(program2) {
|
|
55
55
|
program2.command("briefing").description(
|
|
56
|
-
|
|
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 (
|
|
56
|
+
'Print the full project briefing: last session recap + project context + relevant memories.\n Equivalent to calling get_briefing via MCP. Run before starting any task.\n\n Examples:\n haive briefing\n haive briefing --task "add Stripe payment" --files src/payments/PaymentService.ts\n haive briefing --symbols PaymentService,TenantFilter # look up where symbols live\n'
|
|
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 (surfaces anchored memories)").option("--symbols <csv>", "symbol names to look up in the code-map (e.g. PaymentService,TenantFilter) \u2014 requires haive index code").option("--max-memories <n>", "cap on memories surfaced", "10").option(
|
|
58
58
|
"--scope <scope>",
|
|
59
59
|
"personal | team | module | all (default: team)",
|
|
60
60
|
"team"
|
|
@@ -204,7 +204,9 @@ function parseCsv(value) {
|
|
|
204
204
|
import "commander";
|
|
205
205
|
import { findProjectRoot as findProjectRoot2 } from "@hiveai/core";
|
|
206
206
|
function registerTui(program2) {
|
|
207
|
-
program2.command("tui").description(
|
|
207
|
+
program2.command("tui").description(
|
|
208
|
+
"Interactive terminal dashboard for browsing and managing memories.\n\n Screens (switch with 1 / 2 / 3):\n 1 \u2014 Memories: list + preview, filter by status (Tab), actions (a/r/p/d)\n 2 \u2014 Health: stale, pending review, anchorless memories\n 3 \u2014 Stats: most-read, decaying, total counts\n\n Key bindings:\n \u2191 \u2193 navigate list\n Tab cycle status filter (all \u2192 proposed \u2192 validated \u2192 stale)\n a approve selected memory\n r reject selected memory\n p promote personal \u2192 team (proposed)\n d delete selected memory\n q / Esc exit\n"
|
|
209
|
+
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
208
210
|
if (!process.stdout.isTTY) {
|
|
209
211
|
console.error("haive tui requires an interactive terminal (TTY).");
|
|
210
212
|
process.exitCode = 1;
|
|
@@ -213,7 +215,7 @@ function registerTui(program2) {
|
|
|
213
215
|
const root = findProjectRoot2(opts.dir);
|
|
214
216
|
const { render } = await import("ink");
|
|
215
217
|
const { createElement } = await import("react");
|
|
216
|
-
const { Dashboard } = await import("./Dashboard-
|
|
218
|
+
const { Dashboard } = await import("./Dashboard-Y2AIWFZK.js");
|
|
217
219
|
const { waitUntilExit } = render(createElement(Dashboard, { root }));
|
|
218
220
|
await waitUntilExit();
|
|
219
221
|
});
|
|
@@ -303,9 +305,13 @@ import {
|
|
|
303
305
|
saveCodeMap
|
|
304
306
|
} from "@hiveai/core";
|
|
305
307
|
function registerIndexCode(program2) {
|
|
306
|
-
const idx = program2.command("index").description(
|
|
308
|
+
const idx = program2.command("index").description(
|
|
309
|
+
"Build local indexes that let AIs look up symbols instead of grepping.\n\n Run once after init, then haive sync refreshes it automatically when source changes."
|
|
310
|
+
);
|
|
307
311
|
idx.action(() => idx.help());
|
|
308
|
-
idx.command("code").description(
|
|
312
|
+
idx.command("code").description(
|
|
313
|
+
"Scan source files and write .ai/code-map.json (file \u2192 exports + 1-line description).\n\n Supported languages: TypeScript, JavaScript, Java, Python, Go, Rust, C#, PHP.\n The map is used by:\n \u2022 get_briefing (symbol_locations) \u2014 look up where a class/function lives\n \u2022 code_map MCP tool \u2014 browse exports without grepping\n \u2022 haive briefing --symbols \u2014 look up symbols from the CLI\n\n Run automatically by haive init (autopilot mode) and haive sync (if source changed).\n\n Example:\n haive index code\n haive index code --exclude generated,proto\n"
|
|
314
|
+
).option("-d, --dir <dir>", "project root").option(
|
|
309
315
|
"--exclude <csv>",
|
|
310
316
|
"extra directory names to skip (comma-separated)",
|
|
311
317
|
""
|
|
@@ -341,8 +347,15 @@ function registerIndexCode(program2) {
|
|
|
341
347
|
import { mkdir, writeFile } from "fs/promises";
|
|
342
348
|
import { existsSync as existsSync3 } from "fs";
|
|
343
349
|
import path3 from "path";
|
|
350
|
+
import { spawnSync } from "child_process";
|
|
344
351
|
import "commander";
|
|
345
|
-
import {
|
|
352
|
+
import {
|
|
353
|
+
AUTOPILOT_DEFAULTS,
|
|
354
|
+
buildCodeMap as buildCodeMap2,
|
|
355
|
+
resolveHaivePaths as resolveHaivePaths4,
|
|
356
|
+
saveCodeMap as saveCodeMap2,
|
|
357
|
+
saveConfig
|
|
358
|
+
} from "@hiveai/core";
|
|
346
359
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
347
360
|
|
|
348
361
|
> Generated by \`haive init\`. Edit this file (or let your AI agent fill it via the upcoming MCP \`bootstrap_project\` tool).
|
|
@@ -451,9 +464,15 @@ jobs:
|
|
|
451
464
|
});
|
|
452
465
|
`;
|
|
453
466
|
function registerInit(program2) {
|
|
454
|
-
program2.command("init").description(
|
|
467
|
+
program2.command("init").description(
|
|
468
|
+
"Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Add --manual if you want to control memory approval and session recaps yourself."
|
|
469
|
+
).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) \u2014 included automatically in autopilot mode").option(
|
|
470
|
+
"--manual",
|
|
471
|
+
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
472
|
+
).action(async (opts) => {
|
|
455
473
|
const root = path3.resolve(opts.dir);
|
|
456
474
|
const paths = resolveHaivePaths4(root);
|
|
475
|
+
const autopilot = opts.manual !== true;
|
|
457
476
|
if (existsSync3(paths.haiveDir)) {
|
|
458
477
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
459
478
|
}
|
|
@@ -465,12 +484,22 @@ function registerInit(program2) {
|
|
|
465
484
|
await writeFile(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
466
485
|
ui.success(`Created ${path3.relative(root, paths.projectContext)}`);
|
|
467
486
|
}
|
|
487
|
+
const configExists = existsSync3(
|
|
488
|
+
path3.join(paths.haiveDir, "haive.config.json")
|
|
489
|
+
);
|
|
490
|
+
if (!configExists) {
|
|
491
|
+
await saveConfig(paths, autopilot ? AUTOPILOT_DEFAULTS : { autopilot: false });
|
|
492
|
+
ui.success(
|
|
493
|
+
`Created .ai/haive.config.json (mode: ${autopilot ? "autopilot" : "standard"})`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
468
496
|
if (opts.bridges) {
|
|
469
497
|
await writeBridge(root, "CLAUDE.md");
|
|
470
498
|
await writeBridge(root, ".cursorrules");
|
|
471
499
|
await writeBridge(root, path3.join(".github", "copilot-instructions.md"));
|
|
472
500
|
}
|
|
473
|
-
|
|
501
|
+
const wantCi = opts.withCi || autopilot;
|
|
502
|
+
if (wantCi) {
|
|
474
503
|
const ciPath = path3.join(root, ".github", "workflows", "haive-sync.yml");
|
|
475
504
|
if (existsSync3(ciPath)) {
|
|
476
505
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
@@ -480,29 +509,55 @@ function registerInit(program2) {
|
|
|
480
509
|
ui.success(`Created ${path3.relative(root, ciPath)}`);
|
|
481
510
|
}
|
|
482
511
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
512
|
+
if (autopilot) {
|
|
513
|
+
const haiveBin = process.argv[1];
|
|
514
|
+
const hookResult = spawnSync(
|
|
515
|
+
process.execPath,
|
|
516
|
+
[haiveBin, "install-hooks", "--dir", root],
|
|
517
|
+
{ encoding: "utf8" }
|
|
518
|
+
);
|
|
519
|
+
if (hookResult.status === 0) {
|
|
520
|
+
ui.success("Git hooks installed (auto-sync after pull/merge)");
|
|
521
|
+
} else {
|
|
522
|
+
ui.warn("Git hooks not installed (not a git repo or no .git/ found) \u2014 run `haive install-hooks` manually");
|
|
523
|
+
}
|
|
524
|
+
try {
|
|
525
|
+
ui.info("Building code-map\u2026");
|
|
526
|
+
const map = await buildCodeMap2(root);
|
|
527
|
+
await saveCodeMap2(paths, map);
|
|
528
|
+
ui.success(`Code-map built (${Object.keys(map.files).length} files)`);
|
|
529
|
+
} catch {
|
|
530
|
+
ui.warn("Code-map build failed \u2014 run `haive index code` manually");
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
ui.success(`hAIve initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
503
534
|
console.log();
|
|
504
|
-
|
|
505
|
-
|
|
535
|
+
if (autopilot) {
|
|
536
|
+
console.log(ui.bold("Autopilot mode is ON \u2014 hAIve runs itself:"));
|
|
537
|
+
console.log(ui.dim(" \u2713 Memories go directly to validated (no approval needed)"));
|
|
538
|
+
console.log(ui.dim(" \u2713 Proposed memories auto-approve after 72h without rejection"));
|
|
539
|
+
console.log(ui.dim(" \u2713 Session recap saved automatically when the AI session closes"));
|
|
540
|
+
console.log(ui.dim(" \u2713 Code-map refreshes automatically after every pull"));
|
|
541
|
+
console.log(ui.dim(" \u2713 Git hooks installed (auto-sync after pull/merge)"));
|
|
542
|
+
console.log(ui.dim(" \u2713 CI workflow created (pr-stale-check + sync-on-merge)"));
|
|
543
|
+
console.log();
|
|
544
|
+
console.log(ui.bold("One remaining step:"));
|
|
545
|
+
console.log(" In your AI client, invoke the MCP prompt: " + ui.bold("bootstrap_project"));
|
|
546
|
+
console.log(ui.dim(" This fills .ai/project-context.md \u2014 only needed once."));
|
|
547
|
+
} else {
|
|
548
|
+
console.log(ui.bold("Next steps:"));
|
|
549
|
+
console.log(ui.dim(" 1. Fill project context \u2014 let your AI agent do it:"));
|
|
550
|
+
console.log(" " + ui.bold("In your AI client (Claude, Cursor\u2026), invoke the MCP prompt: bootstrap_project"));
|
|
551
|
+
console.log(ui.dim(" This analyzes your codebase and writes .ai/project-context.md automatically."));
|
|
552
|
+
console.log();
|
|
553
|
+
console.log(ui.dim(" 2. Point your AI client at the MCP server:"));
|
|
554
|
+
console.log(` haive-mcp --root ${root}`);
|
|
555
|
+
console.log();
|
|
556
|
+
console.log(ui.dim(" 3. Start every AI session with:"));
|
|
557
|
+
console.log(" " + ui.bold("get_briefing({ task: '\u2026what you are about to do\u2026' })"));
|
|
558
|
+
console.log();
|
|
559
|
+
console.log(ui.dim(" Tip: run `haive init` (without --manual) for zero-friction autopilot mode."));
|
|
560
|
+
}
|
|
506
561
|
});
|
|
507
562
|
}
|
|
508
563
|
async function writeBridge(root, relPath) {
|
|
@@ -536,7 +591,9 @@ fi
|
|
|
536
591
|
`;
|
|
537
592
|
var HOOKS = ["post-merge", "post-rewrite"];
|
|
538
593
|
function registerInstallHooks(program2) {
|
|
539
|
-
program2.command("install-hooks").description(
|
|
594
|
+
program2.command("install-hooks").description(
|
|
595
|
+
"Install git hooks so haive sync runs automatically after every pull or merge.\n\n Installs a post-merge hook at .git/hooks/post-merge that runs:\n haive sync --quiet --since ORIG_HEAD --embed\n\n This ensures memory anchors are always verified and the embeddings index\n is kept fresh without requiring any manual steps.\n\n Installed automatically by haive init (autopilot mode).\n Use --force to overwrite an existing post-merge hook.\n"
|
|
596
|
+
).option("-d, --dir <dir>", "project root").option("--force", "overwrite existing hooks").action(async (opts) => {
|
|
540
597
|
const root = findProjectRoot6(opts.dir);
|
|
541
598
|
const gitDir = path4.join(root, ".git");
|
|
542
599
|
if (!existsSync4(gitDir)) {
|
|
@@ -577,7 +634,20 @@ import "commander";
|
|
|
577
634
|
import { findProjectRoot as findProjectRoot7 } from "@hiveai/core";
|
|
578
635
|
var require2 = createRequire(import.meta.url);
|
|
579
636
|
function registerMcp(program2) {
|
|
580
|
-
program2.command("mcp").description(
|
|
637
|
+
program2.command("mcp").description(
|
|
638
|
+
`Start the hAIve MCP server (stdio transport) for direct testing.
|
|
639
|
+
|
|
640
|
+
In normal use, your AI client (Claude Code, Cursor, VS Code) starts the server
|
|
641
|
+
automatically via the haive-mcp binary listed in your MCP config file.
|
|
642
|
+
|
|
643
|
+
This command is useful for debugging or for clients that require manual startup.
|
|
644
|
+
|
|
645
|
+
Client config examples (point at haive-mcp binary, not this command):
|
|
646
|
+
Claude Code: ~/.claude.json \u2192 mcpServers.haive.command = "haive-mcp"
|
|
647
|
+
Cursor: ~/.cursor/mcp.json \u2192 mcpServers.haive.command = "haive-mcp"
|
|
648
|
+
VS Code: code --add-mcp '{"name":"haive","command":"haive-mcp",...}'
|
|
649
|
+
`
|
|
650
|
+
).option("-d, --dir <dir>", "project root (defaults to nearest .ai/ or .git/)").action((opts) => {
|
|
581
651
|
const root = findProjectRoot7(opts.dir);
|
|
582
652
|
const bin = locateMcpBin();
|
|
583
653
|
if (!bin) {
|
|
@@ -608,7 +678,7 @@ function locateMcpBin() {
|
|
|
608
678
|
}
|
|
609
679
|
|
|
610
680
|
// src/commands/sync.ts
|
|
611
|
-
import { spawnSync } from "child_process";
|
|
681
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
612
682
|
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
613
683
|
import { existsSync as existsSync6 } from "fs";
|
|
614
684
|
import path6 from "path";
|
|
@@ -619,6 +689,8 @@ import {
|
|
|
619
689
|
getUsage,
|
|
620
690
|
isAutoPromoteEligible,
|
|
621
691
|
isDecaying,
|
|
692
|
+
loadCodeMap as loadCodeMap2,
|
|
693
|
+
loadConfig,
|
|
622
694
|
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
623
695
|
loadUsageIndex,
|
|
624
696
|
resolveHaivePaths as resolveHaivePaths5,
|
|
@@ -628,7 +700,9 @@ import {
|
|
|
628
700
|
var BRIDGE_START = "<!-- haive:memories-start -->";
|
|
629
701
|
var BRIDGE_END = "<!-- haive:memories-end -->";
|
|
630
702
|
function registerSync(program2) {
|
|
631
|
-
program2.command("sync").description(
|
|
703
|
+
program2.command("sync").description(
|
|
704
|
+
"Refresh memory state after a git pull or merge.\n What it does:\n 1. Verifies anchor paths \u2014 marks stale if files/symbols moved or deleted\n 2. Re-validates previously stale memories that are now fresh\n 3. Auto-promotes proposed memories (by usage count or time delay in autopilot)\n 4. Auto-refreshes code-map if source files changed\n 5. Reports decay warnings for memories unused >90 days\n\n Install git hooks to run sync automatically: haive install-hooks\n\n Examples:\n haive sync\n haive sync --since main # also report memories changed since main\n haive sync --embed # also rebuild embeddings index\n"
|
|
705
|
+
).option("-d, --dir <dir>", "project root").option("--quiet", "minimal output (suitable for git hooks)").option(
|
|
632
706
|
"--since <ref>",
|
|
633
707
|
"git ref/commit to compare against; report memories added/modified/removed since"
|
|
634
708
|
).option("--no-verify", "skip the anchor verification step").option("--no-promote", "skip the auto-promotion step").option(
|
|
@@ -645,9 +719,13 @@ function registerSync(program2) {
|
|
|
645
719
|
const log = (msg) => {
|
|
646
720
|
if (!opts.quiet) console.log(msg);
|
|
647
721
|
};
|
|
722
|
+
const config = await loadConfig(paths);
|
|
723
|
+
const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
|
|
724
|
+
const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads;
|
|
648
725
|
let staleMarked = 0;
|
|
649
726
|
let revalidated = 0;
|
|
650
727
|
let promoted = 0;
|
|
728
|
+
let autoApproved = 0;
|
|
651
729
|
if (opts.verify !== false) {
|
|
652
730
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
653
731
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -712,21 +790,39 @@ function registerSync(program2) {
|
|
|
712
790
|
if (opts.promote !== false) {
|
|
713
791
|
const memories = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
714
792
|
const usage = await loadUsageIndex(paths);
|
|
793
|
+
const nowMs = Date.now();
|
|
715
794
|
for (const { memory: memory2, filePath } of memories) {
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
795
|
+
const fm = memory2.frontmatter;
|
|
796
|
+
if (fm.type === "session_recap") continue;
|
|
797
|
+
if (isAutoPromoteEligible(fm, getUsage(usage, fm.id), {
|
|
798
|
+
minReads: autoPromoteMinReads,
|
|
799
|
+
maxRejections: DEFAULT_AUTO_PROMOTE_RULE.maxRejections
|
|
800
|
+
})) {
|
|
721
801
|
await writeFile3(
|
|
722
802
|
filePath,
|
|
723
|
-
serializeMemory({
|
|
724
|
-
frontmatter: { ...memory2.frontmatter, status: "validated" },
|
|
725
|
-
body: memory2.body
|
|
726
|
-
}),
|
|
803
|
+
serializeMemory({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
727
804
|
"utf8"
|
|
728
805
|
);
|
|
729
806
|
promoted++;
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (autoApproveDelayHours !== null && fm.status === "proposed" && fm.scope === "team") {
|
|
810
|
+
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
811
|
+
if (ageHours >= autoApproveDelayHours) {
|
|
812
|
+
await writeFile3(
|
|
813
|
+
filePath,
|
|
814
|
+
serializeMemory({
|
|
815
|
+
frontmatter: {
|
|
816
|
+
...fm,
|
|
817
|
+
status: "validated",
|
|
818
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
819
|
+
},
|
|
820
|
+
body: memory2.body
|
|
821
|
+
}),
|
|
822
|
+
"utf8"
|
|
823
|
+
);
|
|
824
|
+
autoApproved++;
|
|
825
|
+
}
|
|
730
826
|
}
|
|
731
827
|
}
|
|
732
828
|
}
|
|
@@ -735,8 +831,9 @@ function registerSync(program2) {
|
|
|
735
831
|
(m) => m.memory.frontmatter.status === "draft"
|
|
736
832
|
);
|
|
737
833
|
const draftCount = draftMemories.length;
|
|
834
|
+
const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
|
|
738
835
|
log(
|
|
739
|
-
`${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}` : ""}`
|
|
836
|
+
`${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}` : ""}`
|
|
740
837
|
);
|
|
741
838
|
if (!opts.quiet && draftCount > 0) {
|
|
742
839
|
log(
|
|
@@ -781,6 +878,43 @@ function registerSync(program2) {
|
|
|
781
878
|
}
|
|
782
879
|
}
|
|
783
880
|
}
|
|
881
|
+
const existingMap = await loadCodeMap2(paths);
|
|
882
|
+
if (existingMap) {
|
|
883
|
+
const mapAge = new Date(existingMap.generated_at).getTime();
|
|
884
|
+
const gitResult = spawnSync2(
|
|
885
|
+
"git",
|
|
886
|
+
[
|
|
887
|
+
"diff",
|
|
888
|
+
"--name-only",
|
|
889
|
+
"--diff-filter=ACMR",
|
|
890
|
+
`@{${new Date(mapAge).toISOString()}}..HEAD`,
|
|
891
|
+
"--",
|
|
892
|
+
"*.ts",
|
|
893
|
+
"*.tsx",
|
|
894
|
+
"*.js",
|
|
895
|
+
"*.jsx",
|
|
896
|
+
"*.java",
|
|
897
|
+
"*.kt",
|
|
898
|
+
"*.py",
|
|
899
|
+
"*.go",
|
|
900
|
+
"*.rs",
|
|
901
|
+
"*.cs",
|
|
902
|
+
"*.php"
|
|
903
|
+
],
|
|
904
|
+
{ cwd: root, encoding: "utf8" }
|
|
905
|
+
);
|
|
906
|
+
const changedSourceFiles = (gitResult.stdout ?? "").trim();
|
|
907
|
+
if (changedSourceFiles.length > 0) {
|
|
908
|
+
try {
|
|
909
|
+
const { buildCodeMap: buildCodeMap3, saveCodeMap: saveCodeMap3 } = await import("@hiveai/core");
|
|
910
|
+
log(ui.dim("code-map: source files changed \u2014 refreshing index\u2026"));
|
|
911
|
+
const newMap = await buildCodeMap3(root);
|
|
912
|
+
await saveCodeMap3(paths, newMap);
|
|
913
|
+
log(ui.dim(`code-map: refreshed (${Object.keys(newMap.files).length} files)`));
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
784
918
|
if (opts.embed) {
|
|
785
919
|
try {
|
|
786
920
|
const emb = await import("@hiveai/embeddings");
|
|
@@ -849,7 +983,7 @@ ${BRIDGE_END}`;
|
|
|
849
983
|
}
|
|
850
984
|
}
|
|
851
985
|
function collectSinceChanges(root, ref) {
|
|
852
|
-
const result =
|
|
986
|
+
const result = spawnSync2(
|
|
853
987
|
"git",
|
|
854
988
|
["-C", root, "diff", "--name-status", "--diff-filter=AMD", `${ref}...HEAD`, "--", ".ai/memories"],
|
|
855
989
|
{ encoding: "utf8" }
|
|
@@ -883,7 +1017,30 @@ import {
|
|
|
883
1017
|
serializeMemory as serializeMemory2
|
|
884
1018
|
} from "@hiveai/core";
|
|
885
1019
|
function registerMemoryAdd(memory2) {
|
|
886
|
-
memory2.command("add").description(
|
|
1020
|
+
memory2.command("add").description(
|
|
1021
|
+
`Save a piece of knowledge as a persistent memory.
|
|
1022
|
+
|
|
1023
|
+
Memory types:
|
|
1024
|
+
convention \u2014 how things are done here (naming, patterns, tooling)
|
|
1025
|
+
decision \u2014 a choice made and WHY (tradeoffs, constraints)
|
|
1026
|
+
gotcha \u2014 non-obvious behavior that surprises newcomers
|
|
1027
|
+
architecture \u2014 structural overview of a system or module
|
|
1028
|
+
glossary \u2014 domain terms and their meaning in this codebase
|
|
1029
|
+
attempt \u2014 failed approach (prefer 'haive memory tried' for better structure)
|
|
1030
|
+
|
|
1031
|
+
Tips:
|
|
1032
|
+
\u2022 --paths anchors the memory to source files for staleness detection
|
|
1033
|
+
\u2022 --topic enables upsert: future adds with the same topic update the existing memory
|
|
1034
|
+
\u2022 In autopilot mode, memories go directly to validated with team scope by default
|
|
1035
|
+
|
|
1036
|
+
Examples:
|
|
1037
|
+
haive memory add --type gotcha --slug jpa-open-in-view --scope team \\\\
|
|
1038
|
+
--paths src/main/resources/application.properties \\\\
|
|
1039
|
+
--body "spring.jpa.open-in-view=false is intentional \u2014 do not re-enable."
|
|
1040
|
+
haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
|
|
1041
|
+
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
1042
|
+
`
|
|
1043
|
+
).requiredOption("--type <type>", "convention | decision | gotcha | architecture | glossary | attempt").requiredOption("--slug <slug>", "short kebab-case 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 (default: personal, or team in autopilot)", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific 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 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
887
1044
|
const root = findProjectRoot9(opts.dir);
|
|
888
1045
|
const paths = resolveHaivePaths6(root);
|
|
889
1046
|
if (!existsSync7(paths.haiveDir)) {
|
|
@@ -1636,8 +1793,21 @@ import {
|
|
|
1636
1793
|
} from "@hiveai/core";
|
|
1637
1794
|
function registerMemoryTried(memory2) {
|
|
1638
1795
|
memory2.command("tried").description(
|
|
1639
|
-
|
|
1640
|
-
|
|
1796
|
+
`Record a FAILED approach \u2014 prevents repeated mistakes in future sessions.
|
|
1797
|
+
|
|
1798
|
+
This is the most valuable type of negative knowledge. It surfaces FIRST in
|
|
1799
|
+
get_briefing so agents can't miss it. Auto-validated (no approval cycle).
|
|
1800
|
+
|
|
1801
|
+
Use this immediately when you try something and it fails.
|
|
1802
|
+
|
|
1803
|
+
Example:
|
|
1804
|
+
haive memory tried \\\\
|
|
1805
|
+
--what "importing X with ESM dynamic import" \\\\
|
|
1806
|
+
--why-failed "tsup bundles it as CJS, dynamic import fails at runtime" \\\\
|
|
1807
|
+
--instead "use static import in the entry file" \\\\
|
|
1808
|
+
--paths packages/cli/src/index.ts
|
|
1809
|
+
`
|
|
1810
|
+
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal)", "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) => {
|
|
1641
1811
|
const root = findProjectRoot18(opts.dir);
|
|
1642
1812
|
const paths = resolveHaivePaths15(root);
|
|
1643
1813
|
if (!existsSync16(paths.haiveDir)) {
|
|
@@ -2020,7 +2190,9 @@ import {
|
|
|
2020
2190
|
verifyAnchor as verifyAnchor2
|
|
2021
2191
|
} from "@hiveai/core";
|
|
2022
2192
|
function registerMemoryVerify(memory2) {
|
|
2023
|
-
memory2.command("verify").description(
|
|
2193
|
+
memory2.command("verify").description(
|
|
2194
|
+
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
2195
|
+
).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 back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2024
2196
|
const root = findProjectRoot25(opts.dir);
|
|
2025
2197
|
const paths = resolveHaivePaths22(root);
|
|
2026
2198
|
if (!existsSync23(paths.memoriesDir)) {
|
|
@@ -2181,8 +2353,8 @@ var CONFIDENCE_EMOJI = {
|
|
|
2181
2353
|
};
|
|
2182
2354
|
function registerMemoryDigest(program2) {
|
|
2183
2355
|
program2.command("digest").description(
|
|
2184
|
-
"Generate a Markdown review digest of recently added
|
|
2185
|
-
).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) => {
|
|
2356
|
+
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
2357
|
+
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2186
2358
|
const root = findProjectRoot27(opts.dir);
|
|
2187
2359
|
const paths = resolveHaivePaths24(root);
|
|
2188
2360
|
if (!existsSync25(paths.memoriesDir)) {
|
|
@@ -2308,7 +2480,24 @@ function recapTopic(scope, module) {
|
|
|
2308
2480
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
2309
2481
|
}
|
|
2310
2482
|
function registerSessionEnd(session2) {
|
|
2311
|
-
session2.command("end").description(
|
|
2483
|
+
session2.command("end").description(
|
|
2484
|
+
`Save an end-of-session recap so the NEXT session starts with fresh context.
|
|
2485
|
+
|
|
2486
|
+
One recap per scope is kept and updated in-place (topic-upsert). The next
|
|
2487
|
+
session's get_briefing (or haive briefing) shows it at the very top.
|
|
2488
|
+
|
|
2489
|
+
In autopilot mode, a minimal recap saves automatically on MCP server exit.
|
|
2490
|
+
Calling this manually produces a richer, more actionable recap.
|
|
2491
|
+
|
|
2492
|
+
Example:
|
|
2493
|
+
haive session end \\\\
|
|
2494
|
+
--goal "Add Stripe webhook handler" \\\\
|
|
2495
|
+
--accomplished "Implemented webhook endpoint, added idempotency key" \\\\
|
|
2496
|
+
--discoveries "Missing .env.example entry for STRIPE_WEBHOOK_SECRET" \\\\
|
|
2497
|
+
--files src/payments/WebhookController.ts,src/payments/WebhookService.ts \\\\
|
|
2498
|
+
--next "Add integration tests for webhook signature validation"
|
|
2499
|
+
`
|
|
2500
|
+
).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 (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
2312
2501
|
const root = findProjectRoot28(opts.dir);
|
|
2313
2502
|
const paths = resolveHaivePaths25(root);
|
|
2314
2503
|
if (!existsSync26(paths.haiveDir)) {
|
|
@@ -2372,7 +2561,7 @@ function parseCsv5(value) {
|
|
|
2372
2561
|
|
|
2373
2562
|
// src/index.ts
|
|
2374
2563
|
var program = new Command29();
|
|
2375
|
-
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.
|
|
2564
|
+
program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.3.3");
|
|
2376
2565
|
registerInit(program);
|
|
2377
2566
|
registerMcp(program);
|
|
2378
2567
|
registerBriefing(program);
|