@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/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 Command28 } from "commander";
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-SRPCHP7Z.js");
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 { resolveHaivePaths as resolveHaivePaths4 } from "@hiveai/core";
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)").action(async (opts) => {
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
- if (opts.withCi) {
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
- ui.success(`hAIve initialized at ${root}`);
454
- console.log();
455
- console.log(ui.bold("Next steps:"));
456
- console.log(
457
- ui.dim(" 1. Fill project context \u2014 let your AI agent do it:")
458
- );
459
- console.log(
460
- " " + ui.bold("In your AI client (Claude, Cursor\u2026), invoke the MCP prompt: bootstrap_project")
461
- );
462
- console.log(
463
- ui.dim(" This analyzes your codebase and writes .ai/project-context.md automatically.")
464
- );
465
- console.log(
466
- ui.dim(" Without this step, get_briefing returns a blank template (little value).")
467
- );
468
- console.log();
469
- console.log(ui.dim(" 2. Point your AI client at the MCP server:"));
470
- console.log(
471
- ` haive-mcp --root ${root}`
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
- console.log(ui.dim(" 3. Start every AI session with:"));
475
- console.log(" " + ui.bold("get_briefing({ task: '\u2026what you are about to do\u2026' })"));
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
- if (isAutoPromoteEligible(
687
- memory2.frontmatter,
688
- getUsage(usage, memory2.frontmatter.id),
689
- DEFAULT_AUTO_PROMOTE_RULE
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 = spawnSync(
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/session-end.ts
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
- buildFrontmatter as buildFrontmatter3,
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 &nbsp;&nbsp; [ ] reject &nbsp;&nbsp; [ ] 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 resolveHaivePaths24,
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 = findProjectRoot27(opts.dir);
2176
- const paths = resolveHaivePaths24(root);
2177
- if (!existsSync25(paths.haiveDir)) {
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) => !existsSync25(path23.resolve(root, 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 (existsSync25(paths.memoriesDir)) {
2192
- const existing = await loadMemoriesFromDir5(paths.memoriesDir);
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 writeFile12(topicMatch.filePath, serializeMemory10({ frontmatter: newFrontmatter, body }), "utf8");
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=${path23.relative(root, topicMatch.filePath)}`);
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(path23.dirname(file), { recursive: true });
2225
- await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
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=${path23.relative(root, 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 Command28();
2238
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.2.16");
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) => {