ac-framework 1.7.0 → 1.9.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.
Files changed (91) hide show
  1. package/README.md +26 -0
  2. package/bin/postinstall.js +21 -1
  3. package/framework/.agent/skills/acfm-memory/SKILL.md +312 -0
  4. package/framework/.agent/workflows/ac-lite.md +192 -0
  5. package/framework/.agent/workflows/ac.md +40 -0
  6. package/framework/.amazonq/prompts/ac-lite.md +192 -0
  7. package/framework/.amazonq/prompts/ac.md +40 -0
  8. package/framework/.amazonq/skills/acfm-memory/SKILL.md +312 -0
  9. package/framework/.antigravity/skills/acfm-memory/SKILL.md +312 -0
  10. package/framework/.antigravity/workflows/ac-lite.md +192 -0
  11. package/framework/.antigravity/workflows/ac.md +40 -0
  12. package/framework/.augment/commands/ac-lite.md +192 -0
  13. package/framework/.augment/commands/ac.md +40 -0
  14. package/framework/.augment/skills/acfm-memory/SKILL.md +312 -0
  15. package/framework/.claude/commands/opsx/ac-lite.md +192 -0
  16. package/framework/.claude/commands/opsx/ac.md +40 -0
  17. package/framework/.claude/skills/acfm-memory/SKILL.md +312 -0
  18. package/framework/.cline/commands/opsx/ac-lite.md +192 -0
  19. package/framework/.cline/commands/opsx/ac.md +40 -0
  20. package/framework/.cline/skills/acfm-memory/SKILL.md +312 -0
  21. package/framework/.clinerules/skills/acfm-memory/SKILL.md +312 -0
  22. package/framework/.clinerules/workflows/ac-lite.md +192 -0
  23. package/framework/.clinerules/workflows/ac.md +40 -0
  24. package/framework/.codebuddy/commands/opsx/ac-lite.md +192 -0
  25. package/framework/.codebuddy/commands/opsx/ac.md +40 -0
  26. package/framework/.codebuddy/skills/acfm-memory/SKILL.md +312 -0
  27. package/framework/.codex/skills/acfm-memory/SKILL.md +312 -0
  28. package/framework/.continue/prompts/ac-lite.md +192 -0
  29. package/framework/.continue/prompts/ac.md +40 -0
  30. package/framework/.continue/skills/acfm-memory/SKILL.md +312 -0
  31. package/framework/.cospec/openspec/commands/ac-lite.md +192 -0
  32. package/framework/.cospec/openspec/commands/ac.md +40 -0
  33. package/framework/.cospec/skills/acfm-memory/SKILL.md +312 -0
  34. package/framework/.crush/commands/opsx/ac-lite.md +192 -0
  35. package/framework/.crush/commands/opsx/ac.md +40 -0
  36. package/framework/.crush/skills/acfm-memory/SKILL.md +312 -0
  37. package/framework/.cursor/commands/ac-lite.md +192 -0
  38. package/framework/.cursor/commands/ac.md +40 -0
  39. package/framework/.cursor/skills/acfm-memory/SKILL.md +312 -0
  40. package/framework/.factory/commands/ac-lite.md +192 -0
  41. package/framework/.factory/commands/ac.md +40 -0
  42. package/framework/.factory/skills/acfm-memory/SKILL.md +312 -0
  43. package/framework/.gemini/commands/opsx/ac-lite.md +192 -0
  44. package/framework/.gemini/commands/opsx/ac.md +40 -0
  45. package/framework/.gemini/skills/acfm-memory/SKILL.md +312 -0
  46. package/framework/.github/prompts/ac-lite.md +192 -0
  47. package/framework/.github/prompts/ac.md +40 -0
  48. package/framework/.github/skills/acfm-memory/SKILL.md +312 -0
  49. package/framework/.iflow/commands/ac-lite.md +192 -0
  50. package/framework/.iflow/commands/ac.md +40 -0
  51. package/framework/.iflow/skills/acfm-memory/SKILL.md +312 -0
  52. package/framework/.kilocode/skills/acfm-memory/SKILL.md +312 -0
  53. package/framework/.kilocode/workflows/ac-lite.md +192 -0
  54. package/framework/.kilocode/workflows/ac.md +40 -0
  55. package/framework/.kimi/skills/acfm-memory/SKILL.md +312 -0
  56. package/framework/.kimi/workflows/ac-lite.md +192 -0
  57. package/framework/.kimi/workflows/ac.md +40 -0
  58. package/framework/.opencode/command/ac-lite.md +192 -0
  59. package/framework/.opencode/command/ac.md +40 -0
  60. package/framework/.opencode/skills/acfm-memory/SKILL.md +312 -0
  61. package/framework/.qoder/commands/opsx/ac-lite.md +192 -0
  62. package/framework/.qoder/commands/opsx/ac.md +40 -0
  63. package/framework/.qoder/skills/acfm-memory/SKILL.md +312 -0
  64. package/framework/.qwen/commands/ac-lite.md +192 -0
  65. package/framework/.qwen/commands/ac.md +40 -0
  66. package/framework/.qwen/skills/acfm-memory/SKILL.md +312 -0
  67. package/framework/.roo/commands/ac-lite.md +192 -0
  68. package/framework/.roo/commands/ac.md +40 -0
  69. package/framework/.roo/skills/acfm-memory/SKILL.md +312 -0
  70. package/framework/.trae/skills/acfm-memory/SKILL.md +312 -0
  71. package/framework/.windsurf/skills/acfm-memory/SKILL.md +312 -0
  72. package/framework/.windsurf/workflows/ac-lite.md +192 -0
  73. package/framework/.windsurf/workflows/ac.md +40 -0
  74. package/framework/AGENTS.md +39 -0
  75. package/framework/CLAUDE.md +39 -0
  76. package/framework/GEMINI.md +39 -0
  77. package/framework/copilot-instructions.md +39 -0
  78. package/package.json +5 -2
  79. package/src/cli.js +2 -0
  80. package/src/commands/init.js +98 -0
  81. package/src/commands/memory.js +833 -0
  82. package/src/index.js +46 -0
  83. package/src/mcp/server.js +345 -0
  84. package/src/mcp/server.js.bak +727 -0
  85. package/src/memory/autosave.js +382 -0
  86. package/src/memory/database.js +178 -0
  87. package/src/memory/engine.js +727 -0
  88. package/src/memory/index.js +62 -0
  89. package/src/memory/utils.js +128 -0
  90. package/src/services/mcp-installer.js +194 -0
  91. package/src/services/spec-engine.js +69 -1
@@ -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
+ }