@hiveai/mcp 0.2.1 → 0.2.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/server.js CHANGED
@@ -80,7 +80,10 @@ var MemListInputSchema = {
80
80
  scope: z3.enum(["personal", "team", "module"]).optional(),
81
81
  type: z3.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional(),
82
82
  module: z3.string().optional(),
83
- tag: z3.string().optional()
83
+ tag: z3.string().optional(),
84
+ status: z3.enum(["draft", "proposed", "validated", "deprecated", "stale", "rejected"]).optional().describe("Filter by a single status. Omit to return all statuses."),
85
+ exclude_rejected: z3.boolean().default(false).describe("When true, exclude memories with status=rejected from results."),
86
+ include_body: z3.boolean().default(false).describe("Include full body text. Default false to save tokens \u2014 use mem_get for a single memory's full content.")
84
87
  };
85
88
  async function memList(input, ctx) {
86
89
  if (!existsSync3(ctx.paths.memoriesDir)) {
@@ -93,10 +96,13 @@ async function memList(input, ctx) {
93
96
  if (input.type && fm.type !== input.type) return false;
94
97
  if (input.module && fm.module !== input.module) return false;
95
98
  if (input.tag && !fm.tags.includes(input.tag)) return false;
99
+ if (input.status && fm.status !== input.status) return false;
100
+ if (input.exclude_rejected && fm.status === "rejected") return false;
96
101
  return true;
97
102
  });
98
103
  const memories = filtered.map(({ memory, filePath }) => {
99
104
  const fm = memory.frontmatter;
105
+ const snippet = memory.body.replace(/\s+/g, " ").trim().slice(0, 120);
100
106
  return {
101
107
  id: fm.id,
102
108
  scope: fm.scope,
@@ -104,7 +110,9 @@ async function memList(input, ctx) {
104
110
  ...fm.module ? { module: fm.module } : {},
105
111
  status: fm.status,
106
112
  tags: fm.tags,
107
- file_path: filePath
113
+ snippet,
114
+ file_path: filePath,
115
+ ...input.include_body ? { body: memory.body } : {}
108
116
  };
109
117
  });
110
118
  return { memories };
@@ -188,6 +196,8 @@ var MemSearchInputSchema = {
188
196
  scope: z5.enum(["personal", "team", "module"]).optional().describe("Restrict results to a single scope"),
189
197
  type: z5.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional().describe("Restrict results to a memory type"),
190
198
  module: z5.string().optional().describe("Restrict results to a module"),
199
+ status: z5.enum(["draft", "proposed", "validated", "deprecated", "stale", "rejected"]).optional().describe("Filter by a single status. Omit to return all statuses."),
200
+ exclude_rejected: z5.boolean().default(false).describe("When true, exclude memories with status=rejected from results."),
191
201
  limit: z5.number().int().positive().max(100).default(20).describe("Max results"),
192
202
  semantic: z5.boolean().default(false).describe(
193
203
  "Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
@@ -229,6 +239,8 @@ function passesFilters(fm, input) {
229
239
  if (input.scope && fm.scope !== input.scope) return false;
230
240
  if (input.type && fm.type !== input.type) return false;
231
241
  if (input.module && fm.module !== input.module) return false;
242
+ if (input.status && fm.status !== input.status) return false;
243
+ if (input.exclude_rejected && fm.status === "rejected") return false;
232
244
  return true;
233
245
  }
234
246
  function buildLiteralResult(input, filtered, usage) {
@@ -330,6 +342,14 @@ async function memVerify(input, ctx) {
330
342
  const isAnchored = memory.frontmatter.anchor.paths.length > 0 || memory.frontmatter.anchor.symbols.length > 0;
331
343
  if (!isAnchored) {
332
344
  anchorless++;
345
+ results.push({
346
+ id: memory.frontmatter.id,
347
+ file_path: filePath,
348
+ stale: false,
349
+ reason: null,
350
+ status_after: memory.frontmatter.status,
351
+ skipped: true
352
+ });
333
353
  continue;
334
354
  }
335
355
  const result = await verifyAnchor(memory, { projectRoot: ctx.paths.root });
@@ -387,12 +407,14 @@ function applyVerification(mem, result) {
387
407
  }
388
408
 
389
409
  // src/tools/mem-reject.ts
410
+ import { writeFile as writeFile4 } from "fs/promises";
390
411
  import { existsSync as existsSync7 } from "fs";
391
412
  import {
392
413
  loadMemoriesFromDir as loadMemoriesFromDir4,
393
414
  loadUsageIndex as loadUsageIndex2,
394
415
  recordRejection,
395
- saveUsageIndex
416
+ saveUsageIndex,
417
+ serializeMemory as serializeMemory3
396
418
  } from "@hiveai/core";
397
419
  import { z as z7 } from "zod";
398
420
  var MemRejectInputSchema = {
@@ -404,16 +426,23 @@ async function memReject(input, ctx) {
404
426
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
405
427
  }
406
428
  const memories = await loadMemoriesFromDir4(ctx.paths.memoriesDir);
407
- const exists = memories.some((m) => m.memory.frontmatter.id === input.id);
408
- if (!exists) {
409
- throw new Error(`No memory with id "${input.id}".`);
410
- }
429
+ const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
430
+ if (!loaded) throw new Error(`No memory with id "${input.id}".`);
431
+ await writeFile4(
432
+ loaded.filePath,
433
+ serializeMemory3({
434
+ frontmatter: { ...loaded.memory.frontmatter, status: "rejected" },
435
+ body: loaded.memory.body
436
+ }),
437
+ "utf8"
438
+ );
411
439
  const idx = await loadUsageIndex2(ctx.paths);
412
440
  recordRejection(idx, input.id, input.reason ?? null);
413
441
  await saveUsageIndex(ctx.paths, idx);
414
442
  const u = idx.by_id[input.id];
415
443
  return {
416
444
  id: input.id,
445
+ status: "rejected",
417
446
  rejected_count: u?.rejected_count ?? 0,
418
447
  last_rejected_at: u?.last_rejected_at ?? null,
419
448
  rejection_reason: u?.rejection_reason ?? null
@@ -601,20 +630,80 @@ async function memDelete(input, ctx) {
601
630
  return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
602
631
  }
603
632
 
604
- // src/tools/mem-pending.ts
633
+ // src/tools/mem-update.ts
634
+ import { writeFile as writeFile5 } from "fs/promises";
605
635
  import { existsSync as existsSync11 } from "fs";
636
+ import { loadMemoriesFromDir as loadMemoriesFromDir8, serializeMemory as serializeMemory4 } from "@hiveai/core";
637
+ import { z as z11 } from "zod";
638
+ var MemUpdateInputSchema = {
639
+ id: z11.string().min(1).describe("Id of the memory to update"),
640
+ body: z11.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
641
+ tags: z11.array(z11.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
642
+ paths: z11.array(z11.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
643
+ symbols: z11.array(z11.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
644
+ commit: z11.string().optional().describe("New anchor commit SHA"),
645
+ domain: z11.string().optional().describe("New domain label"),
646
+ author: z11.string().optional().describe("New author handle or email")
647
+ };
648
+ async function memUpdate(input, ctx) {
649
+ if (!existsSync11(ctx.paths.memoriesDir)) {
650
+ throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
651
+ }
652
+ const memories = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
653
+ const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
654
+ if (!loaded) throw new Error(`No memory with id "${input.id}".`);
655
+ const { frontmatter, body } = loaded.memory;
656
+ const updated_fields = [];
657
+ const newAnchor = { ...frontmatter.anchor };
658
+ if (input.paths !== void 0) {
659
+ newAnchor.paths = input.paths;
660
+ updated_fields.push("anchor.paths");
661
+ }
662
+ if (input.symbols !== void 0) {
663
+ newAnchor.symbols = input.symbols;
664
+ updated_fields.push("anchor.symbols");
665
+ }
666
+ if (input.commit !== void 0) {
667
+ newAnchor.commit = input.commit;
668
+ updated_fields.push("anchor.commit");
669
+ }
670
+ const newFrontmatter = {
671
+ ...frontmatter,
672
+ anchor: newAnchor,
673
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
674
+ ...input.domain !== void 0 ? { domain: input.domain } : {},
675
+ ...input.author !== void 0 ? { author: input.author } : {}
676
+ };
677
+ if (input.tags !== void 0) updated_fields.push("tags");
678
+ if (input.domain !== void 0) updated_fields.push("domain");
679
+ if (input.author !== void 0) updated_fields.push("author");
680
+ const newBody = input.body !== void 0 ? input.body : body;
681
+ if (input.body !== void 0) updated_fields.push("body");
682
+ if (updated_fields.length === 0) {
683
+ throw new Error("No fields to update \u2014 provide at least one of: body, tags, paths, symbols, commit, domain, author.");
684
+ }
685
+ await writeFile5(
686
+ loaded.filePath,
687
+ serializeMemory4({ frontmatter: newFrontmatter, body: newBody }),
688
+ "utf8"
689
+ );
690
+ return { id: input.id, file_path: loaded.filePath, updated_fields };
691
+ }
692
+
693
+ // src/tools/mem-pending.ts
694
+ import { existsSync as existsSync12 } from "fs";
606
695
  import {
607
696
  getUsage as getUsage4,
608
- loadMemoriesFromDir as loadMemoriesFromDir8,
697
+ loadMemoriesFromDir as loadMemoriesFromDir9,
609
698
  loadUsageIndex as loadUsageIndex6
610
699
  } from "@hiveai/core";
611
- import { z as z11 } from "zod";
700
+ import { z as z12 } from "zod";
612
701
  var MemPendingInputSchema = {
613
- scope: z11.enum(["personal", "team", "module"]).optional()
702
+ scope: z12.enum(["personal", "team", "module"]).optional()
614
703
  };
615
704
  async function memPending(input, ctx) {
616
- if (!existsSync11(ctx.paths.memoriesDir)) return { pending: [] };
617
- const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
705
+ if (!existsSync12(ctx.paths.memoriesDir)) return { pending: [] };
706
+ const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
618
707
  const usage = await loadUsageIndex6(ctx.paths);
619
708
  const now = Date.now();
620
709
  const proposed = all.filter(({ memory }) => {
@@ -645,21 +734,21 @@ async function memPending(input, ctx) {
645
734
  }
646
735
 
647
736
  // src/tools/mem-approve.ts
648
- import { writeFile as writeFile4 } from "fs/promises";
649
- import { existsSync as existsSync12 } from "fs";
737
+ import { writeFile as writeFile6 } from "fs/promises";
738
+ import { existsSync as existsSync13 } from "fs";
650
739
  import {
651
- loadMemoriesFromDir as loadMemoriesFromDir9,
652
- serializeMemory as serializeMemory3
740
+ loadMemoriesFromDir as loadMemoriesFromDir10,
741
+ serializeMemory as serializeMemory5
653
742
  } from "@hiveai/core";
654
- import { z as z12 } from "zod";
743
+ import { z as z13 } from "zod";
655
744
  var MemApproveInputSchema = {
656
- id: z12.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
745
+ id: z13.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
657
746
  };
658
747
  async function memApprove(input, ctx) {
659
- if (!existsSync12(ctx.paths.memoriesDir)) {
748
+ if (!existsSync13(ctx.paths.memoriesDir)) {
660
749
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
661
750
  }
662
- const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
751
+ const all = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
663
752
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
664
753
  if (!found) throw new Error(`No memory with id "${input.id}".`);
665
754
  const previous = found.memory.frontmatter.status;
@@ -667,7 +756,7 @@ async function memApprove(input, ctx) {
667
756
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
668
757
  body: found.memory.body
669
758
  };
670
- await writeFile4(found.filePath, serializeMemory3(next), "utf8");
759
+ await writeFile6(found.filePath, serializeMemory5(next), "utf8");
671
760
  return {
672
761
  id: input.id,
673
762
  previous_status: previous,
@@ -678,7 +767,7 @@ async function memApprove(input, ctx) {
678
767
 
679
768
  // src/tools/get-briefing.ts
680
769
  import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
681
- import { existsSync as existsSync13 } from "fs";
770
+ import { existsSync as existsSync14 } from "fs";
682
771
  import path5 from "path";
683
772
  import {
684
773
  allocateBudget,
@@ -687,37 +776,41 @@ import {
687
776
  getUsage as getUsage5,
688
777
  inferModulesFromPaths as inferModulesFromPaths2,
689
778
  literalMatchesAllTokens as literalMatchesAllTokens2,
690
- loadMemoriesFromDir as loadMemoriesFromDir10,
779
+ loadMemoriesFromDir as loadMemoriesFromDir11,
691
780
  loadUsageIndex as loadUsageIndex7,
692
781
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
693
782
  tokenizeQuery as tokenizeQuery2,
694
783
  trackReads as trackReads3,
695
784
  truncateToTokens
696
785
  } from "@hiveai/core";
697
- import { z as z13 } from "zod";
786
+ import { z as z14 } from "zod";
698
787
  var GetBriefingInputSchema = {
699
- task: z13.string().optional().describe(
788
+ task: z14.string().optional().describe(
700
789
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
701
790
  ),
702
- files: z13.array(z13.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
703
- max_tokens: z13.number().int().positive().default(8e3).describe(
791
+ files: z14.array(z14.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
792
+ max_tokens: z14.number().int().positive().default(8e3).describe(
704
793
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
705
794
  ),
706
- max_memories: z13.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
707
- include_project_context: z13.boolean().default(true),
708
- include_module_contexts: z13.boolean().default(true),
709
- semantic: z13.boolean().default(true).describe(
795
+ max_memories: z14.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
796
+ include_project_context: z14.boolean().default(true),
797
+ include_module_contexts: z14.boolean().default(true),
798
+ semantic: z14.boolean().default(true).describe(
710
799
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
711
800
  ),
712
- track: z13.boolean().default(true).describe("Increment read_count on returned memories")
801
+ track: z14.boolean().default(true).describe("Increment read_count on returned memories")
713
802
  };
714
803
  async function getBriefing(input, ctx) {
715
804
  const inferred = inferModulesFromPaths2(input.files);
716
805
  const memories = [];
717
- if (existsSync13(ctx.paths.memoriesDir)) {
718
- const allMemories = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
806
+ let searchMode = "literal";
807
+ if (existsSync14(ctx.paths.memoriesDir)) {
808
+ const allMemories = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
719
809
  const usage = await loadUsageIndex7(ctx.paths);
720
810
  const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
811
+ if (input.task && input.semantic) {
812
+ searchMode = semanticHits ? "semantic" : "literal_fallback";
813
+ }
721
814
  const seen = /* @__PURE__ */ new Map();
722
815
  const addOrUpdate = (loaded, reason, score) => {
723
816
  const fm = loaded.memory.frontmatter;
@@ -783,7 +876,7 @@ async function getBriefing(input, ctx) {
783
876
  await trackReads3(ctx.paths, memories.map((m) => m.id));
784
877
  }
785
878
  }
786
- const projectContext = input.include_project_context && existsSync13(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
879
+ const projectContext = input.include_project_context && existsSync14(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
787
880
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
788
881
  const memoriesText = memories.map((m) => `### ${m.id} (${m.scope}/${m.type}, ${m.confidence})
789
882
  ${m.body.trim()}`).join("\n\n---\n\n");
@@ -825,6 +918,7 @@ ${m.content}`).join("\n\n---\n\n"),
825
918
  const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
826
919
  return {
827
920
  ...input.task ? { task: input.task } : {},
921
+ search_mode: searchMode,
828
922
  inferred_modules: inferred,
829
923
  project_context: projectContext ? { content: projectSlice.text, truncated: projectSlice.truncated } : null,
830
924
  module_contexts: trimmedModules,
@@ -853,7 +947,7 @@ async function trySemanticHits(ctx, task, limit) {
853
947
  }
854
948
  async function loadModuleContexts2(ctx, modules) {
855
949
  if (modules.length === 0) return [];
856
- if (!existsSync13(ctx.paths.modulesContextDir)) return [];
950
+ if (!existsSync14(ctx.paths.modulesContextDir)) return [];
857
951
  const available = new Set(
858
952
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
859
953
  );
@@ -861,7 +955,7 @@ async function loadModuleContexts2(ctx, modules) {
861
955
  for (const m of modules) {
862
956
  if (!available.has(m)) continue;
863
957
  const file = path5.join(ctx.paths.modulesContextDir, m, "context.md");
864
- if (existsSync13(file)) {
958
+ if (existsSync14(file)) {
865
959
  out.push({ name: m, content: await readFile3(file, "utf8") });
866
960
  }
867
961
  }
@@ -870,11 +964,11 @@ async function loadModuleContexts2(ctx, modules) {
870
964
 
871
965
  // src/tools/code-map.ts
872
966
  import { loadCodeMap, queryCodeMap } from "@hiveai/core";
873
- import { z as z14 } from "zod";
967
+ import { z as z15 } from "zod";
874
968
  var CodeMapInputSchema = {
875
- file: z14.string().optional().describe("Filter to files whose path contains this substring"),
876
- symbol: z14.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
877
- max_files: z14.number().int().positive().default(40).describe("Cap on returned files")
969
+ file: z15.string().optional().describe("Filter to files whose path contains this substring"),
970
+ symbol: z15.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
971
+ max_files: z15.number().int().positive().default(40).describe("Cap on returned files")
878
972
  };
879
973
  async function codeMapTool(input, ctx) {
880
974
  const map = await loadCodeMap(ctx.paths);
@@ -900,12 +994,12 @@ async function codeMapTool(input, ctx) {
900
994
  }
901
995
 
902
996
  // src/prompts/bootstrap-project.ts
903
- import { z as z15 } from "zod";
997
+ import { z as z16 } from "zod";
904
998
  var BootstrapProjectArgsSchema = {
905
- module: z15.string().optional().describe(
999
+ module: z16.string().optional().describe(
906
1000
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
907
1001
  ),
908
- focus: z15.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1002
+ focus: z16.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
909
1003
  };
910
1004
  var ROOT_TEMPLATE = `# Project context
911
1005
 
@@ -988,7 +1082,7 @@ ${template}\`\`\`
988
1082
 
989
1083
  // src/server.ts
990
1084
  var SERVER_NAME = "haive";
991
- var SERVER_VERSION = "0.2.1";
1085
+ var SERVER_VERSION = "0.2.3";
992
1086
  function jsonResult(data) {
993
1087
  return {
994
1088
  content: [
@@ -1077,6 +1171,12 @@ function createHaiveServer(options = {}) {
1077
1171
  MemDeleteInputSchema,
1078
1172
  async (input) => jsonResult(await memDelete(input, context))
1079
1173
  );
1174
+ server.tool(
1175
+ "mem_update",
1176
+ "Update the body, tags, or anchor of an existing memory without changing its id or losing usage history.",
1177
+ MemUpdateInputSchema,
1178
+ async (input) => jsonResult(await memUpdate(input, context))
1179
+ );
1080
1180
  server.tool(
1081
1181
  "mem_pending",
1082
1182
  "List 'proposed' memories awaiting review, sorted by reads (most-read first).",