ac-framework 1.7.0 → 1.8.0
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 +26 -0
- package/bin/postinstall.js +8 -1
- package/framework/.agent/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.agent/workflows/ac-lite.md +192 -0
- package/framework/.agent/workflows/ac.md +40 -0
- package/framework/.amazonq/prompts/ac-lite.md +192 -0
- package/framework/.amazonq/prompts/ac.md +40 -0
- package/framework/.amazonq/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.antigravity/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.antigravity/workflows/ac-lite.md +192 -0
- package/framework/.antigravity/workflows/ac.md +40 -0
- package/framework/.augment/commands/ac-lite.md +192 -0
- package/framework/.augment/commands/ac.md +40 -0
- package/framework/.augment/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.claude/commands/opsx/ac-lite.md +192 -0
- package/framework/.claude/commands/opsx/ac.md +40 -0
- package/framework/.claude/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.cline/commands/opsx/ac-lite.md +192 -0
- package/framework/.cline/commands/opsx/ac.md +40 -0
- package/framework/.cline/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.clinerules/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.clinerules/workflows/ac-lite.md +192 -0
- package/framework/.clinerules/workflows/ac.md +40 -0
- package/framework/.codebuddy/commands/opsx/ac-lite.md +192 -0
- package/framework/.codebuddy/commands/opsx/ac.md +40 -0
- package/framework/.codebuddy/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.codex/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.continue/prompts/ac-lite.md +192 -0
- package/framework/.continue/prompts/ac.md +40 -0
- package/framework/.continue/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.cospec/openspec/commands/ac-lite.md +192 -0
- package/framework/.cospec/openspec/commands/ac.md +40 -0
- package/framework/.cospec/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.crush/commands/opsx/ac-lite.md +192 -0
- package/framework/.crush/commands/opsx/ac.md +40 -0
- package/framework/.crush/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.cursor/commands/ac-lite.md +192 -0
- package/framework/.cursor/commands/ac.md +40 -0
- package/framework/.cursor/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.factory/commands/ac-lite.md +192 -0
- package/framework/.factory/commands/ac.md +40 -0
- package/framework/.factory/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.gemini/commands/opsx/ac-lite.md +192 -0
- package/framework/.gemini/commands/opsx/ac.md +40 -0
- package/framework/.gemini/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.github/prompts/ac-lite.md +192 -0
- package/framework/.github/prompts/ac.md +40 -0
- package/framework/.github/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.iflow/commands/ac-lite.md +192 -0
- package/framework/.iflow/commands/ac.md +40 -0
- package/framework/.iflow/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.kilocode/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.kilocode/workflows/ac-lite.md +192 -0
- package/framework/.kilocode/workflows/ac.md +40 -0
- package/framework/.kimi/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.kimi/workflows/ac-lite.md +192 -0
- package/framework/.kimi/workflows/ac.md +40 -0
- package/framework/.opencode/command/ac-lite.md +192 -0
- package/framework/.opencode/command/ac.md +40 -0
- package/framework/.opencode/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.qoder/commands/opsx/ac-lite.md +192 -0
- package/framework/.qoder/commands/opsx/ac.md +40 -0
- package/framework/.qoder/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.qwen/commands/ac-lite.md +192 -0
- package/framework/.qwen/commands/ac.md +40 -0
- package/framework/.qwen/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.roo/commands/ac-lite.md +192 -0
- package/framework/.roo/commands/ac.md +40 -0
- package/framework/.roo/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.trae/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.windsurf/skills/acfm-memory/SKILL.md +312 -0
- package/framework/.windsurf/workflows/ac-lite.md +192 -0
- package/framework/.windsurf/workflows/ac.md +40 -0
- package/framework/AGENTS.md +39 -0
- package/framework/CLAUDE.md +39 -0
- package/framework/GEMINI.md +39 -0
- package/framework/copilot-instructions.md +39 -0
- package/package.json +2 -1
- package/src/cli.js +2 -0
- package/src/commands/memory.js +772 -0
- package/src/index.js +46 -0
- package/src/memory/autosave.js +382 -0
- package/src/memory/database.js +178 -0
- package/src/memory/engine.js +727 -0
- package/src/memory/index.js +62 -0
- package/src/memory/utils.js +128 -0
- package/src/services/spec-engine.js +69 -1
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine.js — Motor de memoria autónoma
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations, búsqueda FTS5, análisis de patrones,
|
|
5
|
+
* grafo de relaciones, y sistema de confianza.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDatabase } from './database.js';
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { redactPrivateContent } from './utils.js';
|
|
11
|
+
|
|
12
|
+
// Tipos de memoria válidos
|
|
13
|
+
const VALID_TYPES = [
|
|
14
|
+
'architectural_decision',
|
|
15
|
+
'bugfix_pattern',
|
|
16
|
+
'api_pattern',
|
|
17
|
+
'performance_insight',
|
|
18
|
+
'security_fix',
|
|
19
|
+
'refactor_technique',
|
|
20
|
+
'dependency_note',
|
|
21
|
+
'workaround',
|
|
22
|
+
'convention',
|
|
23
|
+
'context_boundary',
|
|
24
|
+
'general_insight',
|
|
25
|
+
'session_summary'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const VALID_IMPORTANCE = ['critical', 'high', 'medium', 'low'];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Guarda una nueva memoria (con upsert por topic_key)
|
|
32
|
+
*/
|
|
33
|
+
export function saveMemory(data) {
|
|
34
|
+
const db = getDatabase();
|
|
35
|
+
|
|
36
|
+
// Redactar contenido privado
|
|
37
|
+
const content = redactPrivateContent(data.content);
|
|
38
|
+
const codeSnippet = data.codeSnippet ? redactPrivateContent(data.codeSnippet) : null;
|
|
39
|
+
const solution = data.solution ? redactPrivateContent(data.solution) : null;
|
|
40
|
+
const errorMessage = data.errorMessage ? redactPrivateContent(data.errorMessage) : null;
|
|
41
|
+
|
|
42
|
+
// Validar tipo
|
|
43
|
+
const type = VALID_TYPES.includes(data.type) ? data.type : 'general_insight';
|
|
44
|
+
|
|
45
|
+
// Calcular confianza si no se proporciona
|
|
46
|
+
const confidence = data.confidence ?? calculateConfidence(content, data);
|
|
47
|
+
|
|
48
|
+
// Determinar importancia
|
|
49
|
+
const importance = data.importance ?? determineImportance(type, confidence);
|
|
50
|
+
|
|
51
|
+
// Generar topic_key si no existe
|
|
52
|
+
const topicKey = data.topicKey || generateTopicKey(content);
|
|
53
|
+
|
|
54
|
+
// Verificar si existe para upsert
|
|
55
|
+
const existing = db.prepare('SELECT id, revision_count FROM memories WHERE topic_key = ?').get(topicKey);
|
|
56
|
+
|
|
57
|
+
if (existing) {
|
|
58
|
+
// Update (upsert)
|
|
59
|
+
const stmt = db.prepare(`
|
|
60
|
+
UPDATE memories SET
|
|
61
|
+
type = ?,
|
|
62
|
+
content = ?,
|
|
63
|
+
project_path = ?,
|
|
64
|
+
change_name = ?,
|
|
65
|
+
session_id = ?,
|
|
66
|
+
confidence = ?,
|
|
67
|
+
importance = ?,
|
|
68
|
+
tags = ?,
|
|
69
|
+
related_to = ?,
|
|
70
|
+
shareable = ?,
|
|
71
|
+
author = ?,
|
|
72
|
+
code_snippet = ?,
|
|
73
|
+
error_message = ?,
|
|
74
|
+
solution = ?,
|
|
75
|
+
revision_count = ?,
|
|
76
|
+
is_deleted = 0
|
|
77
|
+
WHERE id = ?
|
|
78
|
+
`);
|
|
79
|
+
|
|
80
|
+
stmt.run(
|
|
81
|
+
type,
|
|
82
|
+
content,
|
|
83
|
+
data.projectPath || null,
|
|
84
|
+
data.changeName || null,
|
|
85
|
+
data.sessionId || null,
|
|
86
|
+
confidence,
|
|
87
|
+
importance,
|
|
88
|
+
data.tags ? JSON.stringify(data.tags) : null,
|
|
89
|
+
data.relatedTo ? JSON.stringify(data.relatedTo) : null,
|
|
90
|
+
data.shareable !== false ? 1 : 0,
|
|
91
|
+
data.author || 'unknown',
|
|
92
|
+
codeSnippet,
|
|
93
|
+
errorMessage,
|
|
94
|
+
solution,
|
|
95
|
+
existing.revision_count + 1,
|
|
96
|
+
existing.id
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return { id: existing.id, operation: 'updated', revisionCount: existing.revision_count + 1 };
|
|
100
|
+
} else {
|
|
101
|
+
// Insert nuevo
|
|
102
|
+
const stmt = db.prepare(`
|
|
103
|
+
INSERT INTO memories (
|
|
104
|
+
topic_key, type, content, project_path, change_name, session_id,
|
|
105
|
+
confidence, importance, tags, related_to, shareable, author,
|
|
106
|
+
code_snippet, error_message, solution
|
|
107
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
`);
|
|
109
|
+
|
|
110
|
+
const result = stmt.run(
|
|
111
|
+
topicKey,
|
|
112
|
+
type,
|
|
113
|
+
content,
|
|
114
|
+
data.projectPath || null,
|
|
115
|
+
data.changeName || null,
|
|
116
|
+
data.sessionId || null,
|
|
117
|
+
confidence,
|
|
118
|
+
importance,
|
|
119
|
+
data.tags ? JSON.stringify(data.tags) : null,
|
|
120
|
+
data.relatedTo ? JSON.stringify(data.relatedTo) : null,
|
|
121
|
+
data.shareable !== false ? 1 : 0,
|
|
122
|
+
data.author || 'unknown',
|
|
123
|
+
codeSnippet,
|
|
124
|
+
errorMessage,
|
|
125
|
+
solution
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
return { id: result.lastInsertRowid, operation: 'created', revisionCount: 0 };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Busca memorias usando FTS5
|
|
134
|
+
*/
|
|
135
|
+
export function searchMemories(query, options = {}) {
|
|
136
|
+
const db = getDatabase();
|
|
137
|
+
|
|
138
|
+
const {
|
|
139
|
+
limit = 10,
|
|
140
|
+
offset = 0,
|
|
141
|
+
type = null,
|
|
142
|
+
projectPath = null,
|
|
143
|
+
changeName = null,
|
|
144
|
+
importance = null,
|
|
145
|
+
since = null, // fecha ISO
|
|
146
|
+
tags = null, // array
|
|
147
|
+
minConfidence = 0
|
|
148
|
+
} = options;
|
|
149
|
+
|
|
150
|
+
let sql = `
|
|
151
|
+
SELECT m.* FROM memories m
|
|
152
|
+
JOIN memories_fts fts ON m.id = fts.rowid
|
|
153
|
+
WHERE m.is_deleted = 0
|
|
154
|
+
AND fts.memories_fts MATCH ?
|
|
155
|
+
AND m.confidence >= ?
|
|
156
|
+
`;
|
|
157
|
+
|
|
158
|
+
const params = [query, minConfidence];
|
|
159
|
+
|
|
160
|
+
if (type) {
|
|
161
|
+
sql += ' AND m.type = ?';
|
|
162
|
+
params.push(type);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (projectPath) {
|
|
166
|
+
sql += ' AND m.project_path = ?';
|
|
167
|
+
params.push(projectPath);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (changeName) {
|
|
171
|
+
sql += ' AND m.change_name = ?';
|
|
172
|
+
params.push(changeName);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (importance) {
|
|
176
|
+
sql += ' AND m.importance = ?';
|
|
177
|
+
params.push(importance);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (since) {
|
|
181
|
+
sql += ' AND m.created_at >= ?';
|
|
182
|
+
params.push(since);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
sql += ' ORDER BY m.confidence DESC, m.created_at DESC LIMIT ? OFFSET ?';
|
|
186
|
+
params.push(limit, offset);
|
|
187
|
+
|
|
188
|
+
const stmt = db.prepare(sql);
|
|
189
|
+
const rows = stmt.all(...params);
|
|
190
|
+
|
|
191
|
+
// Marcar acceso
|
|
192
|
+
rows.forEach(row => {
|
|
193
|
+
recordAccess(row.id, options.sessionId, 'search');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
return rows.map(formatMemory);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Obtiene contexto relevante para el proyecto/change actual
|
|
201
|
+
*/
|
|
202
|
+
export function getContext(options = {}) {
|
|
203
|
+
const db = getDatabase();
|
|
204
|
+
|
|
205
|
+
const {
|
|
206
|
+
projectPath = null,
|
|
207
|
+
changeName = null,
|
|
208
|
+
currentTask = null, // para búsqueda semántica simple
|
|
209
|
+
limit = 5,
|
|
210
|
+
lookbackDays = 30,
|
|
211
|
+
sessionId = null
|
|
212
|
+
} = options;
|
|
213
|
+
|
|
214
|
+
// Calcular fecha de corte
|
|
215
|
+
const since = new Date();
|
|
216
|
+
since.setDate(since.getDate() - lookbackDays);
|
|
217
|
+
const sinceStr = since.toISOString();
|
|
218
|
+
|
|
219
|
+
let sql = `
|
|
220
|
+
SELECT m.* FROM memories m
|
|
221
|
+
WHERE m.is_deleted = 0
|
|
222
|
+
AND m.created_at >= ?
|
|
223
|
+
AND m.importance IN ('critical', 'high')
|
|
224
|
+
`;
|
|
225
|
+
|
|
226
|
+
const params = [sinceStr];
|
|
227
|
+
|
|
228
|
+
// Priorizar memorias del mismo proyecto
|
|
229
|
+
if (projectPath) {
|
|
230
|
+
sql += ' AND (m.project_path = ? OR m.shareable = 1)';
|
|
231
|
+
params.push(projectPath);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Si hay change específico, incluirlo
|
|
235
|
+
if (changeName) {
|
|
236
|
+
sql += ' AND (m.change_name = ? OR m.change_name IS NULL)';
|
|
237
|
+
params.push(changeName);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
sql += ` ORDER BY
|
|
241
|
+
CASE WHEN m.project_path = ? THEN 2 ELSE 1 END DESC,
|
|
242
|
+
m.access_count DESC,
|
|
243
|
+
m.created_at DESC
|
|
244
|
+
LIMIT ?`;
|
|
245
|
+
|
|
246
|
+
params.push(projectPath || '', limit);
|
|
247
|
+
|
|
248
|
+
const stmt = db.prepare(sql);
|
|
249
|
+
const rows = stmt.all(...params);
|
|
250
|
+
|
|
251
|
+
rows.forEach(row => {
|
|
252
|
+
recordAccess(row.id, sessionId, 'context');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return rows.map(formatMemory);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Obtiene timeline cronológico alrededor de una memoria
|
|
260
|
+
*/
|
|
261
|
+
export function getTimeline(memoryId, options = {}) {
|
|
262
|
+
const db = getDatabase();
|
|
263
|
+
|
|
264
|
+
const { window = 5, projectPath = null } = options;
|
|
265
|
+
|
|
266
|
+
// Obtener la memoria base
|
|
267
|
+
const base = db.prepare('SELECT * FROM memories WHERE id = ?').get(memoryId);
|
|
268
|
+
if (!base) return null;
|
|
269
|
+
|
|
270
|
+
// Memorias anteriores
|
|
271
|
+
const beforeStmt = db.prepare(`
|
|
272
|
+
SELECT * FROM memories
|
|
273
|
+
WHERE is_deleted = 0
|
|
274
|
+
AND created_at < ?
|
|
275
|
+
${projectPath ? 'AND (project_path = ? OR shareable = 1)' : ''}
|
|
276
|
+
ORDER BY created_at DESC
|
|
277
|
+
LIMIT ?
|
|
278
|
+
`);
|
|
279
|
+
|
|
280
|
+
const beforeParams = projectPath ? [base.created_at, projectPath, window] : [base.created_at, window];
|
|
281
|
+
const before = beforeStmt.all(...beforeParams).reverse();
|
|
282
|
+
|
|
283
|
+
// Memorias posteriores
|
|
284
|
+
const afterStmt = db.prepare(`
|
|
285
|
+
SELECT * FROM memories
|
|
286
|
+
WHERE is_deleted = 0
|
|
287
|
+
AND created_at > ?
|
|
288
|
+
${projectPath ? 'AND (project_path = ? OR shareable = 1)' : ''}
|
|
289
|
+
ORDER BY created_at ASC
|
|
290
|
+
LIMIT ?
|
|
291
|
+
`);
|
|
292
|
+
|
|
293
|
+
const afterParams = projectPath ? [base.created_at, projectPath, window] : [base.created_at, window];
|
|
294
|
+
const after = afterStmt.all(...afterParams);
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
base: formatMemory(base),
|
|
298
|
+
before: before.map(formatMemory),
|
|
299
|
+
after: after.map(formatMemory)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Obtiene una memoria específica por ID
|
|
305
|
+
*/
|
|
306
|
+
export function getMemory(id, sessionId = null) {
|
|
307
|
+
const db = getDatabase();
|
|
308
|
+
|
|
309
|
+
const row = db.prepare('SELECT * FROM memories WHERE id = ? AND is_deleted = 0').get(id);
|
|
310
|
+
if (!row) return null;
|
|
311
|
+
|
|
312
|
+
recordAccess(id, sessionId, 'get');
|
|
313
|
+
return formatMemory(row);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Actualiza una memoria existente
|
|
318
|
+
*/
|
|
319
|
+
export function updateMemory(id, updates) {
|
|
320
|
+
const db = getDatabase();
|
|
321
|
+
|
|
322
|
+
const allowedFields = ['type', 'content', 'tags', 'importance', 'shareable', 'relatedTo'];
|
|
323
|
+
const setParts = [];
|
|
324
|
+
const params = [];
|
|
325
|
+
|
|
326
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
327
|
+
if (!allowedFields.includes(key)) continue;
|
|
328
|
+
|
|
329
|
+
const dbKey = key === 'relatedTo' ? 'related_to' : key;
|
|
330
|
+
|
|
331
|
+
if (key === 'content') {
|
|
332
|
+
setParts.push(`${dbKey} = ?`);
|
|
333
|
+
params.push(redactPrivateContent(value));
|
|
334
|
+
} else if (key === 'tags' || key === 'relatedTo') {
|
|
335
|
+
setParts.push(`${dbKey} = ?`);
|
|
336
|
+
params.push(JSON.stringify(value));
|
|
337
|
+
} else {
|
|
338
|
+
setParts.push(`${dbKey} = ?`);
|
|
339
|
+
params.push(value);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (setParts.length === 0) return null;
|
|
344
|
+
|
|
345
|
+
params.push(id);
|
|
346
|
+
|
|
347
|
+
const stmt = db.prepare(`UPDATE memories SET ${setParts.join(', ')} WHERE id = ?`);
|
|
348
|
+
const result = stmt.run(...params);
|
|
349
|
+
|
|
350
|
+
return result.changes > 0 ? getMemory(id) : null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Soft-delete de una memoria
|
|
355
|
+
*/
|
|
356
|
+
export function deleteMemory(id) {
|
|
357
|
+
const db = getDatabase();
|
|
358
|
+
|
|
359
|
+
const stmt = db.prepare('UPDATE memories SET is_deleted = 1 WHERE id = ?');
|
|
360
|
+
const result = stmt.run(id);
|
|
361
|
+
|
|
362
|
+
return result.changes > 0;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Gestión de sesiones
|
|
367
|
+
*/
|
|
368
|
+
export function startSession(projectPath = null, changeName = null) {
|
|
369
|
+
const db = getDatabase();
|
|
370
|
+
const sessionId = randomUUID();
|
|
371
|
+
|
|
372
|
+
const stmt = db.prepare('INSERT INTO sessions (id, project_path, change_name) VALUES (?, ?, ?)');
|
|
373
|
+
stmt.run(sessionId, projectPath, changeName);
|
|
374
|
+
|
|
375
|
+
return sessionId;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function endSession(sessionId, summary = null) {
|
|
379
|
+
const db = getDatabase();
|
|
380
|
+
|
|
381
|
+
const stmt = db.prepare('UPDATE sessions SET ended_at = CURRENT_TIMESTAMP, summary = ? WHERE id = ?');
|
|
382
|
+
stmt.run(summary, sessionId);
|
|
383
|
+
|
|
384
|
+
// Si hay summary, guardarlo como memoria
|
|
385
|
+
if (summary) {
|
|
386
|
+
const session = db.prepare('SELECT * FROM sessions WHERE id = ?').get(sessionId);
|
|
387
|
+
if (session) {
|
|
388
|
+
saveMemory({
|
|
389
|
+
type: 'session_summary',
|
|
390
|
+
content: summary,
|
|
391
|
+
projectPath: session.project_path,
|
|
392
|
+
changeName: session.change_name,
|
|
393
|
+
sessionId,
|
|
394
|
+
author: 'system'
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Estadísticas del sistema de memoria
|
|
404
|
+
*/
|
|
405
|
+
export function getStats(options = {}) {
|
|
406
|
+
const db = getDatabase();
|
|
407
|
+
const { projectPath = null, since = null } = options;
|
|
408
|
+
|
|
409
|
+
let whereClause = 'WHERE is_deleted = 0';
|
|
410
|
+
const params = [];
|
|
411
|
+
|
|
412
|
+
if (projectPath) {
|
|
413
|
+
whereClause += ' AND project_path = ?';
|
|
414
|
+
params.push(projectPath);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (since) {
|
|
418
|
+
whereClause += ' AND created_at >= ?';
|
|
419
|
+
params.push(since);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Totales
|
|
423
|
+
const totalStmt = db.prepare(`SELECT COUNT(*) as count FROM memories ${whereClause}`);
|
|
424
|
+
const total = totalStmt.get(...params).count;
|
|
425
|
+
|
|
426
|
+
// Por tipo
|
|
427
|
+
const byTypeStmt = db.prepare(`
|
|
428
|
+
SELECT type, COUNT(*) as count FROM memories
|
|
429
|
+
${whereClause}
|
|
430
|
+
GROUP BY type ORDER BY count DESC
|
|
431
|
+
`);
|
|
432
|
+
const byType = byTypeStmt.all(...params);
|
|
433
|
+
|
|
434
|
+
// Por importancia
|
|
435
|
+
const byImportanceStmt = db.prepare(`
|
|
436
|
+
SELECT importance, COUNT(*) as count FROM memories
|
|
437
|
+
${whereClause}
|
|
438
|
+
GROUP BY importance ORDER BY count DESC
|
|
439
|
+
`);
|
|
440
|
+
const byImportance = byImportanceStmt.all(...params);
|
|
441
|
+
|
|
442
|
+
// Más accedidas
|
|
443
|
+
const mostAccessedStmt = db.prepare(`
|
|
444
|
+
SELECT id, content, access_count FROM memories
|
|
445
|
+
${whereClause}
|
|
446
|
+
ORDER BY access_count DESC LIMIT 5
|
|
447
|
+
`);
|
|
448
|
+
const mostAccessed = mostAccessedStmt.all(...params);
|
|
449
|
+
|
|
450
|
+
// Patrones de error comunes
|
|
451
|
+
const commonErrorsStmt = db.prepare(`
|
|
452
|
+
SELECT error_message, COUNT(*) as count FROM memories
|
|
453
|
+
${whereClause} AND error_message IS NOT NULL
|
|
454
|
+
GROUP BY error_message ORDER BY count DESC LIMIT 5
|
|
455
|
+
`);
|
|
456
|
+
const commonErrors = commonErrorsStmt.all(...params);
|
|
457
|
+
|
|
458
|
+
return {
|
|
459
|
+
total,
|
|
460
|
+
byType,
|
|
461
|
+
byImportance,
|
|
462
|
+
mostAccessed: mostAccessed.map(m => ({ id: m.id, preview: m.content.slice(0, 100), accessCount: m.access_count })),
|
|
463
|
+
commonErrors: commonErrors.map(e => ({ error: e.error_message, count: e.count }))
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Encuentra patrones y clusters
|
|
469
|
+
*/
|
|
470
|
+
export function findPatterns(options = {}) {
|
|
471
|
+
const db = getDatabase();
|
|
472
|
+
const { type = null, minFrequency = 2 } = options;
|
|
473
|
+
|
|
474
|
+
// Buscar topic_keys similares (simple string similarity)
|
|
475
|
+
let sql = `
|
|
476
|
+
SELECT topic_key, type, COUNT(*) as frequency,
|
|
477
|
+
GROUP_CONCAT(id) as memory_ids,
|
|
478
|
+
MAX(created_at) as last_seen
|
|
479
|
+
FROM memories
|
|
480
|
+
WHERE is_deleted = 0
|
|
481
|
+
`;
|
|
482
|
+
|
|
483
|
+
const params = [];
|
|
484
|
+
|
|
485
|
+
if (type) {
|
|
486
|
+
sql += ' AND type = ?';
|
|
487
|
+
params.push(type);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
sql += ' GROUP BY topic_key HAVING frequency >= ? ORDER BY frequency DESC';
|
|
491
|
+
params.push(minFrequency);
|
|
492
|
+
|
|
493
|
+
const stmt = db.prepare(sql);
|
|
494
|
+
return stmt.all(...params);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Grafo de relaciones entre memorias
|
|
499
|
+
*/
|
|
500
|
+
export function getConnections(memoryId, depth = 1) {
|
|
501
|
+
const db = getDatabase();
|
|
502
|
+
|
|
503
|
+
const visited = new Set();
|
|
504
|
+
const connections = [];
|
|
505
|
+
const queue = [{ id: memoryId, d: 0 }];
|
|
506
|
+
|
|
507
|
+
while (queue.length > 0) {
|
|
508
|
+
const { id, d } = queue.shift();
|
|
509
|
+
if (visited.has(id) || d > depth) continue;
|
|
510
|
+
visited.add(id);
|
|
511
|
+
|
|
512
|
+
const memory = db.prepare('SELECT id, content, related_to FROM memories WHERE id = ? AND is_deleted = 0').get(id);
|
|
513
|
+
if (!memory) continue;
|
|
514
|
+
|
|
515
|
+
const related = memory.related_to ? JSON.parse(memory.related_to) : [];
|
|
516
|
+
|
|
517
|
+
for (const relatedId of related) {
|
|
518
|
+
if (!visited.has(relatedId)) {
|
|
519
|
+
connections.push({ from: id, to: relatedId, depth: d + 1 });
|
|
520
|
+
queue.push({ id: relatedId, d: d + 1 });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return connections;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Sugerencias predictivas
|
|
530
|
+
*/
|
|
531
|
+
export function anticipateNeeds(currentTask, projectPath = null) {
|
|
532
|
+
const db = getDatabase();
|
|
533
|
+
|
|
534
|
+
// Extraer palabras clave simples
|
|
535
|
+
const keywords = currentTask.toLowerCase().split(/\s+/).filter(w => w.length > 3);
|
|
536
|
+
|
|
537
|
+
if (keywords.length === 0) return [];
|
|
538
|
+
|
|
539
|
+
// Construir query FTS
|
|
540
|
+
const query = keywords.join(' OR ');
|
|
541
|
+
|
|
542
|
+
return searchMemories(query, {
|
|
543
|
+
projectPath,
|
|
544
|
+
limit: 5,
|
|
545
|
+
importance: 'high',
|
|
546
|
+
minConfidence: 0.7
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ==================== HELPERS ====================
|
|
551
|
+
|
|
552
|
+
function formatMemory(row) {
|
|
553
|
+
return {
|
|
554
|
+
id: row.id,
|
|
555
|
+
topicKey: row.topic_key,
|
|
556
|
+
type: row.type,
|
|
557
|
+
content: row.content,
|
|
558
|
+
projectPath: row.project_path,
|
|
559
|
+
changeName: row.change_name,
|
|
560
|
+
sessionId: row.session_id,
|
|
561
|
+
confidence: row.confidence,
|
|
562
|
+
importance: row.importance,
|
|
563
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
564
|
+
relatedTo: row.related_to ? JSON.parse(row.related_to) : [],
|
|
565
|
+
shareable: row.shareable === 1,
|
|
566
|
+
author: row.author,
|
|
567
|
+
codeSnippet: row.code_snippet,
|
|
568
|
+
errorMessage: row.error_message,
|
|
569
|
+
solution: row.solution,
|
|
570
|
+
createdAt: row.created_at,
|
|
571
|
+
updatedAt: row.updated_at,
|
|
572
|
+
lastAccessed: row.last_accessed,
|
|
573
|
+
accessCount: row.access_count,
|
|
574
|
+
revisionCount: row.revision_count
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function recordAccess(memoryId, sessionId, accessType) {
|
|
579
|
+
const db = getDatabase();
|
|
580
|
+
|
|
581
|
+
// Log del acceso
|
|
582
|
+
const logStmt = db.prepare('INSERT INTO memory_access_log (memory_id, session_id, access_type) VALUES (?, ?, ?)');
|
|
583
|
+
logStmt.run(memoryId, sessionId, accessType);
|
|
584
|
+
|
|
585
|
+
// Actualizar contador
|
|
586
|
+
const updateStmt = db.prepare('UPDATE memories SET access_count = access_count + 1, last_accessed = CURRENT_TIMESTAMP WHERE id = ?');
|
|
587
|
+
updateStmt.run(memoryId);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function calculateConfidence(content, data) {
|
|
591
|
+
let score = 0.5;
|
|
592
|
+
|
|
593
|
+
// Palabras clave de decisión
|
|
594
|
+
if (/decidimos|elegimos|optamos|mejor usar|recomendado|evitar/i.test(content)) score += 0.2;
|
|
595
|
+
|
|
596
|
+
// Contiene solución
|
|
597
|
+
if (data.solution || /solución|fix|resuelve/i.test(content)) score += 0.15;
|
|
598
|
+
|
|
599
|
+
// Contiene error
|
|
600
|
+
if (data.errorMessage || /error|bug|issue|problema/i.test(content)) score += 0.1;
|
|
601
|
+
|
|
602
|
+
// Es arquitectónico
|
|
603
|
+
if (data.type === 'architectural_decision') score += 0.1;
|
|
604
|
+
|
|
605
|
+
// Tiene código de ejemplo
|
|
606
|
+
if (data.codeSnippet) score += 0.05;
|
|
607
|
+
|
|
608
|
+
return Math.min(0.95, score);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function determineImportance(type, confidence) {
|
|
612
|
+
if (type === 'architectural_decision' || type === 'security_fix') return 'critical';
|
|
613
|
+
if (confidence > 0.8) return 'high';
|
|
614
|
+
if (confidence > 0.6) return 'medium';
|
|
615
|
+
return 'low';
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function generateTopicKey(content) {
|
|
619
|
+
// Generar key estable a partir del contenido
|
|
620
|
+
const normalized = content.toLowerCase()
|
|
621
|
+
.replace(/[^\w\s]/g, '')
|
|
622
|
+
.split(/\s+/)
|
|
623
|
+
.filter(w => w.length > 3)
|
|
624
|
+
.slice(0, 5)
|
|
625
|
+
.join('-');
|
|
626
|
+
|
|
627
|
+
return normalized || `mem-${Date.now()}`;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Exporta memorias para sync
|
|
632
|
+
*/
|
|
633
|
+
export function exportMemories(options = {}) {
|
|
634
|
+
const db = getDatabase();
|
|
635
|
+
const { projectPath = null, shareableOnly = true, since = null } = options;
|
|
636
|
+
|
|
637
|
+
let sql = 'SELECT * FROM memories WHERE is_deleted = 0';
|
|
638
|
+
const params = [];
|
|
639
|
+
|
|
640
|
+
if (shareableOnly) {
|
|
641
|
+
sql += ' AND shareable = 1';
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (projectPath) {
|
|
645
|
+
sql += ' AND project_path = ?';
|
|
646
|
+
params.push(projectPath);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (since) {
|
|
650
|
+
sql += ' AND updated_at >= ?';
|
|
651
|
+
params.push(since);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const stmt = db.prepare(sql);
|
|
655
|
+
return stmt.all(...params).map(formatMemory);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Importa memorias desde export
|
|
660
|
+
*/
|
|
661
|
+
export function importMemories(memories, options = {}) {
|
|
662
|
+
const { merge = true } = options;
|
|
663
|
+
|
|
664
|
+
const results = [];
|
|
665
|
+
|
|
666
|
+
for (const mem of memories) {
|
|
667
|
+
try {
|
|
668
|
+
const result = saveMemory({
|
|
669
|
+
topicKey: mem.topicKey,
|
|
670
|
+
type: mem.type,
|
|
671
|
+
content: mem.content,
|
|
672
|
+
projectPath: mem.projectPath,
|
|
673
|
+
tags: mem.tags,
|
|
674
|
+
importance: mem.importance,
|
|
675
|
+
shareable: mem.shareable,
|
|
676
|
+
codeSnippet: mem.codeSnippet,
|
|
677
|
+
errorMessage: mem.errorMessage,
|
|
678
|
+
solution: mem.solution
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
results.push({ success: true, id: result.id, operation: result.operation });
|
|
682
|
+
} catch (err) {
|
|
683
|
+
results.push({ success: false, error: err.message });
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return results;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Limpieza de memorias obsoletas
|
|
692
|
+
*/
|
|
693
|
+
export function pruneMemories(options = {}) {
|
|
694
|
+
const db = getDatabase();
|
|
695
|
+
const { olderThanDays = 90, lowConfidence = true, unused = true } = options;
|
|
696
|
+
|
|
697
|
+
const cutoff = new Date();
|
|
698
|
+
cutoff.setDate(cutoff.getDate() - olderThanDays);
|
|
699
|
+
const cutoffStr = cutoff.toISOString();
|
|
700
|
+
|
|
701
|
+
let sql = 'UPDATE memories SET is_deleted = 1 WHERE is_deleted = 0';
|
|
702
|
+
const params = [];
|
|
703
|
+
|
|
704
|
+
const conditions = [];
|
|
705
|
+
|
|
706
|
+
if (olderThanDays) {
|
|
707
|
+
conditions.push('created_at < ?');
|
|
708
|
+
params.push(cutoffStr);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (lowConfidence) {
|
|
712
|
+
conditions.push('confidence < 0.4');
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (unused) {
|
|
716
|
+
conditions.push('access_count = 0');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (conditions.length > 0) {
|
|
720
|
+
sql += ' AND (' + conditions.join(' OR ') + ')';
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const stmt = db.prepare(sql);
|
|
724
|
+
const result = stmt.run(...params);
|
|
725
|
+
|
|
726
|
+
return { archived: result.changes };
|
|
727
|
+
}
|