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
package/src/index.js
CHANGED
|
@@ -18,3 +18,49 @@ export {
|
|
|
18
18
|
readProjectConfig,
|
|
19
19
|
isInitialized,
|
|
20
20
|
} from './services/spec-engine.js';
|
|
21
|
+
|
|
22
|
+
// Memory System Exports
|
|
23
|
+
export {
|
|
24
|
+
// Database
|
|
25
|
+
initDatabase,
|
|
26
|
+
getDatabase,
|
|
27
|
+
closeDatabase,
|
|
28
|
+
isDatabaseInitialized,
|
|
29
|
+
|
|
30
|
+
// Core Engine
|
|
31
|
+
saveMemory,
|
|
32
|
+
searchMemories,
|
|
33
|
+
getContext,
|
|
34
|
+
getTimeline,
|
|
35
|
+
getMemory,
|
|
36
|
+
updateMemory,
|
|
37
|
+
deleteMemory,
|
|
38
|
+
startSession,
|
|
39
|
+
endSession,
|
|
40
|
+
getStats,
|
|
41
|
+
findPatterns,
|
|
42
|
+
getConnections,
|
|
43
|
+
anticipateNeeds,
|
|
44
|
+
exportMemories,
|
|
45
|
+
importMemories,
|
|
46
|
+
pruneMemories,
|
|
47
|
+
|
|
48
|
+
// Auto-save
|
|
49
|
+
AutoSaveManager,
|
|
50
|
+
createAutoSaveHook,
|
|
51
|
+
WorkflowHooks,
|
|
52
|
+
|
|
53
|
+
// Utils
|
|
54
|
+
redactPrivateContent,
|
|
55
|
+
extractKeywords,
|
|
56
|
+
textSimilarity,
|
|
57
|
+
truncate,
|
|
58
|
+
isCodeContent,
|
|
59
|
+
detectLanguage,
|
|
60
|
+
generateSummary,
|
|
61
|
+
sanitizeFTSQuery,
|
|
62
|
+
|
|
63
|
+
// Constants
|
|
64
|
+
MEMORY_TYPES,
|
|
65
|
+
IMPORTANCE_LEVELS
|
|
66
|
+
} from './memory/index.js';
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autosave.js — Sistema de auto-guardado autónomo
|
|
3
|
+
*
|
|
4
|
+
* Detecta automáticamente qué información es valiosa
|
|
5
|
+
* y la guarda sin intervención humana.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { saveMemory, searchMemories } from './engine.js';
|
|
9
|
+
import { extractKeywords, isCodeContent, textSimilarity } from './utils.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Manager de auto-save que se integra con el workflow
|
|
13
|
+
*/
|
|
14
|
+
export class AutoSaveManager {
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.sessionId = options.sessionId;
|
|
17
|
+
this.projectPath = options.projectPath;
|
|
18
|
+
this.changeName = options.changeName;
|
|
19
|
+
this.author = options.author || 'ac-agent';
|
|
20
|
+
this.minConfidence = options.minConfidence || 0.5;
|
|
21
|
+
this.enabled = options.enabled !== false;
|
|
22
|
+
|
|
23
|
+
// Buffer de contexto para análisis
|
|
24
|
+
this.contextBuffer = [];
|
|
25
|
+
this.maxBufferSize = 10;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Evalúa si un contenido debe ser guardado
|
|
30
|
+
*/
|
|
31
|
+
shouldSave(content, context = {}) {
|
|
32
|
+
if (!this.enabled) return { shouldSave: false, reason: 'disabled' };
|
|
33
|
+
if (!content || content.length < 20) return { shouldSave: false, reason: 'too_short' };
|
|
34
|
+
|
|
35
|
+
const score = this.calculateSaveScore(content, context);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
shouldSave: score >= this.minConfidence,
|
|
39
|
+
confidence: score,
|
|
40
|
+
reason: score >= this.minConfidence ? 'valuable_content' : 'low_score'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calcula qué tan valioso es guardar este contenido
|
|
46
|
+
*/
|
|
47
|
+
calculateSaveScore(content, context = {}) {
|
|
48
|
+
let score = 0.5;
|
|
49
|
+
const lowerContent = content.toLowerCase();
|
|
50
|
+
|
|
51
|
+
// Decisiones explícitas
|
|
52
|
+
if (/decidimos|elegimos|optamos|vamos a usar|mejor usar|recomendamos/i.test(lowerContent)) {
|
|
53
|
+
score += 0.25;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Soluciones a problemas
|
|
57
|
+
if (/solución|fix|resuelve|solucionado|corregido|arreglado/i.test(lowerContent)) {
|
|
58
|
+
score += 0.2;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Problemas/evitación
|
|
62
|
+
if (/error|bug|issue|problema|cuidado con|evitar|no usar/i.test(lowerContent)) {
|
|
63
|
+
score += 0.15;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Optimizaciones
|
|
67
|
+
if (/optimización|mejora|más rápido|performance|lento/i.test(lowerContent)) {
|
|
68
|
+
score += 0.15;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Arquitectura
|
|
72
|
+
if (/arquitectura|diseño|estructura|pattern|patrón/i.test(lowerContent)) {
|
|
73
|
+
score += 0.2;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Seguridad
|
|
77
|
+
if (/seguridad|security|vulnerabilidad|auth|autenticación/i.test(lowerContent)) {
|
|
78
|
+
score += 0.25;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Contiene código valioso
|
|
82
|
+
if (isCodeContent(content) && /ejemplo|example|snippet/i.test(lowerContent)) {
|
|
83
|
+
score += 0.1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Contiene workaround
|
|
87
|
+
if (/workaround|temporal|mientras tanto|hack/i.test(lowerContent)) {
|
|
88
|
+
score += 0.15;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Contexto temporal (más tiempo = más valioso)
|
|
92
|
+
if (context.timeSpent) {
|
|
93
|
+
if (context.timeSpent > 30) score += 0.15;
|
|
94
|
+
else if (context.timeSpent > 10) score += 0.1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Viene después de error
|
|
98
|
+
if (context.afterError) score += 0.1;
|
|
99
|
+
|
|
100
|
+
// Es repetición de intentos
|
|
101
|
+
if (context.attemptCount && context.attemptCount > 2) score += 0.1;
|
|
102
|
+
|
|
103
|
+
// Penalizaciones
|
|
104
|
+
|
|
105
|
+
// Muy corto
|
|
106
|
+
if (content.length < 50) score -= 0.2;
|
|
107
|
+
|
|
108
|
+
// Muy específico (IDs, nombres únicos)
|
|
109
|
+
if (/\b[0-9a-f]{8,}\b/i.test(content)) score -= 0.15; // UUIDs, hashes
|
|
110
|
+
if (/\buser_\d+|id_\d+\b/i.test(content)) score -= 0.1; // IDs específicos
|
|
111
|
+
|
|
112
|
+
// Solo código sin explicación
|
|
113
|
+
if (isCodeContent(content) && !/[.!?]/.test(content)) score -= 0.1;
|
|
114
|
+
|
|
115
|
+
// Contiene TODO/FIXME (temporal)
|
|
116
|
+
if (/TODO|FIXME|HACK|XXX/i.test(content)) score -= 0.2;
|
|
117
|
+
|
|
118
|
+
return Math.max(0, Math.min(0.95, score));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Determina el tipo de memoria basado en contenido
|
|
123
|
+
*/
|
|
124
|
+
detectType(content, context = {}) {
|
|
125
|
+
const lower = content.toLowerCase();
|
|
126
|
+
|
|
127
|
+
if (context.typeHint) return context.typeHint;
|
|
128
|
+
|
|
129
|
+
if (/bug|error|fix|corregido|arreglado|solucionado/i.test(lower)) {
|
|
130
|
+
if (/seguridad|security|vulnerabilidad/i.test(lower)) return 'security_fix';
|
|
131
|
+
return 'bugfix_pattern';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (/decidimos|elegimos|optamos|arquitectura|diseño/i.test(lower)) {
|
|
135
|
+
return 'architectural_decision';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (/refactor|reestructurar|limpiar|simplificar/i.test(lower)) {
|
|
139
|
+
return 'refactor_technique';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (/performance|lento|rápido|optimización|mejora/i.test(lower)) {
|
|
143
|
+
return 'performance_insight';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (/api|endpoint|route|controller/i.test(lower)) {
|
|
147
|
+
return 'api_pattern';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (/dependencia|package|librería|npm|install/i.test(lower)) {
|
|
151
|
+
return 'dependency_note';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (/workaround|temporal|mientras tanto/i.test(lower)) {
|
|
155
|
+
return 'workaround';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (/convención|estilo|nomenclatura|naming/i.test(lower)) {
|
|
159
|
+
return 'convention';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (/no debe|nunca|siempre|evitar|cuidado/i.test(lower)) {
|
|
163
|
+
return 'context_boundary';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return 'general_insight';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Guarda una memoria si pasa el filtro de confianza
|
|
171
|
+
*/
|
|
172
|
+
async autoSave(content, context = {}) {
|
|
173
|
+
const decision = this.shouldSave(content, context);
|
|
174
|
+
|
|
175
|
+
if (!decision.shouldSave) {
|
|
176
|
+
return { saved: false, reason: decision.reason, confidence: decision.confidence };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const type = this.detectType(content, context);
|
|
180
|
+
const tags = context.tags || extractKeywords(content);
|
|
181
|
+
|
|
182
|
+
const result = saveMemory({
|
|
183
|
+
content,
|
|
184
|
+
type,
|
|
185
|
+
projectPath: this.projectPath,
|
|
186
|
+
changeName: this.changeName,
|
|
187
|
+
sessionId: this.sessionId,
|
|
188
|
+
author: this.author,
|
|
189
|
+
confidence: decision.confidence,
|
|
190
|
+
tags,
|
|
191
|
+
codeSnippet: context.codeSnippet,
|
|
192
|
+
errorMessage: context.errorMessage,
|
|
193
|
+
solution: context.solution,
|
|
194
|
+
shareable: context.shareable !== false
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
saved: true,
|
|
199
|
+
id: result.id,
|
|
200
|
+
operation: result.operation,
|
|
201
|
+
type,
|
|
202
|
+
confidence: decision.confidence
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Guarda en buffer para análisis posterior
|
|
208
|
+
*/
|
|
209
|
+
bufferContext(context) {
|
|
210
|
+
this.contextBuffer.push({
|
|
211
|
+
...context,
|
|
212
|
+
timestamp: Date.now()
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (this.contextBuffer.length > this.maxBufferSize) {
|
|
216
|
+
this.contextBuffer.shift();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Analiza buffer para detectar patrones
|
|
222
|
+
*/
|
|
223
|
+
analyzeBuffer() {
|
|
224
|
+
if (this.contextBuffer.length < 3) return null;
|
|
225
|
+
|
|
226
|
+
const recent = this.contextBuffer.slice(-3);
|
|
227
|
+
|
|
228
|
+
// Detectar patrón de error -> fix
|
|
229
|
+
const hasError = recent.some(c => c.type === 'error' || c.hasError);
|
|
230
|
+
const hasFix = recent.some(c => c.type === 'fix' || c.isFix);
|
|
231
|
+
|
|
232
|
+
if (hasError && hasFix) {
|
|
233
|
+
return {
|
|
234
|
+
pattern: 'error_to_fix',
|
|
235
|
+
confidence: 0.8,
|
|
236
|
+
context: recent
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Recupera contexto relevante antes de empezar tarea
|
|
245
|
+
*/
|
|
246
|
+
async recallForTask(task, options = {}) {
|
|
247
|
+
const keywords = extractKeywords(task, 8);
|
|
248
|
+
const query = keywords.join(' OR ');
|
|
249
|
+
|
|
250
|
+
const results = searchMemories(query, {
|
|
251
|
+
projectPath: this.projectPath,
|
|
252
|
+
limit: options.limit || 5,
|
|
253
|
+
minConfidence: 0.6,
|
|
254
|
+
sessionId: this.sessionId
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Sugiere memorias relacionadas a una existente
|
|
262
|
+
*/
|
|
263
|
+
suggestRelated(memoryId, content) {
|
|
264
|
+
const keywords = extractKeywords(content, 5);
|
|
265
|
+
return searchMemories(keywords.join(' OR '), {
|
|
266
|
+
limit: 3,
|
|
267
|
+
sessionId: this.sessionId
|
|
268
|
+
}).filter(m => m.id !== memoryId);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Crea un hook de auto-save para un evento específico
|
|
274
|
+
*/
|
|
275
|
+
export function createAutoSaveHook(event, handler) {
|
|
276
|
+
return {
|
|
277
|
+
event,
|
|
278
|
+
handler: async (data, context) => {
|
|
279
|
+
const manager = context.autoSaveManager;
|
|
280
|
+
if (!manager) return handler(data, context);
|
|
281
|
+
|
|
282
|
+
const result = await handler(data, context);
|
|
283
|
+
|
|
284
|
+
// Si el handler retorna contenido para guardar
|
|
285
|
+
if (result && result.memoryContent) {
|
|
286
|
+
const saveResult = await manager.autoSave(result.memoryContent, {
|
|
287
|
+
typeHint: result.memoryType,
|
|
288
|
+
...result.memoryContext
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
return { ...result, autoSave: saveResult };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Hooks predefinidos para workflow spec-driven
|
|
300
|
+
export const WorkflowHooks = {
|
|
301
|
+
/**
|
|
302
|
+
* Hook post-proposal: guarda decisiones arquitectónicas
|
|
303
|
+
*/
|
|
304
|
+
afterProposal: (manager) => async (proposalData) => {
|
|
305
|
+
const content = `Arquitectura decidida: ${proposalData.title || 'N/A'}. ${proposalData.description || ''}`;
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
memoryContent: content,
|
|
309
|
+
memoryType: 'architectural_decision',
|
|
310
|
+
memoryContext: {
|
|
311
|
+
importance: 'high',
|
|
312
|
+
tags: ['proposal', 'architecture']
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Hook post-design: guarda patrones técnicos
|
|
319
|
+
*/
|
|
320
|
+
afterDesign: (manager) => async (designData) => {
|
|
321
|
+
const content = `Patrón de diseño: ${designData.approach || 'N/A'}. ${designData.rationale || ''}`;
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
memoryContent: content,
|
|
325
|
+
memoryType: 'architectural_decision',
|
|
326
|
+
memoryContext: {
|
|
327
|
+
importance: 'high',
|
|
328
|
+
tags: ['design', 'pattern']
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Hook post-bugfix: guarda solución
|
|
335
|
+
*/
|
|
336
|
+
afterBugfix: (manager) => async (bugfixData) => {
|
|
337
|
+
const content = `Bug resuelto: ${bugfixData.description}. Solución: ${bugfixData.solution}`;
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
memoryContent: content,
|
|
341
|
+
memoryType: bugfixData.security ? 'security_fix' : 'bugfix_pattern',
|
|
342
|
+
memoryContext: {
|
|
343
|
+
errorMessage: bugfixData.error,
|
|
344
|
+
solution: bugfixData.solution,
|
|
345
|
+
codeSnippet: bugfixData.codeSnippet,
|
|
346
|
+
importance: 'high'
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Hook post-refactor: guarda técnica
|
|
353
|
+
*/
|
|
354
|
+
afterRefactor: (manager) => async (refactorData) => {
|
|
355
|
+
const content = `Refactor: ${refactorData.description}. Beneficio: ${refactorData.benefit}`;
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
memoryContent: content,
|
|
359
|
+
memoryType: 'refactor_technique',
|
|
360
|
+
memoryContext: {
|
|
361
|
+
codeSnippet: refactorData.beforeAfter,
|
|
362
|
+
importance: 'medium'
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
},
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Hook post-optimization: guarda insight de performance
|
|
369
|
+
*/
|
|
370
|
+
afterOptimization: (manager) => async (optData) => {
|
|
371
|
+
const content = `Optimización: ${optData.description}. Mejora: ${optData.improvement}`;
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
memoryContent: content,
|
|
375
|
+
memoryType: 'performance_insight',
|
|
376
|
+
memoryContext: {
|
|
377
|
+
importance: 'medium',
|
|
378
|
+
tags: ['performance', 'optimization']
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* database.js — SQLite + FTS5 para sistema de memoria autónoma
|
|
3
|
+
*
|
|
4
|
+
* Base de datos embebida sin dependencias externas de runtime.
|
|
5
|
+
* Usa better-sqlite3 para sincronía y mejor performance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
import { mkdirSync, existsSync } from 'node:fs';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
// Configuración
|
|
14
|
+
const ACFM_DIR = join(homedir(), '.acfm');
|
|
15
|
+
const DB_PATH = process.env.ACFM_MEMORY_DB || join(ACFM_DIR, 'memory.db');
|
|
16
|
+
|
|
17
|
+
// Singleton de conexión
|
|
18
|
+
let dbInstance = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Schema SQL completo
|
|
22
|
+
*/
|
|
23
|
+
const SCHEMA_SQL = `
|
|
24
|
+
-- Tabla principal de memorias
|
|
25
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
26
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
topic_key TEXT UNIQUE,
|
|
28
|
+
type TEXT NOT NULL,
|
|
29
|
+
content TEXT NOT NULL,
|
|
30
|
+
project_path TEXT,
|
|
31
|
+
change_name TEXT,
|
|
32
|
+
session_id TEXT,
|
|
33
|
+
confidence REAL DEFAULT 0.5,
|
|
34
|
+
importance TEXT DEFAULT 'medium',
|
|
35
|
+
tags TEXT,
|
|
36
|
+
related_to TEXT,
|
|
37
|
+
replaces INTEGER,
|
|
38
|
+
shareable INTEGER DEFAULT 1,
|
|
39
|
+
author TEXT,
|
|
40
|
+
code_snippet TEXT,
|
|
41
|
+
error_message TEXT,
|
|
42
|
+
solution TEXT,
|
|
43
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
44
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
45
|
+
last_accessed DATETIME,
|
|
46
|
+
access_count INTEGER DEFAULT 0,
|
|
47
|
+
revision_count INTEGER DEFAULT 0,
|
|
48
|
+
is_deleted INTEGER DEFAULT 0
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
-- Full-Text Search virtual table
|
|
52
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
53
|
+
content,
|
|
54
|
+
content='memories',
|
|
55
|
+
content_rowid='id'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- Tabla de sesiones
|
|
59
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
60
|
+
id TEXT PRIMARY KEY,
|
|
61
|
+
project_path TEXT,
|
|
62
|
+
change_name TEXT,
|
|
63
|
+
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
64
|
+
ended_at DATETIME,
|
|
65
|
+
summary TEXT
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
-- Log de accesos para analytics
|
|
69
|
+
CREATE TABLE IF NOT EXISTS memory_access_log (
|
|
70
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
71
|
+
memory_id INTEGER,
|
|
72
|
+
session_id TEXT,
|
|
73
|
+
access_type TEXT,
|
|
74
|
+
accessed_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
-- Índices para performance
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_path);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_memories_change ON memories(change_name);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_memories_session ON memories(session_id);
|
|
82
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at);
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_memories_topic ON memories(topic_key);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_memories_deleted ON memories(is_deleted);
|
|
85
|
+
|
|
86
|
+
-- Triggers para mantener FTS sincronizado
|
|
87
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
88
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
89
|
+
END;
|
|
90
|
+
|
|
91
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
92
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
93
|
+
END;
|
|
94
|
+
|
|
95
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
96
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
97
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
98
|
+
END;
|
|
99
|
+
|
|
100
|
+
-- Trigger para actualizar updated_at
|
|
101
|
+
CREATE TRIGGER IF NOT EXISTS memories_update_trigger
|
|
102
|
+
AFTER UPDATE ON memories BEGIN
|
|
103
|
+
UPDATE memories SET updated_at = CURRENT_TIMESTAMP WHERE id = new.id;
|
|
104
|
+
END;
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Inicializa la base de datos
|
|
109
|
+
*/
|
|
110
|
+
export function initDatabase() {
|
|
111
|
+
if (dbInstance) return dbInstance;
|
|
112
|
+
|
|
113
|
+
// Asegurar que el directorio existe
|
|
114
|
+
if (!existsSync(ACFM_DIR)) {
|
|
115
|
+
mkdirSync(ACFM_DIR, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Crear conexión
|
|
119
|
+
dbInstance = new Database(DB_PATH);
|
|
120
|
+
|
|
121
|
+
// Optimizaciones de performance
|
|
122
|
+
dbInstance.pragma('journal_mode = WAL');
|
|
123
|
+
dbInstance.pragma('synchronous = NORMAL');
|
|
124
|
+
dbInstance.pragma('cache_size = -64000'); // 64MB cache
|
|
125
|
+
dbInstance.pragma('temp_store = memory');
|
|
126
|
+
|
|
127
|
+
// Crear schema
|
|
128
|
+
dbInstance.exec(SCHEMA_SQL);
|
|
129
|
+
|
|
130
|
+
return dbInstance;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Obtiene instancia de la base de datos (inicializada)
|
|
135
|
+
*/
|
|
136
|
+
export function getDatabase() {
|
|
137
|
+
if (!dbInstance) {
|
|
138
|
+
return initDatabase();
|
|
139
|
+
}
|
|
140
|
+
return dbInstance;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Cierra la conexión a la base de datos
|
|
145
|
+
*/
|
|
146
|
+
export function closeDatabase() {
|
|
147
|
+
if (dbInstance) {
|
|
148
|
+
dbInstance.close();
|
|
149
|
+
dbInstance = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Verifica si la base de datos está inicializada
|
|
155
|
+
*/
|
|
156
|
+
export function isDatabaseInitialized() {
|
|
157
|
+
try {
|
|
158
|
+
const db = getDatabase();
|
|
159
|
+
const result = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='memories'").get();
|
|
160
|
+
return !!result;
|
|
161
|
+
} catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Reset completo (para testing)
|
|
168
|
+
*/
|
|
169
|
+
export function resetDatabase() {
|
|
170
|
+
closeDatabase();
|
|
171
|
+
try {
|
|
172
|
+
const { unlinkSync } = require('node:fs');
|
|
173
|
+
unlinkSync(DB_PATH);
|
|
174
|
+
} catch {
|
|
175
|
+
// No existe, ignorar
|
|
176
|
+
}
|
|
177
|
+
return initDatabase();
|
|
178
|
+
}
|