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.
@@ -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(dbPath, file_path, mtime, sha256) {
121
+ async function upsertFileManifest(dbOrPath, file_path, mtime, sha256) {
105
122
  return new Promise((resolve, reject) => {
106
- const db = initLocalDb(dbPath);
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
- db.close();
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(dbPath, file_path) {
136
+ async function getFileManifest(dbOrPath, file_path) {
117
137
  return new Promise((resolve, reject) => {
118
- const db = initLocalDb(dbPath);
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
- db.close();
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(dbPath) {
151
+ async function getAllManifests(dbOrPath) {
129
152
  return new Promise((resolve, reject) => {
130
- const db = initLocalDb(dbPath);
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
- db.close();
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(dbPath, file_path) {
166
+ async function removeFileManifest(dbOrPath, file_path) {
141
167
  return new Promise((resolve, reject) => {
142
- const db = initLocalDb(dbPath);
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
- db.close();
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
+ }
@@ -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,
@@ -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>;
@@ -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
+ }
@@ -57,7 +57,8 @@ async function setupMCPServer() {
57
57
  // Ignore write errors to prevent server crash
58
58
  }
59
59
  };
60
- log("Starting MCP Server");
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) => {