local-agent-memory 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/tools.js CHANGED
@@ -43,221 +43,478 @@ const logger_1 = require("./logger");
43
43
  const middleware_1 = require("./middleware");
44
44
  const utils_1 = require("./utils");
45
45
  function registerTools(server) {
46
- // @ts-expect-error - Bypass TS2589
47
- server.tool("get_project_context", "Obtiene el contexto del proyecto actual leyendo el archivo core.md y listando decisiones recientes.", {
46
+ const mcp = server;
47
+ const recallContextSchema = {
48
+ projectPath: zod_1.z
49
+ .string()
50
+ .optional()
51
+ .describe("Absolute path to the root directory of the current workspace/project."),
48
52
  projectRoot: zod_1.z
49
53
  .string()
50
54
  .optional()
51
- .describe("Ruta absoluta del proyecto (usar si MCP es global)"),
52
- }, async (args) => {
53
- const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(args.projectRoot, server);
54
- (0, logger_1.logUsage)(cwd, "TOOL_CALL: get_project_context", {
55
- projectRoot: args.projectRoot,
55
+ .describe("Alternative alias for projectPath."),
56
+ query: zod_1.z
57
+ .string()
58
+ .optional()
59
+ .describe("Optional keywords, technical concepts, or file names to query in context."),
60
+ };
61
+ const recallContextDescription = "CRITICAL MANDATORY FIRST STEP: You MUST call this tool BEFORE starting any task, answering architectural questions, writing code, or debugging. " +
62
+ "It retrieves vital project context, architectural decisions, conventions, and historical bug fixes from local memory. " +
63
+ "Never assume project rules without calling this tool first.";
64
+ const recallContextHandler = async (args, toolName) => {
65
+ const startTime = Date.now();
66
+ const projectArg = args.projectPath || args.projectRoot;
67
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
68
+ (0, logger_1.logUsage)(cwd, `TOOL_CALL: ${toolName}`, {
69
+ projectPath: projectArg,
70
+ query: args.query,
56
71
  });
57
- await (0, middleware_1.syncMiddleware)(cwd);
58
- const memoryDir = path.join(cwd, ".memory");
59
- const corePath = path.join(memoryDir, "core.md");
60
- let coreContent = "No core.md found.";
61
- if (fs.existsSync(corePath)) {
62
- coreContent = fs.readFileSync(corePath, "utf8");
72
+ try {
73
+ await (0, middleware_1.syncMiddleware)(cwd);
74
+ const memoryDir = path.join(cwd, ".memory");
75
+ const corePath = path.join(memoryDir, "core.md");
76
+ let coreContent = "No core.md found.";
77
+ if (fs.existsSync(corePath)) {
78
+ coreContent = fs.readFileSync(corePath, "utf8");
79
+ }
80
+ const decisionsDir = path.join(memoryDir, "decisions");
81
+ let decisionsList = [];
82
+ if (fs.existsSync(decisionsDir)) {
83
+ const files = fs
84
+ .readdirSync(decisionsDir)
85
+ .filter((f) => f.endsWith(".md"))
86
+ .sort()
87
+ .reverse()
88
+ .slice(0, 5);
89
+ decisionsList = files.map((f) => `- ${f}`);
90
+ }
91
+ let extraQuerySection = "";
92
+ if (args.query && args.query.trim().length > 0) {
93
+ const dbPath = (0, project_1.getLocalDbPath)(cwd);
94
+ if (fs.existsSync(dbPath)) {
95
+ const db = (0, project_1.initLocalDb)(dbPath);
96
+ try {
97
+ const queryHits = await (0, search_1.hybridSearch)(args.query, 3, db);
98
+ if (queryHits.length > 0) {
99
+ extraQuerySection = `\n\n## Relevant Context for "${args.query}"\n${queryHits
100
+ .map((h) => `[${h.path}:${h.startLine}-${h.endLine}] ${h.breadcrumbs}\n${h.text}`)
101
+ .join("\n\n---\n\n")}`;
102
+ }
103
+ }
104
+ finally {
105
+ db.close();
106
+ }
107
+ }
108
+ }
109
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, args.query || "recall context", Date.now() - startTime, true);
110
+ return {
111
+ content: [
112
+ {
113
+ type: "text",
114
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}## Core Rules\n${coreContent}\n\n## Recent Decisions\n${decisionsList.join("\n")}${extraQuerySection}`,
115
+ },
116
+ ],
117
+ };
63
118
  }
64
- const decisionsDir = path.join(memoryDir, "decisions");
65
- let decisionsList = [];
66
- if (fs.existsSync(decisionsDir)) {
67
- const files = fs
68
- .readdirSync(decisionsDir)
69
- .filter((f) => f.endsWith(".md"))
70
- .sort()
71
- .reverse()
72
- .slice(0, 5);
73
- decisionsList = files.map((f) => `- ${f}`);
119
+ catch (err) {
120
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, `Error: ${err.message}`, Date.now() - startTime, false);
121
+ throw err;
74
122
  }
75
- return {
76
- content: [
77
- {
78
- type: "text",
79
- text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}## Core Rules\n${coreContent}\n\n## Recent Decisions\n${decisionsList.join("\n")}`,
80
- },
81
- ],
82
- };
83
- });
84
- // @ts-expect-error - Bypass TS2589
85
- server.tool("search_memory", "Busca en la memoria usando búsqueda híbrida (semántica y léxica).", {
86
- query: zod_1.z.string().describe("La consulta a buscar"),
87
- filter: zod_1.z.string().optional().describe("Filtro por ruta de archivo"),
88
- limit: zod_1.z.number().optional().describe("Límite de resultados"),
123
+ };
124
+ // Register get_project_context and its alias memory_recall_context
125
+ mcp.tool("get_project_context", recallContextDescription, recallContextSchema, (args) => recallContextHandler(args, "get_project_context"));
126
+ mcp.tool("memory_recall_context", recallContextDescription, recallContextSchema, (args) => recallContextHandler(args, "memory_recall_context"));
127
+ // search_memory
128
+ const searchMemoryDescription = "CRITICAL STEP: Search local project memory using hybrid search (semantic embeddings and full-text search). " +
129
+ "Call this whenever looking for architectural patterns, past decisions, error solutions, or domain rules.";
130
+ mcp.tool("search_memory", searchMemoryDescription, {
131
+ query: zod_1.z
132
+ .string()
133
+ .describe("Technical keywords, concepts, or file names to search"),
134
+ filter: zod_1.z.string().optional().describe("Filter by file path"),
135
+ limit: zod_1.z.number().optional().describe("Limit of results (default: 10)"),
136
+ full_content: zod_1.z
137
+ .boolean()
138
+ .optional()
139
+ .describe("If true, loads and returns the complete text of the matching documents instead of snippets"),
140
+ projectPath: zod_1.z
141
+ .string()
142
+ .optional()
143
+ .describe("Absolute path to the root directory of the current project"),
89
144
  projectRoot: zod_1.z
90
145
  .string()
91
146
  .optional()
92
- .describe("Ruta absoluta del proyecto (usar si MCP es global)"),
147
+ .describe("Alternative alias for projectPath"),
93
148
  }, async (args) => {
94
- const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(args.projectRoot, server);
149
+ const startTime = Date.now();
150
+ const projectArg = args.projectPath || args.projectRoot;
151
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
95
152
  (0, logger_1.logUsage)(cwd, "TOOL_CALL: search_memory", {
96
153
  query: args.query,
97
154
  filter: args.filter,
98
155
  limit: args.limit,
156
+ full_content: args.full_content,
99
157
  });
100
- await (0, middleware_1.syncMiddleware)(cwd);
101
- const dbPath = (0, project_1.getLocalDbPath)(cwd);
102
- const db = (0, project_1.initLocalDb)(dbPath);
103
158
  try {
104
- const results = await (0, search_1.hybridSearch)(args.query, args.limit || 10, db, args.filter);
105
- let textResults = results
106
- .map((r) => `[${r.path}:${r.startLine}-${r.endLine}] ${r.breadcrumbs}\n${r.text}`)
107
- .join("\n\n---\n\n");
108
- // Truncar para cumplir con límite de tokens (DR-05: ~1500 tokens por retorno, aprox 6000 caracteres)
109
- if (textResults.length > 6000) {
110
- textResults = `${textResults.slice(0, 6000)}\n\n...[Truncated due to token limits]`;
159
+ await (0, middleware_1.syncMiddleware)(cwd);
160
+ const dbPath = (0, project_1.getLocalDbPath)(cwd);
161
+ const db = (0, project_1.initLocalDb)(dbPath);
162
+ try {
163
+ const results = await (0, search_1.hybridSearch)(args.query, args.limit || 10, db, args.filter);
164
+ let textResults = "";
165
+ if (results.length === 0) {
166
+ textResults = "No results found.";
167
+ }
168
+ else if (args.full_content) {
169
+ const uniquePaths = Array.from(new Set(results.map((r) => r.path)));
170
+ const fullDocs = [];
171
+ for (const filePath of uniquePaths) {
172
+ const absPath = path.isAbsolute(filePath)
173
+ ? filePath
174
+ : path.resolve(cwd, filePath);
175
+ if (fs.existsSync(absPath)) {
176
+ const docText = fs.readFileSync(absPath, "utf8");
177
+ fullDocs.push(`### File: ${filePath}\n\n${docText}`);
178
+ }
179
+ else {
180
+ const fileChunks = results
181
+ .filter((r) => r.path === filePath)
182
+ .map((r) => `[${r.path}:${r.startLine}-${r.endLine}] ${r.breadcrumbs}\n${r.text}`)
183
+ .join("\n\n---\n\n");
184
+ fullDocs.push(fileChunks);
185
+ }
186
+ }
187
+ textResults = fullDocs.join("\n\n====================\n\n");
188
+ }
189
+ else {
190
+ textResults = results
191
+ .map((r) => `[${r.path}:${r.startLine}-${r.endLine}] ${r.breadcrumbs}\n${r.text}`)
192
+ .join("\n\n---\n\n");
193
+ }
194
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "search_memory", args.query, Date.now() - startTime, true);
195
+ return {
196
+ content: [
197
+ {
198
+ type: "text",
199
+ text: (fallbackUsed
200
+ ? `[Warning: Used fallback project root at ${cwd}]\n\n`
201
+ : "") + textResults,
202
+ },
203
+ ],
204
+ };
111
205
  }
206
+ finally {
207
+ db.close();
208
+ }
209
+ }
210
+ catch (err) {
211
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "search_memory", `Error: ${err.message}`, Date.now() - startTime, false);
212
+ throw err;
213
+ }
214
+ });
215
+ // get_memory_document & alias read_memory_document
216
+ const getDocumentDescription = "CRITICAL RETRIEVAL STEP: Retrieve the entire, untruncated content of a specific memory document, decision record, ledger session, or core rule file. Use this when search snippets are insufficient or when full file context is needed.";
217
+ const getDocumentSchema = {
218
+ file_path: zod_1.z
219
+ .string()
220
+ .describe("Path or filename of the memory document (e.g., 'core.md', 'decisions/2026-10-14-xxx.md', or '.memory/decisions/2026-10-14-xxx.md')"),
221
+ projectPath: zod_1.z
222
+ .string()
223
+ .optional()
224
+ .describe("Absolute path to the root directory of the current project"),
225
+ projectRoot: zod_1.z
226
+ .string()
227
+ .optional()
228
+ .describe("Alternative alias for projectPath"),
229
+ };
230
+ const getDocumentHandler = async (args, toolName) => {
231
+ const startTime = Date.now();
232
+ const projectArg = args.projectPath || args.projectRoot;
233
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
234
+ (0, logger_1.logUsage)(cwd, `TOOL_CALL: ${toolName}`, {
235
+ file_path: args.file_path,
236
+ });
237
+ try {
238
+ await (0, middleware_1.syncMiddleware)(cwd);
239
+ const requested = (args.file_path || "").trim();
240
+ if (!requested) {
241
+ throw new Error("Parameter 'file_path' is required.");
242
+ }
243
+ const memoryDir = path.join(cwd, ".memory");
244
+ const candidates = [
245
+ path.isAbsolute(requested) ? requested : path.resolve(cwd, requested),
246
+ path.resolve(memoryDir, requested),
247
+ path.resolve(memoryDir, "decisions", requested),
248
+ path.resolve(memoryDir, "ledger", requested),
249
+ ];
250
+ if (!requested.endsWith(".md")) {
251
+ candidates.push(path.resolve(cwd, `${requested}.md`), path.resolve(memoryDir, `${requested}.md`), path.resolve(memoryDir, "decisions", `${requested}.md`), path.resolve(memoryDir, "ledger", `${requested}.md`));
252
+ }
253
+ let foundPath = null;
254
+ for (const cand of candidates) {
255
+ if (fs.existsSync(cand) && fs.statSync(cand).isFile()) {
256
+ foundPath = cand;
257
+ break;
258
+ }
259
+ }
260
+ if (!foundPath && fs.existsSync(memoryDir)) {
261
+ const baseTarget = path.basename(requested).toLowerCase();
262
+ const baseTargetWithExt = baseTarget.endsWith(".md")
263
+ ? baseTarget
264
+ : `${baseTarget}.md`;
265
+ const findFileRecursive = (dir) => {
266
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
267
+ for (const entry of entries) {
268
+ const full = path.join(dir, entry.name);
269
+ if (entry.isDirectory()) {
270
+ const res = findFileRecursive(full);
271
+ if (res)
272
+ return res;
273
+ }
274
+ else if (entry.name.toLowerCase() === baseTarget ||
275
+ entry.name.toLowerCase() === baseTargetWithExt) {
276
+ return full;
277
+ }
278
+ }
279
+ return null;
280
+ };
281
+ foundPath = findFileRecursive(memoryDir);
282
+ }
283
+ if (!foundPath) {
284
+ const decisionsDir = path.join(memoryDir, "decisions");
285
+ let available = [];
286
+ if (fs.existsSync(decisionsDir)) {
287
+ available = fs
288
+ .readdirSync(decisionsDir)
289
+ .filter((f) => f.endsWith(".md"))
290
+ .slice(0, 10);
291
+ }
292
+ const msg = `Memory document not found: '${requested}'.\n` +
293
+ (available.length > 0
294
+ ? `Available decisions:\n${available.map((f) => ` - decisions/${f}`).join("\n")}`
295
+ : "No decisions found in .memory/decisions.");
296
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, `Not found: ${requested}`, Date.now() - startTime, false);
297
+ return {
298
+ content: [
299
+ {
300
+ type: "text",
301
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}${msg}`,
302
+ },
303
+ ],
304
+ };
305
+ }
306
+ const fileContent = fs.readFileSync(foundPath, "utf8");
307
+ const relativeDisplay = path.relative(cwd, foundPath);
308
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, relativeDisplay, Date.now() - startTime, true);
112
309
  return {
113
310
  content: [
114
311
  {
115
312
  type: "text",
116
- text: (fallbackUsed
117
- ? `[Warning: Used fallback project root at ${cwd}]\n\n`
118
- : "") + (textResults || "No results found."),
313
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}### Document: ${relativeDisplay}\n\n${fileContent}`,
119
314
  },
120
315
  ],
121
316
  };
122
317
  }
123
- finally {
124
- db.close();
318
+ catch (err) {
319
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, `Error: ${err.message}`, Date.now() - startTime, false);
320
+ throw err;
125
321
  }
126
- });
127
- server.tool("log_decision", "Registra una nueva decisión arquitectónica (ADR).", {
128
- title: zod_1.z.string().describe("Título corto de la decisión"),
129
- status: zod_1.z.string().describe("Estado (ej. Aceptado, Rechazado)"),
130
- context: zod_1.z.string().describe("Contexto del problema"),
131
- decision: zod_1.z.string().describe("La decisión tomada"),
132
- consequences: zod_1.z.string().describe("Consecuencias de la decisión"),
322
+ };
323
+ mcp.tool("get_memory_document", getDocumentDescription, getDocumentSchema, (args) => getDocumentHandler(args, "get_memory_document"));
324
+ mcp.tool("read_memory_document", getDocumentDescription, getDocumentSchema, (args) => getDocumentHandler(args, "read_memory_document"));
325
+ // Decision recording schema & handler supporting both log_decision and memory_record_decision
326
+ const recordDecisionDescription = "MANDATORY POST-TASK STEP: You MUST call this tool immediately AFTER implementing a feature, fixing a bug, refactoring, or establishing a new pattern. " +
327
+ "Persist technical decisions, root causes of bugs, and important context so future sessions maintain continuity.";
328
+ const recordDecisionSchema = {
329
+ title: zod_1.z
330
+ .string()
331
+ .describe("Short, descriptive title of the decision, fix, or component"),
332
+ status: zod_1.z
333
+ .string()
334
+ .optional()
335
+ .describe("Status (e.g. Accepted, Rejected, Superseded - defaults to Accepted)"),
336
+ context: zod_1.z.string().optional().describe("Context of the problem"),
337
+ decision: zod_1.z.string().optional().describe("The decision taken"),
338
+ consequences: zod_1.z
339
+ .string()
340
+ .optional()
341
+ .describe("Consequences of the decision"),
133
342
  alternatives_considered: zod_1.z
134
343
  .string()
135
344
  .optional()
136
- .describe("Alternativas consideradas"),
345
+ .describe("Alternatives considered"),
346
+ content: zod_1.z
347
+ .string()
348
+ .optional()
349
+ .describe("Detailed markdown explanation of WHAT changed, WHY it was done, and any constraints discovered."),
350
+ tags: zod_1.z
351
+ .array(zod_1.z.string())
352
+ .optional()
353
+ .describe("Categorization tags (e.g. ['architecture', 'bugfix', 'auth', 'database'])"),
354
+ projectPath: zod_1.z
355
+ .string()
356
+ .optional()
357
+ .describe("Absolute path to the root directory of the current workspace/project."),
137
358
  projectRoot: zod_1.z
138
359
  .string()
139
360
  .optional()
140
- .describe("Ruta absoluta del proyecto (usar si MCP es global)"),
141
- }, async (args) => {
142
- // Create slug
361
+ .describe("Alternative alias for projectPath."),
362
+ };
363
+ const recordDecisionHandler = async (args, toolName) => {
364
+ const startTime = Date.now();
143
365
  const date = new Date().toISOString().split("T")[0];
144
366
  const slug = args.title
145
367
  .toLowerCase()
146
368
  .replace(/[^a-z0-9]+/g, "-")
147
369
  .replace(/(^-|-$)/g, "");
148
370
  const filename = `${date}-${slug}.md`;
149
- const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(args.projectRoot, server);
150
- (0, logger_1.logUsage)(cwd, "TOOL_CALL: log_decision", {
371
+ const projectArg = args.projectPath || args.projectRoot;
372
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
373
+ const status = args.status || "Accepted";
374
+ (0, logger_1.logUsage)(cwd, `TOOL_CALL: ${toolName}`, {
151
375
  title: args.title,
152
- status: args.status,
376
+ status,
153
377
  });
154
- const memoryDir = path.join(cwd, ".memory");
155
- const decisionsDir = path.join(memoryDir, "decisions");
156
- if (!fs.existsSync(decisionsDir)) {
157
- fs.mkdirSync(decisionsDir, { recursive: true });
378
+ try {
379
+ const memoryDir = path.join(cwd, ".memory");
380
+ const decisionsDir = path.join(memoryDir, "decisions");
381
+ if (!fs.existsSync(decisionsDir)) {
382
+ fs.mkdirSync(decisionsDir, { recursive: true });
383
+ }
384
+ const filePath = path.join(decisionsDir, filename);
385
+ let frontmatter = `---\ntitle: ${args.title}\nstatus: ${status}\ndate: ${date}\n`;
386
+ if (args.tags && Array.isArray(args.tags) && args.tags.length > 0) {
387
+ frontmatter += `tags: [${args.tags.map((t) => JSON.stringify(t)).join(", ")}]\n`;
388
+ }
389
+ frontmatter += `---\n\n# ${args.title}\n\n`;
390
+ let body = "";
391
+ if (args.content) {
392
+ body = args.content;
393
+ }
394
+ else {
395
+ body = `## Context\n${args.context || "N/A"}\n\n## Decision\n${args.decision || "N/A"}\n\n## Consequences\n${args.consequences || "N/A"}\n`;
396
+ if (args.alternatives_considered) {
397
+ body += `\n## Alternatives Considered\n${args.alternatives_considered}\n`;
398
+ }
399
+ }
400
+ fs.writeFileSync(filePath, `${frontmatter}${body}`);
401
+ // Sincronizar después de escribir
402
+ await (0, middleware_1.syncMiddleware)(cwd);
403
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, args.title, Date.now() - startTime, true);
404
+ return {
405
+ content: [
406
+ {
407
+ type: "text",
408
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Decision logged successfully at ${path.join(".memory", "decisions", filename)}`,
409
+ },
410
+ ],
411
+ };
158
412
  }
159
- const filePath = path.join(decisionsDir, filename);
160
- let content = `---
161
- title: ${args.title}
162
- status: ${args.status}
163
- date: ${date}
164
- ---
165
-
166
- # ${args.title}
167
-
168
- ## Context
169
- ${args.context}
170
-
171
- ## Decision
172
- ${args.decision}
173
-
174
- ## Consequences
175
- ${args.consequences}
176
- `;
177
- if (args.alternatives_considered) {
178
- content += `\n## Alternatives Considered\n${args.alternatives_considered}\n`;
413
+ catch (err) {
414
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", toolName, `Error: ${err.message}`, Date.now() - startTime, false);
415
+ throw err;
179
416
  }
180
- fs.writeFileSync(filePath, content);
181
- // Sincronizar después de escribir
182
- await (0, middleware_1.syncMiddleware)(cwd);
183
- return {
184
- content: [
185
- {
186
- type: "text",
187
- text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Decision logged successfully at ${path.join(".memory", "decisions", filename)}`,
188
- },
189
- ],
190
- };
191
- });
192
- server.tool("update_core_rules", "Añade contenido a las reglas core (core.md).", {
417
+ };
418
+ // Register log_decision and its alias memory_record_decision
419
+ mcp.tool("log_decision", recordDecisionDescription, recordDecisionSchema, (args) => recordDecisionHandler(args, "log_decision"));
420
+ mcp.tool("memory_record_decision", recordDecisionDescription, recordDecisionSchema, (args) => recordDecisionHandler(args, "memory_record_decision"));
421
+ // update_core_rules
422
+ mcp.tool("update_core_rules", "CRITICAL STEP: Appends permanent project conventions, constraints, or architectural guidelines to core.md. Call whenever a core rule must be permanently established or revised.", {
193
423
  changes_to_append: zod_1.z
194
424
  .string()
195
425
  .describe("Contenido a añadir al final del archivo core.md"),
426
+ projectPath: zod_1.z
427
+ .string()
428
+ .optional()
429
+ .describe("Absolute path to the root directory of the current project"),
196
430
  projectRoot: zod_1.z
197
431
  .string()
198
432
  .optional()
199
- .describe("Ruta absoluta del proyecto (usar si MCP es global)"),
433
+ .describe("Alternative alias for projectPath"),
200
434
  }, async (args) => {
201
- const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(args.projectRoot, server);
435
+ const startTime = Date.now();
436
+ const projectArg = args.projectPath || args.projectRoot;
437
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
202
438
  (0, logger_1.logUsage)(cwd, "TOOL_CALL: update_core_rules", {
203
439
  charsAdded: args.changes_to_append.length,
204
440
  });
205
- const memoryDir = path.join(cwd, ".memory");
206
- const corePath = path.join(memoryDir, "core.md");
207
- if (!fs.existsSync(memoryDir)) {
208
- fs.mkdirSync(memoryDir, { recursive: true });
441
+ try {
442
+ const memoryDir = path.join(cwd, ".memory");
443
+ const corePath = path.join(memoryDir, "core.md");
444
+ if (!fs.existsSync(memoryDir)) {
445
+ fs.mkdirSync(memoryDir, { recursive: true });
446
+ }
447
+ fs.appendFileSync(corePath, `\n${args.changes_to_append}\n`);
448
+ // Sincronizar después de escribir
449
+ await (0, middleware_1.syncMiddleware)(cwd);
450
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "update_core_rules", `Appended ${args.changes_to_append.length} chars`, Date.now() - startTime, true);
451
+ return {
452
+ content: [
453
+ {
454
+ type: "text",
455
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Core rules updated successfully.`,
456
+ },
457
+ ],
458
+ };
459
+ }
460
+ catch (err) {
461
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "update_core_rules", `Error: ${err.message}`, Date.now() - startTime, false);
462
+ throw err;
209
463
  }
210
- fs.appendFileSync(corePath, `\n${args.changes_to_append}\n`);
211
- // Sincronizar después de escribir
212
- await (0, middleware_1.syncMiddleware)(cwd);
213
- return {
214
- content: [
215
- {
216
- type: "text",
217
- text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Core rules updated successfully.`,
218
- },
219
- ],
220
- };
221
464
  });
222
- server.tool("end_session", "Finaliza una tarea o sesión, sintetizando qué se modificó y qué acuerdos se tomaron, guardándolo en el ledger de la memoria.", {
465
+ // end_session
466
+ mcp.tool("end_session", "MANDATORY SESSION CLOSING STEP: Call this before concluding a task or session. Synthesizes what was modified, root cause of issues solved, and agreements taken, recording it into the memory ledger.", {
223
467
  summary: zod_1.z
224
468
  .string()
225
469
  .describe("Resumen de lo que se modificó y qué acuerdos se tomaron en la sesión"),
470
+ projectPath: zod_1.z
471
+ .string()
472
+ .optional()
473
+ .describe("Absolute path to the root directory of the current project"),
226
474
  projectRoot: zod_1.z
227
475
  .string()
228
476
  .optional()
229
- .describe("Ruta absoluta del proyecto (usar si MCP es global)"),
477
+ .describe("Alternative alias for projectPath"),
230
478
  }, async (args) => {
231
- const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(args.projectRoot, server);
479
+ const startTime = Date.now();
480
+ const projectArg = args.projectPath || args.projectRoot;
481
+ const { cwd, fallbackUsed } = await (0, utils_1.resolveCwd)(projectArg, server);
232
482
  (0, logger_1.logUsage)(cwd, "TOOL_CALL: end_session", {
233
483
  summaryLength: args.summary.length,
234
484
  });
235
- const memoryDir = path.join(cwd, ".memory");
236
- const ledgerDir = path.join(memoryDir, "ledger");
237
- const dateObj = new Date();
238
- const year = dateObj.getFullYear();
239
- const month = String(dateObj.getMonth() + 1).padStart(2, "0");
240
- const day = String(dateObj.getDate()).padStart(2, "0");
241
- const hours = String(dateObj.getHours()).padStart(2, "0");
242
- const minutes = String(dateObj.getMinutes()).padStart(2, "0");
243
- const seconds = String(dateObj.getSeconds()).padStart(2, "0");
244
- const filename = `${year}-${month}-${day}-${hours}-${minutes}-${seconds}-session.md`;
245
- const summariesPath = path.join(ledgerDir, filename);
246
- if (!fs.existsSync(ledgerDir)) {
247
- fs.mkdirSync(ledgerDir, { recursive: true });
485
+ try {
486
+ const memoryDir = path.join(cwd, ".memory");
487
+ const ledgerDir = path.join(memoryDir, "ledger");
488
+ const dateObj = new Date();
489
+ const year = dateObj.getFullYear();
490
+ const month = String(dateObj.getMonth() + 1).padStart(2, "0");
491
+ const day = String(dateObj.getDate()).padStart(2, "0");
492
+ const hours = String(dateObj.getHours()).padStart(2, "0");
493
+ const minutes = String(dateObj.getMinutes()).padStart(2, "0");
494
+ const seconds = String(dateObj.getSeconds()).padStart(2, "0");
495
+ const filename = `${year}-${month}-${day}-${hours}-${minutes}-${seconds}-session.md`;
496
+ const summariesPath = path.join(ledgerDir, filename);
497
+ if (!fs.existsSync(ledgerDir)) {
498
+ fs.mkdirSync(ledgerDir, { recursive: true });
499
+ }
500
+ const date = dateObj.toISOString();
501
+ const content = `\n## Session ended at ${date}\n\n${args.summary}\n\n---\n`;
502
+ fs.writeFileSync(summariesPath, content);
503
+ // Sincronizar después de escribir
504
+ await (0, middleware_1.syncMiddleware)(cwd);
505
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "end_session", args.summary.slice(0, 80), Date.now() - startTime, true);
506
+ return {
507
+ content: [
508
+ {
509
+ type: "text",
510
+ text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Session summary flushed successfully to .memory/ledger/${filename}`,
511
+ },
512
+ ],
513
+ };
514
+ }
515
+ catch (err) {
516
+ await (0, logger_1.recordMcpEvent)(cwd, "tool_call", "end_session", `Error: ${err.message}`, Date.now() - startTime, false);
517
+ throw err;
248
518
  }
249
- const date = dateObj.toISOString();
250
- const content = `\n## Session ended at ${date}\n\n${args.summary}\n\n---\n`;
251
- fs.writeFileSync(summariesPath, content);
252
- // Sincronizar después de escribir
253
- await (0, middleware_1.syncMiddleware)(cwd);
254
- return {
255
- content: [
256
- {
257
- type: "text",
258
- text: `${fallbackUsed ? `[Warning: Used fallback project root at ${cwd}]\n\n` : ""}Session summary flushed successfully to .memory/ledger/${filename}`,
259
- },
260
- ],
261
- };
262
519
  });
263
520
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-agent-memory",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "scripts": {
17
17
  "test": "jest --coverage",
18
- "build": "node --max-old-space-size=4096 ./node_modules/typescript/bin/tsc",
18
+ "build": "tsc",
19
19
  "lint": "biome check src tests",
20
20
  "format": "biome format --write src tests"
21
21
  },