@hiveai/mcp 0.2.9 → 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/index.js CHANGED
@@ -526,10 +526,14 @@ async function memForFiles(input, ctx) {
526
526
  seen.add(loaded.memory.frontmatter.id);
527
527
  }
528
528
  }
529
+ const pathSegments = extractPathSegments(input.files);
529
530
  for (const loaded of all) {
530
531
  if (seen.has(loaded.memory.frontmatter.id)) continue;
531
532
  const fm = loaded.memory.frontmatter;
532
- const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t));
533
+ const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t)) || fm.tags.some((t) => {
534
+ const tl = t.toLowerCase();
535
+ return pathSegments.has(tl) || pathSegments.has(tl.replace(/[-_]/g, ""));
536
+ });
533
537
  if (moduleHit) {
534
538
  byModule.push(toMatch(loaded, "module", usage));
535
539
  seen.add(fm.id);
@@ -571,6 +575,52 @@ function toMatch(loaded, reason, usage) {
571
575
  body: loaded.memory.body
572
576
  };
573
577
  }
578
+ function extractPathSegments(files) {
579
+ const GENERIC = /* @__PURE__ */ new Set([
580
+ "src",
581
+ "main",
582
+ "java",
583
+ "kotlin",
584
+ "python",
585
+ "go",
586
+ "lib",
587
+ "libs",
588
+ "com",
589
+ "org",
590
+ "net",
591
+ "io",
592
+ "app",
593
+ "apps",
594
+ "pkg",
595
+ "internal",
596
+ "test",
597
+ "tests",
598
+ "spec",
599
+ "specs",
600
+ "impl",
601
+ "domain",
602
+ "shared",
603
+ "resources",
604
+ "static",
605
+ "assets",
606
+ "config",
607
+ "configs"
608
+ ]);
609
+ const out = /* @__PURE__ */ new Set();
610
+ for (const file of files) {
611
+ const parts = file.replace(/\\/g, "/").split("/");
612
+ for (const part of parts) {
613
+ const seg = part.toLowerCase().replace(/\.[^.]+$/, "");
614
+ if (seg.length >= 3 && !GENERIC.has(seg) && /^[a-z]/.test(seg)) {
615
+ out.add(seg);
616
+ for (const sub of seg.split(/[-_]/).filter((s) => s.length >= 3)) {
617
+ out.add(sub);
618
+ }
619
+ }
620
+ }
621
+ }
622
+ return out;
623
+ }
574
624
  async function loadModuleContexts(ctx, modules, enabled) {
575
625
  if (!enabled || modules.length === 0) return [];
576
626
  if (!existsSync8(ctx.paths.modulesContextDir)) return [];
@@ -850,10 +900,62 @@ async function memTried(input, ctx) {
850
900
  return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
851
901
  }
852
902
 
853
- // src/tools/get-briefing.ts
854
- import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
903
+ // src/tools/mem-observe.ts
904
+ import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
855
905
  import { existsSync as existsSync15 } from "fs";
856
906
  import path6 from "path";
907
+ import {
908
+ buildFrontmatter as buildFrontmatter3,
909
+ memoryFilePath as memoryFilePath3,
910
+ serializeMemory as serializeMemory7
911
+ } from "@hiveai/core";
912
+ import { z as z15 } from "zod";
913
+ var MemObserveInputSchema = {
914
+ what: z15.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
915
+ where: z15.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
916
+ impact: z15.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
917
+ fix: z15.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
918
+ scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
919
+ module: z15.string().optional().describe("Module name (required when scope=module)"),
920
+ tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
921
+ author: z15.string().optional().describe("Author handle or email")
922
+ };
923
+ async function memObserve(input, ctx) {
924
+ if (!existsSync15(ctx.paths.haiveDir)) {
925
+ throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
926
+ }
927
+ const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
928
+ const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
929
+ const baseFm = buildFrontmatter3({
930
+ type: "gotcha",
931
+ slug,
932
+ scope: input.scope,
933
+ module: input.module,
934
+ tags: input.tags,
935
+ paths: anchorPaths,
936
+ author: input.author
937
+ });
938
+ const frontmatter = { ...baseFm, status: "validated" };
939
+ const lines = [`# ${input.what}`, ""];
940
+ lines.push(`**Where:** \`${input.where}\``);
941
+ lines.push("", `**Impact:** ${input.impact}`);
942
+ if (input.fix) {
943
+ lines.push("", `**Fix/workaround:** ${input.fix}`);
944
+ }
945
+ const body = lines.join("\n") + "\n";
946
+ const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
947
+ await mkdir4(path6.dirname(file), { recursive: true });
948
+ if (existsSync15(file)) {
949
+ throw new Error(`Memory already exists at ${file}`);
950
+ }
951
+ await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
952
+ return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
953
+ }
954
+
955
+ // src/tools/get-briefing.ts
956
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
957
+ import { existsSync as existsSync16 } from "fs";
958
+ import path7 from "path";
857
959
  import {
858
960
  allocateBudget,
859
961
  deriveConfidence as deriveConfidence4,
@@ -869,24 +971,24 @@ import {
869
971
  trackReads as trackReads3,
870
972
  truncateToTokens
871
973
  } from "@hiveai/core";
872
- import { z as z15 } from "zod";
974
+ import { z as z16 } from "zod";
873
975
  var GetBriefingInputSchema = {
874
- task: z15.string().optional().describe(
976
+ task: z16.string().optional().describe(
875
977
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
876
978
  ),
877
- files: z15.array(z15.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
878
- max_tokens: z15.number().int().positive().default(8e3).describe(
979
+ files: z16.array(z16.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
980
+ max_tokens: z16.number().int().positive().default(8e3).describe(
879
981
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
880
982
  ),
881
- max_memories: z15.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
882
- include_project_context: z15.boolean().default(true),
883
- include_module_contexts: z15.boolean().default(true),
884
- semantic: z15.boolean().default(true).describe(
983
+ max_memories: z16.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
984
+ include_project_context: z16.boolean().default(true),
985
+ include_module_contexts: z16.boolean().default(true),
986
+ semantic: z16.boolean().default(true).describe(
885
987
  "Use semantic ranking when a task is provided (requires `haive embeddings index`)."
886
988
  ),
887
- include_stale: z15.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
888
- track: z15.boolean().default(true).describe("Increment read_count on returned memories"),
889
- format: z15.enum(["full", "compact"]).default("full").describe(
989
+ include_stale: z16.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
990
+ track: z16.boolean().default(true).describe("Increment read_count on returned memories"),
991
+ format: z16.enum(["full", "compact"]).default("full").describe(
890
992
  "Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
891
993
  )
892
994
  };
@@ -896,7 +998,7 @@ async function getBriefing(input, ctx) {
896
998
  let searchMode = "literal";
897
999
  let usage = { version: 1, updated_at: "", by_id: {} };
898
1000
  let byId = /* @__PURE__ */ new Map();
899
- if (existsSync15(ctx.paths.memoriesDir)) {
1001
+ if (existsSync16(ctx.paths.memoriesDir)) {
900
1002
  const allLoaded = await loadMemoriesFromDir12(ctx.paths.memoriesDir);
901
1003
  const allMemories = allLoaded.filter(({ memory }) => {
902
1004
  const s = memory.frontmatter.status;
@@ -991,7 +1093,7 @@ async function getBriefing(input, ctx) {
991
1093
  await trackReads3(ctx.paths, memories.map((m) => m.id));
992
1094
  }
993
1095
  }
994
- const projectContext = input.include_project_context && existsSync15(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
1096
+ const projectContext = input.include_project_context && existsSync16(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
995
1097
  const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
996
1098
  const memoriesText = memories.map((m) => {
997
1099
  const unverified = m.status === "proposed" ? " [UNVERIFIED \u2014 not yet validated]" : "";
@@ -1092,15 +1194,15 @@ async function trySemanticHits(ctx, task, limit) {
1092
1194
  }
1093
1195
  async function loadModuleContexts2(ctx, modules) {
1094
1196
  if (modules.length === 0) return [];
1095
- if (!existsSync15(ctx.paths.modulesContextDir)) return [];
1197
+ if (!existsSync16(ctx.paths.modulesContextDir)) return [];
1096
1198
  const available = new Set(
1097
1199
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
1098
1200
  );
1099
1201
  const out = [];
1100
1202
  for (const m of modules) {
1101
1203
  if (!available.has(m)) continue;
1102
- const file = path6.join(ctx.paths.modulesContextDir, m, "context.md");
1103
- if (existsSync15(file)) {
1204
+ const file = path7.join(ctx.paths.modulesContextDir, m, "context.md");
1205
+ if (existsSync16(file)) {
1104
1206
  out.push({ name: m, content: await readFile3(file, "utf8") });
1105
1207
  }
1106
1208
  }
@@ -1109,11 +1211,11 @@ async function loadModuleContexts2(ctx, modules) {
1109
1211
 
1110
1212
  // src/tools/code-map.ts
1111
1213
  import { loadCodeMap, queryCodeMap } from "@hiveai/core";
1112
- import { z as z16 } from "zod";
1214
+ import { z as z17 } from "zod";
1113
1215
  var CodeMapInputSchema = {
1114
- file: z16.string().optional().describe("Filter to files whose path contains this substring"),
1115
- symbol: z16.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
1116
- max_files: z16.number().int().positive().default(40).describe("Cap on returned files")
1216
+ file: z17.string().optional().describe("Filter to files whose path contains this substring"),
1217
+ symbol: z17.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
1218
+ max_files: z17.number().int().positive().default(40).describe("Cap on returned files")
1117
1219
  };
1118
1220
  async function codeMapTool(input, ctx) {
1119
1221
  const map = await loadCodeMap(ctx.paths);
@@ -1139,15 +1241,15 @@ async function codeMapTool(input, ctx) {
1139
1241
  }
1140
1242
 
1141
1243
  // src/tools/mem-diff.ts
1142
- import { existsSync as existsSync16 } from "fs";
1244
+ import { existsSync as existsSync17 } from "fs";
1143
1245
  import { loadMemoriesFromDir as loadMemoriesFromDir13 } from "@hiveai/core";
1144
- import { z as z17 } from "zod";
1246
+ import { z as z18 } from "zod";
1145
1247
  var MemDiffInputSchema = {
1146
- id_a: z17.string().min(1).describe("First memory id"),
1147
- id_b: z17.string().min(1).describe("Second memory id")
1248
+ id_a: z18.string().min(1).describe("First memory id"),
1249
+ id_b: z18.string().min(1).describe("Second memory id")
1148
1250
  };
1149
1251
  async function memDiff(input, ctx) {
1150
- if (!existsSync16(ctx.paths.memoriesDir)) {
1252
+ if (!existsSync17(ctx.paths.memoriesDir)) {
1151
1253
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
1152
1254
  }
1153
1255
  const all = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
@@ -1184,12 +1286,12 @@ async function memDiff(input, ctx) {
1184
1286
  }
1185
1287
 
1186
1288
  // src/prompts/bootstrap-project.ts
1187
- import { z as z18 } from "zod";
1289
+ import { z as z19 } from "zod";
1188
1290
  var BootstrapProjectArgsSchema = {
1189
- module: z18.string().optional().describe(
1291
+ module: z19.string().optional().describe(
1190
1292
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
1191
1293
  ),
1192
- focus: z18.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1294
+ focus: z19.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
1193
1295
  };
1194
1296
  var ROOT_TEMPLATE = `# Project context
1195
1297
 
@@ -1271,10 +1373,10 @@ ${template}\`\`\`
1271
1373
  }
1272
1374
 
1273
1375
  // src/prompts/post-task.ts
1274
- import { z as z19 } from "zod";
1376
+ import { z as z20 } from "zod";
1275
1377
  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")
1378
+ task_summary: z20.string().optional().describe("One sentence describing what you just did"),
1379
+ files_touched: z20.array(z20.string()).optional().describe("Files you created or modified during the task")
1278
1380
  };
1279
1381
  function postTaskPrompt(args, ctx) {
1280
1382
  const taskLine = args.task_summary ? `
@@ -1290,6 +1392,19 @@ Project root: \`${ctx.paths.root}\`
1290
1392
 
1291
1393
  Go through each item. If the answer is yes, call the corresponding tool immediately.
1292
1394
 
1395
+ ### 0. Did you read existing code and discover bugs, inconsistencies, or security gaps that weren't in the briefing?
1396
+ This is the most important question. Deep code reading surfaces issues that no memory captures yet.
1397
+ Examples of things to look for:
1398
+ - A method with an invalid signature (e.g. two \`@RequestBody\` on the same handler)
1399
+ - A configuration that looks wrong or missing (e.g. webhook path not whitelisted in SecurityConfig)
1400
+ - A component scan / DI issue (e.g. a Spring bean not picked up because the package isn't scanned)
1401
+ - A DB constraint that will break when you add a new enum value
1402
+ - A hardcoded value that should be dynamic (e.g. hardcoded tenant id "default-tenant")
1403
+ - Anything that will silently break in production
1404
+
1405
+ \u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\`, \`scope="team"\`, and **anchor it to the file** with \`paths\`.
1406
+ This transforms your discovery into institutional knowledge that protects every future agent.
1407
+
1293
1408
  ### 1. Did you try an approach that failed?
1294
1409
  \u2192 If yes, call **\`mem_tried\`** with:
1295
1410
  - \`what\`: the approach you tried (e.g. "importing gray-matter with ESM dynamic import")
@@ -1304,7 +1419,7 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
1304
1419
  ### 3. Did you make an architectural decision?
1305
1420
  \u2192 If yes, call **\`mem_save\`** with \`type="decision"\` and document the WHY (constraints, tradeoffs), not just the what
1306
1421
 
1307
- ### 4. Did you hit a non-obvious bug or surprising behavior?
1422
+ ### 4. Did you hit a non-obvious bug or surprising behavior in a library or framework?
1308
1423
  \u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\` and anchor it to the relevant file paths
1309
1424
 
1310
1425
  ### 5. Did you find that an existing memory is outdated or wrong?
@@ -1316,6 +1431,7 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
1316
1431
  - Anchor memories to file paths when possible (the \`paths\` field) \u2014 this enables staleness detection.
1317
1432
  - Prefer \`scope="team"\` for anything a teammate or future agent would benefit from.
1318
1433
  - Skip sections where you genuinely have nothing to add. Don't fabricate memories.
1434
+ - **Question 0 is not optional** \u2014 always scan your exploration history for code-level discoveries.
1319
1435
 
1320
1436
  When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "Nothing new to save."
1321
1437
  `;
@@ -1331,12 +1447,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "N
1331
1447
  }
1332
1448
 
1333
1449
  // src/prompts/import-docs.ts
1334
- import { z as z20 } from "zod";
1450
+ import { z as z21 } from "zod";
1335
1451
  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")
1452
+ content: z21.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
1453
+ source: z21.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
1454
+ scope: z21.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
1455
+ dry_run: z21.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
1340
1456
  };
1341
1457
  function importDocsPrompt(args, ctx) {
1342
1458
  const sourceLine = args.source ? `
@@ -1401,7 +1517,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
1401
1517
 
1402
1518
  // src/server.ts
1403
1519
  var SERVER_NAME = "haive";
1404
- var SERVER_VERSION = "0.2.9";
1520
+ var SERVER_VERSION = "0.2.11";
1405
1521
  function jsonResult(data) {
1406
1522
  return {
1407
1523
  content: [
@@ -1520,6 +1636,12 @@ function createHaiveServer(options = {}) {
1520
1636
  MemDiffInputSchema,
1521
1637
  async (input) => jsonResult(await memDiff(input, context))
1522
1638
  );
1639
+ server.tool(
1640
+ "mem_observe",
1641
+ "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.",
1642
+ MemObserveInputSchema,
1643
+ async (input) => jsonResult(await memObserve(input, context))
1644
+ );
1523
1645
  server.prompt(
1524
1646
  "bootstrap_project",
1525
1647
  "Instructions for the AI client to analyze the project and save the context.",