@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/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/
|
|
854
|
-
import {
|
|
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
|
|
974
|
+
import { z as z16 } from "zod";
|
|
873
975
|
var GetBriefingInputSchema = {
|
|
874
|
-
task:
|
|
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:
|
|
878
|
-
max_tokens:
|
|
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:
|
|
882
|
-
include_project_context:
|
|
883
|
-
include_module_contexts:
|
|
884
|
-
semantic:
|
|
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:
|
|
888
|
-
track:
|
|
889
|
-
format:
|
|
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 (
|
|
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 &&
|
|
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 (!
|
|
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 =
|
|
1103
|
-
if (
|
|
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
|
|
1214
|
+
import { z as z17 } from "zod";
|
|
1113
1215
|
var CodeMapInputSchema = {
|
|
1114
|
-
file:
|
|
1115
|
-
symbol:
|
|
1116
|
-
max_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
|
|
1244
|
+
import { existsSync as existsSync17 } from "fs";
|
|
1143
1245
|
import { loadMemoriesFromDir as loadMemoriesFromDir13 } from "@hiveai/core";
|
|
1144
|
-
import { z as
|
|
1246
|
+
import { z as z18 } from "zod";
|
|
1145
1247
|
var MemDiffInputSchema = {
|
|
1146
|
-
id_a:
|
|
1147
|
-
id_b:
|
|
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 (!
|
|
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
|
|
1289
|
+
import { z as z19 } from "zod";
|
|
1188
1290
|
var BootstrapProjectArgsSchema = {
|
|
1189
|
-
module:
|
|
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:
|
|
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
|
|
1376
|
+
import { z as z20 } from "zod";
|
|
1275
1377
|
var PostTaskArgsSchema = {
|
|
1276
|
-
task_summary:
|
|
1277
|
-
files_touched:
|
|
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
|
|
1450
|
+
import { z as z21 } from "zod";
|
|
1335
1451
|
var ImportDocsArgsSchema = {
|
|
1336
|
-
content:
|
|
1337
|
-
source:
|
|
1338
|
-
scope:
|
|
1339
|
-
dry_run:
|
|
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.
|
|
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.",
|