@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/index.js CHANGED
@@ -85,7 +85,10 @@ var MemListInputSchema = {
85
85
  scope: z3.enum(["personal", "team", "module"]).optional(),
86
86
  type: z3.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional(),
87
87
  module: z3.string().optional(),
88
- tag: z3.string().optional()
88
+ tag: z3.string().optional(),
89
+ status: z3.enum(["draft", "proposed", "validated", "deprecated", "stale", "rejected"]).optional().describe("Filter by a single status. Omit to return all statuses."),
90
+ exclude_rejected: z3.boolean().default(false).describe("When true, exclude memories with status=rejected from results."),
91
+ 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.")
89
92
  };
90
93
  async function memList(input, ctx) {
91
94
  if (!existsSync3(ctx.paths.memoriesDir)) {
@@ -98,10 +101,13 @@ async function memList(input, ctx) {
98
101
  if (input.type && fm.type !== input.type) return false;
99
102
  if (input.module && fm.module !== input.module) return false;
100
103
  if (input.tag && !fm.tags.includes(input.tag)) return false;
104
+ if (input.status && fm.status !== input.status) return false;
105
+ if (input.exclude_rejected && fm.status === "rejected") return false;
101
106
  return true;
102
107
  });
103
108
  const memories = filtered.map(({ memory, filePath }) => {
104
109
  const fm = memory.frontmatter;
110
+ const snippet = memory.body.replace(/\s+/g, " ").trim().slice(0, 120);
105
111
  return {
106
112
  id: fm.id,
107
113
  scope: fm.scope,
@@ -109,7 +115,9 @@ async function memList(input, ctx) {
109
115
  ...fm.module ? { module: fm.module } : {},
110
116
  status: fm.status,
111
117
  tags: fm.tags,
112
- file_path: filePath
118
+ snippet,
119
+ file_path: filePath,
120
+ ...input.include_body ? { body: memory.body } : {}
113
121
  };
114
122
  });
115
123
  return { memories };
@@ -193,6 +201,8 @@ var MemSearchInputSchema = {
193
201
  scope: z5.enum(["personal", "team", "module"]).optional().describe("Restrict results to a single scope"),
194
202
  type: z5.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional().describe("Restrict results to a memory type"),
195
203
  module: z5.string().optional().describe("Restrict results to a module"),
204
+ status: z5.enum(["draft", "proposed", "validated", "deprecated", "stale", "rejected"]).optional().describe("Filter by a single status. Omit to return all statuses."),
205
+ exclude_rejected: z5.boolean().default(false).describe("When true, exclude memories with status=rejected from results."),
196
206
  limit: z5.number().int().positive().max(100).default(20).describe("Max results"),
197
207
  semantic: z5.boolean().default(false).describe(
198
208
  "Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
@@ -234,6 +244,8 @@ function passesFilters(fm, input) {
234
244
  if (input.scope && fm.scope !== input.scope) return false;
235
245
  if (input.type && fm.type !== input.type) return false;
236
246
  if (input.module && fm.module !== input.module) return false;
247
+ if (input.status && fm.status !== input.status) return false;
248
+ if (input.exclude_rejected && fm.status === "rejected") return false;
237
249
  return true;
238
250
  }
239
251
  function buildLiteralResult(input, filtered, usage) {
@@ -335,6 +347,14 @@ async function memVerify(input, ctx) {
335
347
  const isAnchored = memory.frontmatter.anchor.paths.length > 0 || memory.frontmatter.anchor.symbols.length > 0;
336
348
  if (!isAnchored) {
337
349
  anchorless++;
350
+ results.push({
351
+ id: memory.frontmatter.id,
352
+ file_path: filePath,
353
+ stale: false,
354
+ reason: null,
355
+ status_after: memory.frontmatter.status,
356
+ skipped: true
357
+ });
338
358
  continue;
339
359
  }
340
360
  const result = await verifyAnchor(memory, { projectRoot: ctx.paths.root });
@@ -392,12 +412,14 @@ function applyVerification(mem, result) {
392
412
  }
393
413
 
394
414
  // src/tools/mem-reject.ts
415
+ import { writeFile as writeFile4 } from "fs/promises";
395
416
  import { existsSync as existsSync7 } from "fs";
396
417
  import {
397
418
  loadMemoriesFromDir as loadMemoriesFromDir4,
398
419
  loadUsageIndex as loadUsageIndex2,
399
420
  recordRejection,
400
- saveUsageIndex
421
+ saveUsageIndex,
422
+ serializeMemory as serializeMemory3
401
423
  } from "@hiveai/core";
402
424
  import { z as z7 } from "zod";
403
425
  var MemRejectInputSchema = {
@@ -409,16 +431,23 @@ async function memReject(input, ctx) {
409
431
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
410
432
  }
411
433
  const memories = await loadMemoriesFromDir4(ctx.paths.memoriesDir);
412
- const exists = memories.some((m) => m.memory.frontmatter.id === input.id);
413
- if (!exists) {
414
- throw new Error(`No memory with id "${input.id}".`);
415
- }
434
+ const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
435
+ if (!loaded) throw new Error(`No memory with id "${input.id}".`);
436
+ await writeFile4(
437
+ loaded.filePath,
438
+ serializeMemory3({
439
+ frontmatter: { ...loaded.memory.frontmatter, status: "rejected" },
440
+ body: loaded.memory.body
441
+ }),
442
+ "utf8"
443
+ );
416
444
  const idx = await loadUsageIndex2(ctx.paths);
417
445
  recordRejection(idx, input.id, input.reason ?? null);
418
446
  await saveUsageIndex(ctx.paths, idx);
419
447
  const u = idx.by_id[input.id];
420
448
  return {
421
449
  id: input.id,
450
+ status: "rejected",
422
451
  rejected_count: u?.rejected_count ?? 0,
423
452
  last_rejected_at: u?.last_rejected_at ?? null,
424
453
  rejection_reason: u?.rejection_reason ?? null
@@ -606,20 +635,80 @@ async function memDelete(input, ctx) {
606
635
  return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
607
636
  }
608
637
 
609
- // src/tools/mem-pending.ts
638
+ // src/tools/mem-update.ts
639
+ import { writeFile as writeFile5 } from "fs/promises";
610
640
  import { existsSync as existsSync11 } from "fs";
641
+ import { loadMemoriesFromDir as loadMemoriesFromDir8, serializeMemory as serializeMemory4 } from "@hiveai/core";
642
+ import { z as z11 } from "zod";
643
+ var MemUpdateInputSchema = {
644
+ id: z11.string().min(1).describe("Id of the memory to update"),
645
+ body: z11.string().optional().describe("New Markdown body \u2014 replaces the existing body"),
646
+ tags: z11.array(z11.string()).optional().describe("New tags array \u2014 fully replaces existing tags"),
647
+ paths: z11.array(z11.string()).optional().describe("New anchor paths \u2014 fully replaces existing anchor.paths"),
648
+ symbols: z11.array(z11.string()).optional().describe("New anchor symbols \u2014 fully replaces existing anchor.symbols"),
649
+ commit: z11.string().optional().describe("New anchor commit SHA"),
650
+ domain: z11.string().optional().describe("New domain label"),
651
+ author: z11.string().optional().describe("New author handle or email")
652
+ };
653
+ async function memUpdate(input, ctx) {
654
+ if (!existsSync11(ctx.paths.memoriesDir)) {
655
+ throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
656
+ }
657
+ const memories = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
658
+ const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
659
+ if (!loaded) throw new Error(`No memory with id "${input.id}".`);
660
+ const { frontmatter, body } = loaded.memory;
661
+ const updated_fields = [];
662
+ const newAnchor = { ...frontmatter.anchor };
663
+ if (input.paths !== void 0) {
664
+ newAnchor.paths = input.paths;
665
+ updated_fields.push("anchor.paths");
666
+ }
667
+ if (input.symbols !== void 0) {
668
+ newAnchor.symbols = input.symbols;
669
+ updated_fields.push("anchor.symbols");
670
+ }
671
+ if (input.commit !== void 0) {
672
+ newAnchor.commit = input.commit;
673
+ updated_fields.push("anchor.commit");
674
+ }
675
+ const newFrontmatter = {
676
+ ...frontmatter,
677
+ anchor: newAnchor,
678
+ ...input.tags !== void 0 ? { tags: input.tags } : {},
679
+ ...input.domain !== void 0 ? { domain: input.domain } : {},
680
+ ...input.author !== void 0 ? { author: input.author } : {}
681
+ };
682
+ if (input.tags !== void 0) updated_fields.push("tags");
683
+ if (input.domain !== void 0) updated_fields.push("domain");
684
+ if (input.author !== void 0) updated_fields.push("author");
685
+ const newBody = input.body !== void 0 ? input.body : body;
686
+ if (input.body !== void 0) updated_fields.push("body");
687
+ if (updated_fields.length === 0) {
688
+ throw new Error("No fields to update \u2014 provide at least one of: body, tags, paths, symbols, commit, domain, author.");
689
+ }
690
+ await writeFile5(
691
+ loaded.filePath,
692
+ serializeMemory4({ frontmatter: newFrontmatter, body: newBody }),
693
+ "utf8"
694
+ );
695
+ return { id: input.id, file_path: loaded.filePath, updated_fields };
696
+ }
697
+
698
+ // src/tools/mem-pending.ts
699
+ import { existsSync as existsSync12 } from "fs";
611
700
  import {
612
701
  getUsage as getUsage4,
613
- loadMemoriesFromDir as loadMemoriesFromDir8,
702
+ loadMemoriesFromDir as loadMemoriesFromDir9,
614
703
  loadUsageIndex as loadUsageIndex6
615
704
  } from "@hiveai/core";
616
- import { z as z11 } from "zod";
705
+ import { z as z12 } from "zod";
617
706
  var MemPendingInputSchema = {
618
- scope: z11.enum(["personal", "team", "module"]).optional()
707
+ scope: z12.enum(["personal", "team", "module"]).optional()
619
708
  };
620
709
  async function memPending(input, ctx) {
621
- if (!existsSync11(ctx.paths.memoriesDir)) return { pending: [] };
622
- const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
710
+ if (!existsSync12(ctx.paths.memoriesDir)) return { pending: [] };
711
+ const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
623
712
  const usage = await loadUsageIndex6(ctx.paths);
624
713
  const now = Date.now();
625
714
  const proposed = all.filter(({ memory }) => {
@@ -650,21 +739,21 @@ async function memPending(input, ctx) {
650
739
  }
651
740
 
652
741
  // src/tools/mem-approve.ts
653
- import { writeFile as writeFile4 } from "fs/promises";
654
- import { existsSync as existsSync12 } from "fs";
742
+ import { writeFile as writeFile6 } from "fs/promises";
743
+ import { existsSync as existsSync13 } from "fs";
655
744
  import {
656
- loadMemoriesFromDir as loadMemoriesFromDir9,
657
- serializeMemory as serializeMemory3
745
+ loadMemoriesFromDir as loadMemoriesFromDir10,
746
+ serializeMemory as serializeMemory5
658
747
  } from "@hiveai/core";
659
- import { z as z12 } from "zod";
748
+ import { z as z13 } from "zod";
660
749
  var MemApproveInputSchema = {
661
- id: z12.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
750
+ id: z13.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
662
751
  };
663
752
  async function memApprove(input, ctx) {
664
- if (!existsSync12(ctx.paths.memoriesDir)) {
753
+ if (!existsSync13(ctx.paths.memoriesDir)) {
665
754
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
666
755
  }
667
- const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
756
+ const all = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
668
757
  const found = all.find((m) => m.memory.frontmatter.id === input.id);
669
758
  if (!found) throw new Error(`No memory with id "${input.id}".`);
670
759
  const previous = found.memory.frontmatter.status;
@@ -672,7 +761,7 @@ async function memApprove(input, ctx) {
672
761
  frontmatter: { ...found.memory.frontmatter, status: "validated" },
673
762
  body: found.memory.body
674
763
  };
675
- await writeFile4(found.filePath, serializeMemory3(next), "utf8");
764
+ await writeFile6(found.filePath, serializeMemory5(next), "utf8");
676
765
  return {
677
766
  id: input.id,
678
767
  previous_status: previous,
@@ -683,7 +772,7 @@ async function memApprove(input, ctx) {
683
772
 
684
773
  // src/tools/get-briefing.ts
685
774
  import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
686
- import { existsSync as existsSync13 } from "fs";
775
+ import { existsSync as existsSync14 } from "fs";
687
776
  import path5 from "path";
688
777
  import {
689
778
  allocateBudget,
@@ -692,37 +781,41 @@ import {
692
781
  getUsage as getUsage5,
693
782
  inferModulesFromPaths as inferModulesFromPaths2,
694
783
  literalMatchesAllTokens as literalMatchesAllTokens2,
695
- loadMemoriesFromDir as loadMemoriesFromDir10,
784
+ loadMemoriesFromDir as loadMemoriesFromDir11,
696
785
  loadUsageIndex as loadUsageIndex7,
697
786
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
698
787
  tokenizeQuery as tokenizeQuery2,
699
788
  trackReads as trackReads3,
700
789
  truncateToTokens
701
790
  } from "@hiveai/core";
702
- import { z as z13 } from "zod";
791
+ import { z as z14 } from "zod";
703
792
  var GetBriefingInputSchema = {
704
- task: z13.string().optional().describe(
793
+ task: z14.string().optional().describe(
705
794
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
706
795
  ),
707
- files: z13.array(z13.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
708
- max_tokens: z13.number().int().positive().default(8e3).describe(
796
+ files: z14.array(z14.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
797
+ max_tokens: z14.number().int().positive().default(8e3).describe(
709
798
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
710
799
  ),
711
- max_memories: z13.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
712
- include_project_context: z13.boolean().default(true),
713
- include_module_contexts: z13.boolean().default(true),
714
- semantic: z13.boolean().default(true).describe(
800
+ max_memories: z14.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
801
+ include_project_context: z14.boolean().default(true),
802
+ include_module_contexts: z14.boolean().default(true),
803
+ semantic: z14.boolean().default(true).describe(
715
804
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
716
805
  ),
717
- track: z13.boolean().default(true).describe("Increment read_count on returned memories")
806
+ track: z14.boolean().default(true).describe("Increment read_count on returned memories")
718
807
  };
719
808
  async function getBriefing(input, ctx) {
720
809
  const inferred = inferModulesFromPaths2(input.files);
721
810
  const memories = [];
722
- if (existsSync13(ctx.paths.memoriesDir)) {
723
- const allMemories = await loadMemoriesFromDir10(ctx.paths.memoriesDir);
811
+ let searchMode = "literal";
812
+ if (existsSync14(ctx.paths.memoriesDir)) {
813
+ const allMemories = await loadMemoriesFromDir11(ctx.paths.memoriesDir);
724
814
  const usage = await loadUsageIndex7(ctx.paths);
725
815
  const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
816
+ if (input.task && input.semantic) {
817
+ searchMode = semanticHits ? "semantic" : "literal_fallback";
818
+ }
726
819
  const seen = /* @__PURE__ */ new Map();
727
820
  const addOrUpdate = (loaded, reason, score) => {
728
821
  const fm = loaded.memory.frontmatter;
@@ -788,7 +881,7 @@ async function getBriefing(input, ctx) {
788
881
  await trackReads3(ctx.paths, memories.map((m) => m.id));
789
882
  }
790
883
  }
791
- const projectContext = input.include_project_context && existsSync13(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
884
+ const projectContext = input.include_project_context && existsSync14(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
792
885
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
793
886
  const memoriesText = memories.map((m) => `### ${m.id} (${m.scope}/${m.type}, ${m.confidence})
794
887
  ${m.body.trim()}`).join("\n\n---\n\n");
@@ -830,6 +923,7 @@ ${m.content}`).join("\n\n---\n\n"),
830
923
  const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
831
924
  return {
832
925
  ...input.task ? { task: input.task } : {},
926
+ search_mode: searchMode,
833
927
  inferred_modules: inferred,
834
928
  project_context: projectContext ? { content: projectSlice.text, truncated: projectSlice.truncated } : null,
835
929
  module_contexts: trimmedModules,
@@ -858,7 +952,7 @@ async function trySemanticHits(ctx, task, limit) {
858
952
  }
859
953
  async function loadModuleContexts2(ctx, modules) {
860
954
  if (modules.length === 0) return [];
861
- if (!existsSync13(ctx.paths.modulesContextDir)) return [];
955
+ if (!existsSync14(ctx.paths.modulesContextDir)) return [];
862
956
  const available = new Set(
863
957
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
864
958
  );
@@ -866,7 +960,7 @@ async function loadModuleContexts2(ctx, modules) {
866
960
  for (const m of modules) {
867
961
  if (!available.has(m)) continue;
868
962
  const file = path5.join(ctx.paths.modulesContextDir, m, "context.md");
869
- if (existsSync13(file)) {
963
+ if (existsSync14(file)) {
870
964
  out.push({ name: m, content: await readFile3(file, "utf8") });
871
965
  }
872
966
  }
@@ -875,11 +969,11 @@ async function loadModuleContexts2(ctx, modules) {
875
969
 
876
970
  // src/tools/code-map.ts
877
971
  import { loadCodeMap, queryCodeMap } from "@hiveai/core";
878
- import { z as z14 } from "zod";
972
+ import { z as z15 } from "zod";
879
973
  var CodeMapInputSchema = {
880
- file: z14.string().optional().describe("Filter to files whose path contains this substring"),
881
- symbol: z14.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
882
- max_files: z14.number().int().positive().default(40).describe("Cap on returned files")
974
+ file: z15.string().optional().describe("Filter to files whose path contains this substring"),
975
+ symbol: z15.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
976
+ max_files: z15.number().int().positive().default(40).describe("Cap on returned files")
883
977
  };
884
978
  async function codeMapTool(input, ctx) {
885
979
  const map = await loadCodeMap(ctx.paths);
@@ -905,12 +999,12 @@ async function codeMapTool(input, ctx) {
905
999
  }
906
1000
 
907
1001
  // src/prompts/bootstrap-project.ts
908
- import { z as z15 } from "zod";
1002
+ import { z as z16 } from "zod";
909
1003
  var BootstrapProjectArgsSchema = {
910
- module: z15.string().optional().describe(
1004
+ module: z16.string().optional().describe(
911
1005
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
912
1006
  ),
913
- focus: z15.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1007
+ focus: z16.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
914
1008
  };
915
1009
  var ROOT_TEMPLATE = `# Project context
916
1010
 
@@ -993,7 +1087,7 @@ ${template}\`\`\`
993
1087
 
994
1088
  // src/server.ts
995
1089
  var SERVER_NAME = "haive";
996
- var SERVER_VERSION = "0.2.1";
1090
+ var SERVER_VERSION = "0.2.3";
997
1091
  function jsonResult(data) {
998
1092
  return {
999
1093
  content: [
@@ -1082,6 +1176,12 @@ function createHaiveServer(options = {}) {
1082
1176
  MemDeleteInputSchema,
1083
1177
  async (input) => jsonResult(await memDelete(input, context))
1084
1178
  );
1179
+ server.tool(
1180
+ "mem_update",
1181
+ "Update the body, tags, or anchor of an existing memory without changing its id or losing usage history.",
1182
+ MemUpdateInputSchema,
1183
+ async (input) => jsonResult(await memUpdate(input, context))
1184
+ );
1085
1185
  server.tool(
1086
1186
  "mem_pending",
1087
1187
  "List 'proposed' memories awaiting review, sorted by reads (most-read first).",
@@ -1120,7 +1220,7 @@ async function main() {
1120
1220
  const { root } = parseArgs(process.argv);
1121
1221
  const { server, context } = createHaiveServer({ root });
1122
1222
  console.error(
1123
- `[haive-mcp] starting server v0.1.0 (project root: ${context.paths.root})`
1223
+ `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
1124
1224
  );
1125
1225
  const transport = new StdioServerTransport();
1126
1226
  await server.connect(transport);