@hiveai/mcp 0.1.0
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/LICENSE +21 -0
- package/dist/index.js +896 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +20 -0
- package/dist/server.js +869 -0
- package/dist/server.js.map +1 -0
- package/package.json +53 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
|
|
4
|
+
// src/context.ts
|
|
5
|
+
import { findProjectRoot, resolveHaivePaths } from "@hiveai/core";
|
|
6
|
+
function createContext(options = {}) {
|
|
7
|
+
const env = options.env ?? process.env;
|
|
8
|
+
const cwd = options.cwd ?? process.cwd();
|
|
9
|
+
const root = options.root ?? env.HAIVE_PROJECT_ROOT ?? findProjectRoot(cwd);
|
|
10
|
+
return { paths: resolveHaivePaths(root) };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/tools/bootstrap-project-save.ts
|
|
14
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
15
|
+
import { existsSync } from "fs";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
var BootstrapProjectSaveInputSchema = {
|
|
19
|
+
content: z.string().min(1).describe("Full Markdown content for the project (or module) context file"),
|
|
20
|
+
module: z.string().optional().describe(
|
|
21
|
+
"If provided, save under .ai/modules/<module>/context.md instead of .ai/project-context.md"
|
|
22
|
+
),
|
|
23
|
+
overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
|
|
24
|
+
};
|
|
25
|
+
async function bootstrapProjectSave(input, ctx) {
|
|
26
|
+
const target = input.module ? path.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
27
|
+
const exists = existsSync(target);
|
|
28
|
+
if (exists && !input.overwrite) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`${target} already exists. Pass overwrite=true to replace it.`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
34
|
+
await writeFile(target, input.content, "utf8");
|
|
35
|
+
return {
|
|
36
|
+
file_path: target,
|
|
37
|
+
action: exists ? "overwritten" : "created"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/tools/get-project-context.ts
|
|
42
|
+
import { readFile, readdir } from "fs/promises";
|
|
43
|
+
import { existsSync as existsSync2 } from "fs";
|
|
44
|
+
import path2 from "path";
|
|
45
|
+
import { z as z2 } from "zod";
|
|
46
|
+
var GetProjectContextInputSchema = {
|
|
47
|
+
module: z2.string().optional().describe("If provided, also include the matching module's context file"),
|
|
48
|
+
list_modules: z2.boolean().default(false).describe("Return the list of available module context files")
|
|
49
|
+
};
|
|
50
|
+
async function getProjectContext(input, ctx) {
|
|
51
|
+
const out = { root_context: null };
|
|
52
|
+
if (existsSync2(ctx.paths.projectContext)) {
|
|
53
|
+
out.root_context = await readFile(ctx.paths.projectContext, "utf8");
|
|
54
|
+
}
|
|
55
|
+
if (input.module) {
|
|
56
|
+
const modFile = path2.join(ctx.paths.modulesContextDir, input.module, "context.md");
|
|
57
|
+
if (existsSync2(modFile)) {
|
|
58
|
+
out.module_context = {
|
|
59
|
+
name: input.module,
|
|
60
|
+
content: await readFile(modFile, "utf8")
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (input.list_modules) {
|
|
65
|
+
out.available_modules = await listModules(ctx.paths.modulesContextDir);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
async function listModules(modulesDir) {
|
|
70
|
+
if (!existsSync2(modulesDir)) return [];
|
|
71
|
+
const entries = await readdir(modulesDir, { withFileTypes: true });
|
|
72
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/tools/mem-list.ts
|
|
76
|
+
import { existsSync as existsSync3 } from "fs";
|
|
77
|
+
import { loadMemoriesFromDir } from "@hiveai/core";
|
|
78
|
+
import { z as z3 } from "zod";
|
|
79
|
+
var MemListInputSchema = {
|
|
80
|
+
scope: z3.enum(["personal", "team", "module"]).optional(),
|
|
81
|
+
type: z3.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional(),
|
|
82
|
+
module: z3.string().optional(),
|
|
83
|
+
tag: z3.string().optional()
|
|
84
|
+
};
|
|
85
|
+
async function memList(input, ctx) {
|
|
86
|
+
if (!existsSync3(ctx.paths.memoriesDir)) {
|
|
87
|
+
return { memories: [] };
|
|
88
|
+
}
|
|
89
|
+
const all = await loadMemoriesFromDir(ctx.paths.memoriesDir);
|
|
90
|
+
const filtered = all.filter(({ memory }) => {
|
|
91
|
+
const fm = memory.frontmatter;
|
|
92
|
+
if (input.scope && fm.scope !== input.scope) return false;
|
|
93
|
+
if (input.type && fm.type !== input.type) return false;
|
|
94
|
+
if (input.module && fm.module !== input.module) return false;
|
|
95
|
+
if (input.tag && !fm.tags.includes(input.tag)) return false;
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
98
|
+
const memories = filtered.map(({ memory, filePath }) => {
|
|
99
|
+
const fm = memory.frontmatter;
|
|
100
|
+
return {
|
|
101
|
+
id: fm.id,
|
|
102
|
+
scope: fm.scope,
|
|
103
|
+
type: fm.type,
|
|
104
|
+
...fm.module ? { module: fm.module } : {},
|
|
105
|
+
status: fm.status,
|
|
106
|
+
tags: fm.tags,
|
|
107
|
+
file_path: filePath
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
return { memories };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/tools/mem-save.ts
|
|
114
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
115
|
+
import { existsSync as existsSync4 } from "fs";
|
|
116
|
+
import path3 from "path";
|
|
117
|
+
import {
|
|
118
|
+
buildFrontmatter,
|
|
119
|
+
memoryFilePath,
|
|
120
|
+
serializeMemory
|
|
121
|
+
} from "@hiveai/core";
|
|
122
|
+
import { z as z4 } from "zod";
|
|
123
|
+
var MemSaveInputSchema = {
|
|
124
|
+
type: z4.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).describe("Kind of memory being saved"),
|
|
125
|
+
slug: z4.string().min(1).describe("Short human-readable identifier \u2014 becomes part of the filename"),
|
|
126
|
+
body: z4.string().describe("Markdown body of the memory"),
|
|
127
|
+
scope: z4.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope: personal | team | module"),
|
|
128
|
+
module: z4.string().optional().describe("Module name (required when scope=module)"),
|
|
129
|
+
tags: z4.array(z4.string()).default([]).describe("Tags for filtering"),
|
|
130
|
+
domain: z4.string().optional().describe("Domain (e.g. transactions, billing)"),
|
|
131
|
+
author: z4.string().optional().describe("Author handle or email"),
|
|
132
|
+
paths: z4.array(z4.string()).default([]).describe("Anchor paths (file paths this memory references)"),
|
|
133
|
+
symbols: z4.array(z4.string()).default([]).describe("Anchor symbols (function/class names this memory references)"),
|
|
134
|
+
commit: z4.string().optional().describe("Anchor commit SHA (for staleness detection later)")
|
|
135
|
+
};
|
|
136
|
+
async function memSave(input, ctx) {
|
|
137
|
+
if (!existsSync4(ctx.paths.haiveDir)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const frontmatter = buildFrontmatter({
|
|
143
|
+
type: input.type,
|
|
144
|
+
slug: input.slug,
|
|
145
|
+
scope: input.scope,
|
|
146
|
+
module: input.module,
|
|
147
|
+
tags: input.tags,
|
|
148
|
+
domain: input.domain,
|
|
149
|
+
author: input.author,
|
|
150
|
+
paths: input.paths,
|
|
151
|
+
symbols: input.symbols,
|
|
152
|
+
commit: input.commit
|
|
153
|
+
});
|
|
154
|
+
const file = memoryFilePath(
|
|
155
|
+
ctx.paths,
|
|
156
|
+
frontmatter.scope,
|
|
157
|
+
frontmatter.id,
|
|
158
|
+
frontmatter.module
|
|
159
|
+
);
|
|
160
|
+
await mkdir2(path3.dirname(file), { recursive: true });
|
|
161
|
+
if (existsSync4(file)) {
|
|
162
|
+
throw new Error(`Memory already exists at ${file}`);
|
|
163
|
+
}
|
|
164
|
+
await writeFile2(file, serializeMemory({ frontmatter, body: input.body }), "utf8");
|
|
165
|
+
return {
|
|
166
|
+
id: frontmatter.id,
|
|
167
|
+
scope: frontmatter.scope,
|
|
168
|
+
file_path: file
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/tools/mem-search.ts
|
|
173
|
+
import { existsSync as existsSync5 } from "fs";
|
|
174
|
+
import {
|
|
175
|
+
deriveConfidence,
|
|
176
|
+
extractSnippet,
|
|
177
|
+
getUsage,
|
|
178
|
+
literalMatchesAllTokens,
|
|
179
|
+
loadMemoriesFromDir as loadMemoriesFromDir2,
|
|
180
|
+
loadUsageIndex,
|
|
181
|
+
pickSnippetNeedle,
|
|
182
|
+
tokenizeQuery,
|
|
183
|
+
trackReads
|
|
184
|
+
} from "@hiveai/core";
|
|
185
|
+
import { z as z5 } from "zod";
|
|
186
|
+
var MemSearchInputSchema = {
|
|
187
|
+
query: z5.string().describe("Substring matched against id, tags, and body"),
|
|
188
|
+
scope: z5.enum(["personal", "team", "module"]).optional().describe("Restrict results to a single scope"),
|
|
189
|
+
type: z5.enum(["convention", "decision", "gotcha", "architecture", "glossary"]).optional().describe("Restrict results to a memory type"),
|
|
190
|
+
module: z5.string().optional().describe("Restrict results to a module"),
|
|
191
|
+
limit: z5.number().int().positive().max(100).default(20).describe("Max results"),
|
|
192
|
+
semantic: z5.boolean().default(false).describe(
|
|
193
|
+
"Use semantic similarity from the embeddings index (requires `haive embeddings index`)."
|
|
194
|
+
),
|
|
195
|
+
min_score: z5.number().min(0).max(1).default(0).describe("Minimum cosine similarity (semantic mode only)"),
|
|
196
|
+
track: z5.boolean().default(true).describe("Increment read_count on returned memories (used for passive validation)")
|
|
197
|
+
};
|
|
198
|
+
async function memSearch(input, ctx) {
|
|
199
|
+
if (!existsSync5(ctx.paths.memoriesDir)) {
|
|
200
|
+
return { matches: [], total: 0, mode: input.semantic ? "literal_fallback" : "literal" };
|
|
201
|
+
}
|
|
202
|
+
const all = await loadMemoriesFromDir2(ctx.paths.memoriesDir);
|
|
203
|
+
const filtered = all.filter(({ memory }) => passesFilters(memory.frontmatter, input));
|
|
204
|
+
const usage = await loadUsageIndex(ctx.paths);
|
|
205
|
+
let result;
|
|
206
|
+
if (input.semantic) {
|
|
207
|
+
const semantic = await trySemanticSearch(ctx, input, filtered, usage);
|
|
208
|
+
if (semantic) {
|
|
209
|
+
result = semantic;
|
|
210
|
+
} else {
|
|
211
|
+
result = {
|
|
212
|
+
...buildLiteralResult(input, filtered, usage),
|
|
213
|
+
mode: "literal_fallback",
|
|
214
|
+
notice: "Semantic search unavailable (embeddings index missing or @hiveai/embeddings not installed). Falling back to literal search."
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
result = buildLiteralResult(input, filtered, usage);
|
|
219
|
+
}
|
|
220
|
+
if (input.track && result.matches.length > 0) {
|
|
221
|
+
await trackReads(
|
|
222
|
+
ctx.paths,
|
|
223
|
+
result.matches.map((m) => m.id)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
function passesFilters(fm, input) {
|
|
229
|
+
if (input.scope && fm.scope !== input.scope) return false;
|
|
230
|
+
if (input.type && fm.type !== input.type) return false;
|
|
231
|
+
if (input.module && fm.module !== input.module) return false;
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
function buildLiteralResult(input, filtered, usage) {
|
|
235
|
+
const tokens = tokenizeQuery(input.query);
|
|
236
|
+
const matched = filtered.filter(({ memory }) => literalMatchesAllTokens(memory, tokens));
|
|
237
|
+
const snippetNeedle = pickSnippetNeedle(input.query);
|
|
238
|
+
const top = matched.slice(0, input.limit);
|
|
239
|
+
return {
|
|
240
|
+
matches: top.map((loaded) => toHit(loaded, snippetNeedle, usage)),
|
|
241
|
+
total: matched.length,
|
|
242
|
+
mode: "literal"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
async function trySemanticSearch(ctx, input, filtered, usage) {
|
|
246
|
+
let mod;
|
|
247
|
+
try {
|
|
248
|
+
mod = await import("@hiveai/embeddings");
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const result = await mod.semanticSearch(ctx.paths, input.query, {
|
|
253
|
+
limit: Math.min(input.limit * 3, 100),
|
|
254
|
+
minScore: input.min_score
|
|
255
|
+
});
|
|
256
|
+
if (!result) return null;
|
|
257
|
+
const allowedIds = new Set(filtered.map((m) => m.memory.frontmatter.id));
|
|
258
|
+
const byId = new Map(filtered.map((m) => [m.memory.frontmatter.id, m]));
|
|
259
|
+
const ranked = result.hits.filter((h) => allowedIds.has(h.id)).slice(0, input.limit);
|
|
260
|
+
const matches = ranked.map((hit) => {
|
|
261
|
+
const loaded = byId.get(hit.id);
|
|
262
|
+
if (!loaded) {
|
|
263
|
+
return {
|
|
264
|
+
id: hit.id,
|
|
265
|
+
scope: "unknown",
|
|
266
|
+
type: "unknown",
|
|
267
|
+
tags: [],
|
|
268
|
+
status: "unknown",
|
|
269
|
+
confidence: "unverified",
|
|
270
|
+
read_count: 0,
|
|
271
|
+
snippet: "",
|
|
272
|
+
file_path: hit.file_path,
|
|
273
|
+
score: hit.score
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
const base = toHit(loaded, input.query.toLowerCase(), usage);
|
|
277
|
+
return { ...base, score: hit.score };
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
matches,
|
|
281
|
+
total: ranked.length,
|
|
282
|
+
mode: "semantic"
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function toHit(loaded, needle, usage) {
|
|
286
|
+
const fm = loaded.memory.frontmatter;
|
|
287
|
+
const u = getUsage(usage, fm.id);
|
|
288
|
+
return {
|
|
289
|
+
id: fm.id,
|
|
290
|
+
scope: fm.scope,
|
|
291
|
+
type: fm.type,
|
|
292
|
+
...fm.module ? { module: fm.module } : {},
|
|
293
|
+
tags: fm.tags,
|
|
294
|
+
status: fm.status,
|
|
295
|
+
confidence: deriveConfidence(fm, u),
|
|
296
|
+
read_count: u.read_count,
|
|
297
|
+
snippet: extractSnippet(loaded.memory.body, needle),
|
|
298
|
+
file_path: loaded.filePath
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/tools/mem-verify.ts
|
|
303
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
304
|
+
import { existsSync as existsSync6 } from "fs";
|
|
305
|
+
import {
|
|
306
|
+
loadMemoriesFromDir as loadMemoriesFromDir3,
|
|
307
|
+
serializeMemory as serializeMemory2,
|
|
308
|
+
verifyAnchor
|
|
309
|
+
} from "@hiveai/core";
|
|
310
|
+
import { z as z6 } from "zod";
|
|
311
|
+
var MemVerifyInputSchema = {
|
|
312
|
+
id: z6.string().optional().describe("If set, verify only this memory id"),
|
|
313
|
+
update: z6.boolean().default(false).describe("Write the resulting status back to disk (status=stale or validated)")
|
|
314
|
+
};
|
|
315
|
+
async function memVerify(input, ctx) {
|
|
316
|
+
if (!existsSync6(ctx.paths.memoriesDir)) {
|
|
317
|
+
return {
|
|
318
|
+
results: [],
|
|
319
|
+
summary: { checked: 0, fresh: 0, stale: 0, anchorless_skipped: 0, updated: 0 }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
const all = await loadMemoriesFromDir3(ctx.paths.memoriesDir);
|
|
323
|
+
const targets = input.id ? all.filter((m) => m.memory.frontmatter.id === input.id) : all;
|
|
324
|
+
const results = [];
|
|
325
|
+
let fresh = 0;
|
|
326
|
+
let stale = 0;
|
|
327
|
+
let anchorless = 0;
|
|
328
|
+
let updated = 0;
|
|
329
|
+
for (const { memory, filePath } of targets) {
|
|
330
|
+
const isAnchored = memory.frontmatter.anchor.paths.length > 0 || memory.frontmatter.anchor.symbols.length > 0;
|
|
331
|
+
if (!isAnchored) {
|
|
332
|
+
anchorless++;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const result = await verifyAnchor(memory, { projectRoot: ctx.paths.root });
|
|
336
|
+
if (result.stale) stale++;
|
|
337
|
+
else fresh++;
|
|
338
|
+
let statusAfter = memory.frontmatter.status;
|
|
339
|
+
if (input.update) {
|
|
340
|
+
const next = applyVerification(memory, result);
|
|
341
|
+
await writeFile3(filePath, serializeMemory2(next), "utf8");
|
|
342
|
+
statusAfter = next.frontmatter.status;
|
|
343
|
+
updated++;
|
|
344
|
+
}
|
|
345
|
+
results.push({
|
|
346
|
+
id: memory.frontmatter.id,
|
|
347
|
+
file_path: filePath,
|
|
348
|
+
stale: result.stale,
|
|
349
|
+
reason: result.reason,
|
|
350
|
+
status_after: statusAfter
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
results,
|
|
355
|
+
summary: {
|
|
356
|
+
checked: results.length + anchorless,
|
|
357
|
+
fresh,
|
|
358
|
+
stale,
|
|
359
|
+
anchorless_skipped: anchorless,
|
|
360
|
+
updated
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function applyVerification(mem, result) {
|
|
365
|
+
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
366
|
+
if (result.stale) {
|
|
367
|
+
return {
|
|
368
|
+
frontmatter: {
|
|
369
|
+
...mem.frontmatter,
|
|
370
|
+
status: "stale",
|
|
371
|
+
verified_at: verifiedAt,
|
|
372
|
+
stale_reason: result.reason
|
|
373
|
+
},
|
|
374
|
+
body: mem.body
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const nextStatus = mem.frontmatter.status === "stale" || mem.frontmatter.status === "draft" ? "validated" : mem.frontmatter.status;
|
|
378
|
+
return {
|
|
379
|
+
frontmatter: {
|
|
380
|
+
...mem.frontmatter,
|
|
381
|
+
status: nextStatus,
|
|
382
|
+
verified_at: verifiedAt,
|
|
383
|
+
stale_reason: null
|
|
384
|
+
},
|
|
385
|
+
body: mem.body
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/tools/mem-reject.ts
|
|
390
|
+
import { existsSync as existsSync7 } from "fs";
|
|
391
|
+
import {
|
|
392
|
+
loadMemoriesFromDir as loadMemoriesFromDir4,
|
|
393
|
+
loadUsageIndex as loadUsageIndex2,
|
|
394
|
+
recordRejection,
|
|
395
|
+
saveUsageIndex
|
|
396
|
+
} from "@hiveai/core";
|
|
397
|
+
import { z as z7 } from "zod";
|
|
398
|
+
var MemRejectInputSchema = {
|
|
399
|
+
id: z7.string().min(1).describe("Memory id being rejected"),
|
|
400
|
+
reason: z7.string().optional().describe("Why this memory is being rejected (recorded for review)")
|
|
401
|
+
};
|
|
402
|
+
async function memReject(input, ctx) {
|
|
403
|
+
if (!existsSync7(ctx.paths.memoriesDir)) {
|
|
404
|
+
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
405
|
+
}
|
|
406
|
+
const memories = await loadMemoriesFromDir4(ctx.paths.memoriesDir);
|
|
407
|
+
const exists = memories.some((m) => m.memory.frontmatter.id === input.id);
|
|
408
|
+
if (!exists) {
|
|
409
|
+
throw new Error(`No memory with id "${input.id}".`);
|
|
410
|
+
}
|
|
411
|
+
const idx = await loadUsageIndex2(ctx.paths);
|
|
412
|
+
recordRejection(idx, input.id, input.reason ?? null);
|
|
413
|
+
await saveUsageIndex(ctx.paths, idx);
|
|
414
|
+
const u = idx.by_id[input.id];
|
|
415
|
+
return {
|
|
416
|
+
id: input.id,
|
|
417
|
+
rejected_count: u?.rejected_count ?? 0,
|
|
418
|
+
last_rejected_at: u?.last_rejected_at ?? null,
|
|
419
|
+
rejection_reason: u?.rejection_reason ?? null
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/tools/mem-for-files.ts
|
|
424
|
+
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
425
|
+
import { existsSync as existsSync8 } from "fs";
|
|
426
|
+
import path4 from "path";
|
|
427
|
+
import {
|
|
428
|
+
deriveConfidence as deriveConfidence2,
|
|
429
|
+
getUsage as getUsage2,
|
|
430
|
+
inferModulesFromPaths,
|
|
431
|
+
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
432
|
+
loadUsageIndex as loadUsageIndex3,
|
|
433
|
+
memoryMatchesAnchorPaths,
|
|
434
|
+
trackReads as trackReads2
|
|
435
|
+
} from "@hiveai/core";
|
|
436
|
+
import { z as z8 } from "zod";
|
|
437
|
+
var MemForFilesInputSchema = {
|
|
438
|
+
files: z8.array(z8.string()).min(1).describe("Project-relative file paths the agent is currently working on"),
|
|
439
|
+
include_module_contexts: z8.boolean().default(true).describe("Inline the matching .ai/modules/<name>/context.md contents"),
|
|
440
|
+
track: z8.boolean().default(true).describe("Increment read_count on returned memories")
|
|
441
|
+
};
|
|
442
|
+
async function memForFiles(input, ctx) {
|
|
443
|
+
const inferred = inferModulesFromPaths(input.files);
|
|
444
|
+
if (!existsSync8(ctx.paths.memoriesDir)) {
|
|
445
|
+
return {
|
|
446
|
+
inferred_modules: inferred,
|
|
447
|
+
by_anchor: [],
|
|
448
|
+
by_module: [],
|
|
449
|
+
by_domain: [],
|
|
450
|
+
module_contexts: await loadModuleContexts(ctx, inferred, input.include_module_contexts)
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const all = await loadMemoriesFromDir5(ctx.paths.memoriesDir);
|
|
454
|
+
const usage = await loadUsageIndex3(ctx.paths);
|
|
455
|
+
const seen = /* @__PURE__ */ new Set();
|
|
456
|
+
const byAnchor = [];
|
|
457
|
+
const byModule = [];
|
|
458
|
+
const byDomain = [];
|
|
459
|
+
for (const loaded of all) {
|
|
460
|
+
if (memoryMatchesAnchorPaths(loaded.memory, input.files)) {
|
|
461
|
+
byAnchor.push(toMatch(loaded, "anchor_overlap", usage));
|
|
462
|
+
seen.add(loaded.memory.frontmatter.id);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (const loaded of all) {
|
|
466
|
+
if (seen.has(loaded.memory.frontmatter.id)) continue;
|
|
467
|
+
const mod = loaded.memory.frontmatter.module;
|
|
468
|
+
if (mod && inferred.includes(mod)) {
|
|
469
|
+
byModule.push(toMatch(loaded, "module", usage));
|
|
470
|
+
seen.add(loaded.memory.frontmatter.id);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const loaded of all) {
|
|
474
|
+
if (seen.has(loaded.memory.frontmatter.id)) continue;
|
|
475
|
+
const domain = loaded.memory.frontmatter.domain;
|
|
476
|
+
if (domain && inferred.includes(domain)) {
|
|
477
|
+
byDomain.push(toMatch(loaded, "domain", usage));
|
|
478
|
+
seen.add(loaded.memory.frontmatter.id);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (input.track) {
|
|
482
|
+
await trackReads2(ctx.paths, [...seen]);
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
inferred_modules: inferred,
|
|
486
|
+
by_anchor: byAnchor,
|
|
487
|
+
by_module: byModule,
|
|
488
|
+
by_domain: byDomain,
|
|
489
|
+
module_contexts: await loadModuleContexts(ctx, inferred, input.include_module_contexts)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function toMatch(loaded, reason, usage) {
|
|
493
|
+
const fm = loaded.memory.frontmatter;
|
|
494
|
+
const u = getUsage2(usage, fm.id);
|
|
495
|
+
return {
|
|
496
|
+
id: fm.id,
|
|
497
|
+
scope: fm.scope,
|
|
498
|
+
type: fm.type,
|
|
499
|
+
...fm.module ? { module: fm.module } : {},
|
|
500
|
+
tags: fm.tags,
|
|
501
|
+
status: fm.status,
|
|
502
|
+
confidence: deriveConfidence2(fm, u),
|
|
503
|
+
read_count: u.read_count,
|
|
504
|
+
reason,
|
|
505
|
+
file_path: loaded.filePath,
|
|
506
|
+
body: loaded.memory.body
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
async function loadModuleContexts(ctx, modules, enabled) {
|
|
510
|
+
if (!enabled || modules.length === 0) return [];
|
|
511
|
+
if (!existsSync8(ctx.paths.modulesContextDir)) return [];
|
|
512
|
+
const available = new Set(
|
|
513
|
+
(await readdir2(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
514
|
+
);
|
|
515
|
+
const out = [];
|
|
516
|
+
for (const m of modules) {
|
|
517
|
+
if (!available.has(m)) continue;
|
|
518
|
+
const file = path4.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
519
|
+
if (existsSync8(file)) {
|
|
520
|
+
out.push({ name: m, content: await readFile2(file, "utf8") });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return out;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/tools/mem-get.ts
|
|
527
|
+
import { existsSync as existsSync9 } from "fs";
|
|
528
|
+
import {
|
|
529
|
+
deriveConfidence as deriveConfidence3,
|
|
530
|
+
getUsage as getUsage3,
|
|
531
|
+
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
532
|
+
loadUsageIndex as loadUsageIndex4
|
|
533
|
+
} from "@hiveai/core";
|
|
534
|
+
import { z as z9 } from "zod";
|
|
535
|
+
var MemGetInputSchema = {
|
|
536
|
+
id: z9.string().min(1).describe("Memory id to fetch")
|
|
537
|
+
};
|
|
538
|
+
async function memGet(input, ctx) {
|
|
539
|
+
if (!existsSync9(ctx.paths.memoriesDir)) {
|
|
540
|
+
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
541
|
+
}
|
|
542
|
+
const all = await loadMemoriesFromDir6(ctx.paths.memoriesDir);
|
|
543
|
+
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
544
|
+
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
545
|
+
const fm = found.memory.frontmatter;
|
|
546
|
+
const u = getUsage3(await loadUsageIndex4(ctx.paths), fm.id);
|
|
547
|
+
return {
|
|
548
|
+
id: fm.id,
|
|
549
|
+
scope: fm.scope,
|
|
550
|
+
type: fm.type,
|
|
551
|
+
...fm.module ? { module: fm.module } : {},
|
|
552
|
+
tags: fm.tags,
|
|
553
|
+
status: fm.status,
|
|
554
|
+
confidence: deriveConfidence3(fm, u),
|
|
555
|
+
read_count: u.read_count,
|
|
556
|
+
rejected_count: u.rejected_count,
|
|
557
|
+
created_at: fm.created_at,
|
|
558
|
+
verified_at: fm.verified_at,
|
|
559
|
+
stale_reason: fm.stale_reason,
|
|
560
|
+
anchor: {
|
|
561
|
+
...fm.anchor.commit ? { commit: fm.anchor.commit } : {},
|
|
562
|
+
paths: fm.anchor.paths,
|
|
563
|
+
symbols: fm.anchor.symbols
|
|
564
|
+
},
|
|
565
|
+
body: found.memory.body,
|
|
566
|
+
file_path: found.filePath
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/tools/mem-delete.ts
|
|
571
|
+
import { existsSync as existsSync10 } from "fs";
|
|
572
|
+
import { unlink } from "fs/promises";
|
|
573
|
+
import {
|
|
574
|
+
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
575
|
+
loadUsageIndex as loadUsageIndex5,
|
|
576
|
+
saveUsageIndex as saveUsageIndex2
|
|
577
|
+
} from "@hiveai/core";
|
|
578
|
+
import { z as z10 } from "zod";
|
|
579
|
+
var MemDeleteInputSchema = {
|
|
580
|
+
id: z10.string().min(1).describe("Memory id to delete"),
|
|
581
|
+
keep_usage: z10.boolean().default(false).describe("Keep the usage.json entry instead of removing it alongside the file")
|
|
582
|
+
};
|
|
583
|
+
async function memDelete(input, ctx) {
|
|
584
|
+
if (!existsSync10(ctx.paths.memoriesDir)) {
|
|
585
|
+
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
586
|
+
}
|
|
587
|
+
const all = await loadMemoriesFromDir7(ctx.paths.memoriesDir);
|
|
588
|
+
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
589
|
+
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
590
|
+
await unlink(found.filePath);
|
|
591
|
+
let usageRemoved = false;
|
|
592
|
+
if (!input.keep_usage) {
|
|
593
|
+
const idx = await loadUsageIndex5(ctx.paths);
|
|
594
|
+
if (idx.by_id[input.id]) {
|
|
595
|
+
delete idx.by_id[input.id];
|
|
596
|
+
await saveUsageIndex2(ctx.paths, idx);
|
|
597
|
+
usageRemoved = true;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return { id: input.id, deleted_file: found.filePath, usage_removed: usageRemoved };
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// src/tools/mem-pending.ts
|
|
604
|
+
import { existsSync as existsSync11 } from "fs";
|
|
605
|
+
import {
|
|
606
|
+
getUsage as getUsage4,
|
|
607
|
+
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
608
|
+
loadUsageIndex as loadUsageIndex6
|
|
609
|
+
} from "@hiveai/core";
|
|
610
|
+
import { z as z11 } from "zod";
|
|
611
|
+
var MemPendingInputSchema = {
|
|
612
|
+
scope: z11.enum(["personal", "team", "module"]).optional()
|
|
613
|
+
};
|
|
614
|
+
async function memPending(input, ctx) {
|
|
615
|
+
if (!existsSync11(ctx.paths.memoriesDir)) return { pending: [] };
|
|
616
|
+
const all = await loadMemoriesFromDir8(ctx.paths.memoriesDir);
|
|
617
|
+
const usage = await loadUsageIndex6(ctx.paths);
|
|
618
|
+
const now = Date.now();
|
|
619
|
+
const proposed = all.filter(({ memory }) => {
|
|
620
|
+
if (memory.frontmatter.status !== "proposed") return false;
|
|
621
|
+
if (input.scope && memory.frontmatter.scope !== input.scope) return false;
|
|
622
|
+
return true;
|
|
623
|
+
});
|
|
624
|
+
proposed.sort(
|
|
625
|
+
(a, b) => getUsage4(usage, b.memory.frontmatter.id).read_count - getUsage4(usage, a.memory.frontmatter.id).read_count
|
|
626
|
+
);
|
|
627
|
+
return {
|
|
628
|
+
pending: proposed.map(({ memory, filePath }) => {
|
|
629
|
+
const fm = memory.frontmatter;
|
|
630
|
+
const u = getUsage4(usage, fm.id);
|
|
631
|
+
return {
|
|
632
|
+
id: fm.id,
|
|
633
|
+
scope: fm.scope,
|
|
634
|
+
type: fm.type,
|
|
635
|
+
...fm.module ? { module: fm.module } : {},
|
|
636
|
+
tags: fm.tags,
|
|
637
|
+
age_days: Math.floor((now - new Date(fm.created_at).getTime()) / 864e5),
|
|
638
|
+
read_count: u.read_count,
|
|
639
|
+
rejected_count: u.rejected_count,
|
|
640
|
+
file_path: filePath
|
|
641
|
+
};
|
|
642
|
+
})
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/tools/mem-approve.ts
|
|
647
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
648
|
+
import { existsSync as existsSync12 } from "fs";
|
|
649
|
+
import {
|
|
650
|
+
loadMemoriesFromDir as loadMemoriesFromDir9,
|
|
651
|
+
serializeMemory as serializeMemory3
|
|
652
|
+
} from "@hiveai/core";
|
|
653
|
+
import { z as z12 } from "zod";
|
|
654
|
+
var MemApproveInputSchema = {
|
|
655
|
+
id: z12.string().min(1).describe("Memory id to approve (sets status=validated immediately)")
|
|
656
|
+
};
|
|
657
|
+
async function memApprove(input, ctx) {
|
|
658
|
+
if (!existsSync12(ctx.paths.memoriesDir)) {
|
|
659
|
+
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
660
|
+
}
|
|
661
|
+
const all = await loadMemoriesFromDir9(ctx.paths.memoriesDir);
|
|
662
|
+
const found = all.find((m) => m.memory.frontmatter.id === input.id);
|
|
663
|
+
if (!found) throw new Error(`No memory with id "${input.id}".`);
|
|
664
|
+
const previous = found.memory.frontmatter.status;
|
|
665
|
+
const next = {
|
|
666
|
+
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
667
|
+
body: found.memory.body
|
|
668
|
+
};
|
|
669
|
+
await writeFile4(found.filePath, serializeMemory3(next), "utf8");
|
|
670
|
+
return {
|
|
671
|
+
id: input.id,
|
|
672
|
+
previous_status: previous,
|
|
673
|
+
status: "validated",
|
|
674
|
+
file_path: found.filePath
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// src/prompts/bootstrap-project.ts
|
|
679
|
+
import { z as z13 } from "zod";
|
|
680
|
+
var BootstrapProjectArgsSchema = {
|
|
681
|
+
module: z13.string().optional().describe(
|
|
682
|
+
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
683
|
+
),
|
|
684
|
+
focus: z13.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
685
|
+
};
|
|
686
|
+
var ROOT_TEMPLATE = `# Project context
|
|
687
|
+
|
|
688
|
+
## Architecture
|
|
689
|
+
<one or two paragraphs on the high-level architecture>
|
|
690
|
+
|
|
691
|
+
## Key modules
|
|
692
|
+
- <module-name>: <one line on its purpose>
|
|
693
|
+
- ...
|
|
694
|
+
|
|
695
|
+
## Conventions
|
|
696
|
+
- <convention>: <why it matters here>
|
|
697
|
+
- ...
|
|
698
|
+
|
|
699
|
+
## Glossary
|
|
700
|
+
- <term>: <definition in this codebase>
|
|
701
|
+
- ...
|
|
702
|
+
|
|
703
|
+
## Gotchas
|
|
704
|
+
- <surprising behavior, hidden coupling, or known traps>
|
|
705
|
+
- ...
|
|
706
|
+
`;
|
|
707
|
+
var MODULE_TEMPLATE = `# Module context \u2014 {module}
|
|
708
|
+
|
|
709
|
+
## Purpose
|
|
710
|
+
<what this module is for>
|
|
711
|
+
|
|
712
|
+
## Public surface
|
|
713
|
+
- <exported symbol>: <one line>
|
|
714
|
+
- ...
|
|
715
|
+
|
|
716
|
+
## Internals
|
|
717
|
+
<key files / classes / functions and how they connect>
|
|
718
|
+
|
|
719
|
+
## Conventions specific to this module
|
|
720
|
+
- ...
|
|
721
|
+
|
|
722
|
+
## Gotchas
|
|
723
|
+
- ...
|
|
724
|
+
`;
|
|
725
|
+
function bootstrapProjectPrompt(args, ctx) {
|
|
726
|
+
const target = args.module ? `\`.ai/modules/${args.module}/context.md\`` : "`.ai/project-context.md`";
|
|
727
|
+
const template = args.module ? MODULE_TEMPLATE.replace("{module}", args.module) : ROOT_TEMPLATE;
|
|
728
|
+
const focusLine = args.focus ? `
|
|
729
|
+
Emphasis area for this analysis: **${args.focus}**.
|
|
730
|
+
` : "";
|
|
731
|
+
const text = `You are bootstrapping a hAIve shared project context for the team.
|
|
732
|
+
|
|
733
|
+
Project root: \`${ctx.paths.root}\`
|
|
734
|
+
Target file: ${target}
|
|
735
|
+
${focusLine}
|
|
736
|
+
## What to do
|
|
737
|
+
|
|
738
|
+
1. Explore the codebase: read the package manifests, top-level directories, build configs, and a representative sample of source files. Do not read every file \u2014 pick what gives you the highest signal per file (entry points, config, README if present, main domain models).
|
|
739
|
+
2. Synthesize a concise, high-signal context document. Prefer load-bearing facts over exhaustive enumeration. A new teammate (human or AI) should be able to read it in 5 minutes and feel oriented.
|
|
740
|
+
3. Match the structure of the template below. Keep each section short \u2014 link to files instead of repeating large code chunks.
|
|
741
|
+
4. When you are done, call the \`bootstrap_project_save\` tool with the full Markdown content. Use \`overwrite=true\` only if the file already exists and you intend to replace it.
|
|
742
|
+
|
|
743
|
+
## Template to fill
|
|
744
|
+
|
|
745
|
+
\`\`\`markdown
|
|
746
|
+
${template}\`\`\`
|
|
747
|
+
|
|
748
|
+
## Tips
|
|
749
|
+
|
|
750
|
+
- Anchor claims to file paths so future readers can verify them.
|
|
751
|
+
- Write what is true *now*, not aspirational. Note open questions explicitly.
|
|
752
|
+
- Skip sections that have nothing meaningful to say rather than padding them.
|
|
753
|
+
`;
|
|
754
|
+
return {
|
|
755
|
+
description: args.module ? `Bootstrap context for module "${args.module}"` : "Bootstrap the root project context",
|
|
756
|
+
messages: [
|
|
757
|
+
{
|
|
758
|
+
role: "user",
|
|
759
|
+
content: { type: "text", text }
|
|
760
|
+
}
|
|
761
|
+
]
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/server.ts
|
|
766
|
+
var SERVER_NAME = "haive";
|
|
767
|
+
var SERVER_VERSION = "0.1.0";
|
|
768
|
+
function jsonResult(data) {
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: "text",
|
|
773
|
+
text: JSON.stringify(data, null, 2)
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function createHaiveServer(options = {}) {
|
|
779
|
+
const context = createContext(options);
|
|
780
|
+
const server = new McpServer(
|
|
781
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
782
|
+
{ capabilities: { tools: {}, prompts: {} } }
|
|
783
|
+
);
|
|
784
|
+
server.tool(
|
|
785
|
+
"mem_save",
|
|
786
|
+
"Save a new memory (default scope=personal). Use scope=team for shared memories.",
|
|
787
|
+
MemSaveInputSchema,
|
|
788
|
+
async (input) => jsonResult(await memSave(input, context))
|
|
789
|
+
);
|
|
790
|
+
server.tool(
|
|
791
|
+
"mem_search",
|
|
792
|
+
"Search memories by substring across id, tags, and body. Optional filters by scope/type/module.",
|
|
793
|
+
MemSearchInputSchema,
|
|
794
|
+
async (input) => jsonResult(await memSearch(input, context))
|
|
795
|
+
);
|
|
796
|
+
server.tool(
|
|
797
|
+
"mem_list",
|
|
798
|
+
"List memories with optional filters by scope/type/module/tag.",
|
|
799
|
+
MemListInputSchema,
|
|
800
|
+
async (input) => jsonResult(await memList(input, context))
|
|
801
|
+
);
|
|
802
|
+
server.tool(
|
|
803
|
+
"get_project_context",
|
|
804
|
+
"Read the shared .ai/project-context.md (and optionally a module context).",
|
|
805
|
+
GetProjectContextInputSchema,
|
|
806
|
+
async (input) => jsonResult(await getProjectContext(input, context))
|
|
807
|
+
);
|
|
808
|
+
server.tool(
|
|
809
|
+
"bootstrap_project_save",
|
|
810
|
+
"Persist a project (or module) context document analyzed by the AI client.",
|
|
811
|
+
BootstrapProjectSaveInputSchema,
|
|
812
|
+
async (input) => jsonResult(await bootstrapProjectSave(input, context))
|
|
813
|
+
);
|
|
814
|
+
server.tool(
|
|
815
|
+
"mem_verify",
|
|
816
|
+
"Check anchor freshness for one or every memory; optionally write status updates back to disk.",
|
|
817
|
+
MemVerifyInputSchema,
|
|
818
|
+
async (input) => jsonResult(await memVerify(input, context))
|
|
819
|
+
);
|
|
820
|
+
server.tool(
|
|
821
|
+
"mem_reject",
|
|
822
|
+
"Record a rejection for a memory (blocks auto-promotion and lowers its trust signal).",
|
|
823
|
+
MemRejectInputSchema,
|
|
824
|
+
async (input) => jsonResult(await memReject(input, context))
|
|
825
|
+
);
|
|
826
|
+
server.tool(
|
|
827
|
+
"mem_for_files",
|
|
828
|
+
"Given the file paths the agent is currently working on, return relevant memories grouped by reason (anchor overlap, module, domain) plus any matching .ai/modules/<name>/context.md contents.",
|
|
829
|
+
MemForFilesInputSchema,
|
|
830
|
+
async (input) => jsonResult(await memForFiles(input, context))
|
|
831
|
+
);
|
|
832
|
+
server.tool(
|
|
833
|
+
"mem_get",
|
|
834
|
+
"Fetch a single memory by id, including its body, anchor, usage, and confidence.",
|
|
835
|
+
MemGetInputSchema,
|
|
836
|
+
async (input) => jsonResult(await memGet(input, context))
|
|
837
|
+
);
|
|
838
|
+
server.tool(
|
|
839
|
+
"mem_delete",
|
|
840
|
+
"Delete a memory by id (and its usage entry by default).",
|
|
841
|
+
MemDeleteInputSchema,
|
|
842
|
+
async (input) => jsonResult(await memDelete(input, context))
|
|
843
|
+
);
|
|
844
|
+
server.tool(
|
|
845
|
+
"mem_pending",
|
|
846
|
+
"List 'proposed' memories awaiting review, sorted by reads (most-read first).",
|
|
847
|
+
MemPendingInputSchema,
|
|
848
|
+
async (input) => jsonResult(await memPending(input, context))
|
|
849
|
+
);
|
|
850
|
+
server.tool(
|
|
851
|
+
"mem_approve",
|
|
852
|
+
"Mark a memory as validated immediately (explicit team review).",
|
|
853
|
+
MemApproveInputSchema,
|
|
854
|
+
async (input) => jsonResult(await memApprove(input, context))
|
|
855
|
+
);
|
|
856
|
+
server.prompt(
|
|
857
|
+
"bootstrap_project",
|
|
858
|
+
"Instructions for the AI client to analyze the project and save the context.",
|
|
859
|
+
BootstrapProjectArgsSchema,
|
|
860
|
+
(args) => bootstrapProjectPrompt(args, context)
|
|
861
|
+
);
|
|
862
|
+
return { server, context };
|
|
863
|
+
}
|
|
864
|
+
export {
|
|
865
|
+
SERVER_NAME,
|
|
866
|
+
SERVER_VERSION,
|
|
867
|
+
createHaiveServer
|
|
868
|
+
};
|
|
869
|
+
//# sourceMappingURL=server.js.map
|