@hiveai/cli 0.9.5 → 0.9.6

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)));
@@ -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
  );
@@ -5681,12 +5703,16 @@ var MemConflictCandidatesInputSchema = {
5681
5703
  types: z30.array(z30.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
5682
5704
  min_jaccard: z30.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
5683
5705
  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.")
5706
+ 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_topic_pairs: z30.number().int().positive().max(100).default(20).describe(
5708
+ "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
5709
+ )
5685
5710
  };
5686
5711
  async function memConflictCandidates(input, ctx) {
5687
5712
  if (!existsSync27(ctx.paths.memoriesDir)) {
5688
5713
  return {
5689
5714
  pairs: [],
5715
+ topic_status_pairs: [],
5690
5716
  scanned: 0,
5691
5717
  truncated: false,
5692
5718
  notice: "No .ai/memories directory."
@@ -5700,8 +5726,9 @@ async function memConflictCandidates(input, ctx) {
5700
5726
  maxPairs: input.max_pairs,
5701
5727
  maxScan: input.max_scan
5702
5728
  });
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 };
5729
+ const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
5730
+ 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;
5731
+ return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
5705
5732
  }
5706
5733
  var MemResolveProjectInputSchema = {
5707
5734
  cwd: z31.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
@@ -5741,11 +5768,37 @@ async function memTimeline(input, ctx) {
5741
5768
  });
5742
5769
  return { entries, total: entries.length, notice };
5743
5770
  }
5771
+ var RuntimeJournalAppendInputSchema = {
5772
+ message: z34.string().min(1).describe("Short line to append to the runtime session journal"),
5773
+ kind: z34.enum(["note", "session_end", "mcp"]).default("note"),
5774
+ tool: z34.string().optional().describe("When kind=mcp, which tool name (optional)")
5775
+ };
5776
+ async function runtimeJournalAppend(input, ctx) {
5777
+ await appendRuntimeJournalEntry2(ctx.paths, {
5778
+ kind: input.kind,
5779
+ message: input.message,
5780
+ ...input.tool ? { tool: input.tool } : {}
5781
+ });
5782
+ return {
5783
+ ok: true,
5784
+ path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
5785
+ };
5786
+ }
5787
+ var RuntimeJournalTailInputSchema = {
5788
+ limit: z35.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
5789
+ };
5790
+ async function runtimeJournalTail(input, ctx) {
5791
+ const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
5792
+ if (entries.length === 0) {
5793
+ return { entries: [], empty: true };
5794
+ }
5795
+ return { entries };
5796
+ }
5744
5797
  var BootstrapProjectArgsSchema = {
5745
- module: z34.string().optional().describe(
5798
+ module: z36.string().optional().describe(
5746
5799
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
5747
5800
  ),
5748
- focus: z34.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5801
+ focus: z36.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
5749
5802
  };
5750
5803
  var ROOT_TEMPLATE = `# Project context
5751
5804
 
@@ -5826,8 +5879,8 @@ ${template}\`\`\`
5826
5879
  };
5827
5880
  }
5828
5881
  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")
5882
+ task_summary: z37.string().optional().describe("One sentence describing what you just did"),
5883
+ files_touched: z37.array(z37.string()).optional().describe("Files you created or modified during the task")
5831
5884
  };
5832
5885
  function postTaskPrompt(args, ctx) {
5833
5886
  const taskLine = args.task_summary ? `
@@ -5910,10 +5963,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
5910
5963
  };
5911
5964
  }
5912
5965
  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")
5966
+ content: z38.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
5967
+ source: z38.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
5968
+ scope: z38.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
5969
+ dry_run: z38.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
5917
5970
  };
5918
5971
  function importDocsPrompt(args, ctx) {
5919
5972
  const sourceLine = args.source ? `
@@ -5976,7 +6029,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
5976
6029
  };
5977
6030
  }
5978
6031
  var SERVER_NAME = "haive";
5979
- var SERVER_VERSION = "0.9.5";
6032
+ var SERVER_VERSION = "0.9.6";
5980
6033
  function jsonResult(data) {
5981
6034
  return {
5982
6035
  content: [
@@ -6659,14 +6712,17 @@ function createHaiveServer(options = {}) {
6659
6712
  server.tool(
6660
6713
  "mem_conflict_candidates",
6661
6714
  [
6662
- "Bulk lexical scan for decision/architecture-like pairs that look similar (Jaccard on tokens).",
6715
+ "Bulk scan for conflict CANDIDATES (not proof):",
6716
+ "",
6717
+ " 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
6718
+ " 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
6663
6719
  "",
6664
- "Advisory only \u2014 follow with mem_conflicts_with on specific ids for real contradiction checks.",
6720
+ "Advisory only \u2014 follow with mem_conflicts_with on specific ids.",
6665
6721
  "",
6666
6722
  "PARAMETERS:",
6667
- " since_days, types, min_jaccard, max_pairs, max_scan",
6723
+ " since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
6668
6724
  "",
6669
- "RETURNS: { pairs: [{ id_a, id_b, jaccard }], scanned, truncated, notice? }"
6725
+ "RETURNS: { pairs, topic_status_pairs, scanned, truncated, notice? }"
6670
6726
  ].join("\n"),
6671
6727
  MemConflictCandidatesInputSchema,
6672
6728
  async (input) => {
@@ -6674,6 +6730,32 @@ function createHaiveServer(options = {}) {
6674
6730
  return jsonResult(await memConflictCandidates(input, context));
6675
6731
  }
6676
6732
  );
6733
+ server.tool(
6734
+ "runtime_journal_append",
6735
+ [
6736
+ "Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
6737
+ "",
6738
+ "Does NOT replace team memories; complements mem_session_end recaps for local traces.",
6739
+ "",
6740
+ "PARAMETERS: message, kind (note|session_end|mcp), optional tool",
6741
+ "",
6742
+ "RETURNS: { ok, path_hint }"
6743
+ ].join("\n"),
6744
+ RuntimeJournalAppendInputSchema,
6745
+ async (input) => jsonResult(await runtimeJournalAppend(input, context))
6746
+ );
6747
+ server.tool(
6748
+ "runtime_journal_tail",
6749
+ [
6750
+ "Read the last N entries from the runtime session journal (parsed JSON lines).",
6751
+ "",
6752
+ "PARAMETERS: limit (default 30, max 500)",
6753
+ "",
6754
+ "RETURNS: { entries: [...], empty?: true }"
6755
+ ].join("\n"),
6756
+ RuntimeJournalTailInputSchema,
6757
+ async (input) => jsonResult(await runtimeJournalTail(input, context))
6758
+ );
6677
6759
  server.tool(
6678
6760
  "pre_commit_check",
6679
6761
  [
@@ -10284,7 +10366,7 @@ function registerDoctor(program2) {
10284
10366
  timeout: 3e3,
10285
10367
  stdio: ["ignore", "pipe", "ignore"]
10286
10368
  }).trim();
10287
- const cliVersion = "0.9.5";
10369
+ const cliVersion = "0.9.6";
10288
10370
  if (legacyRaw && legacyRaw !== cliVersion) {
10289
10371
  findings.push({
10290
10372
  severity: "warn",
@@ -10748,12 +10830,151 @@ function registerResolveProject(program2) {
10748
10830
  });
10749
10831
  }
10750
10832
 
10833
+ // src/commands/runtime-journal.ts
10834
+ import { existsSync as existsSync64 } from "fs";
10835
+ import path41 from "path";
10836
+ import "commander";
10837
+ import {
10838
+ appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
10839
+ findProjectRoot as findProjectRoot43,
10840
+ readRuntimeJournalTail as readRuntimeJournalTail2,
10841
+ resolveHaivePaths as resolveHaivePaths40
10842
+ } from "@hiveai/core";
10843
+ function registerRuntime(program2) {
10844
+ const runtime = program2.command("runtime").description(
10845
+ "Local-only .ai/.runtime helpers (not versioned team memory). See session-journal.ndjson."
10846
+ );
10847
+ const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
10848
+ 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) => {
10849
+ const root = path41.resolve(opts.dir ?? process.cwd());
10850
+ const paths = resolveHaivePaths40(findProjectRoot43(root));
10851
+ const raw = opts.kind ?? "note";
10852
+ const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
10853
+ await appendRuntimeJournalEntry3(paths, { kind, message });
10854
+ ui.success(`Appended to ${path41.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
10855
+ });
10856
+ 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) => {
10857
+ const root = path41.resolve(opts.dir ?? process.cwd());
10858
+ const paths = resolveHaivePaths40(findProjectRoot43(root));
10859
+ const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
10860
+ if (!existsSync64(paths.haiveDir)) {
10861
+ ui.error("No .ai/ \u2014 run `haive init` first.");
10862
+ process.exitCode = 1;
10863
+ return;
10864
+ }
10865
+ const entries = await readRuntimeJournalTail2(paths, limit);
10866
+ if (entries.length === 0) {
10867
+ ui.info("Journal empty or missing.");
10868
+ return;
10869
+ }
10870
+ console.log(JSON.stringify({ entries, count: entries.length }, null, 2));
10871
+ });
10872
+ }
10873
+
10874
+ // src/commands/memory-timeline.ts
10875
+ import { existsSync as existsSync65 } from "fs";
10876
+ import path43 from "path";
10877
+ import "commander";
10878
+ import {
10879
+ collectTimelineEntries as collectTimelineEntries2,
10880
+ findProjectRoot as findProjectRoot44,
10881
+ resolveHaivePaths as resolveHaivePaths41
10882
+ } from "@hiveai/core";
10883
+ function registerMemoryTimeline(memory2) {
10884
+ memory2.command("timeline").description(
10885
+ "List related memories chronologically (topic, related_ids, anchors) \u2014 same logic as MCP mem_timeline."
10886
+ ).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) => {
10887
+ if (!opts.id && !opts.topic) {
10888
+ ui.error("Provide --id and/or --topic.");
10889
+ process.exitCode = 1;
10890
+ return;
10891
+ }
10892
+ const root = path43.resolve(opts.dir ?? process.cwd());
10893
+ const paths = resolveHaivePaths41(findProjectRoot44(root));
10894
+ if (!existsSync65(paths.memoriesDir)) {
10895
+ ui.error("No memories \u2014 run `haive init`.");
10896
+ process.exitCode = 1;
10897
+ return;
10898
+ }
10899
+ const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
10900
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
10901
+ const { entries, notice } = collectTimelineEntries2(all, {
10902
+ memoryId: opts.id,
10903
+ topic: opts.topic,
10904
+ limit
10905
+ });
10906
+ if (notice) ui.warn(notice);
10907
+ console.log(JSON.stringify({ entries, total: entries.length }, null, 2));
10908
+ });
10909
+ }
10910
+
10911
+ // src/commands/memory-conflict-candidates.ts
10912
+ import { existsSync as existsSync66 } from "fs";
10913
+ import path44 from "path";
10914
+ import "commander";
10915
+ import {
10916
+ findLexicalConflictPairs as findLexicalConflictPairs2,
10917
+ findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
10918
+ findProjectRoot as findProjectRoot45,
10919
+ resolveHaivePaths as resolveHaivePaths42
10920
+ } from "@hiveai/core";
10921
+ function parseTypes(csv) {
10922
+ const allowed = ["decision", "architecture", "convention", "gotcha"];
10923
+ const parts = csv.split(",").map((s) => s.trim().toLowerCase());
10924
+ const out = parts.filter((p) => allowed.includes(p));
10925
+ return out.length ? out : ["decision", "architecture"];
10926
+ }
10927
+ function registerMemoryConflictCandidates(memory2) {
10928
+ memory2.command("conflict-candidates").description(
10929
+ "Heuristic conflict candidates (lexical Jaccard + same-topic validated/rejected pairs) \u2014 aligns with MCP mem_conflict_candidates."
10930
+ ).option("-d, --dir <dir>", "project root", process.cwd()).option("--since-days <n>", "only memories created within N days (lexical scan)", "365").option(
10931
+ "--types <csv>",
10932
+ "decision,architecture,convention,gotcha (lexical scan)",
10933
+ "decision,architecture"
10934
+ ).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) => {
10935
+ const root = path44.resolve(opts.dir ?? process.cwd());
10936
+ const paths = resolveHaivePaths42(findProjectRoot45(root));
10937
+ if (!existsSync66(paths.memoriesDir)) {
10938
+ ui.error("No memories \u2014 run `haive init`.");
10939
+ process.exitCode = 1;
10940
+ return;
10941
+ }
10942
+ const sinceDays = Math.max(1, parseInt(opts.sinceDays, 10) || 365);
10943
+ const minJaccard = parseFloat(opts.minJaccard) || 0.45;
10944
+ const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
10945
+ const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
10946
+ const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
10947
+ const all = await loadMemoriesFromDir25(paths.memoriesDir);
10948
+ const lexical = findLexicalConflictPairs2(all, {
10949
+ sinceDays,
10950
+ types: parseTypes(opts.types),
10951
+ minJaccard,
10952
+ maxPairs,
10953
+ maxScan
10954
+ });
10955
+ const topicStatusPairs = findTopicStatusConflictPairs2(all, maxTopicPairs);
10956
+ console.log(
10957
+ JSON.stringify(
10958
+ {
10959
+ pairs: lexical.pairs,
10960
+ topic_status_pairs: topicStatusPairs,
10961
+ scanned: lexical.scanned,
10962
+ truncated: lexical.truncated
10963
+ },
10964
+ null,
10965
+ 2
10966
+ )
10967
+ );
10968
+ });
10969
+ }
10970
+
10751
10971
  // 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");
10972
+ var program = new Command47();
10973
+ program.name("haive").description("hAIve \u2014 team-first persistent memory layer for AI coding agents").version("0.9.6");
10754
10974
  registerInit(program);
10755
10975
  registerWelcome(program);
10756
10976
  registerResolveProject(program);
10977
+ registerRuntime(program);
10757
10978
  registerMcp(program);
10758
10979
  registerBriefing(program);
10759
10980
  registerTui(program);
@@ -10785,6 +11006,8 @@ registerMemoryImportChangelog(memory);
10785
11006
  registerMemoryDigest(memory);
10786
11007
  registerMemorySuggest(memory);
10787
11008
  registerMemorySuggestTopic(memory);
11009
+ registerMemoryTimeline(memory);
11010
+ registerMemoryConflictCandidates(memory);
10788
11011
  registerMemoryArchive(memory);
10789
11012
  registerMemoryLint(memory);
10790
11013
  var session = program.command("session").description("Manage session lifecycle");