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/db/project.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.upsertFileManifest = upsertFileManifest;
|
|
|
10
10
|
exports.getFileManifest = getFileManifest;
|
|
11
11
|
exports.getAllManifests = getAllManifests;
|
|
12
12
|
exports.removeFileManifest = removeFileManifest;
|
|
13
|
+
exports.insertMcpUsageLog = insertMcpUsageLog;
|
|
14
|
+
exports.getMcpUsageStats = getMcpUsageStats;
|
|
13
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
14
16
|
const node_os_1 = __importDefault(require("node:os"));
|
|
15
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -34,11 +36,26 @@ function getLocalDbPath(projectRoot) {
|
|
|
34
36
|
return node_path_1.default.join(projectRoot, ".memory", "local.sqlite");
|
|
35
37
|
}
|
|
36
38
|
function initLocalDb(dbPath) {
|
|
37
|
-
const db = new sqlite3_1.default.Database(dbPath);
|
|
39
|
+
const db = new sqlite3_1.default.Database(dbPath, () => { });
|
|
40
|
+
db.on("error", () => { });
|
|
38
41
|
db.configure("busyTimeout", 5000);
|
|
39
42
|
db.serialize(() => {
|
|
40
43
|
db.run("PRAGMA journal_mode = WAL;");
|
|
41
44
|
db.run("PRAGMA busy_timeout = 5000;");
|
|
45
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
46
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
47
|
+
db.run(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS mcp_usage_logs (
|
|
49
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
51
|
+
project_path TEXT,
|
|
52
|
+
event_type TEXT NOT NULL,
|
|
53
|
+
tool_name TEXT,
|
|
54
|
+
query_or_summary TEXT,
|
|
55
|
+
execution_time_ms INTEGER,
|
|
56
|
+
success BOOLEAN
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
42
59
|
db.run(`
|
|
43
60
|
CREATE TABLE IF NOT EXISTS file_manifest (
|
|
44
61
|
file_path TEXT PRIMARY KEY,
|
|
@@ -101,11 +118,14 @@ function initLocalDb(dbPath) {
|
|
|
101
118
|
});
|
|
102
119
|
return db;
|
|
103
120
|
}
|
|
104
|
-
async function upsertFileManifest(
|
|
121
|
+
async function upsertFileManifest(dbOrPath, file_path, mtime, sha256) {
|
|
105
122
|
return new Promise((resolve, reject) => {
|
|
106
|
-
const
|
|
123
|
+
const isDb = typeof dbOrPath !== "string";
|
|
124
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
107
125
|
db.run("INSERT OR REPLACE INTO file_manifest (file_path, mtime, sha256, last_synced) VALUES (?, ?, ?, CURRENT_TIMESTAMP)", [file_path, mtime, sha256], (err) => {
|
|
108
|
-
|
|
126
|
+
if (!isDb) {
|
|
127
|
+
db.close();
|
|
128
|
+
}
|
|
109
129
|
if (err)
|
|
110
130
|
reject(err);
|
|
111
131
|
else
|
|
@@ -113,11 +133,14 @@ async function upsertFileManifest(dbPath, file_path, mtime, sha256) {
|
|
|
113
133
|
});
|
|
114
134
|
});
|
|
115
135
|
}
|
|
116
|
-
async function getFileManifest(
|
|
136
|
+
async function getFileManifest(dbOrPath, file_path) {
|
|
117
137
|
return new Promise((resolve, reject) => {
|
|
118
|
-
const
|
|
138
|
+
const isDb = typeof dbOrPath !== "string";
|
|
139
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
119
140
|
db.get("SELECT file_path, mtime, sha256 FROM file_manifest WHERE file_path = ?", [file_path], (err, row) => {
|
|
120
|
-
|
|
141
|
+
if (!isDb) {
|
|
142
|
+
db.close();
|
|
143
|
+
}
|
|
121
144
|
if (err)
|
|
122
145
|
reject(err);
|
|
123
146
|
else
|
|
@@ -125,11 +148,14 @@ async function getFileManifest(dbPath, file_path) {
|
|
|
125
148
|
});
|
|
126
149
|
});
|
|
127
150
|
}
|
|
128
|
-
async function getAllManifests(
|
|
151
|
+
async function getAllManifests(dbOrPath) {
|
|
129
152
|
return new Promise((resolve, reject) => {
|
|
130
|
-
const
|
|
153
|
+
const isDb = typeof dbOrPath !== "string";
|
|
154
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
131
155
|
db.all("SELECT file_path, mtime, sha256 FROM file_manifest", (err, rows) => {
|
|
132
|
-
|
|
156
|
+
if (!isDb) {
|
|
157
|
+
db.close();
|
|
158
|
+
}
|
|
133
159
|
if (err)
|
|
134
160
|
reject(err);
|
|
135
161
|
else
|
|
@@ -137,11 +163,14 @@ async function getAllManifests(dbPath) {
|
|
|
137
163
|
});
|
|
138
164
|
});
|
|
139
165
|
}
|
|
140
|
-
async function removeFileManifest(
|
|
166
|
+
async function removeFileManifest(dbOrPath, file_path) {
|
|
141
167
|
return new Promise((resolve, reject) => {
|
|
142
|
-
const
|
|
168
|
+
const isDb = typeof dbOrPath !== "string";
|
|
169
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
143
170
|
db.run("DELETE FROM file_manifest WHERE file_path = ?", [file_path], (err) => {
|
|
144
|
-
|
|
171
|
+
if (!isDb) {
|
|
172
|
+
db.close();
|
|
173
|
+
}
|
|
145
174
|
if (err)
|
|
146
175
|
reject(err);
|
|
147
176
|
else
|
|
@@ -149,3 +178,96 @@ async function removeFileManifest(dbPath, file_path) {
|
|
|
149
178
|
});
|
|
150
179
|
});
|
|
151
180
|
}
|
|
181
|
+
async function insertMcpUsageLog(dbOrPath, entry) {
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
try {
|
|
184
|
+
const isDb = typeof dbOrPath !== "string";
|
|
185
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
186
|
+
db.run(`INSERT INTO mcp_usage_logs (project_path, event_type, tool_name, query_or_summary, execution_time_ms, success)
|
|
187
|
+
VALUES (?, ?, ?, ?, ?, ?)`, [
|
|
188
|
+
entry.project_path,
|
|
189
|
+
entry.event_type,
|
|
190
|
+
entry.tool_name ?? null,
|
|
191
|
+
entry.query_or_summary ?? null,
|
|
192
|
+
entry.execution_time_ms ?? null,
|
|
193
|
+
entry.success ?? true,
|
|
194
|
+
], (_err) => {
|
|
195
|
+
if (!isDb) {
|
|
196
|
+
try {
|
|
197
|
+
db.close();
|
|
198
|
+
}
|
|
199
|
+
catch { }
|
|
200
|
+
}
|
|
201
|
+
resolve();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
resolve();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async function getMcpUsageStats(dbOrPath) {
|
|
210
|
+
return new Promise((resolve) => {
|
|
211
|
+
const emptyStats = {
|
|
212
|
+
totalCalls: 0,
|
|
213
|
+
readCalls: 0,
|
|
214
|
+
writeCalls: 0,
|
|
215
|
+
lastConnection: null,
|
|
216
|
+
recentQueries: [],
|
|
217
|
+
};
|
|
218
|
+
try {
|
|
219
|
+
const isDb = typeof dbOrPath !== "string";
|
|
220
|
+
const db = isDb ? dbOrPath : initLocalDb(dbOrPath);
|
|
221
|
+
db.serialize(() => {
|
|
222
|
+
db.all(`SELECT
|
|
223
|
+
COUNT(*) as total_count,
|
|
224
|
+
SUM(CASE WHEN tool_name IN ('get_project_context', 'memory_recall_context', 'search_memory') THEN 1 ELSE 0 END) as read_count,
|
|
225
|
+
SUM(CASE WHEN tool_name IN ('log_decision', 'memory_record_decision', 'update_core_rules', 'end_session') THEN 1 ELSE 0 END) as write_count,
|
|
226
|
+
MAX(timestamp) as last_conn
|
|
227
|
+
FROM mcp_usage_logs`, [], (err, statsRows) => {
|
|
228
|
+
if (err) {
|
|
229
|
+
if (!isDb) {
|
|
230
|
+
try {
|
|
231
|
+
db.close();
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
}
|
|
235
|
+
return resolve(emptyStats);
|
|
236
|
+
}
|
|
237
|
+
db.all(`SELECT timestamp, event_type, tool_name, query_or_summary, success
|
|
238
|
+
FROM mcp_usage_logs
|
|
239
|
+
WHERE event_type = 'tool_call'
|
|
240
|
+
ORDER BY id DESC
|
|
241
|
+
LIMIT 5`, [], (err2, recentRows) => {
|
|
242
|
+
if (!isDb) {
|
|
243
|
+
try {
|
|
244
|
+
db.close();
|
|
245
|
+
}
|
|
246
|
+
catch { }
|
|
247
|
+
}
|
|
248
|
+
if (err2) {
|
|
249
|
+
return resolve(emptyStats);
|
|
250
|
+
}
|
|
251
|
+
const row = statsRows?.[0] || {};
|
|
252
|
+
resolve({
|
|
253
|
+
totalCalls: row.total_count || 0,
|
|
254
|
+
readCalls: row.read_count || 0,
|
|
255
|
+
writeCalls: row.write_count || 0,
|
|
256
|
+
lastConnection: row.last_conn || null,
|
|
257
|
+
recentQueries: (recentRows || []).map((r) => ({
|
|
258
|
+
timestamp: r.timestamp,
|
|
259
|
+
event_type: r.event_type,
|
|
260
|
+
tool_name: r.tool_name,
|
|
261
|
+
query_or_summary: r.query_or_summary,
|
|
262
|
+
success: Boolean(r.success),
|
|
263
|
+
})),
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
resolve(emptyStats);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
package/dist/db/registry.js
CHANGED
|
@@ -34,6 +34,8 @@ function initRegistryDb(dbPath = getRegistryDbPath()) {
|
|
|
34
34
|
db.serialize(() => {
|
|
35
35
|
db.run("PRAGMA journal_mode = WAL;");
|
|
36
36
|
db.run("PRAGMA busy_timeout = 5000;");
|
|
37
|
+
db.run("PRAGMA synchronous = NORMAL;");
|
|
38
|
+
db.run("PRAGMA foreign_keys = ON;");
|
|
37
39
|
db.run(`
|
|
38
40
|
CREATE TABLE IF NOT EXISTS projects (
|
|
39
41
|
project_id TEXT PRIMARY KEY,
|
package/dist/mcp/logger.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const getLocalMemoryDir: () => string;
|
|
1
2
|
export interface MemoryConfig {
|
|
2
3
|
project_id?: string;
|
|
3
4
|
created_at?: string;
|
|
@@ -8,3 +9,4 @@ export interface MemoryConfig {
|
|
|
8
9
|
}
|
|
9
10
|
export declare function getMemoryConfig(cwd: string): MemoryConfig | null;
|
|
10
11
|
export declare function logUsage(cwd: string, action: string, details?: Record<string, unknown>): void;
|
|
12
|
+
export declare function recordMcpEvent(cwd: string, eventType: string, toolName?: string, queryOrSummary?: string, executionTimeMs?: number, success?: boolean): Promise<void>;
|
package/dist/mcp/logger.js
CHANGED
|
@@ -33,10 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getLocalMemoryDir = void 0;
|
|
36
37
|
exports.getMemoryConfig = getMemoryConfig;
|
|
37
38
|
exports.logUsage = logUsage;
|
|
39
|
+
exports.recordMcpEvent = recordMcpEvent;
|
|
38
40
|
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
39
42
|
const path = __importStar(require("node:path"));
|
|
43
|
+
const project_1 = require("../db/project");
|
|
44
|
+
const getLocalMemoryDir = () => process.env.MEMORY_GLOBAL_DIR || path.join(os.homedir(), ".local_memory");
|
|
45
|
+
exports.getLocalMemoryDir = getLocalMemoryDir;
|
|
40
46
|
function getMemoryConfig(cwd) {
|
|
41
47
|
const configPath = path.join(cwd, ".memory", "config.json");
|
|
42
48
|
if (!fs.existsSync(configPath)) {
|
|
@@ -76,3 +82,42 @@ function logUsage(cwd, action, details) {
|
|
|
76
82
|
// Fail silently to avoid breaking any MCP operation
|
|
77
83
|
}
|
|
78
84
|
}
|
|
85
|
+
async function recordMcpEvent(cwd, eventType, toolName, queryOrSummary, executionTimeMs, success = true) {
|
|
86
|
+
const timestamp = new Date().toISOString();
|
|
87
|
+
// 1. Global log to ~/.local_memory/mcp-events.log
|
|
88
|
+
try {
|
|
89
|
+
const globalDir = (0, exports.getLocalMemoryDir)();
|
|
90
|
+
if (!fs.existsSync(globalDir)) {
|
|
91
|
+
fs.mkdirSync(globalDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
const globalLog = path.join(globalDir, "mcp-events.log");
|
|
94
|
+
const line = `[${timestamp}] cwd=${cwd} event=${eventType} tool=${toolName || "none"} success=${success}${queryOrSummary ? ` summary="${queryOrSummary.slice(0, 100).replace(/\n/g, " ")}"` : ""}\n`;
|
|
95
|
+
fs.appendFileSync(globalLog, line, "utf8");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// ignore
|
|
99
|
+
}
|
|
100
|
+
// 2. Project SQLite log (mcp_usage_logs) if project DB exists
|
|
101
|
+
try {
|
|
102
|
+
const localDb = (0, project_1.getLocalDbPath)(cwd);
|
|
103
|
+
if (fs.existsSync(localDb)) {
|
|
104
|
+
await (0, project_1.insertMcpUsageLog)(localDb, {
|
|
105
|
+
project_path: cwd,
|
|
106
|
+
event_type: eventType,
|
|
107
|
+
tool_name: toolName ?? null,
|
|
108
|
+
query_or_summary: queryOrSummary ?? null,
|
|
109
|
+
execution_time_ms: executionTimeMs ?? null,
|
|
110
|
+
success,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
// 3. Fallback/compatibility logUsage
|
|
118
|
+
logUsage(cwd, `${eventType}: ${toolName || "event"}`, {
|
|
119
|
+
queryOrSummary,
|
|
120
|
+
executionTimeMs,
|
|
121
|
+
success,
|
|
122
|
+
});
|
|
123
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -57,7 +57,8 @@ async function setupMCPServer() {
|
|
|
57
57
|
// Ignore write errors to prevent server crash
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
-
log(
|
|
60
|
+
log(`Starting MCP Server [pid=${process.pid}, cwd=${process.cwd()}, argv=${JSON.stringify(process.argv)}]`);
|
|
61
|
+
(0, logger_1.recordMcpEvent)(process.cwd(), "init", "server_start", `Started MCP Server (pid: ${process.pid})`).catch(() => { });
|
|
61
62
|
const server = new mcp_js_1.McpServer({
|
|
62
63
|
name: "local-agent-memory",
|
|
63
64
|
version: require("../../package.json").version,
|
|
@@ -81,6 +82,43 @@ async function setupMCPServer() {
|
|
|
81
82
|
],
|
|
82
83
|
};
|
|
83
84
|
});
|
|
85
|
+
server.resource("current-project-overview", "memory://current-project/overview", {
|
|
86
|
+
description: "Overview of current project memory, recent decisions, and status",
|
|
87
|
+
}, async (uri) => {
|
|
88
|
+
const { cwd: resolvedCwd } = await (0, utils_1.resolveCwd)();
|
|
89
|
+
(0, logger_1.recordMcpEvent)(resolvedCwd, "resource_read", "current-project-overview").catch(() => { });
|
|
90
|
+
const memoryDir = path.join(resolvedCwd, ".memory");
|
|
91
|
+
const corePath = path.join(memoryDir, "core.md");
|
|
92
|
+
const coreFound = fs.existsSync(corePath);
|
|
93
|
+
const decisionsDir = path.join(memoryDir, "decisions");
|
|
94
|
+
let recentDecisions = [];
|
|
95
|
+
if (fs.existsSync(decisionsDir)) {
|
|
96
|
+
recentDecisions = fs
|
|
97
|
+
.readdirSync(decisionsDir)
|
|
98
|
+
.filter((f) => f.endsWith(".md"))
|
|
99
|
+
.sort()
|
|
100
|
+
.reverse()
|
|
101
|
+
.slice(0, 10);
|
|
102
|
+
}
|
|
103
|
+
const summaryText = [
|
|
104
|
+
`# Project Memory Overview`,
|
|
105
|
+
`- Root: ${resolvedCwd}`,
|
|
106
|
+
`- Core rules: ${coreFound ? "Present" : "Missing"}`,
|
|
107
|
+
`- Decisions logged: ${recentDecisions.length} recent`,
|
|
108
|
+
recentDecisions.length > 0
|
|
109
|
+
? recentDecisions.map((d) => ` * ${d}`).join("\n")
|
|
110
|
+
: " (None recorded yet)",
|
|
111
|
+
].join("\n");
|
|
112
|
+
return {
|
|
113
|
+
contents: [
|
|
114
|
+
{
|
|
115
|
+
uri: uri.href,
|
|
116
|
+
text: summaryText,
|
|
117
|
+
mimeType: "text/markdown",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
});
|
|
84
122
|
server.prompt("agent-guidelines", "Get the agent guidelines and memory core rules", async () => {
|
|
85
123
|
const { cwd: resolvedCwd } = await (0, utils_1.resolveCwd)();
|
|
86
124
|
(0, logger_1.logUsage)(resolvedCwd, "PROMPT_GET: agent-guidelines");
|
|
@@ -100,6 +138,25 @@ async function setupMCPServer() {
|
|
|
100
138
|
],
|
|
101
139
|
};
|
|
102
140
|
});
|
|
141
|
+
server.prompt("contexto-proyecto", "Obtener el contexto activo, reglas de arquitectura y sincronizar la memoria del proyecto", async () => {
|
|
142
|
+
const { cwd: resolvedCwd } = await (0, utils_1.resolveCwd)();
|
|
143
|
+
(0, logger_1.recordMcpEvent)(resolvedCwd, "prompt_get", "contexto-proyecto").catch(() => { });
|
|
144
|
+
const corePath = path.join(resolvedCwd, ".memory", "core.md");
|
|
145
|
+
const text = fs.existsSync(corePath)
|
|
146
|
+
? fs.readFileSync(corePath, "utf8")
|
|
147
|
+
: "No se encontró core.md en este proyecto.";
|
|
148
|
+
return {
|
|
149
|
+
messages: [
|
|
150
|
+
{
|
|
151
|
+
role: "user",
|
|
152
|
+
content: {
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Por favor sincroniza el contexto local del proyecto e incorpora estas reglas obligatorias:\n\n${text}`,
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
103
160
|
(0, tools_1.registerTools)(server);
|
|
104
161
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
105
162
|
transport.onerror = (err) => {
|