@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 +163 -41
- package/dist/index.js.map +1 -1
- package/dist/server.js +163 -41
- package/dist/server.js.map +1 -1
- package/package.json +23 -12
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/
|
|
849
|
-
import {
|
|
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
|
|
969
|
+
import { z as z16 } from "zod";
|
|
868
970
|
var GetBriefingInputSchema = {
|
|
869
|
-
task:
|
|
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:
|
|
873
|
-
max_tokens:
|
|
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:
|
|
877
|
-
include_project_context:
|
|
878
|
-
include_module_contexts:
|
|
879
|
-
semantic:
|
|
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:
|
|
883
|
-
track:
|
|
884
|
-
format:
|
|
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 (
|
|
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 &&
|
|
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 (!
|
|
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 =
|
|
1098
|
-
if (
|
|
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
|
|
1209
|
+
import { z as z17 } from "zod";
|
|
1108
1210
|
var CodeMapInputSchema = {
|
|
1109
|
-
file:
|
|
1110
|
-
symbol:
|
|
1111
|
-
max_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
|
|
1239
|
+
import { existsSync as existsSync17 } from "fs";
|
|
1138
1240
|
import { loadMemoriesFromDir as loadMemoriesFromDir13 } from "@hiveai/core";
|
|
1139
|
-
import { z as
|
|
1241
|
+
import { z as z18 } from "zod";
|
|
1140
1242
|
var MemDiffInputSchema = {
|
|
1141
|
-
id_a:
|
|
1142
|
-
id_b:
|
|
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 (!
|
|
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
|
|
1284
|
+
import { z as z19 } from "zod";
|
|
1183
1285
|
var BootstrapProjectArgsSchema = {
|
|
1184
|
-
module:
|
|
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:
|
|
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
|
|
1371
|
+
import { z as z20 } from "zod";
|
|
1270
1372
|
var PostTaskArgsSchema = {
|
|
1271
|
-
task_summary:
|
|
1272
|
-
files_touched:
|
|
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
|
|
1445
|
+
import { z as z21 } from "zod";
|
|
1330
1446
|
var ImportDocsArgsSchema = {
|
|
1331
|
-
content:
|
|
1332
|
-
source:
|
|
1333
|
-
scope:
|
|
1334
|
-
dry_run:
|
|
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.
|
|
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.",
|