local-agent-memory 1.0.8 → 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/README.md +36 -1
- package/dist/cli/commands/hook.js +9 -3
- package/dist/cli/commands/init.js +111 -46
- package/dist/cli/commands/reindex.js +15 -6
- package/dist/cli/commands/status.js +24 -0
- package/dist/cli/commands/sync.js +3 -3
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.js +29 -1
- package/dist/db/project.d.ts +29 -4
- package/dist/db/project.js +135 -13
- package/dist/db/registry.js +2 -0
- package/dist/mcp/logger.d.ts +2 -0
- package/dist/mcp/logger.js +45 -0
- package/dist/mcp/server.js +58 -1
- package/dist/mcp/tools.js +408 -151
- package/package.json +2 -2
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
|
-
|
|
47
|
-
|
|
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("
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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("
|
|
147
|
+
.describe("Alternative alias for projectPath"),
|
|
93
148
|
}, async (args) => {
|
|
94
|
-
const
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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:
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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("
|
|
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("
|
|
141
|
-
}
|
|
142
|
-
|
|
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
|
|
150
|
-
(0,
|
|
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
|
|
376
|
+
status,
|
|
153
377
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
fs.
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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("
|
|
433
|
+
.describe("Alternative alias for projectPath"),
|
|
200
434
|
}, async (args) => {
|
|
201
|
-
const
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
fs.
|
|
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
|
-
|
|
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("
|
|
477
|
+
.describe("Alternative alias for projectPath"),
|
|
230
478
|
}, async (args) => {
|
|
231
|
-
const
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
fs.
|
|
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.
|
|
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": "
|
|
18
|
+
"build": "tsc",
|
|
19
19
|
"lint": "biome check src tests",
|
|
20
20
|
"format": "biome format --write src tests"
|
|
21
21
|
},
|