lemma-mcp 0.8.2 → 0.8.4
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 +27 -27
- package/dist/guides/core.d.ts.map +1 -1
- package/dist/guides/core.js +65 -12
- package/dist/guides/core.js.map +1 -1
- package/dist/logger.d.ts +16 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +139 -0
- package/dist/logger.js.map +1 -0
- package/dist/memory/config.d.ts.map +1 -1
- package/dist/memory/config.js +5 -0
- package/dist/memory/config.js.map +1 -1
- package/dist/memory/core.d.ts.map +1 -1
- package/dist/memory/core.js +39 -8
- package/dist/memory/core.js.map +1 -1
- package/dist/server/handlers.d.ts.map +1 -1
- package/dist/server/handlers.js +269 -42
- package/dist/server/handlers.js.map +1 -1
- package/dist/server/hooks.d.ts.map +1 -1
- package/dist/server/hooks.js +16 -4
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +130 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/system-prompt.d.ts.map +1 -1
- package/dist/server/system-prompt.js +19 -4
- package/dist/server/system-prompt.js.map +1 -1
- package/dist/sessions/core.d.ts.map +1 -1
- package/dist/sessions/core.js +24 -7
- package/dist/sessions/core.js.map +1 -1
- package/dist/sessions/virtual.d.ts.map +1 -1
- package/dist/sessions/virtual.js +11 -1
- package/dist/sessions/virtual.js.map +1 -1
- package/package.json +1 -1
package/dist/server/handlers.js
CHANGED
|
@@ -2,28 +2,36 @@ import * as core from "../memory/index.js";
|
|
|
2
2
|
import * as guides from "../guides/index.js";
|
|
3
3
|
import * as sessions from "../sessions/index.js";
|
|
4
4
|
import * as virtualSession from "../sessions/virtual.js";
|
|
5
|
+
import { logger } from "../logger.js";
|
|
5
6
|
let activeSessionId = null;
|
|
6
7
|
let _notifyChange = null;
|
|
7
8
|
export function setNotifyChange(fn) {
|
|
8
9
|
_notifyChange = fn;
|
|
10
|
+
logger.debug("setNotifyChange", "notification handler registered");
|
|
9
11
|
}
|
|
10
12
|
function notifyMemoryChange() {
|
|
11
|
-
if (_notifyChange)
|
|
13
|
+
if (_notifyChange) {
|
|
14
|
+
logger.notify("memory_change", "sending");
|
|
12
15
|
_notifyChange();
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
export async function handleSessionStart(args) {
|
|
15
19
|
const taskType = args?.task_type;
|
|
16
20
|
const technologies = args?.technologies || [];
|
|
17
21
|
const initialApproach = args?.initial_approach || null;
|
|
22
|
+
logger.flow("session_start", "start", { task_type: taskType, technologies, has_initial_approach: !!initialApproach });
|
|
18
23
|
if (!taskType) {
|
|
24
|
+
logger.warn("session_start validation failed", { reason: "missing task_type" });
|
|
19
25
|
return {
|
|
20
26
|
content: [{ type: "text", text: "Error: 'task_type' parameter is required" }],
|
|
21
27
|
isError: true,
|
|
22
28
|
};
|
|
23
29
|
}
|
|
30
|
+
logger.data("sessions.json", "load");
|
|
24
31
|
const allSessions = sessions.loadSessions();
|
|
25
32
|
const existing = sessions.findActiveSession(allSessions);
|
|
26
33
|
if (existing) {
|
|
34
|
+
logger.flow("session_start", "abandon_existing", { session_id: existing.session_id, task_type: existing.task_type });
|
|
27
35
|
existing.status = "abandoned";
|
|
28
36
|
existing.task_outcome = "abandoned";
|
|
29
37
|
}
|
|
@@ -31,18 +39,24 @@ export async function handleSessionStart(args) {
|
|
|
31
39
|
session.initial_approach = initialApproach;
|
|
32
40
|
activeSessionId = session.session_id;
|
|
33
41
|
allSessions.push(session);
|
|
42
|
+
logger.data("sessions.json", "save", { session_id: session.session_id, total_sessions: allSessions.length });
|
|
34
43
|
sessions.saveSessions(allSessions);
|
|
44
|
+
logger.data("guides.json", "load");
|
|
35
45
|
const allGuides = guides.loadGuides();
|
|
36
46
|
const taskDesc = [taskType, ...technologies].join(" ");
|
|
37
47
|
const suggestions = guides.suggestGuides(taskDesc, allGuides);
|
|
48
|
+
logger.flow("session_start", "guide_suggestions", { task_desc: taskDesc, relevant: suggestions.relevant.length, suggested: suggestions.suggested.length });
|
|
38
49
|
const formattedSuggestions = guides.formatSuggestions(suggestions);
|
|
50
|
+
logger.data("memory.json", "load");
|
|
39
51
|
const allMemory = core.loadMemory();
|
|
40
52
|
const projectMemory = core.filterByProject(allMemory, core.detectProject());
|
|
41
53
|
const relevantResults = core.searchAndSortFragments(projectMemory, taskDesc, 3);
|
|
54
|
+
logger.flow("session_start", "preload_memories", { relevant_count: relevantResults.length, project_memory_count: projectMemory.length });
|
|
42
55
|
for (const frag of relevantResults) {
|
|
43
56
|
const boosted = core.boostOnAccess(frag);
|
|
44
57
|
Object.assign(frag, boosted);
|
|
45
58
|
}
|
|
59
|
+
logger.data("memory.json", "save", { reason: "boost_preloaded", boosted_count: relevantResults.length });
|
|
46
60
|
core.saveMemory(allMemory);
|
|
47
61
|
let response = `Session started: ${session.session_id} (${taskType})\n`;
|
|
48
62
|
if (technologies.length > 0) {
|
|
@@ -57,6 +71,7 @@ export async function handleSessionStart(args) {
|
|
|
57
71
|
}
|
|
58
72
|
}
|
|
59
73
|
response += `\n${formattedSuggestions}`;
|
|
74
|
+
logger.flow("session_start", "complete", { session_id: session.session_id, task_type: taskType, suggestions: suggestions.relevant.length + suggestions.suggested.length, preloaded: relevantResults.length });
|
|
60
75
|
return {
|
|
61
76
|
content: [{ type: "text", text: response }],
|
|
62
77
|
};
|
|
@@ -65,26 +80,33 @@ export async function handleSessionEnd(args) {
|
|
|
65
80
|
const outcome = args?.outcome;
|
|
66
81
|
const finalApproach = args?.final_approach || null;
|
|
67
82
|
const lessons = args?.lessons || [];
|
|
83
|
+
logger.flow("session_end", "start", { outcome, has_final_approach: !!finalApproach, lesson_count: lessons.length });
|
|
68
84
|
if (!outcome) {
|
|
85
|
+
logger.warn("session_end validation failed", { reason: "missing outcome" });
|
|
69
86
|
return {
|
|
70
87
|
content: [{ type: "text", text: "Error: 'outcome' parameter is required" }],
|
|
71
88
|
isError: true,
|
|
72
89
|
};
|
|
73
90
|
}
|
|
91
|
+
logger.data("sessions.json", "load");
|
|
74
92
|
const allSessions = sessions.loadSessions();
|
|
75
93
|
const session = activeSessionId
|
|
76
94
|
? sessions.findSession(allSessions, activeSessionId)
|
|
77
95
|
: sessions.findActiveSession(allSessions);
|
|
78
96
|
if (!session) {
|
|
97
|
+
logger.warn("session_end no active session", { activeSessionId });
|
|
79
98
|
return {
|
|
80
99
|
content: [{ type: "text", text: "Error: No active session to end." }],
|
|
81
100
|
isError: true,
|
|
82
101
|
};
|
|
83
102
|
}
|
|
103
|
+
logger.flow("session_end", "session_found", { session_id: session.session_id, task_type: session.task_type });
|
|
84
104
|
sessions.endSession(session, outcome, finalApproach, lessons);
|
|
105
|
+
logger.data("guides.json", "load");
|
|
85
106
|
const allGuides = guides.loadGuides();
|
|
86
107
|
const improvementLines = [];
|
|
87
108
|
if (session.guides_used && session.guides_used.length > 0) {
|
|
109
|
+
logger.flow("session_end", "evaluating_guides", { guides_used: session.guides_used, outcome });
|
|
88
110
|
for (const guideName of session.guides_used) {
|
|
89
111
|
const guide = guides.findGuide(allGuides, guideName);
|
|
90
112
|
if (guide) {
|
|
@@ -97,14 +119,17 @@ export async function handleSessionEnd(args) {
|
|
|
97
119
|
if (total >= 3) {
|
|
98
120
|
const rate = guide.success_count / total;
|
|
99
121
|
if (rate < 0.4) {
|
|
122
|
+
logger.warn("session_end low guide success rate", { guide: guideName, success_rate: rate.toFixed(2), total });
|
|
100
123
|
improvementLines.push(` [!] Guide "${guideName}" success rate is ${rate.toFixed(2)} (${guide.success_count}/${total}). Consider refining with guide_update.`);
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
}
|
|
104
127
|
}
|
|
105
128
|
}
|
|
129
|
+
logger.data("guides.json", "save", { reason: "guide_success_tracking", guides_updated: session.guides_used.length });
|
|
106
130
|
guides.saveGuides(allGuides);
|
|
107
131
|
}
|
|
132
|
+
logger.data("sessions.json", "save", { session_id: session.session_id, outcome });
|
|
108
133
|
sessions.saveSessions(allSessions);
|
|
109
134
|
activeSessionId = null;
|
|
110
135
|
let response = `Session ${session.session_id} ended: ${outcome}\n`;
|
|
@@ -115,6 +140,7 @@ export async function handleSessionEnd(args) {
|
|
|
115
140
|
if (improvementLines.length > 0) {
|
|
116
141
|
response += `\nIMPROVEMENT SUGGESTIONS:\n${improvementLines.join("\n")}\n`;
|
|
117
142
|
}
|
|
143
|
+
logger.flow("session_end", "complete", { session_id: session.session_id, outcome, improvement_suggestions: improvementLines.length });
|
|
118
144
|
return {
|
|
119
145
|
content: [{ type: "text", text: response }],
|
|
120
146
|
};
|
|
@@ -125,9 +151,12 @@ export async function handleMemoryRead(args) {
|
|
|
125
151
|
const detailId = args?.id || null;
|
|
126
152
|
const context = args?.context || null;
|
|
127
153
|
const showAll = args?.all === true;
|
|
154
|
+
logger.flow("memory_read", "start", { project: currentProject, query, id: detailId, ids: args?.ids?.length, all: showAll, context });
|
|
128
155
|
let memory = core.loadMemory();
|
|
156
|
+
logger.data("memory.json", "load", { total_fragments: memory.length });
|
|
129
157
|
const detailIds = args?.ids || null;
|
|
130
158
|
if (detailIds && Array.isArray(detailIds) && detailIds.length > 0) {
|
|
159
|
+
logger.debug("memory_read batch_ids", { ids: detailIds });
|
|
131
160
|
const results = [];
|
|
132
161
|
for (const did of detailIds) {
|
|
133
162
|
const fragment = memory.find((f) => f.id === did);
|
|
@@ -137,18 +166,23 @@ export async function handleMemoryRead(args) {
|
|
|
137
166
|
results.push(core.formatMemoryDetail(fragment));
|
|
138
167
|
}
|
|
139
168
|
else {
|
|
169
|
+
logger.warn("memory_read batch_id not_found", { id: did });
|
|
140
170
|
results.push(`Fragment [${did}] not found.`);
|
|
141
171
|
}
|
|
142
172
|
}
|
|
173
|
+
logger.data("memory.json", "save", { reason: "boost_batch_ids", count: detailIds.length });
|
|
143
174
|
core.saveMemory(memory);
|
|
144
175
|
notifyMemoryChange();
|
|
176
|
+
logger.flow("memory_read", "complete_batch", { ids_requested: detailIds.length });
|
|
145
177
|
return {
|
|
146
178
|
content: [{ type: "text", text: results.join("\n\n") }],
|
|
147
179
|
};
|
|
148
180
|
}
|
|
149
181
|
if (detailId) {
|
|
182
|
+
logger.flow("memory_read", "single_id_lookup", { id: detailId });
|
|
150
183
|
const fragment = memory.find((f) => f.id === detailId);
|
|
151
184
|
if (!fragment) {
|
|
185
|
+
logger.warn("memory_read id not_found", { id: detailId });
|
|
152
186
|
return {
|
|
153
187
|
content: [{ type: "text", text: `Error: Fragment with ID '${detailId}' not found` }],
|
|
154
188
|
isError: true,
|
|
@@ -156,8 +190,10 @@ export async function handleMemoryRead(args) {
|
|
|
156
190
|
}
|
|
157
191
|
const boosted = core.boostOnAccess(fragment, context);
|
|
158
192
|
Object.assign(fragment, boosted);
|
|
193
|
+
logger.data("memory.json", "save", { reason: "boost_single", id: detailId });
|
|
159
194
|
core.saveMemory(memory);
|
|
160
195
|
notifyMemoryChange();
|
|
196
|
+
logger.flow("memory_read", "complete_single", { id: detailId, confidence: fragment.confidence?.toFixed(2) });
|
|
161
197
|
return {
|
|
162
198
|
content: [{ type: "text", text: core.formatMemoryDetail(fragment) }],
|
|
163
199
|
};
|
|
@@ -165,12 +201,14 @@ export async function handleMemoryRead(args) {
|
|
|
165
201
|
const filteredMemory = showAll
|
|
166
202
|
? memory
|
|
167
203
|
: core.filterByProject(memory, currentProject);
|
|
204
|
+
logger.debug("memory_read filter", { showAll, project: currentProject, before_filter: memory.length, after_filter: filteredMemory.length });
|
|
168
205
|
let results = core.searchAndSortFragments(filteredMemory, query, 30);
|
|
169
206
|
results = core.filterFragments(results, {
|
|
170
207
|
minConfidence: args?.minConfidence,
|
|
171
208
|
afterDate: args?.afterDate,
|
|
172
209
|
beforeDate: args?.beforeDate,
|
|
173
210
|
});
|
|
211
|
+
logger.flow("memory_read", "search_results", { query, result_count: results.length, minConfidence: args?.minConfidence });
|
|
174
212
|
const resultIds = new Set(results.map((r) => r.id));
|
|
175
213
|
for (const frag of memory) {
|
|
176
214
|
if (resultIds.has(frag.id)) {
|
|
@@ -180,8 +218,10 @@ export async function handleMemoryRead(args) {
|
|
|
180
218
|
}
|
|
181
219
|
const scopeInfo = showAll ? "all projects" : currentProject || "global";
|
|
182
220
|
const formatted = core.formatMemoryForLLM(results, scopeInfo);
|
|
221
|
+
logger.data("memory.json", "save", { reason: "boost_search_results", boosted_count: resultIds.size });
|
|
183
222
|
core.saveMemory(memory);
|
|
184
223
|
notifyMemoryChange();
|
|
224
|
+
logger.flow("memory_read", "complete_search", { query, results: results.length, scope: scopeInfo });
|
|
185
225
|
return {
|
|
186
226
|
content: [{ type: "text", text: formatted }],
|
|
187
227
|
};
|
|
@@ -192,15 +232,19 @@ export async function handleMemoryAdd(args) {
|
|
|
192
232
|
const description = args?.description || null;
|
|
193
233
|
const project = args?.project === undefined ? null : args.project;
|
|
194
234
|
const source = (args?.source || "ai");
|
|
235
|
+
logger.flow("memory_add", "start", { title, project, source, has_description: !!description, fragment_length: fragment?.length });
|
|
195
236
|
if (!fragment || typeof fragment !== "string") {
|
|
237
|
+
logger.warn("memory_add validation failed", { reason: "missing or invalid fragment" });
|
|
196
238
|
return {
|
|
197
239
|
content: [{ type: "text", text: "Error: 'fragment' parameter is required and must be a string" }],
|
|
198
240
|
isError: true,
|
|
199
241
|
};
|
|
200
242
|
}
|
|
243
|
+
logger.data("memory.json", "load");
|
|
201
244
|
const memory = core.loadMemory();
|
|
202
245
|
const similarMatch = core.findSimilarFragment(memory, fragment, project);
|
|
203
246
|
if (similarMatch) {
|
|
247
|
+
logger.warn("memory_add duplicate_detected", { similar_id: similarMatch.id, similar_title: similarMatch.title });
|
|
204
248
|
return {
|
|
205
249
|
content: [{
|
|
206
250
|
type: "text",
|
|
@@ -209,25 +253,35 @@ export async function handleMemoryAdd(args) {
|
|
|
209
253
|
isError: true,
|
|
210
254
|
};
|
|
211
255
|
}
|
|
256
|
+
logger.flow("memory_add", "secret_detection", { confirm: args?.confirm });
|
|
212
257
|
const { redacted, found } = core.redactSecrets(fragment);
|
|
213
258
|
const hasSecrets = found.length > 0;
|
|
214
259
|
const finalFragment = hasSecrets && !args?.confirm ? redacted : fragment;
|
|
260
|
+
if (hasSecrets) {
|
|
261
|
+
logger.warn("memory_add secrets_detected", { secret_types: found.map(f => f.type), confirmed: !!args?.confirm });
|
|
262
|
+
}
|
|
215
263
|
const newFragment = core.createFragment(finalFragment, source, title, project, description);
|
|
264
|
+
logger.flow("memory_add", "fragment_created", { id: newFragment.id, title: newFragment.title });
|
|
216
265
|
if (activeSessionId) {
|
|
266
|
+
logger.data("sessions.json", "load");
|
|
217
267
|
const allSessions = sessions.loadSessions();
|
|
218
268
|
const session = sessions.findSession(allSessions, activeSessionId);
|
|
219
269
|
if (session) {
|
|
270
|
+
logger.flow("memory_add", "session_link", { session_id: activeSessionId, task_type: session.task_type });
|
|
220
271
|
newFragment.session_id = activeSessionId;
|
|
221
272
|
newFragment.task_type = session.task_type;
|
|
222
273
|
session.memories_created = session.memories_created || [];
|
|
223
274
|
session.memories_created.push(newFragment.id);
|
|
275
|
+
logger.data("sessions.json", "save", { reason: "link_memory", session_id: activeSessionId });
|
|
224
276
|
sessions.saveSessions(allSessions);
|
|
225
277
|
}
|
|
226
278
|
}
|
|
227
279
|
memory.push(newFragment);
|
|
280
|
+
logger.data("memory.json", "save", { reason: "add_fragment", id: newFragment.id, total: memory.length });
|
|
228
281
|
core.saveMemory(memory);
|
|
229
282
|
notifyMemoryChange();
|
|
230
283
|
const overlaps = core.findTopicOverlaps(memory, finalFragment, project, 5);
|
|
284
|
+
logger.flow("memory_add", "overlap_check", { overlap_count: overlaps.length });
|
|
231
285
|
const scopeInfo = newFragment.project ? ` (project: ${newFragment.project})` : " (global)";
|
|
232
286
|
let response = `Added fragment [${newFragment.id}]${scopeInfo}: "${newFragment.title}"\nSummary: ${newFragment.description}`;
|
|
233
287
|
if (hasSecrets) {
|
|
@@ -240,6 +294,7 @@ export async function handleMemoryAdd(args) {
|
|
|
240
294
|
}
|
|
241
295
|
response += `\nConsider: Does this update or contradict existing knowledge? Use memory_update to refine or memory_merge to consolidate.`;
|
|
242
296
|
}
|
|
297
|
+
logger.flow("memory_add", "complete", { id: newFragment.id, title: newFragment.title, overlaps: overlaps.length });
|
|
243
298
|
return {
|
|
244
299
|
content: [{ type: "text", text: response }],
|
|
245
300
|
};
|
|
@@ -249,15 +304,19 @@ export async function handleMemoryUpdate(args) {
|
|
|
249
304
|
const title = args?.title;
|
|
250
305
|
const fragment = args?.fragment;
|
|
251
306
|
const confidence = args?.confidence;
|
|
307
|
+
logger.flow("memory_update", "start", { id, has_title: title !== undefined, has_fragment: fragment !== undefined, has_confidence: confidence !== undefined });
|
|
252
308
|
if (!id || typeof id !== "string") {
|
|
309
|
+
logger.warn("memory_update validation failed", { reason: "missing or invalid id" });
|
|
253
310
|
return {
|
|
254
311
|
content: [{ type: "text", text: "Error: 'id' parameter is required and must be a string" }],
|
|
255
312
|
isError: true,
|
|
256
313
|
};
|
|
257
314
|
}
|
|
315
|
+
logger.data("memory.json", "load");
|
|
258
316
|
const memory = core.loadMemory();
|
|
259
317
|
const targetIndex = memory.findIndex((f) => f.id === id);
|
|
260
318
|
if (targetIndex === -1) {
|
|
319
|
+
logger.warn("memory_update fragment not_found", { id });
|
|
261
320
|
return {
|
|
262
321
|
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
263
322
|
isError: true,
|
|
@@ -265,57 +324,71 @@ export async function handleMemoryUpdate(args) {
|
|
|
265
324
|
}
|
|
266
325
|
if (title !== undefined) {
|
|
267
326
|
if (typeof title !== "string") {
|
|
327
|
+
logger.warn("memory_update validation failed", { reason: "title not string" });
|
|
268
328
|
return {
|
|
269
329
|
content: [{ type: "text", text: "Error: 'title' must be a string" }],
|
|
270
330
|
isError: true,
|
|
271
331
|
};
|
|
272
332
|
}
|
|
333
|
+
logger.debug("memory_update updating_title", { id, new_title: title });
|
|
273
334
|
memory[targetIndex].title = title;
|
|
274
335
|
}
|
|
275
336
|
if (fragment !== undefined) {
|
|
276
337
|
if (typeof fragment !== "string") {
|
|
338
|
+
logger.warn("memory_update validation failed", { reason: "fragment not string" });
|
|
277
339
|
return {
|
|
278
340
|
content: [{ type: "text", text: "Error: 'fragment' must be a string" }],
|
|
279
341
|
isError: true,
|
|
280
342
|
};
|
|
281
343
|
}
|
|
344
|
+
logger.debug("memory_update updating_fragment", { id });
|
|
282
345
|
memory[targetIndex].fragment = fragment;
|
|
283
346
|
memory[targetIndex].accessed++;
|
|
284
347
|
}
|
|
285
348
|
if (confidence !== undefined) {
|
|
286
349
|
if (typeof confidence !== "number" || confidence < 0 || confidence > 1) {
|
|
350
|
+
logger.warn("memory_update validation failed", { reason: "confidence out of range", confidence });
|
|
287
351
|
return {
|
|
288
352
|
content: [{ type: "text", text: "Error: 'confidence' must be a number between 0 and 1" }],
|
|
289
353
|
isError: true,
|
|
290
354
|
};
|
|
291
355
|
}
|
|
356
|
+
logger.debug("memory_update updating_confidence", { id, new_confidence: confidence });
|
|
292
357
|
memory[targetIndex].confidence = confidence;
|
|
293
358
|
}
|
|
359
|
+
logger.data("memory.json", "save", { reason: "update_fragment", id });
|
|
294
360
|
core.saveMemory(memory);
|
|
295
361
|
notifyMemoryChange();
|
|
362
|
+
logger.flow("memory_update", "complete", { id, title: memory[targetIndex].title });
|
|
296
363
|
return {
|
|
297
364
|
content: [{ type: "text", text: `Updated fragment [${id}]: "${memory[targetIndex].title}"` }],
|
|
298
365
|
};
|
|
299
366
|
}
|
|
300
367
|
export async function handleMemoryForget(args) {
|
|
301
368
|
const id = args?.id;
|
|
369
|
+
logger.flow("memory_forget", "start", { id });
|
|
302
370
|
if (!id || typeof id !== "string") {
|
|
371
|
+
logger.warn("memory_forget validation failed", { reason: "missing or invalid id" });
|
|
303
372
|
return {
|
|
304
373
|
content: [{ type: "text", text: "Error: 'id' parameter is required and must be a string" }],
|
|
305
374
|
isError: true,
|
|
306
375
|
};
|
|
307
376
|
}
|
|
377
|
+
logger.data("memory.json", "load");
|
|
308
378
|
const memory = core.loadMemory();
|
|
309
379
|
const initialLength = memory.length;
|
|
310
380
|
const filtered = memory.filter((f) => f.id !== id);
|
|
311
381
|
if (filtered.length === initialLength) {
|
|
382
|
+
logger.warn("memory_forget fragment not_found", { id });
|
|
312
383
|
return {
|
|
313
384
|
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
314
385
|
isError: true,
|
|
315
386
|
};
|
|
316
387
|
}
|
|
388
|
+
logger.data("memory.json", "save", { reason: "forget_fragment", id, before: initialLength, after: filtered.length });
|
|
317
389
|
core.saveMemory(filtered, { force: true });
|
|
318
390
|
notifyMemoryChange();
|
|
391
|
+
logger.flow("memory_forget", "complete", { id });
|
|
319
392
|
return {
|
|
320
393
|
content: [{ type: "text", text: `Forgot fragment with ID: ${id}` }],
|
|
321
394
|
};
|
|
@@ -323,21 +396,26 @@ export async function handleMemoryForget(args) {
|
|
|
323
396
|
export async function handleMemoryFeedback(args) {
|
|
324
397
|
const id = args?.id;
|
|
325
398
|
const useful = args?.useful;
|
|
399
|
+
logger.flow("memory_feedback", "start", { id, useful });
|
|
326
400
|
if (!id || typeof id !== "string") {
|
|
401
|
+
logger.warn("memory_feedback validation failed", { reason: "missing or invalid id" });
|
|
327
402
|
return {
|
|
328
403
|
content: [{ type: "text", text: "Error: 'id' parameter is required" }],
|
|
329
404
|
isError: true,
|
|
330
405
|
};
|
|
331
406
|
}
|
|
332
407
|
if (typeof useful !== "boolean") {
|
|
408
|
+
logger.warn("memory_feedback validation failed", { reason: "missing or invalid useful" });
|
|
333
409
|
return {
|
|
334
410
|
content: [{ type: "text", text: "Error: 'useful' parameter is required and must be a boolean" }],
|
|
335
411
|
isError: true,
|
|
336
412
|
};
|
|
337
413
|
}
|
|
414
|
+
logger.data("memory.json", "load");
|
|
338
415
|
const memory = core.loadMemory();
|
|
339
416
|
const targetIndex = memory.findIndex((f) => f.id === id);
|
|
340
417
|
if (targetIndex === -1) {
|
|
418
|
+
logger.warn("memory_feedback fragment not_found", { id });
|
|
341
419
|
return {
|
|
342
420
|
content: [{ type: "text", text: `Error: Fragment with ID '${id}' not found` }],
|
|
343
421
|
isError: true,
|
|
@@ -347,8 +425,10 @@ export async function handleMemoryFeedback(args) {
|
|
|
347
425
|
const boosted = core.boostOnAccess(memory[targetIndex]);
|
|
348
426
|
Object.assign(memory[targetIndex], boosted);
|
|
349
427
|
memory[targetIndex].positive_feedback = (memory[targetIndex].positive_feedback || 0) + 1;
|
|
428
|
+
logger.data("memory.json", "save", { reason: "positive_feedback", id, new_confidence: memory[targetIndex].confidence?.toFixed(2) });
|
|
350
429
|
core.saveMemory(memory);
|
|
351
430
|
notifyMemoryChange();
|
|
431
|
+
logger.flow("memory_feedback", "complete_positive", { id, confidence: memory[targetIndex].confidence?.toFixed(2) });
|
|
352
432
|
return {
|
|
353
433
|
content: [{ type: "text", text: `Positive feedback recorded for [${id}]. Confidence boosted to ${memory[targetIndex].confidence.toFixed(2)}.` }],
|
|
354
434
|
};
|
|
@@ -357,8 +437,10 @@ export async function handleMemoryFeedback(args) {
|
|
|
357
437
|
const penalized = core.recordNegativeHit(memory[targetIndex]);
|
|
358
438
|
Object.assign(memory[targetIndex], penalized);
|
|
359
439
|
memory[targetIndex].negative_feedback = (memory[targetIndex].negative_feedback || 0) + 1;
|
|
440
|
+
logger.data("memory.json", "save", { reason: "negative_feedback", id, new_confidence: memory[targetIndex].confidence?.toFixed(2) });
|
|
360
441
|
core.saveMemory(memory);
|
|
361
442
|
notifyMemoryChange();
|
|
443
|
+
logger.flow("memory_feedback", "complete_negative", { id, confidence: memory[targetIndex].confidence?.toFixed(2) });
|
|
362
444
|
return {
|
|
363
445
|
content: [{ type: "text", text: `Negative feedback recorded for [${id}]. Confidence reduced to ${memory[targetIndex].confidence.toFixed(2)}.` }],
|
|
364
446
|
};
|
|
@@ -369,38 +451,47 @@ export async function handleMemoryMerge(args) {
|
|
|
369
451
|
const title = args?.title;
|
|
370
452
|
const fragment = args?.fragment;
|
|
371
453
|
const project = args?.project === undefined ? null : args.project;
|
|
454
|
+
logger.flow("memory_merge", "start", { ids, title, project });
|
|
372
455
|
if (!ids || !Array.isArray(ids) || ids.length < 2) {
|
|
456
|
+
logger.warn("memory_merge validation failed", { reason: "ids must be array with at least 2 elements" });
|
|
373
457
|
return {
|
|
374
458
|
content: [{ type: "text", text: "Error: 'ids' must be an array with at least 2 fragment IDs" }],
|
|
375
459
|
isError: true,
|
|
376
460
|
};
|
|
377
461
|
}
|
|
378
462
|
if (!title || typeof title !== "string") {
|
|
463
|
+
logger.warn("memory_merge validation failed", { reason: "title required" });
|
|
379
464
|
return {
|
|
380
465
|
content: [{ type: "text", text: "Error: 'title' is required and must be a string" }],
|
|
381
466
|
isError: true,
|
|
382
467
|
};
|
|
383
468
|
}
|
|
384
469
|
if (!fragment || typeof fragment !== "string") {
|
|
470
|
+
logger.warn("memory_merge validation failed", { reason: "fragment required" });
|
|
385
471
|
return {
|
|
386
472
|
content: [{ type: "text", text: "Error: 'fragment' is required and must be a string" }],
|
|
387
473
|
isError: true,
|
|
388
474
|
};
|
|
389
475
|
}
|
|
476
|
+
logger.data("memory.json", "load");
|
|
390
477
|
const memory = core.loadMemory();
|
|
391
478
|
const notFound = ids.filter((id) => !memory.find((f) => f.id === id));
|
|
392
479
|
if (notFound.length > 0) {
|
|
480
|
+
logger.warn("memory_merge fragments not_found", { missing: notFound });
|
|
393
481
|
return {
|
|
394
482
|
content: [{ type: "text", text: `Error: Fragment(s) not found: ${notFound.join(", ")}` }],
|
|
395
483
|
isError: true,
|
|
396
484
|
};
|
|
397
485
|
}
|
|
398
486
|
const newFragment = core.createFragment(fragment, "ai", title, project);
|
|
487
|
+
logger.flow("memory_merge", "merged_fragment_created", { new_id: newFragment.id, title });
|
|
399
488
|
memory.push(newFragment);
|
|
400
489
|
const mergedMemory = memory.filter((f) => !ids.includes(f.id));
|
|
490
|
+
logger.data("memory.json", "save", { reason: "merge_fragments", new_id: newFragment.id, removed_ids: ids, before: memory.length, after: mergedMemory.length });
|
|
401
491
|
core.saveMemory(mergedMemory);
|
|
402
492
|
notifyMemoryChange();
|
|
403
493
|
const scopeInfo = newFragment.project ? ` (project: ${newFragment.project})` : " (global)";
|
|
494
|
+
logger.flow("memory_merge", "complete", { new_id: newFragment.id, merged_count: ids.length });
|
|
404
495
|
return {
|
|
405
496
|
content: [{ type: "text", text: `Merged ${ids.length} fragments into [${newFragment.id}]${scopeInfo}: "${newFragment.title}"\nRemoved IDs: ${ids.join(", ")}` }],
|
|
406
497
|
};
|
|
@@ -410,7 +501,9 @@ export async function handleMemoryRelate(args) {
|
|
|
410
501
|
const targetId = args?.targetId;
|
|
411
502
|
const type = args?.type;
|
|
412
503
|
const note = args?.note;
|
|
504
|
+
logger.flow("memory_relate", "start", { sourceId, targetId, type, has_note: !!note });
|
|
413
505
|
if (!sourceId || !targetId || !type) {
|
|
506
|
+
logger.warn("memory_relate validation failed", { reason: "missing required params" });
|
|
414
507
|
return {
|
|
415
508
|
content: [{ type: "text", text: "Error: 'sourceId', 'targetId', and 'type' parameters are required" }],
|
|
416
509
|
isError: true,
|
|
@@ -418,25 +511,30 @@ export async function handleMemoryRelate(args) {
|
|
|
418
511
|
}
|
|
419
512
|
const validTypes = ["contradicts", "supersedes", "supports", "related_to"];
|
|
420
513
|
if (!validTypes.includes(type)) {
|
|
514
|
+
logger.warn("memory_relate validation failed", { reason: "invalid type", type });
|
|
421
515
|
return {
|
|
422
516
|
content: [{ type: "text", text: `Error: 'type' must be one of: ${validTypes.join(", ")}` }],
|
|
423
517
|
isError: true,
|
|
424
518
|
};
|
|
425
519
|
}
|
|
426
520
|
if (sourceId === targetId) {
|
|
521
|
+
logger.warn("memory_relate validation failed", { reason: "sourceId equals targetId" });
|
|
427
522
|
return {
|
|
428
523
|
content: [{ type: "text", text: "Error: sourceId and targetId cannot be the same" }],
|
|
429
524
|
isError: true,
|
|
430
525
|
};
|
|
431
526
|
}
|
|
527
|
+
logger.data("memory.json", "load");
|
|
432
528
|
const memory = core.loadMemory();
|
|
433
529
|
if (!memory.find((f) => f.id === sourceId)) {
|
|
530
|
+
logger.warn("memory_relate source not_found", { sourceId });
|
|
434
531
|
return {
|
|
435
532
|
content: [{ type: "text", text: `Error: Source fragment [${sourceId}] not found` }],
|
|
436
533
|
isError: true,
|
|
437
534
|
};
|
|
438
535
|
}
|
|
439
536
|
if (!memory.find((f) => f.id === targetId)) {
|
|
537
|
+
logger.warn("memory_relate target not_found", { targetId });
|
|
440
538
|
return {
|
|
441
539
|
content: [{ type: "text", text: `Error: Target fragment [${targetId}] not found` }],
|
|
442
540
|
isError: true,
|
|
@@ -444,13 +542,16 @@ export async function handleMemoryRelate(args) {
|
|
|
444
542
|
}
|
|
445
543
|
const success = core.addRelation(memory, sourceId, targetId, type, note || undefined);
|
|
446
544
|
if (!success) {
|
|
545
|
+
logger.warn("memory_relate relation_exists", { sourceId, targetId, type });
|
|
447
546
|
return {
|
|
448
547
|
content: [{ type: "text", text: `Relation already exists between [${sourceId}] and [${targetId}] with type '${type}'` }],
|
|
449
548
|
isError: true,
|
|
450
549
|
};
|
|
451
550
|
}
|
|
551
|
+
logger.data("memory.json", "save", { reason: "add_relation", sourceId, targetId, type });
|
|
452
552
|
core.saveMemory(memory);
|
|
453
553
|
notifyMemoryChange();
|
|
554
|
+
logger.flow("memory_relate", "complete", { sourceId, targetId, type });
|
|
454
555
|
return {
|
|
455
556
|
content: [{ type: "text", text: `Created relation: [${sourceId}] --${type}--> [${targetId}]${note ? ` (${note})` : ""}` }],
|
|
456
557
|
};
|
|
@@ -459,16 +560,21 @@ export async function handleGuideGet(args) {
|
|
|
459
560
|
const category = args?.category || null;
|
|
460
561
|
const guideName = args?.guide || null;
|
|
461
562
|
const task = args?.task || null;
|
|
563
|
+
logger.flow("guide_get", "start", { category, guide: guideName, task });
|
|
564
|
+
logger.data("guides.json", "load");
|
|
462
565
|
const allGuides = guides.loadGuides();
|
|
463
566
|
if (task) {
|
|
464
567
|
const result = guides.suggestGuides(task, allGuides);
|
|
568
|
+
logger.flow("guide_get", "task_suggestions", { task, relevant: result.relevant.length, suggested: result.suggested.length });
|
|
465
569
|
const formatted = guides.formatSuggestions(result);
|
|
466
570
|
return {
|
|
467
571
|
content: [{ type: "text", text: formatted }],
|
|
468
572
|
};
|
|
469
573
|
}
|
|
470
574
|
if (guideName) {
|
|
575
|
+
logger.flow("guide_get", "single_guide_lookup", { guide: guideName });
|
|
471
576
|
const guide = guides.findGuide(allGuides, guideName);
|
|
577
|
+
logger.flow("guide_get", "complete_single", { guide: guideName, found: !!guide });
|
|
472
578
|
return {
|
|
473
579
|
content: [{ type: "text", text: guides.formatGuideDetail(guide) }],
|
|
474
580
|
};
|
|
@@ -476,6 +582,7 @@ export async function handleGuideGet(args) {
|
|
|
476
582
|
const filtered = category
|
|
477
583
|
? guides.getGuidesByCategory(allGuides, category)
|
|
478
584
|
: allGuides;
|
|
585
|
+
logger.flow("guide_get", "complete_list", { category, total: allGuides.length, filtered: filtered.length });
|
|
479
586
|
const formatted = guides.formatGuidesForLLM(filtered);
|
|
480
587
|
return {
|
|
481
588
|
content: [{ type: "text", text: formatted }],
|
|
@@ -487,15 +594,21 @@ export async function handleGuidePractice(args) {
|
|
|
487
594
|
const description = args?.description || "";
|
|
488
595
|
const contexts = args?.contexts || [];
|
|
489
596
|
const learnings = args?.learnings || [];
|
|
597
|
+
logger.flow("guide_practice", "start", { guide: guideName, category, outcome: args?.outcome, context_count: contexts.length, learning_count: learnings.length });
|
|
490
598
|
if (!guideName || !category) {
|
|
599
|
+
logger.warn("guide_practice validation failed", { reason: "missing guide or category" });
|
|
491
600
|
return {
|
|
492
601
|
content: [{ type: "text", text: "Error: 'guide' and 'category' parameters are required" }],
|
|
493
602
|
isError: true,
|
|
494
603
|
};
|
|
495
604
|
}
|
|
605
|
+
logger.data("guides.json", "load");
|
|
496
606
|
const allGuides = guides.loadGuides();
|
|
607
|
+
const preUsageCount = guides.findGuide(allGuides, guideName)?.usage_count || 0;
|
|
497
608
|
const updated = guides.practiceGuide(allGuides, guideName, category, description, contexts, learnings, args?.outcome);
|
|
609
|
+
logger.flow("guide_practice", "guide_updated", { guide: guideName, usage_before: preUsageCount, usage_after: updated.usage_count });
|
|
498
610
|
if (activeSessionId) {
|
|
611
|
+
logger.data("sessions.json", "load");
|
|
499
612
|
const allSessions = sessions.loadSessions();
|
|
500
613
|
const session = sessions.findSession(allSessions, activeSessionId);
|
|
501
614
|
if (session) {
|
|
@@ -504,13 +617,16 @@ export async function handleGuidePractice(args) {
|
|
|
504
617
|
if (!session.guides_used.includes(guideName.toLowerCase())) {
|
|
505
618
|
session.guides_used.push(guideName.toLowerCase());
|
|
506
619
|
}
|
|
620
|
+
logger.data("sessions.json", "save", { reason: "track_guide_practice", session_id: activeSessionId, guide: guideName });
|
|
507
621
|
sessions.saveSessions(allSessions);
|
|
508
622
|
}
|
|
509
623
|
}
|
|
624
|
+
logger.data("guides.json", "save", { reason: "practice_guide", guide: guideName });
|
|
510
625
|
guides.saveGuides(allGuides);
|
|
511
626
|
const isNew = updated.usage_count === 1;
|
|
512
627
|
const action = isNew ? "Created" : "Updated";
|
|
513
628
|
const response = `${action} guide "${updated.guide}" (${updated.category}): ${updated.usage_count}x usage, ${updated.learnings.length} learnings, ${updated.contexts.length} contexts`;
|
|
629
|
+
logger.flow("guide_practice", "complete", { guide: guideName, action, usage_count: updated.usage_count });
|
|
514
630
|
return {
|
|
515
631
|
content: [{ type: "text", text: response }],
|
|
516
632
|
};
|
|
@@ -521,16 +637,21 @@ export async function handleGuideCreate(args) {
|
|
|
521
637
|
const description = args?.description;
|
|
522
638
|
const contexts = args?.contexts || [];
|
|
523
639
|
const learnings = args?.learnings || [];
|
|
640
|
+
logger.flow("guide_create", "start", { guide: guideName, category, has_description: !!description });
|
|
524
641
|
if (!guideName || !category || !description) {
|
|
642
|
+
logger.warn("guide_create validation failed", { reason: "missing guide, category, or description" });
|
|
525
643
|
return {
|
|
526
644
|
content: [{ type: "text", text: "Error: 'guide', 'category', and 'description' parameters are required" }],
|
|
527
645
|
isError: true,
|
|
528
646
|
};
|
|
529
647
|
}
|
|
648
|
+
logger.data("guides.json", "load");
|
|
530
649
|
const allGuides = guides.loadGuides();
|
|
531
650
|
const existing = guides.findSimilarGuide(allGuides, guideName);
|
|
532
651
|
if (existing) {
|
|
652
|
+
logger.flow("guide_create", "updating_existing", { guide: guideName, existing_id: existing.guide });
|
|
533
653
|
existing.description = description;
|
|
654
|
+
logger.data("guides.json", "save", { reason: "update_existing_guide", guide: guideName });
|
|
534
655
|
guides.saveGuides(allGuides);
|
|
535
656
|
return {
|
|
536
657
|
content: [{ type: "text", text: `Updated manual for existing guide "${existing.guide}" (${existing.category})` }],
|
|
@@ -538,7 +659,9 @@ export async function handleGuideCreate(args) {
|
|
|
538
659
|
}
|
|
539
660
|
const newGuide = guides.createGuide(guideName, category, description, contexts, learnings);
|
|
540
661
|
allGuides.push(newGuide);
|
|
662
|
+
logger.data("guides.json", "save", { reason: "create_new_guide", guide: guideName, total: allGuides.length });
|
|
541
663
|
guides.saveGuides(allGuides);
|
|
664
|
+
logger.flow("guide_create", "complete", { guide: guideName, category, is_new: true });
|
|
542
665
|
return {
|
|
543
666
|
content: [{ type: "text", text: `Created new guide "${newGuide.guide}" (${newGuide.category}) with a detailed manual.` }],
|
|
544
667
|
};
|
|
@@ -547,25 +670,33 @@ export async function handleGuideDistill(args) {
|
|
|
547
670
|
const memoryId = args?.memory_id;
|
|
548
671
|
const guideName = args?.guide;
|
|
549
672
|
const category = args?.category || "dev-tool";
|
|
673
|
+
logger.flow("guide_distill", "start", { memory_id: memoryId, guide: guideName, category });
|
|
550
674
|
if (!memoryId || !guideName) {
|
|
675
|
+
logger.warn("guide_distill validation failed", { reason: "missing memory_id or guide" });
|
|
551
676
|
return {
|
|
552
677
|
content: [{ type: "text", text: "Error: 'memory_id' and 'guide' parameters are required" }],
|
|
553
678
|
isError: true,
|
|
554
679
|
};
|
|
555
680
|
}
|
|
681
|
+
logger.data("memory.json", "load");
|
|
556
682
|
const allMemory = core.loadMemory();
|
|
557
683
|
const fragment = allMemory.find((m) => m.id === memoryId);
|
|
558
684
|
if (!fragment) {
|
|
685
|
+
logger.warn("guide_distill memory not_found", { memory_id: memoryId });
|
|
559
686
|
return {
|
|
560
687
|
content: [{ type: "text", text: `Error: Memory fragment with ID '${memoryId}' not found.` }],
|
|
561
688
|
isError: true,
|
|
562
689
|
};
|
|
563
690
|
}
|
|
691
|
+
logger.flow("guide_distill", "fragment_found", { memory_id: memoryId, title: fragment.title });
|
|
692
|
+
logger.data("guides.json", "load");
|
|
564
693
|
const allGuides = guides.loadGuides();
|
|
565
694
|
const updated = guides.promoteToGuide(allGuides, guideName, category, fragment.fragment, fragment.project || "global");
|
|
695
|
+
logger.data("guides.json", "save", { reason: "distill_memory_to_guide", memory_id: memoryId, guide: guideName });
|
|
566
696
|
guides.saveGuides(allGuides);
|
|
567
697
|
let response = `Successfully distilled memory [${memoryId}] into guide "${updated.guide}" (${updated.category}).\n\n`;
|
|
568
698
|
response += guides.formatGuideDetail(updated);
|
|
699
|
+
logger.flow("guide_distill", "complete", { memory_id: memoryId, guide: guideName, category });
|
|
569
700
|
return {
|
|
570
701
|
content: [{ type: "text", text: response }],
|
|
571
702
|
};
|
|
@@ -581,42 +712,55 @@ export async function handleGuideUpdate(args) {
|
|
|
581
712
|
superseded_by: args?.superseded_by,
|
|
582
713
|
deprecated: args?.deprecated,
|
|
583
714
|
};
|
|
715
|
+
const fieldsToUpdate = Object.entries(updates).filter(([, v]) => v !== undefined).map(([k]) => k);
|
|
716
|
+
logger.flow("guide_update", "start", { guide: guideName, fields: fieldsToUpdate });
|
|
584
717
|
if (!guideName) {
|
|
718
|
+
logger.warn("guide_update validation failed", { reason: "missing guide name" });
|
|
585
719
|
return {
|
|
586
720
|
content: [{ type: "text", text: "Error: 'guide' parameter is required" }],
|
|
587
721
|
isError: true,
|
|
588
722
|
};
|
|
589
723
|
}
|
|
724
|
+
logger.data("guides.json", "load");
|
|
590
725
|
const allGuides = guides.loadGuides();
|
|
591
726
|
const updated = guides.updateGuide(allGuides, guideName, updates);
|
|
592
727
|
if (!updated) {
|
|
728
|
+
logger.warn("guide_update guide not_found", { guide: guideName });
|
|
593
729
|
return {
|
|
594
730
|
content: [{ type: "text", text: `Error: Guide "${guideName}" not found.` }],
|
|
595
731
|
isError: true,
|
|
596
732
|
};
|
|
597
733
|
}
|
|
734
|
+
logger.data("guides.json", "save", { reason: "update_guide", guide: guideName });
|
|
598
735
|
guides.saveGuides(allGuides);
|
|
736
|
+
logger.flow("guide_update", "complete", { guide: guideName, updated_fields: fieldsToUpdate });
|
|
599
737
|
return {
|
|
600
738
|
content: [{ type: "text", text: `Updated guide "${updated.guide}":\n${guides.formatGuideDetail(updated)}` }],
|
|
601
739
|
};
|
|
602
740
|
}
|
|
603
741
|
export async function handleGuideForget(args) {
|
|
604
742
|
const guideName = args?.guide;
|
|
743
|
+
logger.flow("guide_forget", "start", { guide: guideName });
|
|
605
744
|
if (!guideName) {
|
|
745
|
+
logger.warn("guide_forget validation failed", { reason: "missing guide name" });
|
|
606
746
|
return {
|
|
607
747
|
content: [{ type: "text", text: "Error: 'guide' parameter is required" }],
|
|
608
748
|
isError: true,
|
|
609
749
|
};
|
|
610
750
|
}
|
|
751
|
+
logger.data("guides.json", "load");
|
|
611
752
|
const allGuides = guides.loadGuides();
|
|
612
753
|
const success = guides.deleteGuide(allGuides, guideName);
|
|
613
754
|
if (!success) {
|
|
755
|
+
logger.warn("guide_forget guide not_found", { guide: guideName });
|
|
614
756
|
return {
|
|
615
757
|
content: [{ type: "text", text: `Error: Guide "${guideName}" not found.` }],
|
|
616
758
|
isError: true,
|
|
617
759
|
};
|
|
618
760
|
}
|
|
761
|
+
logger.data("guides.json", "save", { reason: "forget_guide", guide: guideName, remaining: allGuides.length });
|
|
619
762
|
guides.saveGuides(allGuides, { force: true });
|
|
763
|
+
logger.flow("guide_forget", "complete", { guide: guideName });
|
|
620
764
|
return {
|
|
621
765
|
content: [{ type: "text", text: `Successfully forgot guide: ${guideName}` }],
|
|
622
766
|
};
|
|
@@ -628,18 +772,22 @@ export async function handleGuideMerge(args) {
|
|
|
628
772
|
const description = args?.description || "";
|
|
629
773
|
let contexts = args?.contexts;
|
|
630
774
|
let learnings = args?.learnings;
|
|
775
|
+
logger.flow("guide_merge", "start", { guides: guideNames, new_guide: newGuideName, category });
|
|
631
776
|
if (!guideNames || !Array.isArray(guideNames) || guideNames.length < 2) {
|
|
777
|
+
logger.warn("guide_merge validation failed", { reason: "guides must be array with at least 2 elements" });
|
|
632
778
|
return {
|
|
633
779
|
content: [{ type: "text", text: "Error: 'guides' must be an array with at least 2 guide names" }],
|
|
634
780
|
isError: true,
|
|
635
781
|
};
|
|
636
782
|
}
|
|
637
783
|
if (!newGuideName || !category) {
|
|
784
|
+
logger.warn("guide_merge validation failed", { reason: "missing guide name or category" });
|
|
638
785
|
return {
|
|
639
786
|
content: [{ type: "text", text: "Error: 'guide' and 'category' parameters are required" }],
|
|
640
787
|
isError: true,
|
|
641
788
|
};
|
|
642
789
|
}
|
|
790
|
+
logger.data("guides.json", "load");
|
|
643
791
|
const allGuides = guides.loadGuides();
|
|
644
792
|
const sourceGuides = [];
|
|
645
793
|
const notFound = [];
|
|
@@ -653,6 +801,7 @@ export async function handleGuideMerge(args) {
|
|
|
653
801
|
}
|
|
654
802
|
}
|
|
655
803
|
if (notFound.length > 0) {
|
|
804
|
+
logger.warn("guide_merge guides not_found", { missing: notFound });
|
|
656
805
|
return {
|
|
657
806
|
content: [{ type: "text", text: `Error: Guide(s) not found: ${notFound.join(", ")}` }],
|
|
658
807
|
isError: true,
|
|
@@ -667,39 +816,50 @@ export async function handleGuideMerge(args) {
|
|
|
667
816
|
const antiPatterns = [...new Set(sourceGuides.flatMap((g) => g.anti_patterns || []))];
|
|
668
817
|
const pitfalls = [...new Set(sourceGuides.flatMap((g) => g.known_pitfalls || []))];
|
|
669
818
|
const totalUsage = sourceGuides.reduce((sum, g) => sum + g.usage_count, 0);
|
|
819
|
+
logger.flow("guide_merge", "source_stats", { source_count: sourceGuides.length, total_usage: totalUsage });
|
|
670
820
|
const newGuide = guides.createGuide(newGuideName, category, description, contexts, learnings);
|
|
671
821
|
newGuide.usage_count = totalUsage;
|
|
672
822
|
newGuide.anti_patterns = antiPatterns;
|
|
673
823
|
newGuide.known_pitfalls = pitfalls;
|
|
674
824
|
allGuides.push(newGuide);
|
|
675
825
|
const mergedGuides = allGuides.filter((g) => !guideNames.map((n) => n.toLowerCase()).includes(g.guide));
|
|
826
|
+
logger.data("guides.json", "save", { reason: "merge_guides", new_guide: newGuideName, removed: guideNames, total_usage: totalUsage });
|
|
676
827
|
guides.saveGuides(mergedGuides);
|
|
677
828
|
let response = `Merged ${guideNames.length} guides into "${newGuide.guide}" (${newGuide.category})\n`;
|
|
678
829
|
response += `Total usage: ${totalUsage}x | Contexts: ${contexts.length} | Learnings: ${learnings.length}\n`;
|
|
679
830
|
response += `Removed: ${guideNames.join(", ")}`;
|
|
831
|
+
logger.flow("guide_merge", "complete", { new_guide: newGuideName, merged_count: guideNames.length, total_usage: totalUsage });
|
|
680
832
|
return {
|
|
681
833
|
content: [{ type: "text", text: response }],
|
|
682
834
|
};
|
|
683
835
|
}
|
|
684
836
|
export async function handleMemoryStats(args) {
|
|
685
837
|
const project = args?.project || null;
|
|
838
|
+
logger.flow("memory_stats", "start", { project });
|
|
839
|
+
logger.data("memory.json", "load");
|
|
686
840
|
const memory = core.loadMemory();
|
|
687
841
|
const stats = core.calculateStats(memory, project);
|
|
842
|
+
logger.flow("memory_stats", "complete", { project, total: stats.total, avg_confidence: stats.avg_confidence?.toFixed(2) });
|
|
688
843
|
return {
|
|
689
844
|
content: [{ type: "text", text: core.formatStats(stats) }],
|
|
690
845
|
};
|
|
691
846
|
}
|
|
692
847
|
export async function handleMemoryAudit(_args) {
|
|
848
|
+
logger.flow("memory_audit", "start");
|
|
849
|
+
logger.data("memory.json", "load");
|
|
693
850
|
const memory = core.loadMemory();
|
|
694
851
|
const result = core.auditMemory(memory);
|
|
852
|
+
logger.flow("memory_audit", "complete", { issues_count: result.issues?.length || 0 });
|
|
695
853
|
return {
|
|
696
854
|
content: [{ type: "text", text: core.formatAuditReport(result) }],
|
|
697
855
|
};
|
|
698
856
|
}
|
|
699
857
|
export async function handleSessionStats(args) {
|
|
700
858
|
const count = args?.count || 10;
|
|
859
|
+
logger.flow("session_stats", "start", { count });
|
|
701
860
|
const recentSessions = virtualSession.getRecentSessions(count);
|
|
702
861
|
const current = virtualSession.getCurrentVirtualSession();
|
|
862
|
+
logger.debug("session_stats data", { requested: count, recent_count: recentSessions.length, has_current: !!current });
|
|
703
863
|
let output = `## Session Stats\n`;
|
|
704
864
|
if (current) {
|
|
705
865
|
output += `Active session: ${current.tool_calls.length} tool calls\n`;
|
|
@@ -721,60 +881,127 @@ export async function handleSessionStats(args) {
|
|
|
721
881
|
else {
|
|
722
882
|
output += `No past sessions recorded yet.\n`;
|
|
723
883
|
}
|
|
884
|
+
logger.flow("session_stats", "complete", { count, recent_count: recentSessions.length, has_current: !!current });
|
|
724
885
|
return { content: [{ type: "text", text: output }] };
|
|
725
886
|
}
|
|
726
887
|
export async function handleCallTool(request) {
|
|
727
888
|
const { name, arguments: args } = request.params;
|
|
889
|
+
logger.request(name, args);
|
|
890
|
+
const startTime = Date.now();
|
|
728
891
|
try {
|
|
729
892
|
switch (name) {
|
|
730
|
-
case "session_start":
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
return
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
case "
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
return
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
case "
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
return
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
case "
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
return
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
893
|
+
case "session_start": {
|
|
894
|
+
const result = await handleSessionStart(args);
|
|
895
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
896
|
+
return result;
|
|
897
|
+
}
|
|
898
|
+
case "session_end": {
|
|
899
|
+
const result = await handleSessionEnd(args);
|
|
900
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
903
|
+
case "memory_read": {
|
|
904
|
+
const result = await handleMemoryRead(args);
|
|
905
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
case "memory_add": {
|
|
909
|
+
const result = await handleMemoryAdd(args);
|
|
910
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
911
|
+
return result;
|
|
912
|
+
}
|
|
913
|
+
case "memory_update": {
|
|
914
|
+
const result = await handleMemoryUpdate(args);
|
|
915
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
916
|
+
return result;
|
|
917
|
+
}
|
|
918
|
+
case "memory_forget": {
|
|
919
|
+
const result = await handleMemoryForget(args);
|
|
920
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
921
|
+
return result;
|
|
922
|
+
}
|
|
923
|
+
case "memory_feedback": {
|
|
924
|
+
const result = await handleMemoryFeedback(args);
|
|
925
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
case "memory_merge": {
|
|
929
|
+
const result = await handleMemoryMerge(args);
|
|
930
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
931
|
+
return result;
|
|
932
|
+
}
|
|
933
|
+
case "memory_relate": {
|
|
934
|
+
const result = await handleMemoryRelate(args);
|
|
935
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
936
|
+
return result;
|
|
937
|
+
}
|
|
938
|
+
case "memory_stats": {
|
|
939
|
+
const result = await handleMemoryStats(args);
|
|
940
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
941
|
+
return result;
|
|
942
|
+
}
|
|
943
|
+
case "memory_audit": {
|
|
944
|
+
const result = await handleMemoryAudit(args);
|
|
945
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
case "guide_get": {
|
|
949
|
+
const result = await handleGuideGet(args);
|
|
950
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
951
|
+
return result;
|
|
952
|
+
}
|
|
953
|
+
case "guide_practice": {
|
|
954
|
+
const result = await handleGuidePractice(args);
|
|
955
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
956
|
+
return result;
|
|
957
|
+
}
|
|
958
|
+
case "guide_create": {
|
|
959
|
+
const result = await handleGuideCreate(args);
|
|
960
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
961
|
+
return result;
|
|
962
|
+
}
|
|
963
|
+
case "guide_distill": {
|
|
964
|
+
const result = await handleGuideDistill(args);
|
|
965
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
case "guide_update": {
|
|
969
|
+
const result = await handleGuideUpdate(args);
|
|
970
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
971
|
+
return result;
|
|
972
|
+
}
|
|
973
|
+
case "guide_forget": {
|
|
974
|
+
const result = await handleGuideForget(args);
|
|
975
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
976
|
+
return result;
|
|
977
|
+
}
|
|
978
|
+
case "guide_merge": {
|
|
979
|
+
const result = await handleGuideMerge(args);
|
|
980
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
981
|
+
return result;
|
|
982
|
+
}
|
|
983
|
+
case "session_stats": {
|
|
984
|
+
const result = await handleSessionStats(args);
|
|
985
|
+
logger.response(name, !!result.isError, Date.now() - startTime);
|
|
986
|
+
return result;
|
|
987
|
+
}
|
|
988
|
+
default: {
|
|
989
|
+
logger.warn("handleCallTool unknown_tool", { tool: name });
|
|
990
|
+
const result = {
|
|
770
991
|
content: [{ type: "text", text: `Error: Unknown tool '${name}'` }],
|
|
771
992
|
isError: true,
|
|
772
993
|
};
|
|
994
|
+
logger.response(name, true, Date.now() - startTime);
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
773
997
|
}
|
|
774
998
|
}
|
|
775
999
|
catch (error) {
|
|
1000
|
+
const err = error;
|
|
1001
|
+
logger.error("handleCallTool exception", { tool: name, error: err.message });
|
|
1002
|
+
logger.response(name, true, Date.now() - startTime, { error: err.message });
|
|
776
1003
|
return {
|
|
777
|
-
content: [{ type: "text", text: `Error: ${
|
|
1004
|
+
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
778
1005
|
isError: true,
|
|
779
1006
|
};
|
|
780
1007
|
}
|