@hiveai/cli 0.9.5 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command44 } from "commander";
4
+ import { Command as Command47 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync } from "fs";
@@ -197,7 +197,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
197
197
  if (!f) continue;
198
198
  counts.set(f, (counts.get(f) ?? 0) + 1);
199
199
  }
200
- let entries = [...counts.entries()].map(([path41, changes]) => ({ path: path41, changes }));
200
+ let entries = [...counts.entries()].map(([path45, changes]) => ({ path: path45, changes }));
201
201
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
202
202
  if (lowerPaths.length > 0) {
203
203
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -843,7 +843,7 @@ async function scanDirs(root, maxDepth = 2) {
843
843
  if (depth > maxDepth) return;
844
844
  let entries;
845
845
  try {
846
- entries = await readdir(dir, { withFileTypes: true });
846
+ entries = await readdir(dir, { withFileTypes: true, encoding: "utf8" });
847
847
  } catch {
848
848
  return;
849
849
  }
@@ -2297,6 +2297,8 @@ var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
2297
2297
  Not team truth. Use for machine-local session notes or tooling scratch files.
2298
2298
  Official memories belong in .ai/memories/ (versioned in Git).
2299
2299
  Only .gitignore and this README are meant to commit; everything else stays untracked.
2300
+
2301
+ Session continuity (local): agents may append \`session-journal.ndjson\` via MCP \`runtime_journal_append\` or \`haive runtime journal append\`.
2300
2302
  `;
2301
2303
  var RUNTIME_GITIGNORE_BODY = `*
2302
2304
  !.gitignore
@@ -2795,7 +2797,11 @@ import {
2795
2797
  serializeMemory as serializeMemory8
2796
2798
  } from "@hiveai/core";
2797
2799
  import { z as z16 } from "zod";
2798
- import { appendUsageEvent, loadConfig as loadConfig2 } from "@hiveai/core";
2800
+ import {
2801
+ appendUsageEvent,
2802
+ appendRuntimeJournalEntry,
2803
+ loadConfig as loadConfig2
2804
+ } from "@hiveai/core";
2799
2805
  import { mkdir as mkdir52, writeFile as writeFile9, rm } from "fs/promises";
2800
2806
  import { existsSync as existsSync16 } from "fs";
2801
2807
  import path72 from "path";
@@ -2900,7 +2906,11 @@ import {
2900
2906
  } from "@hiveai/core";
2901
2907
  import { z as z29 } from "zod";
2902
2908
  import { existsSync as existsSync27 } from "fs";
2903
- import { findLexicalConflictPairs, loadMemoriesFromDir as loadMemoriesFromDir21 } from "@hiveai/core";
2909
+ import {
2910
+ findLexicalConflictPairs,
2911
+ findTopicStatusConflictPairs,
2912
+ loadMemoriesFromDir as loadMemoriesFromDir21
2913
+ } from "@hiveai/core";
2904
2914
  import { z as z30 } from "zod";
2905
2915
  import { resolveProjectInfo } from "@hiveai/core";
2906
2916
  import { z as z31 } from "zod";
@@ -2909,9 +2919,13 @@ import { z as z32 } from "zod";
2909
2919
  import { existsSync as existsSync28 } from "fs";
2910
2920
  import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir222 } from "@hiveai/core";
2911
2921
  import { z as z33 } from "zod";
2922
+ import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hiveai/core";
2912
2923
  import { z as z34 } from "zod";
2924
+ import { readRuntimeJournalTail } from "@hiveai/core";
2913
2925
  import { z as z35 } from "zod";
2914
2926
  import { z as z36 } from "zod";
2927
+ import { z as z37 } from "zod";
2928
+ import { z as z38 } from "zod";
2915
2929
  function createContext(options = {}) {
2916
2930
  const env = options.env ?? process.env;
2917
2931
  const cwd = options.cwd ?? process.cwd();
@@ -3936,6 +3950,14 @@ var SessionTracker = class {
3936
3950
  recapId = result.id;
3937
3951
  } catch {
3938
3952
  }
3953
+ void appendRuntimeJournalEntry(this.ctx.paths, {
3954
+ kind: "session_end",
3955
+ message: recapId ? `auto session close | ${toolSummary} | recap:${recapId}` : `auto session close | ${toolSummary}`,
3956
+ meta: {
3957
+ recap_id: recapId ?? null,
3958
+ total_tool_calls: totalCalls
3959
+ }
3960
+ });
3939
3961
  const ranPostTask = this.events.some(
3940
3962
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
3941
3963
  );
@@ -4600,6 +4622,7 @@ var CodeMapInputSchema = {
4600
4622
  "Approximate token budget for the response. When the matching set exceeds it, files are ranked by export density (exports per LOC) and the highest-signal ones are kept first. Omit to disable budgeting (legacy behavior)."
4601
4623
  )
4602
4624
  };
4625
+ var CodeMapInputZod = z18.object(CodeMapInputSchema);
4603
4626
  async function codeMapTool(input, ctx) {
4604
4627
  const map = await loadCodeMap22(ctx.paths);
4605
4628
  if (!map) {
@@ -5681,12 +5704,16 @@ var MemConflictCandidatesInputSchema = {
5681
5704
  types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
5682
5705
  min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
5683
5706
  max_pairs: z30.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
5684
- max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort.")
5707
+ max_scan: z30.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
5708
+ max_topic_pairs: z30.number().int().positive().max(100).default(20).describe(
5709
+ "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
5710
+ )
5685
5711
  };
5686
5712
  async function memConflictCandidates(input, ctx) {
5687
5713
  if (!existsSync27(ctx.paths.memoriesDir)) {
5688
5714
  return {
5689
5715
  pairs: [],
5716
+ topic_status_pairs: [],
5690
5717
  scanned: 0,
5691
5718
  truncated: false,
5692
5719
  notice: "No .ai/memories directory."
@@ -5700,8 +5727,9 @@ async function memConflictCandidates(input, ctx) {
5700
5727
  maxPairs: input.max_pairs,
5701
5728
  maxScan: input.max_scan
5702
5729
  });
5703
- const notice = pairs.length === 0 ? "No lexical candidate pairs \u2265 threshold \u2014 try lowering min_jaccard or widen since_days/types." : void 0;
5704
- return { pairs, scanned, truncated, notice };
5730
+ const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
5731
+ const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
5732
+ return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
5705
5733
  }
5706
5734
  var MemResolveProjectInputSchema = {
5707
5735
  cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
@@ -5741,11 +5769,37 @@ async function memTimeline(input, ctx) {
5741
5769
  });
5742
5770
  return { entries, total: entries.length, notice };
5743
5771
  }
5772
+ var RuntimeJournalAppendInputSchema = {
5773
+ message: z34.string().min(1).describe("Short line to append to the runtime session journal"),
5774
+ kind: z34.enum(["note", "session_end", "mcp"]).default("note"),
5775
+ tool: z34.string().optional().describe("When kind=mcp, which tool name (optional)")
5776
+ };
5777
+ async function runtimeJournalAppend(input, ctx) {
5778
+ await appendRuntimeJournalEntry2(ctx.paths, {
5779
+ kind: input.kind,
5780
+ message: input.message,
5781
+ ...input.tool ? { tool: input.tool } : {}
5782
+ });
5783
+ return {
5784
+ ok: true,
5785
+ path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
5786
+ };
5787
+ }
5788
+ var RuntimeJournalTailInputSchema = {
5789
+ limit: z35.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
5790
+ };
5791
+ async function runtimeJournalTail(input, ctx) {
5792
+ const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
5793
+ if (entries.length === 0) {
5794
+ return { entries: [], empty: true };
5795
+ }
5796
+ return { entries };
5797
+ }
5744
5798
  var BootstrapProjectArgsSchema = {
5745
- module: z34.string().optional().describe(
5799
+ module: z36.string().optional().describe(
5746
5800
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
5747
5801
  ),
5748
- focus: z34.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5802
+ focus: z36.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5749
5803
  };
5750
5804
  var ROOT_TEMPLATE = `# Project context
5751
5805
 
@@ -5826,8 +5880,8 @@ ${template}\`\`\`
5826
5880
  };
5827
5881
  }
5828
5882
  var PostTaskArgsSchema = {
5829
- task_summary: z35.string().optional().describe("One sentence describing what you just did"),
5830
- files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
5883
+ task_summary: z37.string().optional().describe("One sentence describing what you just did"),
5884
+ files_touched: z37.array(z37.string()).optional().describe("Files you created or modified during the task")
5831
5885
  };
5832
5886
  function postTaskPrompt(args, ctx) {
5833
5887
  const taskLine = args.task_summary ? `
@@ -5910,10 +5964,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
5910
5964
  };
5911
5965
  }
5912
5966
  var ImportDocsArgsSchema = {
5913
- content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
5914
- source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
5915
- scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
5916
- dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
5967
+ content: z38.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
5968
+ source: z38.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
5969
+ scope: z38.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
5970
+ dry_run: z38.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
5917
5971
  };
5918
5972
  function importDocsPrompt(args, ctx) {
5919
5973
  const sourceLine = args.source ? `
@@ -5976,7 +6030,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
5976
6030
  };
5977
6031
  }
5978
6032
  var SERVER_NAME = "haive";
5979
- var SERVER_VERSION = "0.9.5";
6033
+ var SERVER_VERSION = "0.9.7";
5980
6034
  function jsonResult(data) {
5981
6035
  return {
5982
6036
  content: [
@@ -6659,14 +6713,17 @@ function createHaiveServer(options = {}) {
6659
6713
  server.tool(
6660
6714
  "mem_conflict_candidates",
6661
6715
  [
6662
- "Bulk lexical scan for decision/architecture-like pairs that look similar (Jaccard on tokens).",
6716
+ "Bulk scan for conflict CANDIDATES (not proof):",
6717
+ "",
6718
+ " 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
6719
+ " 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
6663
6720
  "",
6664
- "Advisory only \u2014 follow with mem_conflicts_with on specific ids for real contradiction checks.",
6721
+ "Advisory only \u2014 follow with mem_conflicts_with on specific ids.",
6665
6722
  "",
6666
6723
  "PARAMETERS:",
6667
- " since_days, types, min_jaccard, max_pairs, max_scan",
6724
+ " since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
6668
6725
  "",
6669
- "RETURNS: { pairs: [{ id_a, id_b, jaccard }], scanned, truncated, notice? }"
6726
+ "RETURNS: { pairs, topic_status_pairs, scanned, truncated, notice? }"
6670
6727
  ].join("\n"),
6671
6728
  MemConflictCandidatesInputSchema,
6672
6729
  async (input) => {
@@ -6674,6 +6731,32 @@ function createHaiveServer(options = {}) {
6674
6731
  return jsonResult(await memConflictCandidates(input, context));
6675
6732
  }
6676
6733
  );
6734
+ server.tool(
6735
+ "runtime_journal_append",
6736
+ [
6737
+ "Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
6738
+ "",
6739
+ "Does NOT replace team memories; complements mem_session_end recaps for local traces.",
6740
+ "",
6741
+ "PARAMETERS: message, kind (note|session_end|mcp), optional tool",
6742
+ "",
6743
+ "RETURNS: { ok, path_hint }"
6744
+ ].join("\n"),
6745
+ RuntimeJournalAppendInputSchema,
6746
+ async (input) => jsonResult(await runtimeJournalAppend(input, context))
6747
+ );
6748
+ server.tool(
6749
+ "runtime_journal_tail",
6750
+ [
6751
+ "Read the last N entries from the runtime session journal (parsed JSON lines).",
6752
+ "",
6753
+ "PARAMETERS: limit (default 30, max 500)",
6754
+ "",
6755
+ "RETURNS: { entries: [...], empty?: true }"
6756
+ ].join("\n"),
6757
+ RuntimeJournalTailInputSchema,
6758
+ async (input) => jsonResult(await runtimeJournalTail(input, context))
6759
+ );
6677
6760
  server.tool(
6678
6761
  "pre_commit_check",
6679
6762
  [
@@ -7205,9 +7288,10 @@ Attends une **confirmation explicite** avant d'agir.
7205
7288
  }
7206
7289
  if (opts.embed) {
7207
7290
  try {
7208
- const emb = await import("@hiveai/embeddings");
7291
+ const { Embedder, rebuildIndex } = await import("@hiveai/embeddings");
7209
7292
  log(ui.dim("embed: rebuilding index\u2026"));
7210
- const report = await emb.rebuildIndex(paths);
7293
+ const embedder = await Embedder.create();
7294
+ const { report } = await rebuildIndex(paths, embedder);
7211
7295
  log(ui.dim(`embed: index rebuilt (${report.added} added, ${report.updated} updated, ${report.removed} removed)`));
7212
7296
  } catch {
7213
7297
  ui.warn("--embed: @hiveai/embeddings not available or index build failed. Run `haive embeddings index` manually.");
@@ -8636,8 +8720,8 @@ function parseChangelog(content) {
8636
8720
  const sections = content.split(/^#{1,3}\s+/m).slice(1);
8637
8721
  for (const section of sections) {
8638
8722
  const versionMatch = section.match(/^(?:\[?)([0-9]+\.[0-9]+[.0-9]*)/);
8639
- if (!versionMatch) continue;
8640
- const version = versionMatch[1];
8723
+ const version = versionMatch?.[1];
8724
+ if (!version) continue;
8641
8725
  const entry = {
8642
8726
  version,
8643
8727
  breaking: [],
@@ -8648,7 +8732,7 @@ function parseChangelog(content) {
8648
8732
  };
8649
8733
  const subSections = section.split(/^#{2,4}\s+/m);
8650
8734
  for (const sub of subSections) {
8651
- const firstLine = sub.split("\n")[0].toLowerCase().trim();
8735
+ const firstLine = (sub.split("\n")[0] ?? "").toLowerCase().trim();
8652
8736
  const items = sub.split("\n").slice(1).filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*")).map((l) => l.replace(/^[\s\-*]+/, "").trim()).filter(Boolean);
8653
8737
  if (/breaking/.test(firstLine)) {
8654
8738
  entry.breaking.push(...items);
@@ -8664,8 +8748,9 @@ function parseChangelog(content) {
8664
8748
  for (const sub2 of subSections) {
8665
8749
  for (const line of sub2.split("\n")) {
8666
8750
  const breakingMatch = line.match(/BREAKING CHANGE[S]?:\s*(.+)/i);
8667
- if (breakingMatch && !entry.breaking.includes(breakingMatch[1].trim())) {
8668
- entry.breaking.push(breakingMatch[1].trim());
8751
+ const breakingText = breakingMatch?.[1]?.trim();
8752
+ if (breakingText && !entry.breaking.includes(breakingText)) {
8753
+ entry.breaking.push(breakingText);
8669
8754
  }
8670
8755
  }
8671
8756
  }
@@ -8707,7 +8792,8 @@ function registerMemoryImportChangelog(memory2) {
8707
8792
  }
8708
8793
  if (opts.versions) {
8709
8794
  if (opts.versions === "latest") {
8710
- entries = [entries[0]];
8795
+ const latest = entries[0];
8796
+ entries = latest ? [latest] : [];
8711
8797
  } else {
8712
8798
  const requested = opts.versions.split(",").map((v) => v.trim());
8713
8799
  entries = entries.filter((e) => requested.includes(e.version));
@@ -10284,7 +10370,7 @@ function registerDoctor(program2) {
10284
10370
  timeout: 3e3,
10285
10371
  stdio: ["ignore", "pipe", "ignore"]
10286
10372
  }).trim();
10287
- const cliVersion = "0.9.5";
10373
+ const cliVersion = "0.9.7";
10288
10374
  if (legacyRaw && legacyRaw !== cliVersion) {
10289
10375
  findings.push({
10290
10376
  severity: "warn",
@@ -10748,12 +10834,151 @@ function registerResolveProject(program2) {
10748
10834
  });
10749
10835
  }
10750
10836
 
10837
+ // src/commands/runtime-journal.ts
10838
+ import { existsSync as existsSync64 } from "fs";
10839
+ import path41 from "path";
10840
+ import "commander";
10841
+ import {
10842
+ appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
10843
+ findProjectRoot as findProjectRoot43,
10844
+ readRuntimeJournalTail as readRuntimeJournalTail2,
10845
+ resolveHaivePaths as resolveHaivePaths40
10846
+ } from "@hiveai/core";
10847
+ function registerRuntime(program2) {
10848
+ const runtime = program2.command("runtime").description(
10849
+ "Local-only .ai/.runtime helpers (not versioned team memory). See session-journal.ndjson."
10850
+ );
10851
+ const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
10852
+ journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
10853
+ const root = path41.resolve(opts.dir ?? process.cwd());
10854
+ const paths = resolveHaivePaths40(findProjectRoot43(root));
10855
+ const raw = opts.kind ?? "note";
10856
+ const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
10857
+ await appendRuntimeJournalEntry3(paths, { kind, message });
10858
+ ui.success(`Appended to ${path41.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
10859
+ });
10860
+ journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
10861
+ const root = path41.resolve(opts.dir ?? process.cwd());
10862
+ const paths = resolveHaivePaths40(findProjectRoot43(root));
10863
+ const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
10864
+ if (!existsSync64(paths.haiveDir)) {
10865
+ ui.error("No .ai/ \u2014 run `haive init` first.");
10866
+ process.exitCode = 1;
10867
+ return;
10868
+ }
10869
+ const entries = await readRuntimeJournalTail2(paths, limit);
10870
+ if (entries.length === 0) {
10871
+ ui.info("Journal empty or missing.");
10872
+ return;
10873
+ }
10874
+ console.log(JSON.stringify({ entries, count: entries.length }, null, 2));
10875
+ });
10876
+ }
10877
+
10878
+ // src/commands/memory-timeline.ts
10879
+ import { existsSync as existsSync65 } from "fs";
10880
+ import path43 from "path";
10881
+ import "commander";
10882
+ import {
10883
+ collectTimelineEntries as collectTimelineEntries2,
10884
+ findProjectRoot as findProjectRoot44,
10885
+ resolveHaivePaths as resolveHaivePaths41
10886
+ } from "@hiveai/core";
10887
+ function registerMemoryTimeline(memory2) {
10888
+ memory2.command("timeline").description(
10889
+ "List related memories chronologically (topic, related_ids, anchors) \u2014 same logic as MCP mem_timeline."
10890
+ ).option("--id <id>", "seed memory id").option("--topic <key>", "filter by frontmatter.topic (use without --id for topic-only)").option("-n, --limit <n>", "max entries", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
10891
+ if (!opts.id && !opts.topic) {
10892
+ ui.error("Provide --id and/or --topic.");
10893
+ process.exitCode = 1;
10894
+ return;
10895
+ }
10896
+ const root = path43.resolve(opts.dir ?? process.cwd());
10897
+ const paths = resolveHaivePaths41(findProjectRoot44(root));
10898
+ if (!existsSync65(paths.memoriesDir)) {
10899
+ ui.error("No memories \u2014 run `haive init`.");
10900
+ process.exitCode = 1;
10901
+ return;
10902
+ }
10903
+ const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
10904
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
10905
+ const { entries, notice } = collectTimelineEntries2(all, {
10906
+ memoryId: opts.id,
10907
+ topic: opts.topic,
10908
+ limit
10909
+ });
10910
+ if (notice) ui.warn(notice);
10911
+ console.log(JSON.stringify({ entries, total: entries.length }, null, 2));
10912
+ });
10913
+ }
10914
+
10915
+ // src/commands/memory-conflict-candidates.ts
10916
+ import { existsSync as existsSync66 } from "fs";
10917
+ import path44 from "path";
10918
+ import "commander";
10919
+ import {
10920
+ findLexicalConflictPairs as findLexicalConflictPairs2,
10921
+ findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
10922
+ findProjectRoot as findProjectRoot45,
10923
+ resolveHaivePaths as resolveHaivePaths42
10924
+ } from "@hiveai/core";
10925
+ function parseTypes(csv) {
10926
+ const allowed = ["decision", "architecture", "convention", "gotcha"];
10927
+ const parts = csv.split(",").map((s) => s.trim().toLowerCase());
10928
+ const out = parts.filter((p) => allowed.includes(p));
10929
+ return out.length ? out : ["decision", "architecture"];
10930
+ }
10931
+ function registerMemoryConflictCandidates(memory2) {
10932
+ memory2.command("conflict-candidates").description(
10933
+ "Heuristic conflict candidates (lexical Jaccard + same-topic validated/rejected pairs) \u2014 aligns with MCP mem_conflict_candidates."
10934
+ ).option("-d, --dir <dir>", "project root", process.cwd()).option("--since-days <n>", "only memories created within N days (lexical scan)", "365").option(
10935
+ "--types <csv>",
10936
+ "decision,architecture,convention,gotcha (lexical scan)",
10937
+ "decision,architecture"
10938
+ ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
10939
+ const root = path44.resolve(opts.dir ?? process.cwd());
10940
+ const paths = resolveHaivePaths42(findProjectRoot45(root));
10941
+ if (!existsSync66(paths.memoriesDir)) {
10942
+ ui.error("No memories \u2014 run `haive init`.");
10943
+ process.exitCode = 1;
10944
+ return;
10945
+ }
10946
+ const sinceDays = Math.max(1, parseInt(opts.sinceDays, 10) || 365);
10947
+ const minJaccard = parseFloat(opts.minJaccard) || 0.45;
10948
+ const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
10949
+ const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
10950
+ const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
10951
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
10952
+ const lexical = findLexicalConflictPairs2(all, {
10953
+ sinceDays,
10954
+ types: parseTypes(opts.types),
10955
+ minJaccard,
10956
+ maxPairs,
10957
+ maxScan
10958
+ });
10959
+ const topicStatusPairs = findTopicStatusConflictPairs2(all, maxTopicPairs);
10960
+ console.log(
10961
+ JSON.stringify(
10962
+ {
10963
+ pairs: lexical.pairs,
10964
+ topic_status_pairs: topicStatusPairs,
10965
+ scanned: lexical.scanned,
10966
+ truncated: lexical.truncated
10967
+ },
10968
+ null,
10969
+ 2
10970
+ )
10971
+ );
10972
+ });
10973
+ }
10974
+
10751
10975
  // src/index.ts
10752
- var program = new Command44();
10753
- program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.5");
10976
+ var program = new Command47();
10977
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.7");
10754
10978
  registerInit(program);
10755
10979
  registerWelcome(program);
10756
10980
  registerResolveProject(program);
10981
+ registerRuntime(program);
10757
10982
  registerMcp(program);
10758
10983
  registerBriefing(program);
10759
10984
  registerTui(program);
@@ -10785,6 +11010,8 @@ registerMemoryImportChangelog(memory);
10785
11010
  registerMemoryDigest(memory);
10786
11011
  registerMemorySuggest(memory);
10787
11012
  registerMemorySuggestTopic(memory);
11013
+ registerMemoryTimeline(memory);
11014
+ registerMemoryConflictCandidates(memory);
10788
11015
  registerMemoryArchive(memory);
10789
11016
  registerMemoryLint(memory);
10790
11017
  var session = program.command("session").description("Manage session lifecycle");