@hiveai/mcp 0.2.6 → 0.2.8

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
@@ -397,6 +397,7 @@ async function memVerify(input, ctx) {
397
397
  file_path: filePath,
398
398
  stale: result.stale,
399
399
  reason: result.reason,
400
+ ...result.possibleRenames.length > 0 ? { possible_renames: result.possibleRenames } : {},
400
401
  status_after: statusAfter
401
402
  });
402
403
  }
@@ -859,6 +860,7 @@ import {
859
860
  estimateTokens,
860
861
  getUsage as getUsage5,
861
862
  inferModulesFromPaths as inferModulesFromPaths2,
863
+ isDecaying,
862
864
  literalMatchesAllTokens as literalMatchesAllTokens2,
863
865
  loadMemoriesFromDir as loadMemoriesFromDir12,
864
866
  loadUsageIndex as loadUsageIndex7,
@@ -883,12 +885,17 @@ var GetBriefingInputSchema = {
883
885
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
884
886
  ),
885
887
  include_stale: z15.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
886
- track: z15.boolean().default(true).describe("Increment read_count on returned memories")
888
+ track: z15.boolean().default(true).describe("Increment read_count on returned memories"),
889
+ format: z15.enum(["full", "compact"]).default("full").describe(
890
+ "Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
891
+ )
887
892
  };
888
893
  async function getBriefing(input, ctx) {
889
894
  const inferred = inferModulesFromPaths2(input.files);
890
895
  const memories = [];
891
896
  let searchMode = "literal";
897
+ let usage = { version: 1, updated_at: "", by_id: {} };
898
+ let byId = /* @__PURE__ */ new Map();
892
899
  if (existsSync15(ctx.paths.memoriesDir)) {
893
900
  const allLoaded = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
894
901
  const allMemories = allLoaded.filter(({ memory }) => {
@@ -897,13 +904,13 @@ async function getBriefing(input, ctx) {
897
904
  if (!input.include_stale && s === "stale") return false;
898
905
  return true;
899
906
  });
900
- const usage = await loadUsageIndex7(ctx.paths);
907
+ usage = await loadUsageIndex7(ctx.paths);
901
908
  const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
902
909
  if (input.task && input.semantic) {
903
910
  searchMode = semanticHits ? "semantic" : "literal_fallback";
904
911
  }
905
912
  const seen = /* @__PURE__ */ new Map();
906
- const addOrUpdate = (loaded, reason, score) => {
913
+ const addOrUpdate = (loaded, reason, score, matchQuality) => {
907
914
  const fm = loaded.memory.frontmatter;
908
915
  const existing = seen.get(fm.id);
909
916
  if (existing) {
@@ -911,6 +918,11 @@ async function getBriefing(input, ctx) {
911
918
  if (score !== void 0 && (existing.semantic_score ?? 0) < score) {
912
919
  existing.semantic_score = score;
913
920
  }
921
+ if (matchQuality === "exact" && existing.match_quality !== "exact") {
922
+ existing.match_quality = "exact";
923
+ } else if (matchQuality === "semantic" && existing.match_quality === "partial") {
924
+ existing.match_quality = "semantic";
925
+ }
914
926
  return;
915
927
  }
916
928
  const u = getUsage5(usage, fm.id);
@@ -924,6 +936,7 @@ async function getBriefing(input, ctx) {
924
936
  confidence: deriveConfidence4(fm, u),
925
937
  read_count: u.read_count,
926
938
  reasons: [reason],
939
+ match_quality: matchQuality ?? "partial",
927
940
  ...score !== void 0 ? { semantic_score: score } : {},
928
941
  body: loaded.memory.body,
929
942
  file_path: loaded.filePath
@@ -931,37 +944,48 @@ async function getBriefing(input, ctx) {
931
944
  };
932
945
  if (input.files.length > 0) {
933
946
  for (const loaded of allMemories) {
934
- if (memoryMatchesAnchorPaths2(loaded.memory, input.files)) addOrUpdate(loaded, "anchor");
947
+ if (memoryMatchesAnchorPaths2(loaded.memory, input.files)) addOrUpdate(loaded, "anchor", void 0, "exact");
935
948
  }
936
949
  for (const loaded of allMemories) {
937
950
  const fm = loaded.memory.frontmatter;
938
- if (fm.module && inferred.includes(fm.module)) addOrUpdate(loaded, "module");
939
- if (fm.domain && inferred.includes(fm.domain)) addOrUpdate(loaded, "domain");
940
- if (fm.tags.some((t) => inferred.includes(t))) addOrUpdate(loaded, "module");
951
+ if (fm.module && inferred.includes(fm.module)) addOrUpdate(loaded, "module", void 0, "partial");
952
+ if (fm.domain && inferred.includes(fm.domain)) addOrUpdate(loaded, "domain", void 0, "partial");
953
+ if (fm.tags.some((t) => inferred.includes(t))) addOrUpdate(loaded, "module", void 0, "partial");
941
954
  }
942
955
  }
943
956
  if (input.task) {
944
957
  const tokens = tokenizeQuery2(input.task);
945
958
  for (const loaded of allMemories) {
946
959
  if (literalMatchesAllTokens2(loaded.memory, tokens)) {
947
- addOrUpdate(loaded, "semantic");
960
+ addOrUpdate(loaded, "semantic", void 0, "exact");
948
961
  }
949
962
  }
950
963
  if (semanticHits) {
951
- const byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
952
964
  for (const hit of semanticHits) {
953
965
  const loaded = byId.get(hit.id);
954
- if (loaded) addOrUpdate(loaded, "semantic", hit.score);
966
+ if (loaded) addOrUpdate(loaded, "semantic", hit.score, "semantic");
955
967
  }
956
968
  }
957
969
  }
958
970
  const ranked = [...seen.values()].sort((a, b) => {
959
- const reasonScore = (m) => (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
971
+ const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + // attempt = negative knowledge, surface first to prevent repeating mistakes
972
+ (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
960
973
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
961
974
  const sa = reasonScore(a) + confidenceScore(a) + (a.semantic_score ?? 0);
962
975
  const sb = reasonScore(b) + confidenceScore(b) + (b.semantic_score ?? 0);
963
976
  return sb - sa;
964
977
  });
978
+ byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
979
+ for (const mem of ranked.slice(0, input.max_memories)) {
980
+ if (seen.size >= input.max_memories * 2) break;
981
+ const loaded = byId.get(mem.id);
982
+ if (!loaded) continue;
983
+ for (const relId of loaded.memory.frontmatter.related_ids ?? []) {
984
+ if (seen.has(relId)) continue;
985
+ const related = byId.get(relId);
986
+ if (related) addOrUpdate(related, "anchor", void 0, "partial");
987
+ }
988
+ }
965
989
  memories.push(...ranked.slice(0, input.max_memories));
966
990
  if (input.track && memories.length > 0) {
967
991
  await trackReads3(ctx.paths, memories.map((m) => m.id));
@@ -1002,21 +1026,41 @@ ${m.content}`).join("\n\n---\n\n"),
1002
1026
  trimmedModules.push({ name: m.name, content: sub.text, truncated: sub.truncated });
1003
1027
  }
1004
1028
  }
1005
- const trimmedMemoriesText = memoriesSlice.text;
1006
- const trimmedMemories = memories.map((m) => {
1007
- if (!memoriesSlice.truncated) return m;
1008
- const tokensPer = Math.floor(memoriesSlice.allocatedTokens / Math.max(1, memories.length));
1009
- const t = truncateToTokens(m.body, { maxTokens: tokensPer, mode: "head" });
1010
- return { ...m, body: t.text };
1011
- });
1029
+ const trimmedMemories = [];
1030
+ if (!memoriesSlice.truncated) {
1031
+ trimmedMemories.push(...memories);
1032
+ } else {
1033
+ let remaining = memoriesSlice.allocatedTokens;
1034
+ for (const m of memories) {
1035
+ const bodyTokens = estimateTokens(m.body);
1036
+ if (remaining <= 0) break;
1037
+ if (bodyTokens <= remaining) {
1038
+ trimmedMemories.push(m);
1039
+ remaining -= bodyTokens;
1040
+ } else if (remaining > 80) {
1041
+ const t = truncateToTokens(m.body, { maxTokens: remaining, mode: "head" });
1042
+ trimmedMemories.push({ ...m, body: t.text });
1043
+ remaining = 0;
1044
+ }
1045
+ }
1046
+ }
1012
1047
  const totalTokens = projectSlice.estimatedTokens + modulesSlice.estimatedTokens + memoriesSlice.estimatedTokens;
1048
+ const decayWarnings = [];
1049
+ for (const m of trimmedMemories) {
1050
+ const u = getUsage5(usage, m.id);
1051
+ const loaded = byId.get(m.id);
1052
+ const createdAt = loaded?.memory.frontmatter.created_at ?? (/* @__PURE__ */ new Date()).toISOString();
1053
+ if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
1054
+ }
1055
+ const outputMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : trimmedMemories;
1013
1056
  return {
1014
1057
  ...input.task ? { task: input.task } : {},
1015
1058
  search_mode: searchMode,
1016
1059
  inferred_modules: inferred,
1017
1060
  project_context: projectContext ? { content: projectSlice.text, truncated: projectSlice.truncated } : null,
1018
1061
  module_contexts: trimmedModules,
1019
- memories: trimmedMemories,
1062
+ memories: outputMemories,
1063
+ decay_warnings: decayWarnings,
1020
1064
  estimated_tokens: totalTokens,
1021
1065
  budget: {
1022
1066
  max_tokens: input.max_tokens,
@@ -1028,6 +1072,13 @@ ${m.content}`).join("\n\n---\n\n"),
1028
1072
  }
1029
1073
  };
1030
1074
  }
1075
+ function compactSummary(body) {
1076
+ for (const line of body.split("\n")) {
1077
+ const trimmed = line.replace(/^#+\s*/, "").trim();
1078
+ if (trimmed.length > 0) return trimmed.slice(0, 120);
1079
+ }
1080
+ return body.slice(0, 120);
1081
+ }
1031
1082
  async function trySemanticHits(ctx, task, limit) {
1032
1083
  let mod;
1033
1084
  try {
@@ -1087,13 +1138,58 @@ async function codeMapTool(input, ctx) {
1087
1138
  };
1088
1139
  }
1089
1140
 
1090
- // src/prompts/bootstrap-project.ts
1141
+ // src/tools/mem-diff.ts
1142
+ import { existsSync as existsSync16 } from "fs";
1143
+ import { loadMemoriesFromDir as loadMemoriesFromDir13 } from "@hiveai/core";
1091
1144
  import { z as z17 } from "zod";
1145
+ var MemDiffInputSchema = {
1146
+ id_a: z17.string().min(1).describe("First memory id"),
1147
+ id_b: z17.string().min(1).describe("Second memory id")
1148
+ };
1149
+ async function memDiff(input, ctx) {
1150
+ if (!existsSync16(ctx.paths.memoriesDir)) {
1151
+ throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
1152
+ }
1153
+ const all = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
1154
+ const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
1155
+ const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
1156
+ if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
1157
+ if (!foundB) throw new Error(`No memory with id "${input.id_b}".`);
1158
+ const fmA = foundA.memory.frontmatter;
1159
+ const fmB = foundB.memory.frontmatter;
1160
+ const frontmatterDiff = {};
1161
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(fmA), ...Object.keys(fmB)]);
1162
+ for (const key of allKeys) {
1163
+ const va = fmA[key];
1164
+ const vb = fmB[key];
1165
+ if (JSON.stringify(va) !== JSON.stringify(vb)) {
1166
+ frontmatterDiff[key] = { a: va, b: vb };
1167
+ }
1168
+ }
1169
+ const linesA = new Set(foundA.memory.body.split("\n").map((l) => l.trim()).filter(Boolean));
1170
+ const linesB = new Set(foundB.memory.body.split("\n").map((l) => l.trim()).filter(Boolean));
1171
+ const onlyA = [...linesA].filter((l) => !linesB.has(l));
1172
+ const onlyB = [...linesB].filter((l) => !linesA.has(l));
1173
+ const common = [...linesA].filter((l) => linesB.has(l)).length;
1174
+ return {
1175
+ id_a: input.id_a,
1176
+ id_b: input.id_b,
1177
+ frontmatter_diff: frontmatterDiff,
1178
+ body_diff: {
1179
+ lines_only_in_a: onlyA,
1180
+ lines_only_in_b: onlyB,
1181
+ common_lines: common
1182
+ }
1183
+ };
1184
+ }
1185
+
1186
+ // src/prompts/bootstrap-project.ts
1187
+ import { z as z18 } from "zod";
1092
1188
  var BootstrapProjectArgsSchema = {
1093
- module: z17.string().optional().describe(
1189
+ module: z18.string().optional().describe(
1094
1190
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
1095
1191
  ),
1096
- focus: z17.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1192
+ focus: z18.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1097
1193
  };
1098
1194
  var ROOT_TEMPLATE = `# Project context
1099
1195
 
@@ -1174,9 +1270,138 @@ ${template}\`\`\`
1174
1270
  };
1175
1271
  }
1176
1272
 
1273
+ // src/prompts/post-task.ts
1274
+ import { z as z19 } from "zod";
1275
+ var PostTaskArgsSchema = {
1276
+ task_summary: z19.string().optional().describe("One sentence describing what you just did"),
1277
+ files_touched: z19.array(z19.string()).optional().describe("Files you created or modified during the task")
1278
+ };
1279
+ function postTaskPrompt(args, ctx) {
1280
+ const taskLine = args.task_summary ? `
1281
+ Task just completed: **${args.task_summary}**` : "";
1282
+ const filesLine = args.files_touched && args.files_touched.length > 0 ? `
1283
+ Files touched: ${args.files_touched.map((f) => `\`${f}\``).join(", ")}` : "";
1284
+ const text = `You have just finished a task. Before closing this session, take 60 seconds to capture what you learned.
1285
+ ${taskLine}${filesLine}
1286
+
1287
+ Project root: \`${ctx.paths.root}\`
1288
+
1289
+ ## Checklist \u2014 answer each question honestly
1290
+
1291
+ Go through each item. If the answer is yes, call the corresponding tool immediately.
1292
+
1293
+ ### 1. Did you try an approach that failed?
1294
+ \u2192 If yes, call **\`mem_tried\`** with:
1295
+ - \`what\`: the approach you tried (e.g. "importing gray-matter with ESM dynamic import")
1296
+ - \`why_failed\`: why it didn't work
1297
+ - \`instead\`: what worked instead
1298
+ - \`scope\`: "team" if others will hit the same issue, "personal" if specific to your setup
1299
+ - \`paths\`: the files where the issue manifested
1300
+
1301
+ ### 2. Did you discover a convention that isn't documented?
1302
+ \u2192 If yes, call **\`mem_save\`** with \`type="convention"\` and \`scope="team"\`
1303
+
1304
+ ### 3. Did you make an architectural decision?
1305
+ \u2192 If yes, call **\`mem_save\`** with \`type="decision"\` and document the WHY (constraints, tradeoffs), not just the what
1306
+
1307
+ ### 4. Did you hit a non-obvious bug or surprising behavior?
1308
+ \u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\` and anchor it to the relevant file paths
1309
+
1310
+ ### 5. Did you find that an existing memory is outdated or wrong?
1311
+ \u2192 If yes, call **\`mem_update\`** with the correct information, or **\`mem_reject\`** if it's completely wrong
1312
+
1313
+ ## Rules
1314
+
1315
+ - One memory per insight. Don't cram multiple lessons into one body.
1316
+ - Anchor memories to file paths when possible (the \`paths\` field) \u2014 this enables staleness detection.
1317
+ - Prefer \`scope="team"\` for anything a teammate or future agent would benefit from.
1318
+ - Skip sections where you genuinely have nothing to add. Don't fabricate memories.
1319
+
1320
+ When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "Nothing new to save."
1321
+ `;
1322
+ return {
1323
+ description: "Post-task reflection: capture what you learned before closing the session",
1324
+ messages: [
1325
+ {
1326
+ role: "user",
1327
+ content: { type: "text", text }
1328
+ }
1329
+ ]
1330
+ };
1331
+ }
1332
+
1333
+ // src/prompts/import-docs.ts
1334
+ import { z as z20 } from "zod";
1335
+ var ImportDocsArgsSchema = {
1336
+ content: z20.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
1337
+ source: z20.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
1338
+ scope: z20.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
1339
+ dry_run: z20.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
1340
+ };
1341
+ function importDocsPrompt(args, ctx) {
1342
+ const sourceLine = args.source ? `
1343
+ Source: **${args.source}**` : "";
1344
+ const dryRunNote = args.dry_run ? "\n> **DRY RUN** \u2014 describe what you would save but do not call any tools." : "";
1345
+ const text = `You are given documentation to analyze and import into the hAIve memory system.
1346
+ ${sourceLine}
1347
+ Scope: **${args.scope}**
1348
+ Project root: \`${ctx.paths.root}\`
1349
+ ${dryRunNote}
1350
+
1351
+ ## Your task
1352
+
1353
+ Read the documentation below and extract actionable memories. For each distinct piece of knowledge:
1354
+
1355
+ 1. **Identify the memory type** \u2014 which category fits best?
1356
+ - \`convention\` \u2014 how things are done here (naming, patterns, workflow)
1357
+ - \`decision\` \u2014 a choice that was made and why (tradeoffs, constraints)
1358
+ - \`gotcha\` \u2014 non-obvious behavior, traps, things that surprise newcomers
1359
+ - \`architecture\` \u2014 structural overview of a system or module
1360
+ - \`glossary\` \u2014 domain terms and their meaning in this project
1361
+
1362
+ 2. **Determine the anchor** \u2014 which files or symbols does this knowledge apply to? List them in \`paths\`.
1363
+
1364
+ 3. **Write a focused body** \u2014 one memory = one insight. Do not combine multiple unrelated facts.
1365
+ - Start with the key fact or rule
1366
+ - Add context: why it matters, when it applies
1367
+ - Add examples if helpful
1368
+
1369
+ 4. **Call \`mem_save\`** for each memory (unless dry_run).
1370
+ - Set \`scope="${args.scope}"\`
1371
+ - Set \`slug\` to a short kebab-case identifier
1372
+ - Set \`paths\` to the relevant file paths (extracted from the doc if present)
1373
+
1374
+ ## Rules
1375
+
1376
+ - Skip generic documentation that applies to any project (e.g., "install with npm install").
1377
+ - Prioritize gotchas, non-obvious decisions, and domain-specific conventions.
1378
+ - If the same knowledge is repeated in different sections, save it once.
1379
+ - Maximum 10 memories per import \u2014 select the most actionable ones.
1380
+
1381
+ ## Documentation to import
1382
+
1383
+ ---
1384
+
1385
+ ${args.content}
1386
+
1387
+ ---
1388
+
1389
+ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing actionable found."
1390
+ `;
1391
+ return {
1392
+ description: "Import documentation as hAIve memories",
1393
+ messages: [
1394
+ {
1395
+ role: "user",
1396
+ content: { type: "text", text }
1397
+ }
1398
+ ]
1399
+ };
1400
+ }
1401
+
1177
1402
  // src/server.ts
1178
1403
  var SERVER_NAME = "haive";
1179
- var SERVER_VERSION = "0.2.6";
1404
+ var SERVER_VERSION = "0.2.8";
1180
1405
  function jsonResult(data) {
1181
1406
  return {
1182
1407
  content: [
@@ -1195,7 +1420,7 @@ function createHaiveServer(options = {}) {
1195
1420
  );
1196
1421
  server.tool(
1197
1422
  "mem_save",
1198
- "Save a new memory (default scope=personal). Use scope=team for shared memories.",
1423
+ "Save a new memory (convention, decision, gotcha, architecture, glossary). For failed approaches use mem_tried instead \u2014 it enforces a structured format that is more useful to future agents. Use scope=team to share with the whole team.",
1199
1424
  MemSaveInputSchema,
1200
1425
  async (input) => jsonResult(await memSave(input, context))
1201
1426
  );
@@ -1285,16 +1510,34 @@ function createHaiveServer(options = {}) {
1285
1510
  );
1286
1511
  server.tool(
1287
1512
  "mem_tried",
1288
- "Record a failed approach as negative knowledge (type=attempt, auto-validated). Use this whenever you tried something and it didn't work \u2014 saves future agents from repeating the mistake.",
1513
+ "Preferred way to record a failed approach. Enforces a structured what/why_failed/instead format that is immediately actionable for future agents. Auto-validated (no approval cycle). Use whenever you tried an approach and it failed \u2014 prevents the same mistake from happening in the next session.",
1289
1514
  MemTriedInputSchema,
1290
1515
  async (input) => jsonResult(await memTried(input, context))
1291
1516
  );
1517
+ server.tool(
1518
+ "mem_diff",
1519
+ "Compare two memories side-by-side: shows frontmatter fields that differ and lines unique to each body. Useful before merging or deduplicating memories.",
1520
+ MemDiffInputSchema,
1521
+ async (input) => jsonResult(await memDiff(input, context))
1522
+ );
1292
1523
  server.prompt(
1293
1524
  "bootstrap_project",
1294
1525
  "Instructions for the AI client to analyze the project and save the context.",
1295
1526
  BootstrapProjectArgsSchema,
1296
1527
  (args) => bootstrapProjectPrompt(args, context)
1297
1528
  );
1529
+ server.prompt(
1530
+ "post_task",
1531
+ "Post-task checklist: run this after completing a task to capture failed approaches, new conventions, decisions, and gotchas before closing the session.",
1532
+ PostTaskArgsSchema,
1533
+ (args) => postTaskPrompt(args, context)
1534
+ );
1535
+ server.prompt(
1536
+ "import_docs",
1537
+ "Analyze documentation (README, ADR, wiki page, etc.) and save the actionable knowledge as hAIve memories. Pass the content and an optional source/scope.",
1538
+ ImportDocsArgsSchema,
1539
+ (args) => importDocsPrompt(args, context)
1540
+ );
1298
1541
  return { server, context };
1299
1542
  }
1300
1543