@hiveai/mcp 0.2.10 → 0.2.12
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 +372 -53
- package/dist/index.js.map +1 -1
- package/dist/server.js +372 -53
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -119,6 +119,7 @@ async function memList(input, ctx) {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
// src/tools/mem-save.ts
|
|
122
|
+
import { createHash } from "crypto";
|
|
122
123
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
123
124
|
import { existsSync as existsSync4 } from "fs";
|
|
124
125
|
import path3 from "path";
|
|
@@ -130,7 +131,9 @@ import {
|
|
|
130
131
|
} from "@hiveai/core";
|
|
131
132
|
import { z as z4 } from "zod";
|
|
132
133
|
var MemSaveInputSchema = {
|
|
133
|
-
type: z4.enum(["convention", "decision", "gotcha", "architecture", "glossary", "attempt"]).describe(
|
|
134
|
+
type: z4.enum(["convention", "decision", "gotcha", "architecture", "glossary", "attempt", "session_recap"]).describe(
|
|
135
|
+
"Kind of memory being saved. Use 'attempt' for failed approaches (auto-validated). Use 'session_recap' via mem_session_end instead."
|
|
136
|
+
),
|
|
134
137
|
slug: z4.string().min(1).describe("Short human-readable identifier \u2014 becomes part of the filename"),
|
|
135
138
|
body: z4.string().describe("Markdown body of the memory"),
|
|
136
139
|
scope: z4.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope: personal | team | module"),
|
|
@@ -140,14 +143,61 @@ var MemSaveInputSchema = {
|
|
|
140
143
|
author: z4.string().optional().describe("Author handle or email"),
|
|
141
144
|
paths: z4.array(z4.string()).default([]).describe("Anchor paths (file paths this memory references)"),
|
|
142
145
|
symbols: z4.array(z4.string()).default([]).describe("Anchor symbols (function/class names this memory references)"),
|
|
143
|
-
commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)")
|
|
146
|
+
commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)"),
|
|
147
|
+
topic: z4.string().optional().describe(
|
|
148
|
+
"Stable key for this memory. If a memory with the same topic already exists in this scope, it is updated in-place (revision_count++). Use for knowledge that evolves over time."
|
|
149
|
+
)
|
|
144
150
|
};
|
|
151
|
+
function bodyHash(body) {
|
|
152
|
+
return createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
153
|
+
}
|
|
145
154
|
async function memSave(input, ctx) {
|
|
146
155
|
if (!existsSync4(ctx.paths.haiveDir)) {
|
|
147
156
|
throw new Error(
|
|
148
157
|
`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`
|
|
149
158
|
);
|
|
150
159
|
}
|
|
160
|
+
const existing = existsSync4(ctx.paths.memoriesDir) ? await loadMemoriesFromDir2(ctx.paths.memoriesDir) : [];
|
|
161
|
+
const incomingHash = bodyHash(input.body);
|
|
162
|
+
const hashDuplicate = existing.find(
|
|
163
|
+
({ memory }) => bodyHash(memory.body) === incomingHash && memory.frontmatter.scope === input.scope
|
|
164
|
+
);
|
|
165
|
+
if (hashDuplicate) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Duplicate content detected \u2014 identical body already saved as "${hashDuplicate.memory.frontmatter.id}". Use mem_update to modify it, or change the body to add new information.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (input.topic) {
|
|
171
|
+
const topicMatch = existing.find(
|
|
172
|
+
({ memory }) => memory.frontmatter.topic === input.topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
173
|
+
);
|
|
174
|
+
if (topicMatch) {
|
|
175
|
+
const fm = topicMatch.memory.frontmatter;
|
|
176
|
+
const newFrontmatter = {
|
|
177
|
+
...fm,
|
|
178
|
+
body: input.body,
|
|
179
|
+
tags: input.tags.length ? input.tags : fm.tags,
|
|
180
|
+
revision_count: (fm.revision_count ?? 0) + 1,
|
|
181
|
+
anchor: {
|
|
182
|
+
commit: input.commit ?? fm.anchor.commit,
|
|
183
|
+
paths: input.paths.length ? input.paths : fm.anchor.paths,
|
|
184
|
+
symbols: input.symbols.length ? input.symbols : fm.anchor.symbols
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
await writeFile2(
|
|
188
|
+
topicMatch.filePath,
|
|
189
|
+
serializeMemory({ frontmatter: newFrontmatter, body: input.body }),
|
|
190
|
+
"utf8"
|
|
191
|
+
);
|
|
192
|
+
return {
|
|
193
|
+
id: fm.id,
|
|
194
|
+
scope: fm.scope,
|
|
195
|
+
file_path: topicMatch.filePath,
|
|
196
|
+
action: "updated",
|
|
197
|
+
revision_count: newFrontmatter.revision_count
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
151
201
|
const frontmatter = buildFrontmatter({
|
|
152
202
|
type: input.type,
|
|
153
203
|
slug: input.slug,
|
|
@@ -158,7 +208,8 @@ async function memSave(input, ctx) {
|
|
|
158
208
|
author: input.author,
|
|
159
209
|
paths: input.paths,
|
|
160
210
|
symbols: input.symbols,
|
|
161
|
-
commit: input.commit
|
|
211
|
+
commit: input.commit,
|
|
212
|
+
topic: input.topic
|
|
162
213
|
});
|
|
163
214
|
const file = memoryFilePath(
|
|
164
215
|
ctx.paths,
|
|
@@ -171,15 +222,16 @@ async function memSave(input, ctx) {
|
|
|
171
222
|
throw new Error(`Memory already exists at ${file}`);
|
|
172
223
|
}
|
|
173
224
|
let warning;
|
|
174
|
-
|
|
175
|
-
|
|
225
|
+
let similar_found;
|
|
226
|
+
if (existing.length > 0) {
|
|
176
227
|
const slugTokens = input.slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
177
228
|
const similar = existing.filter(({ memory }) => {
|
|
178
229
|
const id = memory.frontmatter.id.toLowerCase();
|
|
179
230
|
return slugTokens.length >= 2 && slugTokens.filter((t) => id.includes(t)).length >= Math.ceil(slugTokens.length * 0.6);
|
|
180
231
|
});
|
|
181
232
|
if (similar.length > 0) {
|
|
182
|
-
|
|
233
|
+
similar_found = similar.map((m) => m.memory.frontmatter.id);
|
|
234
|
+
warning = `Possible duplicate: similar memories already exist (${similar_found.join(", ")}). Consider updating one of these instead.`;
|
|
183
235
|
}
|
|
184
236
|
}
|
|
185
237
|
await writeFile2(file, serializeMemory({ frontmatter, body: input.body }), "utf8");
|
|
@@ -187,7 +239,9 @@ async function memSave(input, ctx) {
|
|
|
187
239
|
id: frontmatter.id,
|
|
188
240
|
scope: frontmatter.scope,
|
|
189
241
|
file_path: file,
|
|
190
|
-
|
|
242
|
+
action: "created",
|
|
243
|
+
...warning ? { warning } : {},
|
|
244
|
+
...similar_found ? { similar_found } : {}
|
|
191
245
|
};
|
|
192
246
|
}
|
|
193
247
|
|
|
@@ -521,10 +575,14 @@ async function memForFiles(input, ctx) {
|
|
|
521
575
|
seen.add(loaded.memory.frontmatter.id);
|
|
522
576
|
}
|
|
523
577
|
}
|
|
578
|
+
const pathSegments = extractPathSegments(input.files);
|
|
524
579
|
for (const loaded of all) {
|
|
525
580
|
if (seen.has(loaded.memory.frontmatter.id)) continue;
|
|
526
581
|
const fm = loaded.memory.frontmatter;
|
|
527
|
-
const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t))
|
|
582
|
+
const moduleHit = fm.module && inferred.includes(fm.module) || fm.tags.some((t) => inferred.includes(t)) || fm.tags.some((t) => {
|
|
583
|
+
const tl = t.toLowerCase();
|
|
584
|
+
return pathSegments.has(tl) || pathSegments.has(tl.replace(/[-_]/g, ""));
|
|
585
|
+
});
|
|
528
586
|
if (moduleHit) {
|
|
529
587
|
byModule.push(toMatch(loaded, "module", usage));
|
|
530
588
|
seen.add(fm.id);
|
|
@@ -566,6 +624,52 @@ function toMatch(loaded, reason, usage) {
|
|
|
566
624
|
body: loaded.memory.body
|
|
567
625
|
};
|
|
568
626
|
}
|
|
627
|
+
function extractPathSegments(files) {
|
|
628
|
+
const GENERIC = /* @__PURE__ */ new Set([
|
|
629
|
+
"src",
|
|
630
|
+
"main",
|
|
631
|
+
"java",
|
|
632
|
+
"kotlin",
|
|
633
|
+
"python",
|
|
634
|
+
"go",
|
|
635
|
+
"lib",
|
|
636
|
+
"libs",
|
|
637
|
+
"com",
|
|
638
|
+
"org",
|
|
639
|
+
"net",
|
|
640
|
+
"io",
|
|
641
|
+
"app",
|
|
642
|
+
"apps",
|
|
643
|
+
"pkg",
|
|
644
|
+
"internal",
|
|
645
|
+
"test",
|
|
646
|
+
"tests",
|
|
647
|
+
"spec",
|
|
648
|
+
"specs",
|
|
649
|
+
"impl",
|
|
650
|
+
"domain",
|
|
651
|
+
"shared",
|
|
652
|
+
"resources",
|
|
653
|
+
"static",
|
|
654
|
+
"assets",
|
|
655
|
+
"config",
|
|
656
|
+
"configs"
|
|
657
|
+
]);
|
|
658
|
+
const out = /* @__PURE__ */ new Set();
|
|
659
|
+
for (const file of files) {
|
|
660
|
+
const parts = file.replace(/\\/g, "/").split("/");
|
|
661
|
+
for (const part of parts) {
|
|
662
|
+
const seg = part.toLowerCase().replace(/\.[^.]+$/, "");
|
|
663
|
+
if (seg.length >= 3 && !GENERIC.has(seg) && /^[a-z]/.test(seg)) {
|
|
664
|
+
out.add(seg);
|
|
665
|
+
for (const sub of seg.split(/[-_]/).filter((s) => s.length >= 3)) {
|
|
666
|
+
out.add(sub);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return out;
|
|
672
|
+
}
|
|
569
673
|
async function loadModuleContexts(ctx, modules, enabled) {
|
|
570
674
|
if (!enabled || modules.length === 0) return [];
|
|
571
675
|
if (!existsSync8(ctx.paths.modulesContextDir)) return [];
|
|
@@ -845,10 +949,172 @@ async function memTried(input, ctx) {
|
|
|
845
949
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
846
950
|
}
|
|
847
951
|
|
|
848
|
-
// src/tools/
|
|
849
|
-
import {
|
|
952
|
+
// src/tools/mem-observe.ts
|
|
953
|
+
import { mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
|
|
850
954
|
import { existsSync as existsSync15 } from "fs";
|
|
851
955
|
import path6 from "path";
|
|
956
|
+
import {
|
|
957
|
+
buildFrontmatter as buildFrontmatter3,
|
|
958
|
+
memoryFilePath as memoryFilePath3,
|
|
959
|
+
serializeMemory as serializeMemory7
|
|
960
|
+
} from "@hiveai/core";
|
|
961
|
+
import { z as z15 } from "zod";
|
|
962
|
+
var MemObserveInputSchema = {
|
|
963
|
+
what: z15.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
|
|
964
|
+
where: z15.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
|
|
965
|
+
impact: z15.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
|
|
966
|
+
fix: z15.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
|
|
967
|
+
scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
|
|
968
|
+
module: z15.string().optional().describe("Module name (required when scope=module)"),
|
|
969
|
+
tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
|
|
970
|
+
author: z15.string().optional().describe("Author handle or email")
|
|
971
|
+
};
|
|
972
|
+
async function memObserve(input, ctx) {
|
|
973
|
+
if (!existsSync15(ctx.paths.haiveDir)) {
|
|
974
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
975
|
+
}
|
|
976
|
+
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
|
|
977
|
+
const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
|
|
978
|
+
const baseFm = buildFrontmatter3({
|
|
979
|
+
type: "gotcha",
|
|
980
|
+
slug,
|
|
981
|
+
scope: input.scope,
|
|
982
|
+
module: input.module,
|
|
983
|
+
tags: input.tags,
|
|
984
|
+
paths: anchorPaths,
|
|
985
|
+
author: input.author
|
|
986
|
+
});
|
|
987
|
+
const frontmatter = { ...baseFm, status: "validated" };
|
|
988
|
+
const lines = [`# ${input.what}`, ""];
|
|
989
|
+
lines.push(`**Where:** \`${input.where}\``);
|
|
990
|
+
lines.push("", `**Impact:** ${input.impact}`);
|
|
991
|
+
if (input.fix) {
|
|
992
|
+
lines.push("", `**Fix/workaround:** ${input.fix}`);
|
|
993
|
+
}
|
|
994
|
+
const body = lines.join("\n") + "\n";
|
|
995
|
+
const file = memoryFilePath3(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
996
|
+
await mkdir4(path6.dirname(file), { recursive: true });
|
|
997
|
+
if (existsSync15(file)) {
|
|
998
|
+
throw new Error(`Memory already exists at ${file}`);
|
|
999
|
+
}
|
|
1000
|
+
await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
1001
|
+
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/tools/mem-session-end.ts
|
|
1005
|
+
import { writeFile as writeFile9, mkdir as mkdir5 } from "fs/promises";
|
|
1006
|
+
import { existsSync as existsSync16 } from "fs";
|
|
1007
|
+
import path7 from "path";
|
|
1008
|
+
import {
|
|
1009
|
+
buildFrontmatter as buildFrontmatter4,
|
|
1010
|
+
loadMemoriesFromDir as loadMemoriesFromDir12,
|
|
1011
|
+
memoryFilePath as memoryFilePath4,
|
|
1012
|
+
serializeMemory as serializeMemory8
|
|
1013
|
+
} from "@hiveai/core";
|
|
1014
|
+
import { z as z16 } from "zod";
|
|
1015
|
+
var MemSessionEndInputSchema = {
|
|
1016
|
+
goal: z16.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
1017
|
+
accomplished: z16.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
1018
|
+
discoveries: z16.string().default("").describe(
|
|
1019
|
+
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
1020
|
+
),
|
|
1021
|
+
files_touched: z16.array(z16.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
1022
|
+
next_steps: z16.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
1023
|
+
scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
1024
|
+
module: z16.string().optional().describe("Module name (required when scope=module)")
|
|
1025
|
+
};
|
|
1026
|
+
function recapTopic(scope, module) {
|
|
1027
|
+
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
1028
|
+
}
|
|
1029
|
+
function buildBody(input) {
|
|
1030
|
+
const lines = [];
|
|
1031
|
+
lines.push(`## Goal
|
|
1032
|
+
${input.goal}`);
|
|
1033
|
+
lines.push(`
|
|
1034
|
+
## Accomplished
|
|
1035
|
+
${input.accomplished}`);
|
|
1036
|
+
if (input.discoveries.trim()) {
|
|
1037
|
+
lines.push(`
|
|
1038
|
+
## Discoveries & surprises
|
|
1039
|
+
${input.discoveries}`);
|
|
1040
|
+
}
|
|
1041
|
+
if (input.files_touched.length > 0) {
|
|
1042
|
+
lines.push(`
|
|
1043
|
+
## Files touched
|
|
1044
|
+
${input.files_touched.map((f) => `- \`${f}\``).join("\n")}`);
|
|
1045
|
+
}
|
|
1046
|
+
if (input.next_steps.trim()) {
|
|
1047
|
+
lines.push(`
|
|
1048
|
+
## Next steps
|
|
1049
|
+
${input.next_steps}`);
|
|
1050
|
+
}
|
|
1051
|
+
return lines.join("\n");
|
|
1052
|
+
}
|
|
1053
|
+
async function memSessionEnd(input, ctx) {
|
|
1054
|
+
if (!existsSync16(ctx.paths.haiveDir)) {
|
|
1055
|
+
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
1056
|
+
}
|
|
1057
|
+
const body = buildBody(input);
|
|
1058
|
+
const topic = recapTopic(input.scope, input.module);
|
|
1059
|
+
const existing = existsSync16(ctx.paths.memoriesDir) ? await loadMemoriesFromDir12(ctx.paths.memoriesDir) : [];
|
|
1060
|
+
const topicMatch = existing.find(
|
|
1061
|
+
({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
1062
|
+
);
|
|
1063
|
+
if (topicMatch) {
|
|
1064
|
+
const fm = topicMatch.memory.frontmatter;
|
|
1065
|
+
const revisionCount = (fm.revision_count ?? 0) + 1;
|
|
1066
|
+
const newFrontmatter = {
|
|
1067
|
+
...fm,
|
|
1068
|
+
revision_count: revisionCount,
|
|
1069
|
+
anchor: {
|
|
1070
|
+
...fm.anchor,
|
|
1071
|
+
paths: input.files_touched.length ? input.files_touched : fm.anchor.paths
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
await writeFile9(
|
|
1075
|
+
topicMatch.filePath,
|
|
1076
|
+
serializeMemory8({ frontmatter: newFrontmatter, body }),
|
|
1077
|
+
"utf8"
|
|
1078
|
+
);
|
|
1079
|
+
return {
|
|
1080
|
+
id: fm.id,
|
|
1081
|
+
scope: fm.scope,
|
|
1082
|
+
file_path: topicMatch.filePath,
|
|
1083
|
+
action: "updated",
|
|
1084
|
+
revision_count: revisionCount
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
const frontmatter = buildFrontmatter4({
|
|
1088
|
+
type: "session_recap",
|
|
1089
|
+
slug: "session-recap",
|
|
1090
|
+
scope: input.scope,
|
|
1091
|
+
module: input.module,
|
|
1092
|
+
tags: ["session", "recap"],
|
|
1093
|
+
paths: input.files_touched,
|
|
1094
|
+
topic,
|
|
1095
|
+
status: "validated"
|
|
1096
|
+
});
|
|
1097
|
+
const file = memoryFilePath4(
|
|
1098
|
+
ctx.paths,
|
|
1099
|
+
frontmatter.scope,
|
|
1100
|
+
frontmatter.id,
|
|
1101
|
+
frontmatter.module
|
|
1102
|
+
);
|
|
1103
|
+
await mkdir5(path7.dirname(file), { recursive: true });
|
|
1104
|
+
await writeFile9(file, serializeMemory8({ frontmatter, body }), "utf8");
|
|
1105
|
+
return {
|
|
1106
|
+
id: frontmatter.id,
|
|
1107
|
+
scope: frontmatter.scope,
|
|
1108
|
+
file_path: file,
|
|
1109
|
+
action: "created",
|
|
1110
|
+
revision_count: 0
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/tools/get-briefing.ts
|
|
1115
|
+
import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
|
|
1116
|
+
import { existsSync as existsSync17 } from "fs";
|
|
1117
|
+
import path8 from "path";
|
|
852
1118
|
import {
|
|
853
1119
|
allocateBudget,
|
|
854
1120
|
deriveConfidence as deriveConfidence4,
|
|
@@ -857,31 +1123,31 @@ import {
|
|
|
857
1123
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
858
1124
|
isDecaying,
|
|
859
1125
|
literalMatchesAllTokens as literalMatchesAllTokens2,
|
|
860
|
-
loadMemoriesFromDir as
|
|
1126
|
+
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
861
1127
|
loadUsageIndex as loadUsageIndex7,
|
|
862
1128
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
863
1129
|
tokenizeQuery as tokenizeQuery2,
|
|
864
1130
|
trackReads as trackReads3,
|
|
865
1131
|
truncateToTokens
|
|
866
1132
|
} from "@hiveai/core";
|
|
867
|
-
import { z as
|
|
1133
|
+
import { z as z17 } from "zod";
|
|
868
1134
|
var GetBriefingInputSchema = {
|
|
869
|
-
task:
|
|
1135
|
+
task: z17.string().optional().describe(
|
|
870
1136
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
871
1137
|
),
|
|
872
|
-
files:
|
|
873
|
-
max_tokens:
|
|
1138
|
+
files: z17.array(z17.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
1139
|
+
max_tokens: z17.number().int().positive().default(8e3).describe(
|
|
874
1140
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
875
1141
|
),
|
|
876
|
-
max_memories:
|
|
877
|
-
include_project_context:
|
|
878
|
-
include_module_contexts:
|
|
879
|
-
semantic:
|
|
1142
|
+
max_memories: z17.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
1143
|
+
include_project_context: z17.boolean().default(true),
|
|
1144
|
+
include_module_contexts: z17.boolean().default(true),
|
|
1145
|
+
semantic: z17.boolean().default(true).describe(
|
|
880
1146
|
"Use semantic ranking when a task is provided (requires `haive embeddings index`)."
|
|
881
1147
|
),
|
|
882
|
-
include_stale:
|
|
883
|
-
track:
|
|
884
|
-
format:
|
|
1148
|
+
include_stale: z17.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
1149
|
+
track: z17.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
1150
|
+
format: z17.enum(["full", "compact"]).default("full").describe(
|
|
885
1151
|
"Output format: 'full' returns complete memory bodies; 'compact' returns id + 1-line summary only (call mem_get for details)."
|
|
886
1152
|
)
|
|
887
1153
|
};
|
|
@@ -891,12 +1157,27 @@ async function getBriefing(input, ctx) {
|
|
|
891
1157
|
let searchMode = "literal";
|
|
892
1158
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
893
1159
|
let byId = /* @__PURE__ */ new Map();
|
|
894
|
-
|
|
895
|
-
|
|
1160
|
+
let lastSession;
|
|
1161
|
+
if (existsSync17(ctx.paths.memoriesDir)) {
|
|
1162
|
+
const allLoaded = await loadMemoriesFromDir13(ctx.paths.memoriesDir);
|
|
1163
|
+
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
1164
|
+
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
1165
|
+
);
|
|
1166
|
+
if (recaps.length > 0) {
|
|
1167
|
+
const r = recaps[0];
|
|
1168
|
+
const fm = r.memory.frontmatter;
|
|
1169
|
+
lastSession = {
|
|
1170
|
+
id: fm.id,
|
|
1171
|
+
scope: fm.scope,
|
|
1172
|
+
revision_count: fm.revision_count ?? 0,
|
|
1173
|
+
body: r.memory.body
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
896
1176
|
const allMemories = allLoaded.filter(({ memory }) => {
|
|
897
1177
|
const s = memory.frontmatter.status;
|
|
898
1178
|
if (s === "rejected" || s === "deprecated") return false;
|
|
899
1179
|
if (!input.include_stale && s === "stale") return false;
|
|
1180
|
+
if (memory.frontmatter.type === "session_recap") return false;
|
|
900
1181
|
return true;
|
|
901
1182
|
});
|
|
902
1183
|
usage = await loadUsageIndex7(ctx.paths);
|
|
@@ -986,7 +1267,7 @@ async function getBriefing(input, ctx) {
|
|
|
986
1267
|
await trackReads3(ctx.paths, memories.map((m) => m.id));
|
|
987
1268
|
}
|
|
988
1269
|
}
|
|
989
|
-
const projectContext = input.include_project_context &&
|
|
1270
|
+
const projectContext = input.include_project_context && existsSync17(ctx.paths.projectContext) ? await readFile3(ctx.paths.projectContext, "utf8") : "";
|
|
990
1271
|
const moduleContents = input.include_module_contexts ? await loadModuleContexts2(ctx, inferred) : [];
|
|
991
1272
|
const memoriesText = memories.map((m) => {
|
|
992
1273
|
const unverified = m.status === "proposed" ? " [UNVERIFIED \u2014 not yet validated]" : "";
|
|
@@ -1052,6 +1333,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
1052
1333
|
...input.task ? { task: input.task } : {},
|
|
1053
1334
|
search_mode: searchMode,
|
|
1054
1335
|
inferred_modules: inferred,
|
|
1336
|
+
...lastSession ? { last_session: lastSession } : {},
|
|
1055
1337
|
project_context: projectContext ? { content: projectSlice.text, truncated: projectSlice.truncated } : null,
|
|
1056
1338
|
module_contexts: trimmedModules,
|
|
1057
1339
|
memories: outputMemories,
|
|
@@ -1087,15 +1369,15 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
1087
1369
|
}
|
|
1088
1370
|
async function loadModuleContexts2(ctx, modules) {
|
|
1089
1371
|
if (modules.length === 0) return [];
|
|
1090
|
-
if (!
|
|
1372
|
+
if (!existsSync17(ctx.paths.modulesContextDir)) return [];
|
|
1091
1373
|
const available = new Set(
|
|
1092
1374
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
1093
1375
|
);
|
|
1094
1376
|
const out = [];
|
|
1095
1377
|
for (const m of modules) {
|
|
1096
1378
|
if (!available.has(m)) continue;
|
|
1097
|
-
const file =
|
|
1098
|
-
if (
|
|
1379
|
+
const file = path8.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
1380
|
+
if (existsSync17(file)) {
|
|
1099
1381
|
out.push({ name: m, content: await readFile3(file, "utf8") });
|
|
1100
1382
|
}
|
|
1101
1383
|
}
|
|
@@ -1104,11 +1386,11 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
1104
1386
|
|
|
1105
1387
|
// src/tools/code-map.ts
|
|
1106
1388
|
import { loadCodeMap, queryCodeMap } from "@hiveai/core";
|
|
1107
|
-
import { z as
|
|
1389
|
+
import { z as z18 } from "zod";
|
|
1108
1390
|
var CodeMapInputSchema = {
|
|
1109
|
-
file:
|
|
1110
|
-
symbol:
|
|
1111
|
-
max_files:
|
|
1391
|
+
file: z18.string().optional().describe("Filter to files whose path contains this substring"),
|
|
1392
|
+
symbol: z18.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
1393
|
+
max_files: z18.number().int().positive().default(40).describe("Cap on returned files")
|
|
1112
1394
|
};
|
|
1113
1395
|
async function codeMapTool(input, ctx) {
|
|
1114
1396
|
const map = await loadCodeMap(ctx.paths);
|
|
@@ -1134,18 +1416,18 @@ async function codeMapTool(input, ctx) {
|
|
|
1134
1416
|
}
|
|
1135
1417
|
|
|
1136
1418
|
// src/tools/mem-diff.ts
|
|
1137
|
-
import { existsSync as
|
|
1138
|
-
import { loadMemoriesFromDir as
|
|
1139
|
-
import { z as
|
|
1419
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1420
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir14 } from "@hiveai/core";
|
|
1421
|
+
import { z as z19 } from "zod";
|
|
1140
1422
|
var MemDiffInputSchema = {
|
|
1141
|
-
id_a:
|
|
1142
|
-
id_b:
|
|
1423
|
+
id_a: z19.string().min(1).describe("First memory id"),
|
|
1424
|
+
id_b: z19.string().min(1).describe("Second memory id")
|
|
1143
1425
|
};
|
|
1144
1426
|
async function memDiff(input, ctx) {
|
|
1145
|
-
if (!
|
|
1427
|
+
if (!existsSync18(ctx.paths.memoriesDir)) {
|
|
1146
1428
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
1147
1429
|
}
|
|
1148
|
-
const all = await
|
|
1430
|
+
const all = await loadMemoriesFromDir14(ctx.paths.memoriesDir);
|
|
1149
1431
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
1150
1432
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
1151
1433
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -1179,12 +1461,12 @@ async function memDiff(input, ctx) {
|
|
|
1179
1461
|
}
|
|
1180
1462
|
|
|
1181
1463
|
// src/prompts/bootstrap-project.ts
|
|
1182
|
-
import { z as
|
|
1464
|
+
import { z as z20 } from "zod";
|
|
1183
1465
|
var BootstrapProjectArgsSchema = {
|
|
1184
|
-
module:
|
|
1466
|
+
module: z20.string().optional().describe(
|
|
1185
1467
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
1186
1468
|
),
|
|
1187
|
-
focus:
|
|
1469
|
+
focus: z20.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
1188
1470
|
};
|
|
1189
1471
|
var ROOT_TEMPLATE = `# Project context
|
|
1190
1472
|
|
|
@@ -1266,10 +1548,10 @@ ${template}\`\`\`
|
|
|
1266
1548
|
}
|
|
1267
1549
|
|
|
1268
1550
|
// src/prompts/post-task.ts
|
|
1269
|
-
import { z as
|
|
1551
|
+
import { z as z21 } from "zod";
|
|
1270
1552
|
var PostTaskArgsSchema = {
|
|
1271
|
-
task_summary:
|
|
1272
|
-
files_touched:
|
|
1553
|
+
task_summary: z21.string().optional().describe("One sentence describing what you just did"),
|
|
1554
|
+
files_touched: z21.array(z21.string()).optional().describe("Files you created or modified during the task")
|
|
1273
1555
|
};
|
|
1274
1556
|
function postTaskPrompt(args, ctx) {
|
|
1275
1557
|
const taskLine = args.task_summary ? `
|
|
@@ -1285,6 +1567,19 @@ Project root: \`${ctx.paths.root}\`
|
|
|
1285
1567
|
|
|
1286
1568
|
Go through each item. If the answer is yes, call the corresponding tool immediately.
|
|
1287
1569
|
|
|
1570
|
+
### 0. Did you read existing code and discover bugs, inconsistencies, or security gaps that weren't in the briefing?
|
|
1571
|
+
This is the most important question. Deep code reading surfaces issues that no memory captures yet.
|
|
1572
|
+
Examples of things to look for:
|
|
1573
|
+
- A method with an invalid signature (e.g. two \`@RequestBody\` on the same handler)
|
|
1574
|
+
- A configuration that looks wrong or missing (e.g. webhook path not whitelisted in SecurityConfig)
|
|
1575
|
+
- A component scan / DI issue (e.g. a Spring bean not picked up because the package isn't scanned)
|
|
1576
|
+
- A DB constraint that will break when you add a new enum value
|
|
1577
|
+
- A hardcoded value that should be dynamic (e.g. hardcoded tenant id "default-tenant")
|
|
1578
|
+
- Anything that will silently break in production
|
|
1579
|
+
|
|
1580
|
+
\u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\`, \`scope="team"\`, and **anchor it to the file** with \`paths\`.
|
|
1581
|
+
This transforms your discovery into institutional knowledge that protects every future agent.
|
|
1582
|
+
|
|
1288
1583
|
### 1. Did you try an approach that failed?
|
|
1289
1584
|
\u2192 If yes, call **\`mem_tried\`** with:
|
|
1290
1585
|
- \`what\`: the approach you tried (e.g. "importing gray-matter with ESM dynamic import")
|
|
@@ -1299,7 +1594,7 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
|
|
|
1299
1594
|
### 3. Did you make an architectural decision?
|
|
1300
1595
|
\u2192 If yes, call **\`mem_save\`** with \`type="decision"\` and document the WHY (constraints, tradeoffs), not just the what
|
|
1301
1596
|
|
|
1302
|
-
### 4. Did you hit a non-obvious bug or surprising behavior?
|
|
1597
|
+
### 4. Did you hit a non-obvious bug or surprising behavior in a library or framework?
|
|
1303
1598
|
\u2192 If yes, call **\`mem_save\`** with \`type="gotcha"\` and anchor it to the relevant file paths
|
|
1304
1599
|
|
|
1305
1600
|
### 5. Did you find that an existing memory is outdated or wrong?
|
|
@@ -1311,8 +1606,20 @@ Go through each item. If the answer is yes, call the corresponding tool immediat
|
|
|
1311
1606
|
- Anchor memories to file paths when possible (the \`paths\` field) \u2014 this enables staleness detection.
|
|
1312
1607
|
- Prefer \`scope="team"\` for anything a teammate or future agent would benefit from.
|
|
1313
1608
|
- Skip sections where you genuinely have nothing to add. Don't fabricate memories.
|
|
1609
|
+
- **Question 0 is not optional** \u2014 always scan your exploration history for code-level discoveries.
|
|
1610
|
+
|
|
1611
|
+
### 6. Close the session \u2014 always
|
|
1612
|
+
Call **\`mem_session_end\`** with:
|
|
1613
|
+
- \`goal\`: what you set out to do
|
|
1614
|
+
- \`accomplished\`: what was actually done (bullet list)
|
|
1615
|
+
- \`discoveries\`: anything surprising or broken found during this session (leave empty if none)
|
|
1616
|
+
- \`files_touched\`: the key files you read or modified
|
|
1617
|
+
- \`next_steps\`: what remains for the next session or a teammate
|
|
1618
|
+
- \`scope\`: "team" if this task affects the whole team, "personal" otherwise
|
|
1314
1619
|
|
|
1315
|
-
|
|
1620
|
+
This creates/updates a single rolling recap that **get_briefing automatically surfaces** at the start of every subsequent session \u2014 no token waste re-explaining what happened.
|
|
1621
|
+
|
|
1622
|
+
When done, respond with a brief summary: "Saved N memories: [list of IDs]. Session recap saved."
|
|
1316
1623
|
`;
|
|
1317
1624
|
return {
|
|
1318
1625
|
description: "Post-task reflection: capture what you learned before closing the session",
|
|
@@ -1326,12 +1633,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]" or "N
|
|
|
1326
1633
|
}
|
|
1327
1634
|
|
|
1328
1635
|
// src/prompts/import-docs.ts
|
|
1329
|
-
import { z as
|
|
1636
|
+
import { z as z22 } from "zod";
|
|
1330
1637
|
var ImportDocsArgsSchema = {
|
|
1331
|
-
content:
|
|
1332
|
-
source:
|
|
1333
|
-
scope:
|
|
1334
|
-
dry_run:
|
|
1638
|
+
content: z22.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
1639
|
+
source: z22.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
1640
|
+
scope: z22.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
1641
|
+
dry_run: z22.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
1335
1642
|
};
|
|
1336
1643
|
function importDocsPrompt(args, ctx) {
|
|
1337
1644
|
const sourceLine = args.source ? `
|
|
@@ -1396,7 +1703,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
1396
1703
|
|
|
1397
1704
|
// src/server.ts
|
|
1398
1705
|
var SERVER_NAME = "haive";
|
|
1399
|
-
var SERVER_VERSION = "0.2.
|
|
1706
|
+
var SERVER_VERSION = "0.2.12";
|
|
1400
1707
|
function jsonResult(data) {
|
|
1401
1708
|
return {
|
|
1402
1709
|
content: [
|
|
@@ -1515,6 +1822,18 @@ function createHaiveServer(options = {}) {
|
|
|
1515
1822
|
MemDiffInputSchema,
|
|
1516
1823
|
async (input) => jsonResult(await memDiff(input, context))
|
|
1517
1824
|
);
|
|
1825
|
+
server.tool(
|
|
1826
|
+
"mem_observe",
|
|
1827
|
+
"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.",
|
|
1828
|
+
MemObserveInputSchema,
|
|
1829
|
+
async (input) => jsonResult(await memObserve(input, context))
|
|
1830
|
+
);
|
|
1831
|
+
server.tool(
|
|
1832
|
+
"mem_session_end",
|
|
1833
|
+
"Save a structured end-of-session recap (goal / accomplished / discoveries / next steps). Uses topic-upsert: one recap per scope is kept and updated in-place so the next session always has fresh context. Call this before closing every significant session. get_briefing automatically surfaces the latest recap at the top of the next session's briefing.",
|
|
1834
|
+
MemSessionEndInputSchema,
|
|
1835
|
+
async (input) => jsonResult(await memSessionEnd(input, context))
|
|
1836
|
+
);
|
|
1518
1837
|
server.prompt(
|
|
1519
1838
|
"bootstrap_project",
|
|
1520
1839
|
"Instructions for the AI client to analyze the project and save the context.",
|