@hiveai/mcp 0.2.10 → 0.2.11

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
@@ -521,10 +521,14 @@ async function memForFiles(input, ctx) {
521
521
  seen.add(loaded.memory.frontmatter.id);
522
522
  }
523
523
  }
524
+ const pathSegments = extractPathSegments(input.files);
524
525
  for (const loaded of all) {
525
526
  if (seen.has(loaded.memory.frontmatter.id)) continue;
526
527
  const fm = loaded.memory.frontmatter;
527
- const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t));
528
+ const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t)) || fm.tags.some((t) => {
529
+ const tl = t.toLowerCase();
530
+ return pathSegments.has(tl) || pathSegments.has(tl.replace(/[-_]/g, ""));
531
+ });
528
532
  if (moduleHit) {
529
533
  byModule.push(toMatch(loaded, "module", usage));
530
534
  seen.add(fm.id);
@@ -566,6 +570,52 @@ function toMatch(loaded, reason, usage) {
566
570
  body: loaded.memory.body
567
571
  };
568
572
  }
573
+ function extractPathSegments(files) {
574
+ const GENERIC = /* @__PURE__ */ new Set([
575
+ "src",
576
+ "main",
577
+ "java",
578
+ "kotlin",
579
+ "python",
580
+ "go",
581
+ "lib",
582
+ "libs",
583
+ "com",
584
+ "org",
585
+ "net",
586
+ "io",
587
+ "app",
588
+ "apps",
589
+ "pkg",
590
+ "internal",
591
+ "test",
592
+ "tests",
593
+ "spec",
594
+ "specs",
595
+ "impl",
596
+ "domain",
597
+ "shared",
598
+ "resources",
599
+ "static",
600
+ "assets",
601
+ "config",
602
+ "configs"
603
+ ]);
604
+ const out = /* @__PURE__ */ new Set();
605
+ for (const file of files) {
606
+ const parts = file.replace(/\\/g, "/").split("/");
607
+ for (const part of parts) {
608
+ const seg = part.toLowerCase().replace(/\.[^.]+$/, "");
609
+ if (seg.length >= 3 && !GENERIC.has(seg) && /^[a-z]/.test(seg)) {
610
+ out.add(seg);
611
+ for (const sub of seg.split(/[-_]/).filter((s) => s.length >= 3)) {
612
+ out.add(sub);
613
+ }
614
+ }
615
+ }
616
+ }
617
+ return out;
618
+ }
569
619
  async function loadModuleContexts(ctx, modules, enabled) {
570
620
  if (!enabled || modules.length === 0) return [];
571
621
  if (!existsSync8(ctx.paths.modulesContextDir)) return [];
@@ -845,10 +895,62 @@ async function memTried(input, ctx) {
845
895
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
846
896
  }
847
897
 
848
- // src/tools/get-briefing.ts
849
- import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
898
+ // src/tools/mem-observe.ts
899
+ import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
850
900
  import { existsSync as existsSync15 } from "fs";
851
901
  import path6 from "path";
902
+ import {
903
+ buildFrontmatter as buildFrontmatter3,
904
+ memoryFilePath as memoryFilePath3,
905
+ serializeMemory as serializeMemory7
906
+ } from "@hiveai/core";
907
+ import { z as z15 } from "zod";
908
+ var MemObserveInputSchema = {
909
+ what: z15.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
910
+ where: z15.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
911
+ impact: z15.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
912
+ fix: z15.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
913
+ scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
914
+ module: z15.string().optional().describe("Module name (required when scope=module)"),
915
+ tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
916
+ author: z15.string().optional().describe("Author handle or email")
917
+ };
918
+ async function memObserve(input, ctx) {
919
+ if (!existsSync15(ctx.paths.haiveDir)) {
920
+ throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
921
+ }
922
+ const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
923
+ const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
924
+ const baseFm = buildFrontmatter3({
925
+ type: "gotcha",
926
+ slug,
927
+ scope: input.scope,
928
+ module: input.module,
929
+ tags: input.tags,
930
+ paths: anchorPaths,
931
+ author: input.author
932
+ });
933
+ const frontmatter = { ...baseFm, status: "validated" };
934
+ const lines = [`# ${input.what}`, ""];
935
+ lines.push(`**Where:** \`${input.where}\``);
936
+ lines.push("", `**Impact:** ${input.impact}`);
937
+ if (input.fix) {
938
+ lines.push("", `**Fix/workaround:** ${input.fix}`);
939
+ }
940
+ const body = lines.join("\n") + "\n";
941
+ const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
942
+ await mkdir4(path6.dirname(file), { recursive: true });
943
+ if (existsSync15(file)) {
944
+ throw new Error(`Memory already exists at ${file}`);
945
+ }
946
+ await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
947
+ return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
948
+ }
949
+
950
+ // src/tools/get-briefing.ts
951
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
952
+ import { existsSync as existsSync16 } from "fs";
953
+ import path7 from "path";
852
954
  import {
853
955
  allocateBudget,
854
956
  deriveConfidence as deriveConfidence4,
@@ -864,24 +966,24 @@ import {
864
966
  trackReads as trackReads3,
865
967
  truncateToTokens
866
968
  } from "@hiveai/core";
867
- import { z as z15 } from "zod";
969
+ import { z as z16 } from "zod";
868
970
  var GetBriefingInputSchema = {
869
- task: z15.string().optional().describe(
971
+ task: z16.string().optional().describe(
870
972
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
871
973
  ),
872
- files: z15.array(z15.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
873
- max_tokens: z15.number().int().positive().default(8e3).describe(
974
+ files: z16.array(z16.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
975
+ max_tokens: z16.number().int().positive().default(8e3).describe(
874
976
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
875
977
  ),
876
- max_memories: z15.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
877
- include_project_context: z15.boolean().default(true),
878
- include_module_contexts: z15.boolean().default(true),
879
- semantic: z15.boolean().default(true).describe(
978
+ max_memories: z16.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
979
+ include_project_context: z16.boolean().default(true),
980
+ include_module_contexts: z16.boolean().default(true),
981
+ semantic: z16.boolean().default(true).describe(
880
982
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
881
983
  ),
882
- include_stale: z15.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
883
- track: z15.boolean().default(true).describe("Increment read_count on returned memories"),
884
- format: z15.enum(["full", "compact"]).default("full").describe(
984
+ include_stale: z16.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
985
+ track: z16.boolean().default(true).describe("Increment read_count on returned memories"),
986
+ format: z16.enum(["full", "compact"]).default("full").describe(
885
987
  "Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
886
988
  )
887
989
  };
@@ -891,7 +993,7 @@ async function getBriefing(input, ctx) {
891
993
  let searchMode = "literal";
892
994
  let usage = { version: 1, updated_at: "", by_id: {} };
893
995
  let byId = /* @__PURE__ */ new Map();
894
- if (existsSync15(ctx.paths.memoriesDir)) {
996
+ if (existsSync16(ctx.paths.memoriesDir)) {
895
997
  const allLoaded = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
896
998
  const allMemories = allLoaded.filter(({ memory }) => {
897
999
  const s = memory.frontmatter.status;
@@ -986,7 +1088,7 @@ async function getBriefing(input, ctx) {
986
1088
  await trackReads3(ctx.paths, memories.map((m) => m.id));
987
1089
  }
988
1090
  }
989
- const projectContext = input.include_project_context && existsSync15(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
1091
+ const projectContext = input.include_project_context && existsSync16(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
990
1092
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
991
1093
  const memoriesText = memories.map((m) => {
992
1094
  const unverified = m.status === "proposed" ? " [UNVERIFIED \u2014 not yet validated]" : "";
@@ -1087,15 +1189,15 @@ async function trySemanticHits(ctx, task, limit) {
1087
1189
  }
1088
1190
  async function loadModuleContexts2(ctx, modules) {
1089
1191
  if (modules.length === 0) return [];
1090
- if (!existsSync15(ctx.paths.modulesContextDir)) return [];
1192
+ if (!existsSync16(ctx.paths.modulesContextDir)) return [];
1091
1193
  const available = new Set(
1092
1194
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
1093
1195
  );
1094
1196
  const out = [];
1095
1197
  for (const m of modules) {
1096
1198
  if (!available.has(m)) continue;
1097
- const file = path6.join(ctx.paths.modulesContextDir, m, "context.md");
1098
- if (existsSync15(file)) {
1199
+ const file = path7.join(ctx.paths.modulesContextDir, m, "context.md");
1200
+ if (existsSync16(file)) {
1099
1201
  out.push({ name: m, content: await readFile3(file, "utf8") });
1100
1202
  }
1101
1203
  }
@@ -1104,11 +1206,11 @@ async function loadModuleContexts2(ctx, modules) {
1104
1206
 
1105
1207
  // src/tools/code-map.ts
1106
1208
  import { loadCodeMap, queryCodeMap } from "@hiveai/core";
1107
- import { z as z16 } from "zod";
1209
+ import { z as z17 } from "zod";
1108
1210
  var CodeMapInputSchema = {
1109
- file: z16.string().optional().describe("Filter to files whose path contains this substring"),
1110
- symbol: z16.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
1111
- max_files: z16.number().int().positive().default(40).describe("Cap on returned files")
1211
+ file: z17.string().optional().describe("Filter to files whose path contains this substring"),
1212
+ symbol: z17.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
1213
+ max_files: z17.number().int().positive().default(40).describe("Cap on returned files")
1112
1214
  };
1113
1215
  async function codeMapTool(input, ctx) {
1114
1216
  const map = await loadCodeMap(ctx.paths);
@@ -1134,15 +1236,15 @@ async function codeMapTool(input, ctx) {
1134
1236
  }
1135
1237
 
1136
1238
  // src/tools/mem-diff.ts
1137
- import { existsSync as existsSync16 } from "fs";
1239
+ import { existsSync as existsSync17 } from "fs";
1138
1240
  import { loadMemoriesFromDir as loadMemoriesFromDir13 } from "@hiveai/core";
1139
- import { z as z17 } from "zod";
1241
+ import { z as z18 } from "zod";
1140
1242
  var MemDiffInputSchema = {
1141
- id_a: z17.string().min(1).describe("First memory id"),
1142
- id_b: z17.string().min(1).describe("Second memory id")
1243
+ id_a: z18.string().min(1).describe("First memory id"),
1244
+ id_b: z18.string().min(1).describe("Second memory id")
1143
1245
  };
1144
1246
  async function memDiff(input, ctx) {
1145
- if (!existsSync16(ctx.paths.memoriesDir)) {
1247
+ if (!existsSync17(ctx.paths.memoriesDir)) {
1146
1248
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
1147
1249
  }
1148
1250
  const all = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
@@ -1179,12 +1281,12 @@ async function memDiff(input, ctx) {
1179
1281
  }
1180
1282
 
1181
1283
  // src/prompts/bootstrap-project.ts
1182
- import { z as z18 } from "zod";
1284
+ import { z as z19 } from "zod";
1183
1285
  var BootstrapProjectArgsSchema = {
1184
- module: z18.string().optional().describe(
1286
+ module: z19.string().optional().describe(
1185
1287
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
1186
1288
  ),
1187
- focus: z18.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1289
+ focus: z19.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1188
1290
  };
1189
1291
  var ROOT_TEMPLATE = `# Project context
1190
1292
 
@@ -1266,10 +1368,10 @@ ${template}\`\`\`
1266
1368
  }
1267
1369
 
1268
1370
  // src/prompts/post-task.ts
1269
- import { z as z19 } from "zod";
1371
+ import { z as z20 } from "zod";
1270
1372
  var PostTaskArgsSchema = {
1271
- task_summary: z19.string().optional().describe("One sentence describing what you just did"),
1272
- files_touched: z19.array(z19.string()).optional().describe("Files you created or modified during the task")
1373
+ task_summary: z20.string().optional().describe("One sentence describing what you just did"),
1374
+ files_touched: z20.array(z20.string()).optional().describe("Files you created or modified during the task")
1273
1375
  };
1274
1376
  function postTaskPrompt(args, ctx) {
1275
1377
  const taskLine = args.task_summary ? `
@@ -1285,6 +1387,19 @@ Project root: \`${ctx.paths.root}\`
1285
1387
 
1286
1388
  Go through each item. If the answer is yes, call the corresponding tool immediately.
1287
1389
 
1390
+ ### 0. Did you read existing code and discover bugs, inconsistencies, or security gaps that weren't in the briefing?
1391
+ This is the most important question. Deep code reading surfaces issues that no memory captures yet.
1392
+ Examples of things to look for:
1393
+ - A method with an invalid signature (e.g. two \`@RequestBody\` on the same handler)
1394
+ - A configuration that looks wrong or missing (e.g. webhook path not whitelisted in SecurityConfig)
1395
+ - A component scan / DI issue (e.g. a Spring bean not picked up because the package isn't scanned)
1396
+ - A DB constraint that will break when you add a new enum value
1397
+ - A hardcoded value that should be dynamic (e.g. hardcoded tenant id "default-tenant")
1398
+ - Anything that will silently break in production
1399
+
1400
+ \u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\`, \`scope="team"\`, and **anchor it to the file** with \`paths\`.
1401
+ This transforms your discovery into institutional knowledge that protects every future agent.
1402
+
1288
1403
  ### 1. Did you try an approach that failed?
1289
1404
  \u2192 If yes, call **\`mem_tried\`** with:
1290
1405
  - \`what\`: the approach you tried (e.g. "importing gray-matter with ESM dynamic import")
@@ -1299,7 +1414,7 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
1299
1414
  ### 3. Did you make an architectural decision?
1300
1415
  \u2192 If yes, call **\`mem_save\`** with \`type="decision"\` and document the WHY (constraints, tradeoffs), not just the what
1301
1416
 
1302
- ### 4. Did you hit a non-obvious bug or surprising behavior?
1417
+ ### 4. Did you hit a non-obvious bug or surprising behavior in a library or framework?
1303
1418
  \u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\` and anchor it to the relevant file paths
1304
1419
 
1305
1420
  ### 5. Did you find that an existing memory is outdated or wrong?
@@ -1311,6 +1426,7 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
1311
1426
  - Anchor memories to file paths when possible (the \`paths\` field) \u2014 this enables staleness detection.
1312
1427
  - Prefer \`scope="team"\` for anything a teammate or future agent would benefit from.
1313
1428
  - Skip sections where you genuinely have nothing to add. Don't fabricate memories.
1429
+ - **Question 0 is not optional** \u2014 always scan your exploration history for code-level discoveries.
1314
1430
 
1315
1431
  When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "Nothing new to save."
1316
1432
  `;
@@ -1326,12 +1442,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "N
1326
1442
  }
1327
1443
 
1328
1444
  // src/prompts/import-docs.ts
1329
- import { z as z20 } from "zod";
1445
+ import { z as z21 } from "zod";
1330
1446
  var ImportDocsArgsSchema = {
1331
- content: z20.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
1332
- source: z20.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
1333
- scope: z20.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
1334
- dry_run: z20.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
1447
+ content: z21.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
1448
+ source: z21.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
1449
+ scope: z21.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
1450
+ dry_run: z21.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
1335
1451
  };
1336
1452
  function importDocsPrompt(args, ctx) {
1337
1453
  const sourceLine = args.source ? `
@@ -1396,7 +1512,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
1396
1512
 
1397
1513
  // src/server.ts
1398
1514
  var SERVER_NAME = "haive";
1399
- var SERVER_VERSION = "0.2.10";
1515
+ var SERVER_VERSION = "0.2.11";
1400
1516
  function jsonResult(data) {
1401
1517
  return {
1402
1518
  content: [
@@ -1515,6 +1631,12 @@ function createHaiveServer(options = {}) {
1515
1631
  MemDiffInputSchema,
1516
1632
  async (input) => jsonResult(await memDiff(input, context))
1517
1633
  );
1634
+ server.tool(
1635
+ "mem_observe",
1636
+ "Capture a code-level discovery made during exploration: a bug, inconsistency, missing config, or security gap found by reading existing code that was NOT in the briefing. Use this whenever you read code and spot something that could silently break in production. Auto-validated, anchored to file paths for staleness detection. Prefer this over mem_save for reactive discoveries during code reading.",
1637
+ MemObserveInputSchema,
1638
+ async (input) => jsonResult(await memObserve(input, context))
1639
+ );
1518
1640
  server.prompt(
1519
1641
  "bootstrap_project",
1520
1642
  "Instructions for the AI client to analyze the project and save the context.",