lemma-mcp 0.7.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/README.md +474 -0
- package/README.tr.md +384 -0
- package/dist/guides/core.d.ts +33 -0
- package/dist/guides/core.d.ts.map +1 -0
- package/dist/guides/core.js +424 -0
- package/dist/guides/core.js.map +1 -0
- package/dist/guides/index.d.ts +2 -0
- package/dist/guides/index.d.ts.map +1 -0
- package/dist/guides/index.js +2 -0
- package/dist/guides/index.js.map +1 -0
- package/dist/guides/task-map.d.ts +3 -0
- package/dist/guides/task-map.d.ts.map +1 -0
- package/dist/guides/task-map.js +107 -0
- package/dist/guides/task-map.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/config.d.ts +6 -0
- package/dist/memory/config.d.ts.map +1 -0
- package/dist/memory/config.js +69 -0
- package/dist/memory/config.js.map +1 -0
- package/dist/memory/core.d.ts +27 -0
- package/dist/memory/core.d.ts.map +1 -0
- package/dist/memory/core.js +415 -0
- package/dist/memory/core.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/server/handlers.d.ts +131 -0
- package/dist/server/handlers.d.ts.map +1 -0
- package/dist/server/handlers.js +694 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/hooks.d.ts +16 -0
- package/dist/server/hooks.d.ts.map +1 -0
- package/dist/server/hooks.js +77 -0
- package/dist/server/hooks.js.map +1 -0
- package/dist/server/index.d.ts +5 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +322 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/system-prompt.d.ts +5 -0
- package/dist/server/system-prompt.d.ts.map +1 -0
- package/dist/server/system-prompt.js +143 -0
- package/dist/server/system-prompt.js.map +1 -0
- package/dist/server/tools.d.ts +22 -0
- package/dist/server/tools.d.ts.map +1 -0
- package/dist/server/tools.js +443 -0
- package/dist/server/tools.js.map +1 -0
- package/dist/sessions/core.d.ts +17 -0
- package/dist/sessions/core.d.ts.map +1 -0
- package/dist/sessions/core.js +162 -0
- package/dist/sessions/core.js.map +1 -0
- package/dist/sessions/index.d.ts +2 -0
- package/dist/sessions/index.d.ts.map +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/virtual.d.ts +23 -0
- package/dist/sessions/virtual.d.ts.map +1 -0
- package/dist/sessions/virtual.js +157 -0
- package/dist/sessions/virtual.js.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
import * as core from "../memory/index.js";
|
|
2
|
+
import * as guides from "../guides/index.js";
|
|
3
|
+
import * as sessions from "../sessions/index.js";
|
|
4
|
+
import * as virtualSession from "../sessions/virtual.js";
|
|
5
|
+
let activeSessionId = null;
|
|
6
|
+
let _notifyChange = null;
|
|
7
|
+
export function setNotifyChange(fn) {
|
|
8
|
+
_notifyChange = fn;
|
|
9
|
+
}
|
|
10
|
+
function notifyMemoryChange() {
|
|
11
|
+
if (_notifyChange)
|
|
12
|
+
_notifyChange();
|
|
13
|
+
}
|
|
14
|
+
export async function handleSessionStart(args) {
|
|
15
|
+
const taskType = args?.task_type;
|
|
16
|
+
const technologies = args?.technologies || [];
|
|
17
|
+
const initialApproach = args?.initial_approach || null;
|
|
18
|
+
if (!taskType) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{ type: "text", text: "Error: 'task_type' parameter is required" }],
|
|
21
|
+
isError: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const allSessions = sessions.loadSessions();
|
|
25
|
+
const existing = sessions.findActiveSession(allSessions);
|
|
26
|
+
if (existing) {
|
|
27
|
+
existing.status = "abandoned";
|
|
28
|
+
existing.task_outcome = "abandoned";
|
|
29
|
+
}
|
|
30
|
+
const session = sessions.createSession(taskType, technologies);
|
|
31
|
+
session.initial_approach = initialApproach;
|
|
32
|
+
activeSessionId = session.session_id;
|
|
33
|
+
allSessions.push(session);
|
|
34
|
+
sessions.saveSessions(allSessions);
|
|
35
|
+
const allGuides = guides.loadGuides();
|
|
36
|
+
const taskDesc = [taskType, ...technologies].join(" ");
|
|
37
|
+
const suggestions = guides.suggestGuides(taskDesc, allGuides);
|
|
38
|
+
const formattedSuggestions = guides.formatSuggestions(suggestions);
|
|
39
|
+
let response = `Session started: ${session.session_id} (${taskType})\n`;
|
|
40
|
+
if (technologies.length > 0) {
|
|
41
|
+
response += `Technologies: ${technologies.join(", ")}\n`;
|
|
42
|
+
}
|
|
43
|
+
response += `\n${formattedSuggestions}`;
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: response }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function handleSessionEnd(args) {
|
|
49
|
+
const outcome = args?.outcome;
|
|
50
|
+
const finalApproach = args?.final_approach || null;
|
|
51
|
+
const lessons = args?.lessons || [];
|
|
52
|
+
if (!outcome) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: "Error: 'outcome' parameter is required" }],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const allSessions = sessions.loadSessions();
|
|
59
|
+
const session = activeSessionId
|
|
60
|
+
? sessions.findSession(allSessions, activeSessionId)
|
|
61
|
+
: sessions.findActiveSession(allSessions);
|
|
62
|
+
if (!session) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: "text", text: "Error: No active session to end." }],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
sessions.endSession(session, outcome, finalApproach, lessons);
|
|
69
|
+
const allGuides = guides.loadGuides();
|
|
70
|
+
const improvementLines = [];
|
|
71
|
+
if (session.guides_used && session.guides_used.length > 0) {
|
|
72
|
+
for (const guideName of session.guides_used) {
|
|
73
|
+
const guide = guides.findGuide(allGuides, guideName);
|
|
74
|
+
if (guide) {
|
|
75
|
+
if (outcome === "success") {
|
|
76
|
+
guide.success_count = (guide.success_count || 0) + 1;
|
|
77
|
+
}
|
|
78
|
+
else if (outcome === "failure") {
|
|
79
|
+
guide.failure_count = (guide.failure_count || 0) + 1;
|
|
80
|
+
const total = (guide.success_count || 0) + (guide.failure_count || 0);
|
|
81
|
+
if (total >= 3) {
|
|
82
|
+
const rate = guide.success_count / total;
|
|
83
|
+
if (rate < 0.4) {
|
|
84
|
+
improvementLines.push(` [!] Guide "${guideName}" success rate is ${rate.toFixed(2)} (${guide.success_count}/${total}). Consider refining with guide_update.`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
guides.saveGuides(allGuides);
|
|
91
|
+
}
|
|
92
|
+
sessions.saveSessions(allSessions);
|
|
93
|
+
activeSessionId = null;
|
|
94
|
+
let response = `Session ${session.session_id} ended: ${outcome}\n`;
|
|
95
|
+
response += `Task: ${session.task_type} | Duration: ${session.timestamp} → ${session.completed_at}\n`;
|
|
96
|
+
if (lessons.length > 0) {
|
|
97
|
+
response += `Lessons: ${lessons.length} recorded\n`;
|
|
98
|
+
}
|
|
99
|
+
if (improvementLines.length > 0) {
|
|
100
|
+
response += `\nIMPROVEMENT SUGGESTIONS:\n${improvementLines.join("\n")}\n`;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: response }],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export async function handleMemoryRead(args) {
|
|
107
|
+
const currentProject = args?.project || core.detectProject();
|
|
108
|
+
const query = args?.query || null;
|
|
109
|
+
const detailId = args?.id || null;
|
|
110
|
+
const context = args?.context || null;
|
|
111
|
+
const showAll = args?.all === true;
|
|
112
|
+
let memory = core.loadMemory();
|
|
113
|
+
const detailIds = args?.ids || null;
|
|
114
|
+
if (detailIds && Array.isArray(detailIds) && detailIds.length > 0) {
|
|
115
|
+
const results = [];
|
|
116
|
+
for (const did of detailIds) {
|
|
117
|
+
const fragment = memory.find((f) => f.id === did);
|
|
118
|
+
if (fragment) {
|
|
119
|
+
const boosted = core.boostOnAccess(fragment, context);
|
|
120
|
+
Object.assign(fragment, boosted);
|
|
121
|
+
results.push(core.formatMemoryDetail(fragment));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
results.push(`Fragment [${did}] not found.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
core.saveMemory(memory);
|
|
128
|
+
notifyMemoryChange();
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: results.join("\n\n") }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (detailId) {
|
|
134
|
+
const fragment = memory.find((f) => f.id === detailId);
|
|
135
|
+
if (!fragment) {
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: `Error: Fragment with ID '${detailId}' not found` }],
|
|
138
|
+
isError: true,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const boosted = core.boostOnAccess(fragment, context);
|
|
142
|
+
Object.assign(fragment, boosted);
|
|
143
|
+
core.saveMemory(memory);
|
|
144
|
+
notifyMemoryChange();
|
|
145
|
+
return {
|
|
146
|
+
content: [{ type: "text", text: core.formatMemoryDetail(fragment) }],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const filteredMemory = showAll
|
|
150
|
+
? memory
|
|
151
|
+
: core.filterByProject(memory, currentProject);
|
|
152
|
+
const results = core.searchAndSortFragments(filteredMemory, query, 30);
|
|
153
|
+
const resultIds = new Set(results.map((r) => r.id));
|
|
154
|
+
for (const frag of memory) {
|
|
155
|
+
if (resultIds.has(frag.id)) {
|
|
156
|
+
const boosted = core.boostOnAccess(frag, context);
|
|
157
|
+
Object.assign(frag, boosted);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const scopeInfo = showAll ? "all projects" : currentProject || "global";
|
|
161
|
+
const formatted = core.formatMemoryForLLM(results, scopeInfo);
|
|
162
|
+
core.saveMemory(memory);
|
|
163
|
+
notifyMemoryChange();
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: formatted }],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
export async function handleMemoryAdd(args) {
|
|
169
|
+
const fragment = args?.fragment;
|
|
170
|
+
const title = args?.title || null;
|
|
171
|
+
const description = args?.description || null;
|
|
172
|
+
const project = args?.project === undefined ? null : args.project;
|
|
173
|
+
const source = (args?.source || "ai");
|
|
174
|
+
if (!fragment || typeof fragment !== "string") {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: "Error: 'fragment' parameter is required and must be a string" }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const memory = core.loadMemory();
|
|
181
|
+
const similarMatch = core.findSimilarFragment(memory, fragment, project);
|
|
182
|
+
if (similarMatch) {
|
|
183
|
+
return {
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `A similar memory already exists [${similarMatch.id}]: "${similarMatch.title}"\nUse memory_update on [${similarMatch.id}] if you want to modify it.`
|
|
187
|
+
}],
|
|
188
|
+
isError: true,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const newFragment = core.createFragment(fragment, source, title, project, description);
|
|
192
|
+
if (activeSessionId) {
|
|
193
|
+
const allSessions = sessions.loadSessions();
|
|
194
|
+
const session = sessions.findSession(allSessions, activeSessionId);
|
|
195
|
+
if (session) {
|
|
196
|
+
newFragment.session_id = activeSessionId;
|
|
197
|
+
newFragment.task_type = session.task_type;
|
|
198
|
+
session.memories_created = session.memories_created || [];
|
|
199
|
+
session.memories_created.push(newFragment.id);
|
|
200
|
+
sessions.saveSessions(allSessions);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
memory.push(newFragment);
|
|
204
|
+
core.saveMemory(memory);
|
|
205
|
+
notifyMemoryChange();
|
|
206
|
+
const scopeInfo = newFragment.project ? ` (project: ${newFragment.project})` : " (global)";
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: `Added fragment [${newFragment.id}]${scopeInfo}: "${newFragment.title}"\nSummary: ${newFragment.description}` }],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
export async function handleMemoryUpdate(args) {
|
|
212
|
+
const id = args?.id;
|
|
213
|
+
const title = args?.title;
|
|
214
|
+
const fragment = args?.fragment;
|
|
215
|
+
const confidence = args?.confidence;
|
|
216
|
+
if (!id || typeof id !== "string") {
|
|
217
|
+
return {
|
|
218
|
+
content: [{ type: "text", text: "Error: 'id' parameter is required and must be a string" }],
|
|
219
|
+
isError: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const memory = core.loadMemory();
|
|
223
|
+
const targetIndex = memory.findIndex((f) => f.id === id);
|
|
224
|
+
if (targetIndex === -1) {
|
|
225
|
+
return {
|
|
226
|
+
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
227
|
+
isError: true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (title !== undefined) {
|
|
231
|
+
if (typeof title !== "string") {
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: "Error: 'title' must be a string" }],
|
|
234
|
+
isError: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
memory[targetIndex].title = title;
|
|
238
|
+
}
|
|
239
|
+
if (fragment !== undefined) {
|
|
240
|
+
if (typeof fragment !== "string") {
|
|
241
|
+
return {
|
|
242
|
+
content: [{ type: "text", text: "Error: 'fragment' must be a string" }],
|
|
243
|
+
isError: true,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
memory[targetIndex].fragment = fragment;
|
|
247
|
+
memory[targetIndex].accessed++;
|
|
248
|
+
}
|
|
249
|
+
if (confidence !== undefined) {
|
|
250
|
+
if (typeof confidence !== "number" || confidence < 0 || confidence > 1) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: "text", text: "Error: 'confidence' must be a number between 0 and 1" }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
memory[targetIndex].confidence = confidence;
|
|
257
|
+
}
|
|
258
|
+
core.saveMemory(memory);
|
|
259
|
+
notifyMemoryChange();
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: "text", text: `Updated fragment [${id}]: "${memory[targetIndex].title}"` }],
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
export async function handleMemoryForget(args) {
|
|
265
|
+
const id = args?.id;
|
|
266
|
+
if (!id || typeof id !== "string") {
|
|
267
|
+
return {
|
|
268
|
+
content: [{ type: "text", text: "Error: 'id' parameter is required and must be a string" }],
|
|
269
|
+
isError: true,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const memory = core.loadMemory();
|
|
273
|
+
const initialLength = memory.length;
|
|
274
|
+
const filtered = memory.filter((f) => f.id !== id);
|
|
275
|
+
if (filtered.length === initialLength) {
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
278
|
+
isError: true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
core.saveMemory(filtered, { force: true });
|
|
282
|
+
notifyMemoryChange();
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: "text", text: `Forgot fragment with ID: ${id}` }],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
export async function handleMemoryFeedback(args) {
|
|
288
|
+
const id = args?.id;
|
|
289
|
+
const useful = args?.useful;
|
|
290
|
+
if (!id || typeof id !== "string") {
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: "text", text: "Error: 'id' parameter is required" }],
|
|
293
|
+
isError: true,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (typeof useful !== "boolean") {
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: "text", text: "Error: 'useful' parameter is required and must be a boolean" }],
|
|
299
|
+
isError: true,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const memory = core.loadMemory();
|
|
303
|
+
const targetIndex = memory.findIndex((f) => f.id === id);
|
|
304
|
+
if (targetIndex === -1) {
|
|
305
|
+
return {
|
|
306
|
+
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
307
|
+
isError: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (useful) {
|
|
311
|
+
const boosted = core.boostOnAccess(memory[targetIndex]);
|
|
312
|
+
Object.assign(memory[targetIndex], boosted);
|
|
313
|
+
memory[targetIndex].positive_feedback = (memory[targetIndex].positive_feedback || 0) + 1;
|
|
314
|
+
core.saveMemory(memory);
|
|
315
|
+
notifyMemoryChange();
|
|
316
|
+
return {
|
|
317
|
+
content: [{ type: "text", text: `Positive feedback recorded for [${id}]. Confidence boosted to ${memory[targetIndex].confidence.toFixed(2)}.` }],
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const penalized = core.recordNegativeHit(memory[targetIndex]);
|
|
322
|
+
Object.assign(memory[targetIndex], penalized);
|
|
323
|
+
memory[targetIndex].negative_feedback = (memory[targetIndex].negative_feedback || 0) + 1;
|
|
324
|
+
core.saveMemory(memory);
|
|
325
|
+
notifyMemoryChange();
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: `Negative feedback recorded for [${id}]. Confidence reduced to ${memory[targetIndex].confidence.toFixed(2)}.` }],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
export async function handleMemoryMerge(args) {
|
|
332
|
+
const ids = args?.ids;
|
|
333
|
+
const title = args?.title;
|
|
334
|
+
const fragment = args?.fragment;
|
|
335
|
+
const project = args?.project === undefined ? null : args.project;
|
|
336
|
+
if (!ids || !Array.isArray(ids) || ids.length < 2) {
|
|
337
|
+
return {
|
|
338
|
+
content: [{ type: "text", text: "Error: 'ids' must be an array with at least 2 fragment IDs" }],
|
|
339
|
+
isError: true,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (!title || typeof title !== "string") {
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: "Error: 'title' is required and must be a string" }],
|
|
345
|
+
isError: true,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (!fragment || typeof fragment !== "string") {
|
|
349
|
+
return {
|
|
350
|
+
content: [{ type: "text", text: "Error: 'fragment' is required and must be a string" }],
|
|
351
|
+
isError: true,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const memory = core.loadMemory();
|
|
355
|
+
const notFound = ids.filter((id) => !memory.find((f) => f.id === id));
|
|
356
|
+
if (notFound.length > 0) {
|
|
357
|
+
return {
|
|
358
|
+
content: [{ type: "text", text: `Error: Fragment(s) not found: ${notFound.join(", ")}` }],
|
|
359
|
+
isError: true,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const newFragment = core.createFragment(fragment, "ai", title, project);
|
|
363
|
+
memory.push(newFragment);
|
|
364
|
+
const mergedMemory = memory.filter((f) => !ids.includes(f.id));
|
|
365
|
+
core.saveMemory(mergedMemory);
|
|
366
|
+
notifyMemoryChange();
|
|
367
|
+
const scopeInfo = newFragment.project ? ` (project: ${newFragment.project})` : " (global)";
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: "text", text: `Merged ${ids.length} fragments into [${newFragment.id}]${scopeInfo}: "${newFragment.title}"\nRemoved IDs: ${ids.join(", ")}` }],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
export async function handleGuideGet(args) {
|
|
373
|
+
const category = args?.category || null;
|
|
374
|
+
const guideName = args?.guide || null;
|
|
375
|
+
const task = args?.task || null;
|
|
376
|
+
const allGuides = guides.loadGuides();
|
|
377
|
+
if (task) {
|
|
378
|
+
const result = guides.suggestGuides(task, allGuides);
|
|
379
|
+
const formatted = guides.formatSuggestions(result);
|
|
380
|
+
return {
|
|
381
|
+
content: [{ type: "text", text: formatted }],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
if (guideName) {
|
|
385
|
+
const guide = guides.findGuide(allGuides, guideName);
|
|
386
|
+
return {
|
|
387
|
+
content: [{ type: "text", text: guides.formatGuideDetail(guide) }],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const filtered = category
|
|
391
|
+
? guides.getGuidesByCategory(allGuides, category)
|
|
392
|
+
: allGuides;
|
|
393
|
+
const formatted = guides.formatGuidesForLLM(filtered);
|
|
394
|
+
return {
|
|
395
|
+
content: [{ type: "text", text: formatted }],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
export async function handleGuidePractice(args) {
|
|
399
|
+
const guideName = args?.guide;
|
|
400
|
+
const category = args?.category;
|
|
401
|
+
const description = args?.description || "";
|
|
402
|
+
const contexts = args?.contexts || [];
|
|
403
|
+
const learnings = args?.learnings || [];
|
|
404
|
+
if (!guideName || !category) {
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: "text", text: "Error: 'guide' and 'category' parameters are required" }],
|
|
407
|
+
isError: true,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
const allGuides = guides.loadGuides();
|
|
411
|
+
const updated = guides.practiceGuide(allGuides, guideName, category, description, contexts, learnings, args?.outcome);
|
|
412
|
+
if (activeSessionId) {
|
|
413
|
+
const allSessions = sessions.loadSessions();
|
|
414
|
+
const session = sessions.findSession(allSessions, activeSessionId);
|
|
415
|
+
if (session) {
|
|
416
|
+
if (!session.guides_used)
|
|
417
|
+
session.guides_used = [];
|
|
418
|
+
if (!session.guides_used.includes(guideName.toLowerCase())) {
|
|
419
|
+
session.guides_used.push(guideName.toLowerCase());
|
|
420
|
+
}
|
|
421
|
+
sessions.saveSessions(allSessions);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
guides.saveGuides(allGuides);
|
|
425
|
+
const isNew = updated.usage_count === 1;
|
|
426
|
+
const action = isNew ? "Created" : "Updated";
|
|
427
|
+
const response = `${action} guide "${updated.guide}" (${updated.category}): ${updated.usage_count}x usage, ${updated.learnings.length} learnings, ${updated.contexts.length} contexts`;
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: response }],
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
export async function handleGuideCreate(args) {
|
|
433
|
+
const guideName = args?.guide;
|
|
434
|
+
const category = args?.category;
|
|
435
|
+
const description = args?.description;
|
|
436
|
+
const contexts = args?.contexts || [];
|
|
437
|
+
const learnings = args?.learnings || [];
|
|
438
|
+
if (!guideName || !category || !description) {
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: "Error: 'guide', 'category', and 'description' parameters are required" }],
|
|
441
|
+
isError: true,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
const allGuides = guides.loadGuides();
|
|
445
|
+
const existing = guides.findSimilarGuide(allGuides, guideName);
|
|
446
|
+
if (existing) {
|
|
447
|
+
existing.description = description;
|
|
448
|
+
guides.saveGuides(allGuides);
|
|
449
|
+
return {
|
|
450
|
+
content: [{ type: "text", text: `Updated manual for existing guide "${existing.guide}" (${existing.category})` }],
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const newGuide = guides.createGuide(guideName, category, description, contexts, learnings);
|
|
454
|
+
allGuides.push(newGuide);
|
|
455
|
+
guides.saveGuides(allGuides);
|
|
456
|
+
return {
|
|
457
|
+
content: [{ type: "text", text: `Created new guide "${newGuide.guide}" (${newGuide.category}) with a detailed manual.` }],
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
export async function handleGuideDistill(args) {
|
|
461
|
+
const memoryId = args?.memory_id;
|
|
462
|
+
const guideName = args?.guide;
|
|
463
|
+
const category = args?.category || "dev-tool";
|
|
464
|
+
if (!memoryId || !guideName) {
|
|
465
|
+
return {
|
|
466
|
+
content: [{ type: "text", text: "Error: 'memory_id' and 'guide' parameters are required" }],
|
|
467
|
+
isError: true,
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const allMemory = core.loadMemory();
|
|
471
|
+
const fragment = allMemory.find((m) => m.id === memoryId);
|
|
472
|
+
if (!fragment) {
|
|
473
|
+
return {
|
|
474
|
+
content: [{ type: "text", text: `Error: Memory fragment with ID '${memoryId}' not found.` }],
|
|
475
|
+
isError: true,
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
const allGuides = guides.loadGuides();
|
|
479
|
+
const updated = guides.promoteToGuide(allGuides, guideName, category, fragment.fragment, fragment.project || "global");
|
|
480
|
+
guides.saveGuides(allGuides);
|
|
481
|
+
let response = `Successfully distilled memory [${memoryId}] into guide "${updated.guide}" (${updated.category}).\n\n`;
|
|
482
|
+
response += guides.formatGuideDetail(updated);
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: "text", text: response }],
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
export async function handleGuideUpdate(args) {
|
|
488
|
+
const guideName = args?.guide;
|
|
489
|
+
const updates = {
|
|
490
|
+
guide: args?.new_name,
|
|
491
|
+
category: args?.category,
|
|
492
|
+
description: args?.description,
|
|
493
|
+
add_anti_patterns: args?.add_anti_patterns,
|
|
494
|
+
add_pitfalls: args?.add_pitfalls,
|
|
495
|
+
superseded_by: args?.superseded_by,
|
|
496
|
+
deprecated: args?.deprecated,
|
|
497
|
+
};
|
|
498
|
+
if (!guideName) {
|
|
499
|
+
return {
|
|
500
|
+
content: [{ type: "text", text: "Error: 'guide' parameter is required" }],
|
|
501
|
+
isError: true,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const allGuides = guides.loadGuides();
|
|
505
|
+
const updated = guides.updateGuide(allGuides, guideName, updates);
|
|
506
|
+
if (!updated) {
|
|
507
|
+
return {
|
|
508
|
+
content: [{ type: "text", text: `Error: Guide "${guideName}" not found.` }],
|
|
509
|
+
isError: true,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
guides.saveGuides(allGuides);
|
|
513
|
+
return {
|
|
514
|
+
content: [{ type: "text", text: `Updated guide "${updated.guide}":\n${guides.formatGuideDetail(updated)}` }],
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
export async function handleGuideForget(args) {
|
|
518
|
+
const guideName = args?.guide;
|
|
519
|
+
if (!guideName) {
|
|
520
|
+
return {
|
|
521
|
+
content: [{ type: "text", text: "Error: 'guide' parameter is required" }],
|
|
522
|
+
isError: true,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const allGuides = guides.loadGuides();
|
|
526
|
+
const success = guides.deleteGuide(allGuides, guideName);
|
|
527
|
+
if (!success) {
|
|
528
|
+
return {
|
|
529
|
+
content: [{ type: "text", text: `Error: Guide "${guideName}" not found.` }],
|
|
530
|
+
isError: true,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
guides.saveGuides(allGuides, { force: true });
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: "text", text: `Successfully forgot guide: ${guideName}` }],
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
export async function handleGuideMerge(args) {
|
|
539
|
+
const guideNames = args?.guides;
|
|
540
|
+
const newGuideName = args?.guide;
|
|
541
|
+
const category = args?.category;
|
|
542
|
+
const description = args?.description || "";
|
|
543
|
+
let contexts = args?.contexts;
|
|
544
|
+
let learnings = args?.learnings;
|
|
545
|
+
if (!guideNames || !Array.isArray(guideNames) || guideNames.length < 2) {
|
|
546
|
+
return {
|
|
547
|
+
content: [{ type: "text", text: "Error: 'guides' must be an array with at least 2 guide names" }],
|
|
548
|
+
isError: true,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
if (!newGuideName || !category) {
|
|
552
|
+
return {
|
|
553
|
+
content: [{ type: "text", text: "Error: 'guide' and 'category' parameters are required" }],
|
|
554
|
+
isError: true,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
const allGuides = guides.loadGuides();
|
|
558
|
+
const sourceGuides = [];
|
|
559
|
+
const notFound = [];
|
|
560
|
+
for (const name of guideNames) {
|
|
561
|
+
const g = guides.findGuide(allGuides, name);
|
|
562
|
+
if (g) {
|
|
563
|
+
sourceGuides.push(g);
|
|
564
|
+
}
|
|
565
|
+
else {
|
|
566
|
+
notFound.push(name);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (notFound.length > 0) {
|
|
570
|
+
return {
|
|
571
|
+
content: [{ type: "text", text: `Error: Guide(s) not found: ${notFound.join(", ")}` }],
|
|
572
|
+
isError: true,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
if (!contexts) {
|
|
576
|
+
contexts = [...new Set(sourceGuides.flatMap((g) => g.contexts))];
|
|
577
|
+
}
|
|
578
|
+
if (!learnings) {
|
|
579
|
+
learnings = [...new Set(sourceGuides.flatMap((g) => g.learnings))];
|
|
580
|
+
}
|
|
581
|
+
const antiPatterns = [...new Set(sourceGuides.flatMap((g) => g.anti_patterns || []))];
|
|
582
|
+
const pitfalls = [...new Set(sourceGuides.flatMap((g) => g.known_pitfalls || []))];
|
|
583
|
+
const totalUsage = sourceGuides.reduce((sum, g) => sum + g.usage_count, 0);
|
|
584
|
+
const newGuide = guides.createGuide(newGuideName, category, description, contexts, learnings);
|
|
585
|
+
newGuide.usage_count = totalUsage;
|
|
586
|
+
newGuide.anti_patterns = antiPatterns;
|
|
587
|
+
newGuide.known_pitfalls = pitfalls;
|
|
588
|
+
allGuides.push(newGuide);
|
|
589
|
+
const mergedGuides = allGuides.filter((g) => !guideNames.map((n) => n.toLowerCase()).includes(g.guide));
|
|
590
|
+
guides.saveGuides(mergedGuides);
|
|
591
|
+
let response = `Merged ${guideNames.length} guides into "${newGuide.guide}" (${newGuide.category})\n`;
|
|
592
|
+
response += `Total usage: ${totalUsage}x | Contexts: ${contexts.length} | Learnings: ${learnings.length}\n`;
|
|
593
|
+
response += `Removed: ${guideNames.join(", ")}`;
|
|
594
|
+
return {
|
|
595
|
+
content: [{ type: "text", text: response }],
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
export async function handleMemoryStats(args) {
|
|
599
|
+
const project = args?.project || null;
|
|
600
|
+
const memory = core.loadMemory();
|
|
601
|
+
const stats = core.calculateStats(memory, project);
|
|
602
|
+
return {
|
|
603
|
+
content: [{ type: "text", text: core.formatStats(stats) }],
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
export async function handleMemoryAudit(_args) {
|
|
607
|
+
const memory = core.loadMemory();
|
|
608
|
+
const result = core.auditMemory(memory);
|
|
609
|
+
return {
|
|
610
|
+
content: [{ type: "text", text: core.formatAuditReport(result) }],
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
export async function handleSessionStats(args) {
|
|
614
|
+
const count = args?.count || 10;
|
|
615
|
+
const recentSessions = virtualSession.getRecentSessions(count);
|
|
616
|
+
const current = virtualSession.getCurrentVirtualSession();
|
|
617
|
+
let output = `## Session Stats\n`;
|
|
618
|
+
if (current) {
|
|
619
|
+
output += `Active session: ${current.tool_calls.length} tool calls\n`;
|
|
620
|
+
if (current.technologies_seen.size > 0) {
|
|
621
|
+
output += `Technologies: ${[...current.technologies_seen].join(", ")}\n`;
|
|
622
|
+
}
|
|
623
|
+
if (current.guides_used.size > 0) {
|
|
624
|
+
output += `Guides used: ${[...current.guides_used].join(", ")}\n`;
|
|
625
|
+
}
|
|
626
|
+
output += `\n`;
|
|
627
|
+
}
|
|
628
|
+
if (recentSessions.length > 0) {
|
|
629
|
+
output += `Recent sessions (${recentSessions.length}):\n`;
|
|
630
|
+
for (const s of recentSessions.slice(0, 5)) {
|
|
631
|
+
const techs = s.technologies?.length > 0 ? ` [${s.technologies.join(", ")}]` : "";
|
|
632
|
+
output += ` ${s.id}: ${s.duration_tool_calls} calls${techs}\n`;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
output += `No past sessions recorded yet.\n`;
|
|
637
|
+
}
|
|
638
|
+
return { content: [{ type: "text", text: output }] };
|
|
639
|
+
}
|
|
640
|
+
export async function handleCallTool(request) {
|
|
641
|
+
const { name, arguments: args } = request.params;
|
|
642
|
+
try {
|
|
643
|
+
switch (name) {
|
|
644
|
+
case "session_start":
|
|
645
|
+
return await handleSessionStart(args);
|
|
646
|
+
case "session_end":
|
|
647
|
+
return await handleSessionEnd(args);
|
|
648
|
+
case "memory_read":
|
|
649
|
+
return await handleMemoryRead(args);
|
|
650
|
+
case "memory_add":
|
|
651
|
+
return await handleMemoryAdd(args);
|
|
652
|
+
case "memory_update":
|
|
653
|
+
return await handleMemoryUpdate(args);
|
|
654
|
+
case "memory_forget":
|
|
655
|
+
return await handleMemoryForget(args);
|
|
656
|
+
case "memory_feedback":
|
|
657
|
+
return await handleMemoryFeedback(args);
|
|
658
|
+
case "memory_merge":
|
|
659
|
+
return await handleMemoryMerge(args);
|
|
660
|
+
case "memory_stats":
|
|
661
|
+
return await handleMemoryStats(args);
|
|
662
|
+
case "memory_audit":
|
|
663
|
+
return await handleMemoryAudit(args);
|
|
664
|
+
case "guide_get":
|
|
665
|
+
return await handleGuideGet(args);
|
|
666
|
+
case "guide_practice":
|
|
667
|
+
return await handleGuidePractice(args);
|
|
668
|
+
case "guide_create":
|
|
669
|
+
return await handleGuideCreate(args);
|
|
670
|
+
case "guide_distill":
|
|
671
|
+
return await handleGuideDistill(args);
|
|
672
|
+
case "guide_update":
|
|
673
|
+
return await handleGuideUpdate(args);
|
|
674
|
+
case "guide_forget":
|
|
675
|
+
return await handleGuideForget(args);
|
|
676
|
+
case "guide_merge":
|
|
677
|
+
return await handleGuideMerge(args);
|
|
678
|
+
case "session_stats":
|
|
679
|
+
return await handleSessionStats(args);
|
|
680
|
+
default:
|
|
681
|
+
return {
|
|
682
|
+
content: [{ type: "text", text: `Error: Unknown tool '${name}'` }],
|
|
683
|
+
isError: true,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
return {
|
|
689
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
690
|
+
isError: true,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
//# sourceMappingURL=handlers.js.map
|