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