claude-launchpad 0.6.1 → 0.7.1
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/README.md +54 -20
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-6ZVXZ4EF.js +101 -0
- package/dist/chunk-6ZVXZ4EF.js.map +1 -0
- package/dist/chunk-CSLWJEGD.js +25 -0
- package/dist/chunk-CSLWJEGD.js.map +1 -0
- package/dist/chunk-EBM7RBPB.js +35 -0
- package/dist/chunk-EBM7RBPB.js.map +1 -0
- package/dist/chunk-IILH26C7.js +258 -0
- package/dist/chunk-IILH26C7.js.map +1 -0
- package/dist/chunk-JE3BZ5S4.js +311 -0
- package/dist/chunk-JE3BZ5S4.js.map +1 -0
- package/dist/chunk-NAW47BYA.js +25 -0
- package/dist/chunk-NAW47BYA.js.map +1 -0
- package/dist/chunk-TALTTAMW.js +390 -0
- package/dist/chunk-TALTTAMW.js.map +1 -0
- package/dist/cli.js +348 -212
- package/dist/cli.js.map +1 -1
- package/dist/commands/memory/server.js +685 -0
- package/dist/commands/memory/server.js.map +1 -0
- package/dist/context-LNUZ4GCF.js +316 -0
- package/dist/context-LNUZ4GCF.js.map +1 -0
- package/dist/extract-NVAXO5CK.js +217 -0
- package/dist/extract-NVAXO5CK.js.map +1 -0
- package/dist/install-65P6LMUN.js +230 -0
- package/dist/install-65P6LMUN.js.map +1 -0
- package/dist/stats-FYAK7KZW.js +73 -0
- package/dist/stats-FYAK7KZW.js.map +1 -0
- package/dist/tui-R25NTQ4K.js +1100 -0
- package/dist/tui-R25NTQ4K.js.map +1 -0
- package/package.json +19 -4
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
RetrievalService,
|
|
4
|
+
getGitContext
|
|
5
|
+
} from "../../chunk-JE3BZ5S4.js";
|
|
6
|
+
import {
|
|
7
|
+
detectProject
|
|
8
|
+
} from "../../chunk-NAW47BYA.js";
|
|
9
|
+
import {
|
|
10
|
+
MemoryRepo,
|
|
11
|
+
RelationRepo,
|
|
12
|
+
SearchRepo
|
|
13
|
+
} from "../../chunk-TALTTAMW.js";
|
|
14
|
+
import {
|
|
15
|
+
closeDatabase,
|
|
16
|
+
createDatabase,
|
|
17
|
+
loadConfig,
|
|
18
|
+
migrate,
|
|
19
|
+
resolveDataDir
|
|
20
|
+
} from "../../chunk-IILH26C7.js";
|
|
21
|
+
import "../../chunk-2H7UOFLK.js";
|
|
22
|
+
|
|
23
|
+
// src/commands/memory/server.ts
|
|
24
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
|
|
27
|
+
// src/commands/memory/tools/store.ts
|
|
28
|
+
import { z as z2 } from "zod";
|
|
29
|
+
|
|
30
|
+
// src/commands/memory/types.ts
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
var MEMORY_TYPES = ["working", "episodic", "semantic", "procedural", "pattern"];
|
|
33
|
+
var MEMORY_SOURCES = ["manual", "session_end", "consolidation", "hook", "import"];
|
|
34
|
+
var RELATION_TYPES = [
|
|
35
|
+
"relates_to",
|
|
36
|
+
"depends_on",
|
|
37
|
+
"contradicts",
|
|
38
|
+
"extends",
|
|
39
|
+
"implements",
|
|
40
|
+
"derived_from"
|
|
41
|
+
];
|
|
42
|
+
var StoreInputSchema = z.object({
|
|
43
|
+
type: z.enum(MEMORY_TYPES),
|
|
44
|
+
content: z.string().min(1).max(1e4),
|
|
45
|
+
title: z.string().max(200).optional(),
|
|
46
|
+
tags: z.array(z.string()).max(20).default([]),
|
|
47
|
+
importance: z.number().min(0).max(1).default(0.5),
|
|
48
|
+
context: z.string().optional(),
|
|
49
|
+
source: z.enum(MEMORY_SOURCES).default("manual"),
|
|
50
|
+
project: z.string().max(200).optional()
|
|
51
|
+
});
|
|
52
|
+
var SearchInputSchema = z.object({
|
|
53
|
+
query: z.string().min(1).max(500),
|
|
54
|
+
id: z.string().optional(),
|
|
55
|
+
type: z.enum(MEMORY_TYPES).optional(),
|
|
56
|
+
tags: z.array(z.string()).max(10).optional(),
|
|
57
|
+
limit: z.number().int().min(1).max(50).default(10),
|
|
58
|
+
min_importance: z.number().min(0).max(1).default(0),
|
|
59
|
+
project: z.string().max(200).optional()
|
|
60
|
+
});
|
|
61
|
+
var ForgetInputSchema = z.object({
|
|
62
|
+
id: z.string(),
|
|
63
|
+
hard_delete: z.boolean().default(false)
|
|
64
|
+
});
|
|
65
|
+
var RelateInputSchema = z.object({
|
|
66
|
+
source_id: z.string(),
|
|
67
|
+
target_id: z.string(),
|
|
68
|
+
relation_type: z.enum(RELATION_TYPES)
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// src/commands/memory/utils/content-validation.ts
|
|
72
|
+
var SOFT_LENGTH_LIMIT = 1500;
|
|
73
|
+
var HARD_LENGTH_LIMIT = 5e3;
|
|
74
|
+
var CODE_RATIO_THRESHOLD = 0.5;
|
|
75
|
+
function validateMemoryContent(content) {
|
|
76
|
+
const warnings = [];
|
|
77
|
+
if (isGitLog(content)) {
|
|
78
|
+
return { valid: false, reason: "Content looks like raw git log output. Use git log directly \u2014 don't store it as memory.", warnings: [] };
|
|
79
|
+
}
|
|
80
|
+
if (isCodeHeavy(content)) {
|
|
81
|
+
return { valid: false, reason: "Content is >50% code blocks. Code belongs in files, not memory. Store the insight or decision instead.", warnings: [] };
|
|
82
|
+
}
|
|
83
|
+
if (content.length > HARD_LENGTH_LIMIT) {
|
|
84
|
+
return { valid: false, reason: `Content is ${content.length} chars (limit: ${HARD_LENGTH_LIMIT}). Break it into smaller, atomic memories.`, warnings: [] };
|
|
85
|
+
}
|
|
86
|
+
if (content.length > SOFT_LENGTH_LIMIT) {
|
|
87
|
+
warnings.push(`Content is ${content.length} chars. Shorter memories (<${SOFT_LENGTH_LIMIT} chars) are easier to retrieve and less likely to decay.`);
|
|
88
|
+
}
|
|
89
|
+
return { valid: true, warnings };
|
|
90
|
+
}
|
|
91
|
+
function isCodeHeavy(content) {
|
|
92
|
+
const fencedBlockPattern = /```[\s\S]*?```/g;
|
|
93
|
+
let codeChars = 0;
|
|
94
|
+
let match;
|
|
95
|
+
while ((match = fencedBlockPattern.exec(content)) !== null) {
|
|
96
|
+
codeChars += match[0].length;
|
|
97
|
+
}
|
|
98
|
+
return content.length > 0 && codeChars / content.length > CODE_RATIO_THRESHOLD;
|
|
99
|
+
}
|
|
100
|
+
function isGitLog(content) {
|
|
101
|
+
const lines = content.split("\n");
|
|
102
|
+
let gitLogLines = 0;
|
|
103
|
+
for (const line of lines) {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
if (/^commit\s+[0-9a-f]{7,40}$/i.test(trimmed)) gitLogLines++;
|
|
106
|
+
else if (/^Author:\s+.+/i.test(trimmed)) gitLogLines++;
|
|
107
|
+
else if (/^Date:\s+.+/i.test(trimmed)) gitLogLines++;
|
|
108
|
+
else if (/^[0-9a-f]{7,12}\s+\S+/i.test(trimmed) && trimmed.length < 200) gitLogLines++;
|
|
109
|
+
}
|
|
110
|
+
const nonEmptyLines = lines.filter((l) => l.trim().length > 0).length;
|
|
111
|
+
return gitLogLines >= 3 && nonEmptyLines > 0 && gitLogLines / nonEmptyLines > 0.3;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/commands/memory/utils/contradiction.ts
|
|
115
|
+
var NEGATION_PATTERNS = [
|
|
116
|
+
/\bnot\b/i,
|
|
117
|
+
/\bno longer\b/i,
|
|
118
|
+
/\binstead of\b/i,
|
|
119
|
+
/\breplaced\b/i,
|
|
120
|
+
/\bremoved\b/i,
|
|
121
|
+
/\bdon'?t\b/i,
|
|
122
|
+
/\bwon'?t\b/i,
|
|
123
|
+
/\bshouldn'?t\b/i,
|
|
124
|
+
/\bdeprecated\b/i,
|
|
125
|
+
/\bdisabled\b/i,
|
|
126
|
+
/\bstopped\b/i,
|
|
127
|
+
/\bavoid\b/i,
|
|
128
|
+
/\bnever\b/i,
|
|
129
|
+
/\bwithout\b/i
|
|
130
|
+
];
|
|
131
|
+
var KEYWORD_OVERLAP_THRESHOLD = 0.4;
|
|
132
|
+
var MIN_KEYWORD_LENGTH = 3;
|
|
133
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
134
|
+
"the",
|
|
135
|
+
"and",
|
|
136
|
+
"for",
|
|
137
|
+
"are",
|
|
138
|
+
"but",
|
|
139
|
+
"not",
|
|
140
|
+
"you",
|
|
141
|
+
"all",
|
|
142
|
+
"can",
|
|
143
|
+
"had",
|
|
144
|
+
"her",
|
|
145
|
+
"was",
|
|
146
|
+
"one",
|
|
147
|
+
"our",
|
|
148
|
+
"out",
|
|
149
|
+
"has",
|
|
150
|
+
"have",
|
|
151
|
+
"been",
|
|
152
|
+
"from",
|
|
153
|
+
"that",
|
|
154
|
+
"this",
|
|
155
|
+
"with",
|
|
156
|
+
"they",
|
|
157
|
+
"will",
|
|
158
|
+
"each",
|
|
159
|
+
"make",
|
|
160
|
+
"like",
|
|
161
|
+
"than",
|
|
162
|
+
"them",
|
|
163
|
+
"then",
|
|
164
|
+
"what",
|
|
165
|
+
"when",
|
|
166
|
+
"into",
|
|
167
|
+
"more",
|
|
168
|
+
"some",
|
|
169
|
+
"such",
|
|
170
|
+
"also",
|
|
171
|
+
"use",
|
|
172
|
+
"used",
|
|
173
|
+
"using",
|
|
174
|
+
"should",
|
|
175
|
+
"would",
|
|
176
|
+
"could",
|
|
177
|
+
"about",
|
|
178
|
+
"which",
|
|
179
|
+
"their",
|
|
180
|
+
"there",
|
|
181
|
+
"these",
|
|
182
|
+
"those",
|
|
183
|
+
"does",
|
|
184
|
+
"done",
|
|
185
|
+
"just",
|
|
186
|
+
"very"
|
|
187
|
+
]);
|
|
188
|
+
function extractKeywords(text) {
|
|
189
|
+
const words = text.toLowerCase().match(/[a-z][a-z0-9_-]+/g) ?? [];
|
|
190
|
+
return new Set(
|
|
191
|
+
words.filter((w) => w.length >= MIN_KEYWORD_LENGTH && !STOP_WORDS.has(w))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
function checkContradiction(newContent, existingContent) {
|
|
195
|
+
const newKeywords = extractKeywords(newContent);
|
|
196
|
+
const existingKeywords = extractKeywords(existingContent);
|
|
197
|
+
if (newKeywords.size === 0 || existingKeywords.size === 0) return false;
|
|
198
|
+
let overlap = 0;
|
|
199
|
+
for (const kw of newKeywords) {
|
|
200
|
+
if (existingKeywords.has(kw)) overlap++;
|
|
201
|
+
}
|
|
202
|
+
const smallerSize = Math.min(newKeywords.size, existingKeywords.size);
|
|
203
|
+
const overlapRatio = overlap / smallerSize;
|
|
204
|
+
if (overlapRatio < KEYWORD_OVERLAP_THRESHOLD) return false;
|
|
205
|
+
return NEGATION_PATTERNS.some((pattern) => pattern.test(newContent));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/commands/memory/tools/store.ts
|
|
209
|
+
var inputSchema = {
|
|
210
|
+
type: z2.enum(MEMORY_TYPES).describe("Memory type: working, episodic, semantic, procedural, or pattern"),
|
|
211
|
+
content: z2.string().min(1).max(1e4).describe("The memory content to store"),
|
|
212
|
+
title: z2.string().max(200).optional().describe("Short title for the memory"),
|
|
213
|
+
tags: z2.array(z2.string()).max(20).default([]).describe("Tags for categorization. Suggested: #bug, #decision, #gotcha, #howto, #pattern"),
|
|
214
|
+
importance: z2.number().min(0).max(1).default(0.5).describe("0-0.3 ephemeral, 0.3-0.6 reference, 0.6-0.8 important, 0.8-1.0 critical"),
|
|
215
|
+
context: z2.string().optional().describe('JSON: {"files": [...], "branch": "...", "intent": "..."}. Auto-detected from git if omitted.'),
|
|
216
|
+
source: z2.enum(MEMORY_SOURCES).default("manual").describe("How this memory was created"),
|
|
217
|
+
project: z2.string().max(200).optional().describe("Project scope (auto-detected from CWD if omitted)")
|
|
218
|
+
};
|
|
219
|
+
function registerStore(server, deps) {
|
|
220
|
+
server.registerTool(
|
|
221
|
+
"memory_store",
|
|
222
|
+
{
|
|
223
|
+
description: "Store a new memory. Use memory_search first to check for duplicates. Prefer updating existing semantic/procedural memories over creating new ones. Types: episodic (events), semantic (facts), procedural (how-to), pattern (recurring), working (temporary). Importance: 0-0.3 ephemeral, 0.3-0.6 reference, 0.6-0.8 important, 0.8-1.0 critical. Suggested tags: #bug, #decision, #gotcha, #howto, #pattern, #architecture. Context is auto-detected from git (branch, recent files) if omitted.",
|
|
224
|
+
inputSchema,
|
|
225
|
+
annotations: { idempotentHint: false }
|
|
226
|
+
},
|
|
227
|
+
async (args) => {
|
|
228
|
+
const validation = validateMemoryContent(args.content);
|
|
229
|
+
if (!validation.valid) {
|
|
230
|
+
return {
|
|
231
|
+
content: [{
|
|
232
|
+
type: "text",
|
|
233
|
+
text: `Rejected: ${validation.reason}`
|
|
234
|
+
}],
|
|
235
|
+
isError: true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const context = args.context ?? JSON.stringify(getGitContext());
|
|
239
|
+
const project = args.project ?? deps.project ?? void 0;
|
|
240
|
+
const contradictions = [];
|
|
241
|
+
try {
|
|
242
|
+
const existing = await deps.retrievalService.search({
|
|
243
|
+
query: args.content.slice(0, 200),
|
|
244
|
+
limit: 3,
|
|
245
|
+
min_importance: 0,
|
|
246
|
+
project
|
|
247
|
+
});
|
|
248
|
+
for (const result of existing) {
|
|
249
|
+
if (result.score > 0.6 && checkContradiction(args.content, result.memory.content)) {
|
|
250
|
+
contradictions.push({ id: result.memory.id, title: result.memory.title });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
}
|
|
255
|
+
const memory = deps.memoryRepo.create(
|
|
256
|
+
{
|
|
257
|
+
type: args.type,
|
|
258
|
+
content: args.content,
|
|
259
|
+
title: args.title,
|
|
260
|
+
tags: args.tags,
|
|
261
|
+
importance: args.importance,
|
|
262
|
+
context,
|
|
263
|
+
source: args.source,
|
|
264
|
+
project
|
|
265
|
+
},
|
|
266
|
+
null
|
|
267
|
+
);
|
|
268
|
+
for (const c of contradictions) {
|
|
269
|
+
try {
|
|
270
|
+
deps.relationRepo.create(memory.id, c.id, "contradicts");
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const parts = [
|
|
275
|
+
`Stored memory ${memory.id} (type: ${memory.type}, importance: ${memory.importance})`
|
|
276
|
+
];
|
|
277
|
+
for (const warning of validation.warnings) {
|
|
278
|
+
parts.push(`Warning: ${warning}`);
|
|
279
|
+
}
|
|
280
|
+
for (const c of contradictions) {
|
|
281
|
+
parts.push(`Warning: potential contradiction with memory ${c.id}${c.title ? ` ("${c.title}")` : ""}. Linked with 'contradicts' relation.`);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
content: [{
|
|
285
|
+
type: "text",
|
|
286
|
+
text: parts.join("\n")
|
|
287
|
+
}]
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/commands/memory/tools/search.ts
|
|
294
|
+
import { z as z3 } from "zod";
|
|
295
|
+
var inputSchema2 = {
|
|
296
|
+
query: z3.string().min(1).max(500).describe("Search query (natural language or keywords)"),
|
|
297
|
+
id: z3.string().optional().describe("Direct lookup by memory ID (bypasses search)"),
|
|
298
|
+
type: z3.enum(MEMORY_TYPES).optional().describe("Filter by memory type"),
|
|
299
|
+
tags: z3.array(z3.string()).max(10).optional().describe("Filter to memories containing ALL of these tags"),
|
|
300
|
+
limit: z3.number().int().min(1).max(50).default(10).describe("Maximum results to return"),
|
|
301
|
+
min_importance: z3.number().min(0).max(1).default(0).describe("Minimum importance threshold")
|
|
302
|
+
};
|
|
303
|
+
function registerSearch(server, deps) {
|
|
304
|
+
server.registerTool(
|
|
305
|
+
"memory_search",
|
|
306
|
+
{
|
|
307
|
+
description: "Search memories by keyword match, context relevance, and relation graph. Returns up to `limit` results ranked by composite score (text match, importance, recency, access frequency, git context). Related memories are automatically surfaced via 1-hop graph expansion. Pass `id` for direct lookup. Use `type` and `tags` to filter.",
|
|
308
|
+
inputSchema: inputSchema2,
|
|
309
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
310
|
+
},
|
|
311
|
+
async (args) => {
|
|
312
|
+
const results = await deps.retrievalService.search({
|
|
313
|
+
query: args.query,
|
|
314
|
+
id: args.id,
|
|
315
|
+
type: args.type,
|
|
316
|
+
tags: args.tags,
|
|
317
|
+
limit: args.limit,
|
|
318
|
+
min_importance: args.min_importance,
|
|
319
|
+
project: deps.project ?? void 0
|
|
320
|
+
});
|
|
321
|
+
if (results.length === 0) {
|
|
322
|
+
return {
|
|
323
|
+
content: [{ type: "text", text: "No memories found matching your query." }]
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const formatted = results.map((r, i) => ({
|
|
327
|
+
rank: i + 1,
|
|
328
|
+
id: r.memory.id,
|
|
329
|
+
type: r.memory.type,
|
|
330
|
+
title: r.memory.title,
|
|
331
|
+
content: r.memory.content.slice(0, 500),
|
|
332
|
+
score: Math.round(r.score * 100) / 100,
|
|
333
|
+
explanation: r.explanation,
|
|
334
|
+
importance: r.memory.importance,
|
|
335
|
+
tags: r.memory.tags,
|
|
336
|
+
accessCount: r.memory.accessCount,
|
|
337
|
+
createdAt: r.memory.createdAt
|
|
338
|
+
}));
|
|
339
|
+
return {
|
|
340
|
+
content: [{
|
|
341
|
+
type: "text",
|
|
342
|
+
text: JSON.stringify(formatted, null, 2)
|
|
343
|
+
}]
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/commands/memory/tools/forget.ts
|
|
350
|
+
import { z as z4 } from "zod";
|
|
351
|
+
|
|
352
|
+
// src/commands/memory/utils/errors.ts
|
|
353
|
+
function formatError(err) {
|
|
354
|
+
return `${err.what}
|
|
355
|
+
Why: ${err.why}
|
|
356
|
+
Fix: ${err.fix}`;
|
|
357
|
+
}
|
|
358
|
+
function formatMcpError(err) {
|
|
359
|
+
return {
|
|
360
|
+
isError: true,
|
|
361
|
+
content: [{ type: "text", text: formatError(err) }]
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
function memoryNotFound(id) {
|
|
365
|
+
return {
|
|
366
|
+
what: `Memory "${id}" not found.`,
|
|
367
|
+
why: "The ID may be incorrect, or the memory was deleted or pruned.",
|
|
368
|
+
fix: "Use memory_search to find valid IDs."
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/commands/memory/tools/forget.ts
|
|
373
|
+
var inputSchema3 = {
|
|
374
|
+
id: z4.string().describe("ID of the memory to forget"),
|
|
375
|
+
hard_delete: z4.boolean().default(false).describe("true = permanent delete, false = set importance to 0 (soft delete)")
|
|
376
|
+
};
|
|
377
|
+
function registerForget(server, deps) {
|
|
378
|
+
server.registerTool(
|
|
379
|
+
"memory_forget",
|
|
380
|
+
{
|
|
381
|
+
description: "Forget a memory. By default, soft-deletes (sets importance to 0, allowing natural decay). Pass hard_delete=true to permanently remove. Use memory_search to find the ID first.",
|
|
382
|
+
inputSchema: inputSchema3,
|
|
383
|
+
annotations: { destructiveHint: true, idempotentHint: true }
|
|
384
|
+
},
|
|
385
|
+
async (args) => {
|
|
386
|
+
const memory = deps.memoryRepo.getById(args.id);
|
|
387
|
+
if (!memory) {
|
|
388
|
+
return formatMcpError(memoryNotFound(args.id));
|
|
389
|
+
}
|
|
390
|
+
if (deps.project && memory.project !== null && memory.project !== deps.project) {
|
|
391
|
+
return formatMcpError({
|
|
392
|
+
what: `Memory ${args.id} belongs to project "${memory.project}".`,
|
|
393
|
+
why: `Current project is "${deps.project}". Cross-project deletion is not allowed.`,
|
|
394
|
+
fix: "Switch to the correct project or use a global context."
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
if (args.hard_delete) {
|
|
398
|
+
deps.memoryRepo.hardDelete(args.id);
|
|
399
|
+
return {
|
|
400
|
+
content: [{
|
|
401
|
+
type: "text",
|
|
402
|
+
text: `Permanently deleted memory ${args.id} ("${memory.title ?? memory.content.slice(0, 50)}")`
|
|
403
|
+
}]
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
deps.memoryRepo.softDelete(args.id);
|
|
407
|
+
return {
|
|
408
|
+
content: [{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: `Soft-deleted memory ${args.id} (importance set to 0, will decay naturally)`
|
|
411
|
+
}]
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/commands/memory/tools/relate.ts
|
|
418
|
+
import { z as z5 } from "zod";
|
|
419
|
+
var inputSchema4 = {
|
|
420
|
+
source_id: z5.string().describe("ID of the source memory"),
|
|
421
|
+
target_id: z5.string().describe("ID of the target memory"),
|
|
422
|
+
relation_type: z5.enum(RELATION_TYPES).describe(
|
|
423
|
+
"Type of relation: relates_to, depends_on, contradicts, extends, implements, derived_from"
|
|
424
|
+
)
|
|
425
|
+
};
|
|
426
|
+
function registerRelate(server, deps) {
|
|
427
|
+
server.registerTool(
|
|
428
|
+
"memory_relate",
|
|
429
|
+
{
|
|
430
|
+
description: "Create a typed relation between two memories. Relations affect retrieval ranking (connected memories decay slower). Types: relates_to, depends_on, contradicts, extends, implements, derived_from.",
|
|
431
|
+
inputSchema: inputSchema4,
|
|
432
|
+
annotations: { idempotentHint: true }
|
|
433
|
+
},
|
|
434
|
+
async (args) => {
|
|
435
|
+
if (args.source_id === args.target_id) {
|
|
436
|
+
return formatMcpError({
|
|
437
|
+
what: "Cannot create self-relation.",
|
|
438
|
+
why: "source_id and target_id are the same memory.",
|
|
439
|
+
fix: "Provide two different memory IDs."
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
const source = deps.memoryRepo.getById(args.source_id);
|
|
443
|
+
if (!source) {
|
|
444
|
+
return formatMcpError(memoryNotFound(args.source_id));
|
|
445
|
+
}
|
|
446
|
+
const target = deps.memoryRepo.getById(args.target_id);
|
|
447
|
+
if (!target) {
|
|
448
|
+
return formatMcpError(memoryNotFound(args.target_id));
|
|
449
|
+
}
|
|
450
|
+
if (deps.project) {
|
|
451
|
+
for (const mem of [source, target]) {
|
|
452
|
+
if (mem.project !== null && mem.project !== deps.project) {
|
|
453
|
+
return formatMcpError({
|
|
454
|
+
what: `Memory ${mem.id} belongs to project "${mem.project}".`,
|
|
455
|
+
why: `Current project is "${deps.project}". Cross-project relations are not allowed.`,
|
|
456
|
+
fix: "Both memories must belong to the same project."
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const created = deps.relationRepo.create(args.source_id, args.target_id, args.relation_type);
|
|
462
|
+
if (!created) {
|
|
463
|
+
return {
|
|
464
|
+
content: [{
|
|
465
|
+
type: "text",
|
|
466
|
+
text: `Relation already exists: ${args.source_id} --[${args.relation_type}]--> ${args.target_id}`
|
|
467
|
+
}]
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
content: [{
|
|
472
|
+
type: "text",
|
|
473
|
+
text: `Created relation: "${source.title ?? source.content.slice(0, 40)}" --[${args.relation_type}]--> "${target.title ?? target.content.slice(0, 40)}"`
|
|
474
|
+
}]
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// src/commands/memory/tools/stats.ts
|
|
481
|
+
import { statSync } from "fs";
|
|
482
|
+
import { join } from "path";
|
|
483
|
+
function registerStats(server, deps) {
|
|
484
|
+
server.registerTool(
|
|
485
|
+
"memory_stats",
|
|
486
|
+
{
|
|
487
|
+
description: "Show memory dashboard: total count, breakdown by type, storage size, and most-injected memories. No arguments needed.",
|
|
488
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
489
|
+
},
|
|
490
|
+
async () => {
|
|
491
|
+
const countByType = deps.memoryRepo.countByType();
|
|
492
|
+
const total = deps.memoryRepo.count();
|
|
493
|
+
const topInjected = deps.memoryRepo.topInjected(5);
|
|
494
|
+
const totalRelations = deps.relationRepo.count();
|
|
495
|
+
let dbSizeBytes = 0;
|
|
496
|
+
try {
|
|
497
|
+
const dbPath = join(deps.dataDir, "memory.db");
|
|
498
|
+
dbSizeBytes = statSync(dbPath).size;
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
const { oldest, newest } = deps.memoryRepo.dateRange();
|
|
502
|
+
const stats = {
|
|
503
|
+
totalMemories: total,
|
|
504
|
+
byType: Object.fromEntries(
|
|
505
|
+
MEMORY_TYPES.map((t) => [t, countByType[t] ?? 0])
|
|
506
|
+
),
|
|
507
|
+
totalRelations,
|
|
508
|
+
dbSizeBytes,
|
|
509
|
+
oldestMemory: oldest,
|
|
510
|
+
newestMemory: newest,
|
|
511
|
+
topInjected
|
|
512
|
+
};
|
|
513
|
+
return {
|
|
514
|
+
content: [{
|
|
515
|
+
type: "text",
|
|
516
|
+
text: JSON.stringify(stats, null, 2)
|
|
517
|
+
}]
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/commands/memory/tools/recent.ts
|
|
524
|
+
import { z as z6 } from "zod";
|
|
525
|
+
var inputSchema5 = {
|
|
526
|
+
limit: z6.number().int().min(1).max(50).default(10).describe("Maximum memories to return"),
|
|
527
|
+
type: z6.enum(MEMORY_TYPES).optional().describe("Filter by memory type")
|
|
528
|
+
};
|
|
529
|
+
function registerRecent(server, deps) {
|
|
530
|
+
server.registerTool(
|
|
531
|
+
"memory_recent",
|
|
532
|
+
{
|
|
533
|
+
description: "Load session context: returns context-matched, recent, and related memories. Call this at the start of every session to load context from previous work. Results are grouped into sections: contextMatched (matching current branch/files), recent (most recent), and related (connected via relations).",
|
|
534
|
+
inputSchema: inputSchema5,
|
|
535
|
+
annotations: { readOnlyHint: true, idempotentHint: true }
|
|
536
|
+
},
|
|
537
|
+
async (args) => {
|
|
538
|
+
const results = deps.retrievalService.loadSessionContext({
|
|
539
|
+
limit: args.limit,
|
|
540
|
+
project: deps.project ?? void 0,
|
|
541
|
+
type: args.type
|
|
542
|
+
});
|
|
543
|
+
if (results.length === 0) {
|
|
544
|
+
return {
|
|
545
|
+
content: [{ type: "text", text: "No memories found for this project." }]
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
const formatEntry = (entry, rank) => ({
|
|
549
|
+
rank,
|
|
550
|
+
section: entry.section,
|
|
551
|
+
id: entry.result.memory.id,
|
|
552
|
+
type: entry.result.memory.type,
|
|
553
|
+
title: entry.result.memory.title,
|
|
554
|
+
content: entry.result.memory.content.slice(0, 500),
|
|
555
|
+
importance: entry.result.memory.importance,
|
|
556
|
+
tags: entry.result.memory.tags,
|
|
557
|
+
score: Math.round(entry.result.score * 100) / 100,
|
|
558
|
+
explanation: entry.result.explanation,
|
|
559
|
+
createdAt: entry.result.memory.createdAt
|
|
560
|
+
});
|
|
561
|
+
const contextMatched = results.filter((r) => r.section === "context").map((r, i) => formatEntry(r, i + 1));
|
|
562
|
+
const recent = results.filter((r) => r.section === "recent").map((r, i) => formatEntry(r, i + 1));
|
|
563
|
+
const related = results.filter((r) => r.section === "related").map((r, i) => formatEntry(r, i + 1));
|
|
564
|
+
return {
|
|
565
|
+
content: [{
|
|
566
|
+
type: "text",
|
|
567
|
+
text: JSON.stringify({ contextMatched, recent, related }, null, 2)
|
|
568
|
+
}]
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/commands/memory/tools/update.ts
|
|
575
|
+
import { z as z7 } from "zod";
|
|
576
|
+
var inputSchema6 = {
|
|
577
|
+
id: z7.string().describe("Memory ID to update (use memory_search to find it)"),
|
|
578
|
+
title: z7.string().max(200).optional().describe("Updated title"),
|
|
579
|
+
content: z7.string().min(1).max(1e4).optional().describe("Updated content"),
|
|
580
|
+
tags: z7.array(z7.string()).max(20).optional().describe("Updated tags"),
|
|
581
|
+
importance: z7.number().min(0).max(1).optional().describe("Updated importance (0-1)"),
|
|
582
|
+
context: z7.string().optional().describe("Updated context JSON")
|
|
583
|
+
};
|
|
584
|
+
function registerUpdate(server, deps) {
|
|
585
|
+
server.registerTool(
|
|
586
|
+
"memory_update",
|
|
587
|
+
{
|
|
588
|
+
description: "Update an existing memory in-place. Preserves access count, injection count, and creation date. Use memory_search to find the ID first. At least one field must be provided.",
|
|
589
|
+
inputSchema: inputSchema6,
|
|
590
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
|
|
591
|
+
},
|
|
592
|
+
async (args) => {
|
|
593
|
+
const hasUpdate = args.title !== void 0 || args.content !== void 0 || args.tags !== void 0 || args.importance !== void 0 || args.context !== void 0;
|
|
594
|
+
if (!hasUpdate) {
|
|
595
|
+
return {
|
|
596
|
+
content: [{ type: "text", text: "No fields to update. Provide at least one of: title, content, tags, importance, context." }],
|
|
597
|
+
isError: true
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const existing = deps.memoryRepo.getById(args.id);
|
|
601
|
+
if (!existing) {
|
|
602
|
+
return {
|
|
603
|
+
content: [{ type: "text", text: `Memory ${args.id} not found.` }],
|
|
604
|
+
isError: true
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const updated = deps.memoryRepo.updateContent(args.id, {
|
|
608
|
+
title: args.title,
|
|
609
|
+
content: args.content,
|
|
610
|
+
tags: args.tags,
|
|
611
|
+
importance: args.importance,
|
|
612
|
+
context: args.context
|
|
613
|
+
});
|
|
614
|
+
if (!updated) {
|
|
615
|
+
return {
|
|
616
|
+
content: [{ type: "text", text: `Failed to update memory ${args.id}.` }],
|
|
617
|
+
isError: true
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
const fields = [
|
|
621
|
+
args.title !== void 0 && "title",
|
|
622
|
+
args.content !== void 0 && "content",
|
|
623
|
+
args.tags !== void 0 && "tags",
|
|
624
|
+
args.importance !== void 0 && "importance",
|
|
625
|
+
args.context !== void 0 && "context"
|
|
626
|
+
].filter(Boolean).join(", ");
|
|
627
|
+
return {
|
|
628
|
+
content: [{ type: "text", text: `Updated memory ${args.id} (fields: ${fields})` }]
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/commands/memory/tools/register.ts
|
|
635
|
+
function registerTools(server, deps) {
|
|
636
|
+
registerStore(server, deps);
|
|
637
|
+
registerSearch(server, deps);
|
|
638
|
+
registerRecent(server, deps);
|
|
639
|
+
registerForget(server, deps);
|
|
640
|
+
registerRelate(server, deps);
|
|
641
|
+
registerStats(server, deps);
|
|
642
|
+
registerUpdate(server, deps);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/commands/memory/server.ts
|
|
646
|
+
async function startServer(deps) {
|
|
647
|
+
const config = deps?.config ?? loadConfig();
|
|
648
|
+
const dataDir = resolveDataDir(config.dataDir);
|
|
649
|
+
const db = createDatabase({ dataDir });
|
|
650
|
+
migrate(db);
|
|
651
|
+
const memoryRepo = new MemoryRepo(db);
|
|
652
|
+
const relationRepo = new RelationRepo(db);
|
|
653
|
+
const searchRepo = new SearchRepo(db);
|
|
654
|
+
const retrievalService = new RetrievalService({
|
|
655
|
+
memoryRepo,
|
|
656
|
+
relationRepo,
|
|
657
|
+
searchRepo
|
|
658
|
+
});
|
|
659
|
+
const server = new McpServer(
|
|
660
|
+
{ name: "agentic-memory", version: "0.1.0" },
|
|
661
|
+
{
|
|
662
|
+
instructions: "Use memory_search before memory_store to check for duplicates. Use memory_update to modify existing memories instead of creating duplicates - it preserves access history. Store memories at the semantic level - capture WHY, not just WHAT happened. Only store knowledge worth remembering across sessions. Memory context is automatically injected at session start via hook - no need to call memory_recent manually."
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
const project = detectProject(process.cwd());
|
|
666
|
+
registerTools(server, { memoryRepo, relationRepo, retrievalService, dataDir, project });
|
|
667
|
+
const transport = new StdioServerTransport();
|
|
668
|
+
await server.connect(transport);
|
|
669
|
+
const shutdown = async () => {
|
|
670
|
+
await server.close();
|
|
671
|
+
closeDatabase(db);
|
|
672
|
+
process.exit(0);
|
|
673
|
+
};
|
|
674
|
+
process.on("SIGINT", () => void shutdown());
|
|
675
|
+
process.on("SIGTERM", () => void shutdown());
|
|
676
|
+
}
|
|
677
|
+
startServer().catch((err) => {
|
|
678
|
+
process.stderr.write(`[agentic-memory] ${err}
|
|
679
|
+
`);
|
|
680
|
+
process.exit(1);
|
|
681
|
+
});
|
|
682
|
+
export {
|
|
683
|
+
startServer
|
|
684
|
+
};
|
|
685
|
+
//# sourceMappingURL=server.js.map
|