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/README.md
CHANGED
|
@@ -31,6 +31,21 @@ Inicializa el entorno de memoria en el repositorio actual. Esto crea las bases d
|
|
|
31
31
|
memory init --rules all --docs ./docs
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
#### Actualización de Reglas en Proyectos Existentes
|
|
35
|
+
Si ya tienes proyectos donde se inicializó la memoria y deseas migrar o incorporar el protocolo actualizado para tus agentes de IA sin perder tus reglas de `core.md` ni alterar el `project_id`:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Para actualizar las reglas de Antigravity (IDE y CLI)
|
|
39
|
+
memory init --rules=antigravity
|
|
40
|
+
|
|
41
|
+
# O si utilizas múltiples herramientas (Cursor, Claude, Antigravity, etc.)
|
|
42
|
+
memory init --rules=all
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**¿Por qué es necesario?**
|
|
46
|
+
- **Garantiza la adherencia del agente:** Inyecta en los archivos de instrucciones (`.agents/rules/memory.md`, `GEMINI.md`, `AGENTS.md`, `.cursorrules`, etc.) las directivas imperativas obligatorias (`memory_recall_context` al inicio y `memory_record_decision` al finalizar).
|
|
47
|
+
- **Actualización no destructiva:** Preserva intactos el archivo `core.md`, las decisiones previas (`.memory/decisions/`) y la identidad del proyecto (`project_id`).
|
|
48
|
+
|
|
34
49
|
### 2. `sync`
|
|
35
50
|
Sincroniza los archivos Markdown del proyecto en el índice de memoria local. Escanea los documentos y actualiza la base de datos para que los agentes cuenten con el contexto más reciente.
|
|
36
51
|
|
|
@@ -39,12 +54,21 @@ memory sync
|
|
|
39
54
|
```
|
|
40
55
|
|
|
41
56
|
### 3. `status`
|
|
42
|
-
Comprueba el estado de sincronización del proyecto de memoria actual
|
|
57
|
+
Comprueba el estado de sincronización del proyecto de memoria actual y presenta el nuevo panel de métricas y auditoría de actividad MCP.
|
|
43
58
|
|
|
44
59
|
```bash
|
|
60
|
+
# Verificar que la BD se actualizó y ver el nuevo panel de métricas
|
|
45
61
|
memory status
|
|
46
62
|
```
|
|
47
63
|
|
|
64
|
+
**¿Por qué es necesario?**
|
|
65
|
+
- **Verificación de la Base de Datos:** Valida que la base de datos local SQLite (`.memory/local.sqlite`) esté operando en modo WAL de alta concurrencia y que todos los documentos Markdown estén indexados.
|
|
66
|
+
- **Panel de Métricas MCP:** Muestra la telemetría en tiempo real sobre la actividad del agente:
|
|
67
|
+
- Conteo acumulado de lecturas (`memory_recall_context`, `search_memory`, `get_memory_document`).
|
|
68
|
+
- Conteo acumulado de escrituras (`memory_record_decision`, `update_core_rules`, `end_session`).
|
|
69
|
+
- Marca temporal de la última actividad del agente.
|
|
70
|
+
- Registro de las decisiones y resúmenes de sesión más recientes.
|
|
71
|
+
|
|
48
72
|
### 4. `list` (o `projects`)
|
|
49
73
|
Muestra de forma interactiva una lista de todos los proyectos registrados globalmente en tu máquina. Permite seleccionar un proyecto y ver detalles como el total de archivos indexados, los registros de decisiones (ADRs) y los resúmenes de sesión (Ledgers) para ese proyecto.
|
|
50
74
|
|
|
@@ -112,6 +136,17 @@ memory-mcp
|
|
|
112
136
|
|
|
113
137
|
*(Si configuras MCP manualmente, deberás establecer que tu cliente ejecute `memory-mcp` sin argumentos extra)*
|
|
114
138
|
|
|
139
|
+
### Herramientas MCP Expuestas al Agente
|
|
140
|
+
|
|
141
|
+
| Herramienta (y Alias) | Tipo | Descripción |
|
|
142
|
+
| :--- | :--- | :--- |
|
|
143
|
+
| `memory_recall_context`<br>*(alias: `get_project_context`)* | Lectura | **Paso Obligatorio al Iniciar:** Recupera `core.md`, las decisiones más recientes y contexto relevante antes de modificar código. |
|
|
144
|
+
| `memory_record_decision`<br>*(alias: `log_decision`)* | Escritura | **Paso Obligatorio al Finalizar:** Registra una decisión técnica o solución de bug en `.memory/decisions/`. |
|
|
145
|
+
| `search_memory` | Lectura | Búsqueda híbrida (vectorial + FTS5) sin cortes arbitrarios. Soporta `full_content: true` para obtener documentos completos. |
|
|
146
|
+
| `get_memory_document`<br>*(alias: `read_memory_document`)* | Lectura | Recupera el texto íntegro e intacto de cualquier documento en `.memory/`. |
|
|
147
|
+
| `update_core_rules` | Escritura | Agrega permanentemente reglas o directivas a `core.md`. |
|
|
148
|
+
| `end_session` | Escritura | Registra el resumen de cierre de sesión en `.memory/ledger/`. |
|
|
149
|
+
|
|
115
150
|
---
|
|
116
151
|
|
|
117
152
|
## Licencia
|
|
@@ -43,15 +43,21 @@ echo "# You can run: memory record-session --summary \\"..."\\"" >> "$1"
|
|
|
43
43
|
const postHookPath = node_path_1.default.join(hooksDir, postHookName);
|
|
44
44
|
const postHookContent = `#!/bin/sh
|
|
45
45
|
# local-agent-memory post-commit hook
|
|
46
|
-
# Automatically records a memory session for this commit.
|
|
46
|
+
# Automatically records a memory session for this commit with changed files.
|
|
47
47
|
|
|
48
48
|
COMMIT_MSG=$(git log -1 --pretty=%B)
|
|
49
|
+
CHANGED_FILES=$(git diff-tree --no-commit-id --name-status -r HEAD)
|
|
50
|
+
|
|
51
|
+
SUMMARY="Commit: $COMMIT_MSG
|
|
52
|
+
|
|
53
|
+
Modified files:
|
|
54
|
+
$CHANGED_FILES"
|
|
49
55
|
|
|
50
56
|
# If memory CLI exists, run it
|
|
51
57
|
if command -v memory >/dev/null 2>&1; then
|
|
52
|
-
memory record-session --summary "
|
|
58
|
+
memory record-session --summary "$SUMMARY" > /dev/null 2>&1 &
|
|
53
59
|
elif npx --no-install memory --version >/dev/null 2>&1; then
|
|
54
|
-
npx --no-install memory record-session --summary "
|
|
60
|
+
npx --no-install memory record-session --summary "$SUMMARY" > /dev/null 2>&1 &
|
|
55
61
|
fi
|
|
56
62
|
`;
|
|
57
63
|
await node_fs_1.default.promises.writeFile(postHookPath, postHookContent, { mode: 0o755 });
|
|
@@ -22,18 +22,12 @@ async function initProject(options = {}) {
|
|
|
22
22
|
const spinner = (0, ora_1.default)("Initializing project...").start();
|
|
23
23
|
try {
|
|
24
24
|
const configFile = node_path_1.default.join(memoryDir, "config.json");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
// If force, we just continue (or we might want to clear it, but prompt says "a menos que se use --force", meaning we can overwrite or just proceed)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
await node_fs_1.default.promises.mkdir(memoryDir, { recursive: true });
|
|
25
|
+
const isAlreadyInitialized = node_fs_1.default.existsSync(memoryDir) && node_fs_1.default.existsSync(configFile);
|
|
26
|
+
if (isAlreadyInitialized && !options.force && !options.rules) {
|
|
27
|
+
spinner.fail("Project already initialized. Use --rules <targets> to update agent rules, or --force to overwrite.");
|
|
28
|
+
process.exit(1);
|
|
36
29
|
}
|
|
30
|
+
await node_fs_1.default.promises.mkdir(memoryDir, { recursive: true });
|
|
37
31
|
// Subdirectories
|
|
38
32
|
await node_fs_1.default.promises.mkdir(node_path_1.default.join(memoryDir, "decisions"), {
|
|
39
33
|
recursive: true,
|
|
@@ -41,22 +35,48 @@ async function initProject(options = {}) {
|
|
|
41
35
|
await node_fs_1.default.promises.mkdir(node_path_1.default.join(memoryDir, "context"), {
|
|
42
36
|
recursive: true,
|
|
43
37
|
});
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
let projectId = "";
|
|
39
|
+
if (isAlreadyInitialized && !options.force) {
|
|
40
|
+
try {
|
|
41
|
+
const rawConfig = await node_fs_1.default.promises.readFile(configFile, "utf8");
|
|
42
|
+
const parsed = JSON.parse(rawConfig);
|
|
43
|
+
projectId = parsed.project_id || node_crypto_1.default.randomUUID();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
projectId = node_crypto_1.default.randomUUID();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
projectId = node_crypto_1.default.randomUUID();
|
|
51
|
+
}
|
|
46
52
|
// Config file
|
|
47
|
-
|
|
53
|
+
let configData = {
|
|
48
54
|
project_id: projectId,
|
|
49
55
|
created_at: new Date().toISOString(),
|
|
50
56
|
include_paths: [".memory"],
|
|
51
57
|
log_usage: options.log !== false,
|
|
52
58
|
};
|
|
53
|
-
if (options.
|
|
59
|
+
if (isAlreadyInitialized && !options.force) {
|
|
60
|
+
try {
|
|
61
|
+
const rawConfig = await node_fs_1.default.promises.readFile(configFile, "utf8");
|
|
62
|
+
configData = {
|
|
63
|
+
...JSON.parse(rawConfig),
|
|
64
|
+
log_usage: options.log !== false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
}
|
|
69
|
+
if (options.docs && !configData.include_paths?.includes(options.docs)) {
|
|
70
|
+
configData.include_paths = configData.include_paths || [".memory"];
|
|
54
71
|
configData.include_paths.push(options.docs);
|
|
55
72
|
}
|
|
56
|
-
await node_fs_1.default.promises.writeFile(
|
|
57
|
-
// Core markdown
|
|
58
|
-
const
|
|
59
|
-
|
|
73
|
+
await node_fs_1.default.promises.writeFile(configFile, JSON.stringify(configData, null, 2));
|
|
74
|
+
// Core markdown: preserve existing core.md if project already initialized
|
|
75
|
+
const corePath = node_path_1.default.join(memoryDir, "core.md");
|
|
76
|
+
if (!node_fs_1.default.existsSync(corePath) || options.force) {
|
|
77
|
+
const coreMd = `# Core Rules\n\nThis file contains the core behavior and rules for this project.\n`;
|
|
78
|
+
await node_fs_1.default.promises.writeFile(corePath, coreMd);
|
|
79
|
+
}
|
|
60
80
|
// Initialize local DB
|
|
61
81
|
const localDbPath = (0, project_1.getLocalDbPath)(cwd);
|
|
62
82
|
const localDb = (0, project_1.initLocalDb)(localDbPath);
|
|
@@ -80,34 +100,49 @@ async function initProject(options = {}) {
|
|
|
80
100
|
});
|
|
81
101
|
});
|
|
82
102
|
await (0, registry_1.registerProject)(projectId, cwd);
|
|
83
|
-
// Inject agent instructions
|
|
103
|
+
// Inject agent instructions with mandatory protocol
|
|
84
104
|
const memoryInstructions = `
|
|
85
|
-
<!--
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
<!-- START LOCAL-AGENT-MEMORY PROTOCOL -->
|
|
106
|
+
# Protocolo Obligatorio de Memoria Local (\`local-agent-memory\`)
|
|
107
|
+
|
|
108
|
+
Este proyecto cuenta con un sistema de memoria persistente conectado vía MCP. Su uso NO es opcional.
|
|
109
|
+
|
|
110
|
+
1. **Al iniciar cualquier tarea (Lectura):**
|
|
111
|
+
- Antes de proponer código, modificar archivos o responder dudas sobre el proyecto, **DEBES** ejecutar la herramienta MCP \`memory_recall_context\` (o \`get_project_context\`) pasando el \`projectPath\` actual.
|
|
112
|
+
2. **Al finalizar cualquier cambio (Escritura):**
|
|
113
|
+
- Antes de dar por concluida tu respuesta tras haber editado código, resuelto un bug o definido arquitectura, **DEBES** ejecutar la herramienta MCP \`memory_record_decision\` (o \`log_decision\`) documentando el cambio y su justificación técnica.
|
|
114
|
+
3. **Para dudas sobre reglas o patrones existentes:**
|
|
115
|
+
- Utiliza la herramienta MCP \`search_memory\` para consultar el histórico y las decisiones previas (pasa \`full_content: true\` para obtener documentos enteros en vez de fragmentos).
|
|
116
|
+
4. **Para leer un documento de memoria completo:**
|
|
117
|
+
- Utiliza la herramienta MCP \`get_memory_document\` (o \`read_memory_document\`) especificando la ruta o nombre del archivo (ej. \`decisions/2026-10-14-xxx.md\` o \`core.md\`).
|
|
118
|
+
<!-- END LOCAL-AGENT-MEMORY PROTOCOL -->
|
|
89
119
|
`;
|
|
90
120
|
spinner.stop();
|
|
91
|
-
let
|
|
121
|
+
let selectedKeys = [];
|
|
92
122
|
const availableRules = {
|
|
93
|
-
cursor: ".cursorrules",
|
|
94
|
-
windsurf: ".windsurfrules",
|
|
95
|
-
copilot: ".github/copilot-instructions.md",
|
|
96
|
-
claude: "CLAUDE.md",
|
|
97
|
-
"antigravity-ide": ".agents/rules/memory.md",
|
|
98
|
-
"antigravity-cli": ".
|
|
123
|
+
cursor: [".cursorrules", ".cursor/rules/memory-protocol.mdc"],
|
|
124
|
+
windsurf: [".windsurfrules"],
|
|
125
|
+
copilot: [".github/copilot-instructions.md"],
|
|
126
|
+
claude: ["CLAUDE.md"],
|
|
127
|
+
"antigravity-ide": [".agents/rules/memory.md", "GEMINI.md"],
|
|
128
|
+
"antigravity-cli": ["AGENTS.md"],
|
|
129
|
+
antigravity: [".agents/rules/memory.md", "GEMINI.md", "AGENTS.md"],
|
|
130
|
+
".cursorrules": [".cursorrules"],
|
|
131
|
+
".windsurfrules": [".windsurfrules"],
|
|
132
|
+
".github/copilot-instructions.md": [".github/copilot-instructions.md"],
|
|
133
|
+
"claude.md": ["CLAUDE.md"],
|
|
134
|
+
".agents/rules/memory.md": [".agents/rules/memory.md"],
|
|
135
|
+
"gemini.md": ["GEMINI.md"],
|
|
136
|
+
"agents.md": ["AGENTS.md"],
|
|
99
137
|
};
|
|
100
138
|
if (options.rules) {
|
|
101
139
|
if (options.rules.toLowerCase() === "all") {
|
|
102
|
-
|
|
140
|
+
selectedKeys = Object.keys(availableRules);
|
|
103
141
|
}
|
|
104
142
|
else {
|
|
105
|
-
|
|
143
|
+
selectedKeys = options.rules
|
|
106
144
|
.split(",")
|
|
107
145
|
.map((r) => r.trim().toLowerCase());
|
|
108
|
-
tools = Array.from(new Set(rulesList
|
|
109
|
-
.map((r) => availableRules[r])
|
|
110
|
-
.filter((r) => Boolean(r))));
|
|
111
146
|
}
|
|
112
147
|
}
|
|
113
148
|
else {
|
|
@@ -117,32 +152,62 @@ async function initProject(options = {}) {
|
|
|
117
152
|
name: "tools",
|
|
118
153
|
message: "Select the IDEs/CLIs to generate or inject memory rules into:",
|
|
119
154
|
choices: [
|
|
120
|
-
{
|
|
121
|
-
|
|
155
|
+
{
|
|
156
|
+
name: "Cursor (.cursorrules, .cursor/rules/memory-protocol.mdc)",
|
|
157
|
+
value: "cursor",
|
|
158
|
+
},
|
|
159
|
+
{ name: "Windsurf (.windsurfrules)", value: "windsurf" },
|
|
122
160
|
{
|
|
123
161
|
name: "GitHub Copilot (.github/copilot-instructions.md)",
|
|
124
|
-
value: "
|
|
162
|
+
value: "copilot",
|
|
163
|
+
},
|
|
164
|
+
{ name: "Claude Code (CLAUDE.md)", value: "claude" },
|
|
165
|
+
{
|
|
166
|
+
name: "Antigravity IDE (.agents/rules/memory.md, GEMINI.md)",
|
|
167
|
+
value: "antigravity-ide",
|
|
125
168
|
},
|
|
126
|
-
{ name: "Claude Code (CLAUDE.md)", value: "CLAUDE.md" },
|
|
127
169
|
{
|
|
128
|
-
name: "Antigravity
|
|
129
|
-
value: "
|
|
170
|
+
name: "Antigravity CLI (AGENTS.md)",
|
|
171
|
+
value: "antigravity-cli",
|
|
130
172
|
},
|
|
131
173
|
],
|
|
132
174
|
},
|
|
133
175
|
]);
|
|
134
|
-
|
|
176
|
+
selectedKeys = answers.tools;
|
|
135
177
|
}
|
|
136
178
|
spinner.start("Finishing initialization...");
|
|
137
|
-
|
|
179
|
+
const filesToInject = new Set();
|
|
180
|
+
for (const key of selectedKeys) {
|
|
181
|
+
const paths = availableRules[key.toLowerCase()];
|
|
182
|
+
if (paths) {
|
|
183
|
+
for (const p of paths)
|
|
184
|
+
filesToInject.add(p);
|
|
185
|
+
}
|
|
186
|
+
else if (key.endsWith(".md") ||
|
|
187
|
+
key.endsWith(".mdc") ||
|
|
188
|
+
key.startsWith(".")) {
|
|
189
|
+
filesToInject.add(key);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
for (const file of Array.from(filesToInject)) {
|
|
138
193
|
const filePath = node_path_1.default.join(cwd, file);
|
|
139
194
|
const dir = node_path_1.default.dirname(filePath);
|
|
140
195
|
if (!node_fs_1.default.existsSync(dir)) {
|
|
141
196
|
await node_fs_1.default.promises.mkdir(dir, { recursive: true });
|
|
142
197
|
}
|
|
143
198
|
if (node_fs_1.default.existsSync(filePath)) {
|
|
144
|
-
|
|
145
|
-
|
|
199
|
+
let content = await node_fs_1.default.promises.readFile(filePath, "utf8");
|
|
200
|
+
const protocolRegex = /<!-- START LOCAL-AGENT-MEMORY PROTOCOL -->[\s\S]*?<!-- END LOCAL-AGENT-MEMORY PROTOCOL -->/;
|
|
201
|
+
const legacyRegex = /<!-- local-agent-memory rules -->[\s\S]*?(?=\n\n|$)/;
|
|
202
|
+
if (protocolRegex.test(content)) {
|
|
203
|
+
content = content.replace(protocolRegex, memoryInstructions.trim());
|
|
204
|
+
await node_fs_1.default.promises.writeFile(filePath, content);
|
|
205
|
+
}
|
|
206
|
+
else if (legacyRegex.test(content)) {
|
|
207
|
+
content = content.replace(legacyRegex, memoryInstructions.trim());
|
|
208
|
+
await node_fs_1.default.promises.writeFile(filePath, content);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
146
211
|
await node_fs_1.default.promises.appendFile(filePath, `\n${memoryInstructions}`);
|
|
147
212
|
}
|
|
148
213
|
}
|
|
@@ -22,12 +22,21 @@ async function reindexProject() {
|
|
|
22
22
|
// Check if db exists, then delete to force complete reindex
|
|
23
23
|
if (node_fs_1.default.existsSync(localDbPath)) {
|
|
24
24
|
spinner.text = "Deleting corrupted or existing local index...";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
node_fs_1.default.
|
|
29
|
-
|
|
30
|
-
node_fs_1.default.
|
|
25
|
+
try {
|
|
26
|
+
node_fs_1.default.unlinkSync(localDbPath);
|
|
27
|
+
// Also delete the WAL and SHM files if they exist
|
|
28
|
+
if (node_fs_1.default.existsSync(`${localDbPath}-wal`))
|
|
29
|
+
node_fs_1.default.unlinkSync(`${localDbPath}-wal`);
|
|
30
|
+
if (node_fs_1.default.existsSync(`${localDbPath}-shm`))
|
|
31
|
+
node_fs_1.default.unlinkSync(`${localDbPath}-shm`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
if (err.code === "EBUSY" || err.code === "EPERM") {
|
|
35
|
+
spinner.fail("Cannot reindex: local.sqlite is currently open and locked by an active process (e.g. MCP server). Please stop the MCP process and try again.");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
31
40
|
}
|
|
32
41
|
spinner.text = "Rebuilding index from scratch...";
|
|
33
42
|
const { newCount } = await (0, sync_1.doSyncProject)(cwd);
|
|
@@ -57,6 +57,16 @@ async function projectStatus() {
|
|
|
57
57
|
manifestMap.delete(relativePath);
|
|
58
58
|
}
|
|
59
59
|
delCount = manifestMap.size;
|
|
60
|
+
// Check MCP usage stats if DB exists
|
|
61
|
+
let mcpStats = null;
|
|
62
|
+
if (node_fs_1.default.existsSync(localDbPath)) {
|
|
63
|
+
try {
|
|
64
|
+
mcpStats = await (0, project_1.getMcpUsageStats)(localDbPath);
|
|
65
|
+
}
|
|
66
|
+
catch (_e) {
|
|
67
|
+
// Table may not exist yet in older databases
|
|
68
|
+
}
|
|
69
|
+
}
|
|
60
70
|
spinner.stop();
|
|
61
71
|
console.log(`\nProject ID: ${projectId || "Unknown (Not in global registry)"}`);
|
|
62
72
|
console.log(`Total MD files tracking: ${currentFiles.length}`);
|
|
@@ -66,6 +76,20 @@ async function projectStatus() {
|
|
|
66
76
|
console.log(`Modified (unsynced): ${modCount}`);
|
|
67
77
|
console.log(`Deleted (orphaned in db): ${delCount}`);
|
|
68
78
|
console.log(`\nRun \`memory sync\` to update index.`);
|
|
79
|
+
if (mcpStats) {
|
|
80
|
+
console.log(`\nAgent & MCP Activity:`);
|
|
81
|
+
console.log(`---------------------------------`);
|
|
82
|
+
console.log(`Total MCP tool invocations: ${mcpStats.totalCalls}`);
|
|
83
|
+
console.log(` - Context & search recalls: ${mcpStats.readCalls}`);
|
|
84
|
+
console.log(` - Decisions & rules recorded: ${mcpStats.writeCalls}`);
|
|
85
|
+
console.log(`Last agent activity: ${mcpStats.lastConnection || "No recorded activity yet"}`);
|
|
86
|
+
if (mcpStats.recentQueries && mcpStats.recentQueries.length > 0) {
|
|
87
|
+
console.log(`\nRecent agent actions:`);
|
|
88
|
+
for (const q of mcpStats.recentQueries) {
|
|
89
|
+
console.log(` [${q.timestamp}] ${q.tool_name || q.event_type}: ${q.query_or_summary || "success"}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
69
93
|
}
|
|
70
94
|
catch (err) {
|
|
71
95
|
spinner.fail("Status check failed.");
|
|
@@ -20,7 +20,7 @@ async function doSyncProject(cwd) {
|
|
|
20
20
|
const localDbPath = (0, project_1.getLocalDbPath)(cwd);
|
|
21
21
|
const db = (0, project_1.initLocalDb)(localDbPath);
|
|
22
22
|
const _asyncDb = new sync_1.AsyncDatabase(db);
|
|
23
|
-
const manifests = await (0, project_1.getAllManifests)(
|
|
23
|
+
const manifests = await (0, project_1.getAllManifests)(db);
|
|
24
24
|
const manifestMap = new Map(manifests.map((m) => [m.file_path, m]));
|
|
25
25
|
const configPath = node_path_1.default.join(memoryDir, "config.json");
|
|
26
26
|
let includePaths = [".memory"];
|
|
@@ -64,7 +64,7 @@ async function doSyncProject(cwd) {
|
|
|
64
64
|
if (!existing) {
|
|
65
65
|
// New file
|
|
66
66
|
const sha256 = await computeSha256(file);
|
|
67
|
-
await (0, project_1.upsertFileManifest)(
|
|
67
|
+
await (0, project_1.upsertFileManifest)(db, relativePath, mtime, sha256);
|
|
68
68
|
const content = await node_fs_1.default.promises.readFile(file, "utf8");
|
|
69
69
|
await (0, sync_1.syncFile)(relativePath, content, db);
|
|
70
70
|
newCount++;
|
|
@@ -73,7 +73,7 @@ async function doSyncProject(cwd) {
|
|
|
73
73
|
// Potentially modified, check hash
|
|
74
74
|
const sha256 = await computeSha256(file);
|
|
75
75
|
if (sha256 !== existing.sha256) {
|
|
76
|
-
await (0, project_1.upsertFileManifest)(
|
|
76
|
+
await (0, project_1.upsertFileManifest)(db, relativePath, mtime, sha256);
|
|
77
77
|
const content = await node_fs_1.default.promises.readFile(file, "utf8");
|
|
78
78
|
await (0, sync_1.syncFile)(relativePath, content, db);
|
|
79
79
|
modCount++;
|
package/dist/core/search.d.ts
CHANGED
|
@@ -10,6 +10,12 @@ export interface SearchResult extends Chunk {
|
|
|
10
10
|
* or punctuation (like *, AND, OR, NEAR).
|
|
11
11
|
*/
|
|
12
12
|
export declare function sanitizeFtsQuery(query: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Calculates a time decay multiplier for search results:
|
|
15
|
+
* Final_Score = RRF_Score * (1 / (1 + lambda * delta_days))
|
|
16
|
+
* Core rules or pinned files have lambda = 0 (multiplier = 1.0).
|
|
17
|
+
*/
|
|
18
|
+
export declare function calculateTimeDecayMultiplier(filePath: string, nowMs?: number, mtimeMs?: number): number;
|
|
13
19
|
/**
|
|
14
20
|
* Performs a hybrid search combining FTS5 lexical search and sqlite-vec vector search.
|
|
15
21
|
* Uses Reciprocal Rank Fusion (RRF) to combine results.
|
package/dist/core/search.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sanitizeFtsQuery = sanitizeFtsQuery;
|
|
4
|
+
exports.calculateTimeDecayMultiplier = calculateTimeDecayMultiplier;
|
|
4
5
|
exports.hybridSearch = hybridSearch;
|
|
5
6
|
const embeddings_1 = require("./embeddings");
|
|
6
7
|
const sync_1 = require("./sync");
|
|
@@ -17,6 +18,31 @@ function sanitizeFtsQuery(query) {
|
|
|
17
18
|
return "";
|
|
18
19
|
return terms.map((t) => `"${t.replace(/"/g, '""')}"`).join(" ");
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Calculates a time decay multiplier for search results:
|
|
23
|
+
* Final_Score = RRF_Score * (1 / (1 + lambda * delta_days))
|
|
24
|
+
* Core rules or pinned files have lambda = 0 (multiplier = 1.0).
|
|
25
|
+
*/
|
|
26
|
+
function calculateTimeDecayMultiplier(filePath, nowMs = Date.now(), mtimeMs) {
|
|
27
|
+
if (filePath.endsWith("core.md") || filePath.includes("pinned")) {
|
|
28
|
+
return 1.0;
|
|
29
|
+
}
|
|
30
|
+
// Check if filename starts with YYYY-MM-DD (e.g. ADRs or ledger sessions)
|
|
31
|
+
const baseName = filePath.split(/[/\\]/).pop() || "";
|
|
32
|
+
const match = baseName.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
33
|
+
let ageDays = 0;
|
|
34
|
+
if (match) {
|
|
35
|
+
const fileDate = new Date(`${match[1]}-${match[2]}-${match[3]}`).getTime();
|
|
36
|
+
if (!Number.isNaN(fileDate)) {
|
|
37
|
+
ageDays = Math.max(0, (nowMs - fileDate) / (1000 * 60 * 60 * 24));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (mtimeMs) {
|
|
41
|
+
ageDays = Math.max(0, (nowMs - mtimeMs) / (1000 * 60 * 60 * 24));
|
|
42
|
+
}
|
|
43
|
+
const LAMBDA = 0.005; // Gentle decay factor
|
|
44
|
+
return 1 / (1 + LAMBDA * ageDays);
|
|
45
|
+
}
|
|
20
46
|
/**
|
|
21
47
|
* Performs a hybrid search combining FTS5 lexical search and sqlite-vec vector search.
|
|
22
48
|
* Uses Reciprocal Rank Fusion (RRF) to combine results.
|
|
@@ -129,6 +155,7 @@ async function hybridSearch(query, limit, db, filter) {
|
|
|
129
155
|
row = await asyncDb.get("SELECT * FROM chunks WHERE id = ?", [id]);
|
|
130
156
|
}
|
|
131
157
|
if (row) {
|
|
158
|
+
const decay = calculateTimeDecayMultiplier(row.path);
|
|
132
159
|
finalResults.push({
|
|
133
160
|
id: row.id,
|
|
134
161
|
path: row.path,
|
|
@@ -136,9 +163,10 @@ async function hybridSearch(query, limit, db, filter) {
|
|
|
136
163
|
breadcrumbs: row.breadcrumbs,
|
|
137
164
|
startLine: row.startLine,
|
|
138
165
|
endLine: row.endLine,
|
|
139
|
-
score,
|
|
166
|
+
score: score * decay,
|
|
140
167
|
});
|
|
141
168
|
}
|
|
142
169
|
}
|
|
170
|
+
finalResults.sort((a, b) => b.score - a.score);
|
|
143
171
|
return finalResults;
|
|
144
172
|
}
|
package/dist/db/project.d.ts
CHANGED
|
@@ -7,7 +7,32 @@ export interface FileManifestRow {
|
|
|
7
7
|
export declare function getVecExtensionPath(): string | null;
|
|
8
8
|
export declare function getLocalDbPath(projectRoot: string): string;
|
|
9
9
|
export declare function initLocalDb(dbPath: string): sqlite3.Database;
|
|
10
|
-
export declare function upsertFileManifest(
|
|
11
|
-
export declare function getFileManifest(
|
|
12
|
-
export declare function getAllManifests(
|
|
13
|
-
export declare function removeFileManifest(
|
|
10
|
+
export declare function upsertFileManifest(dbOrPath: string | sqlite3.Database, file_path: string, mtime: number, sha256: string): Promise<void>;
|
|
11
|
+
export declare function getFileManifest(dbOrPath: string | sqlite3.Database, file_path: string): Promise<FileManifestRow | null>;
|
|
12
|
+
export declare function getAllManifests(dbOrPath: string | sqlite3.Database): Promise<FileManifestRow[]>;
|
|
13
|
+
export declare function removeFileManifest(dbOrPath: string | sqlite3.Database, file_path: string): Promise<void>;
|
|
14
|
+
export interface McpUsageLogRow {
|
|
15
|
+
id?: number;
|
|
16
|
+
timestamp?: string;
|
|
17
|
+
project_path: string;
|
|
18
|
+
event_type: string;
|
|
19
|
+
tool_name?: string | null | undefined;
|
|
20
|
+
query_or_summary?: string | null | undefined;
|
|
21
|
+
execution_time_ms?: number | null | undefined;
|
|
22
|
+
success?: boolean | null | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface McpUsageStats {
|
|
25
|
+
totalCalls: number;
|
|
26
|
+
readCalls: number;
|
|
27
|
+
writeCalls: number;
|
|
28
|
+
lastConnection: string | null;
|
|
29
|
+
recentQueries: Array<{
|
|
30
|
+
timestamp: string;
|
|
31
|
+
event_type: string;
|
|
32
|
+
tool_name: string | null;
|
|
33
|
+
query_or_summary: string | null;
|
|
34
|
+
success: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
37
|
+
export declare function insertMcpUsageLog(dbOrPath: string | sqlite3.Database, entry: Omit<McpUsageLogRow, "id" | "timestamp">): Promise<void>;
|
|
38
|
+
export declare function getMcpUsageStats(dbOrPath: string | sqlite3.Database): Promise<McpUsageStats>;
|