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.
Files changed (87) hide show
  1. package/README.md +26 -0
  2. package/bin/postinstall.js +8 -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 +2 -1
  79. package/src/cli.js +2 -0
  80. package/src/commands/memory.js +772 -0
  81. package/src/index.js +46 -0
  82. package/src/memory/autosave.js +382 -0
  83. package/src/memory/database.js +178 -0
  84. package/src/memory/engine.js +727 -0
  85. package/src/memory/index.js +62 -0
  86. package/src/memory/utils.js +128 -0
  87. package/src/services/spec-engine.js +69 -1
@@ -0,0 +1,772 @@
1
+ /**
2
+ * memory.js — Comando `acfm memory` y subcomandos
3
+ *
4
+ * Sistema de memoria persistente con funcionalidades avanzadas:
5
+ * - Búsqueda semántica simple (FTS5)
6
+ * - Grafo de conexiones
7
+ * - Análisis de patrones
8
+ * - Contexto predictivo
9
+ * - Sync inteligente
10
+ */
11
+
12
+ import { Command } from 'commander';
13
+ import chalk from 'chalk';
14
+ import { readFileSync, writeFileSync } from 'node:fs';
15
+ import { homedir } from 'node:os';
16
+ import { join } from 'node:path';
17
+ import { initDatabase, isDatabaseInitialized } from '../memory/database.js';
18
+ import {
19
+ saveMemory,
20
+ searchMemories,
21
+ getContext,
22
+ getTimeline,
23
+ getMemory,
24
+ updateMemory,
25
+ deleteMemory,
26
+ startSession,
27
+ endSession,
28
+ getStats,
29
+ findPatterns,
30
+ getConnections,
31
+ anticipateNeeds,
32
+ exportMemories,
33
+ importMemories,
34
+ pruneMemories,
35
+ MEMORY_TYPES
36
+ } from '../memory/index.js';
37
+ import { truncate, extractKeywords } from '../memory/utils.js';
38
+ import { AutoSaveManager } from '../memory/autosave.js';
39
+
40
+ // Helper de output
41
+ function output(data, json) {
42
+ if (json) {
43
+ console.log(JSON.stringify(data, null, 2));
44
+ return;
45
+ }
46
+ }
47
+
48
+ // Helper para formatear memoria
49
+ function formatMemoryLine(memory, index = null) {
50
+ const prefix = index !== null ? chalk.gray(`${index}.`) : '';
51
+ const idBadge = chalk.gray(`[#${memory.id}]`);
52
+ const typeBadge = chalk.cyan(`[${memory.type}]`);
53
+ const importanceColor = {
54
+ critical: chalk.red,
55
+ high: chalk.yellow,
56
+ medium: chalk.white,
57
+ low: chalk.gray
58
+ }[memory.importance] || chalk.white;
59
+
60
+ const confidence = chalk.dim(`${Math.round(memory.confidence * 100)}%`);
61
+ const content = truncate(memory.content, 80);
62
+
63
+ return `${prefix} ${idBadge} ${typeBadge} ${importanceColor(content)} ${confidence}`;
64
+ }
65
+
66
+ export function memoryCommand() {
67
+ const memory = new Command('memory')
68
+ .description('AC Framework Memory — Sistema de memoria autónoma persistente');
69
+
70
+ // ─── acfm memory init ──────────────────────────────────────────────────────
71
+ memory
72
+ .command('init')
73
+ .description('Inicializa la base de datos de memoria')
74
+ .option('--json', 'Output as JSON')
75
+ .action((opts) => {
76
+ try {
77
+ if (isDatabaseInitialized()) {
78
+ output({ initialized: false, reason: 'already_initialized' }, opts.json);
79
+ if (!opts.json) console.log(chalk.yellow('Memory database already initialized'));
80
+ return;
81
+ }
82
+
83
+ initDatabase();
84
+ const dbPath = join(homedir(), '.acfm', 'memory.db');
85
+
86
+ output({ initialized: true, path: dbPath }, opts.json);
87
+ if (!opts.json) {
88
+ console.log(chalk.green('✓ Memory system initialized'));
89
+ console.log(chalk.dim(` Database: ${dbPath}`));
90
+ }
91
+ } catch (err) {
92
+ output({ error: err.message }, opts.json);
93
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
94
+ process.exit(1);
95
+ }
96
+ });
97
+
98
+ // ─── acfm memory save ──────────────────────────────────────────────────────
99
+ memory
100
+ .command('save <content>')
101
+ .description('Guarda una observación en memoria')
102
+ .option('-t, --type <type>', 'Tipo de memoria', 'general_insight')
103
+ .option('--topic-key <key>', 'Clave única para deduplicación')
104
+ .option('--project <path>', 'Proyecto asociado')
105
+ .option('--change <name>', 'Change de .acfm asociado')
106
+ .option('--tags <tags>', 'Tags separados por coma')
107
+ .option('--importance <level>', 'critical|high|medium|low')
108
+ .option('--code-snippet <code>', 'Snippet de código relacionado')
109
+ .option('--error <msg>', 'Mensaje de error relacionado')
110
+ .option('--solution <sol>', 'Solución aplicada')
111
+ .option('--confidence <score>', 'Score de confianza 0-1')
112
+ .option('--json', 'Output as JSON')
113
+ .action((content, opts) => {
114
+ try {
115
+ ensureInitialized();
116
+
117
+ const result = saveMemory({
118
+ content,
119
+ type: opts.type,
120
+ topicKey: opts.topicKey,
121
+ projectPath: opts.project,
122
+ changeName: opts.change,
123
+ tags: opts.tags ? opts.tags.split(',').map(t => t.trim()) : undefined,
124
+ importance: opts.importance,
125
+ codeSnippet: opts.codeSnippet,
126
+ errorMessage: opts.error,
127
+ solution: opts.solution,
128
+ confidence: opts.confidence ? parseFloat(opts.confidence) : undefined
129
+ });
130
+
131
+ output({
132
+ success: true,
133
+ id: result.id,
134
+ operation: result.operation,
135
+ revisionCount: result.revisionCount
136
+ }, opts.json);
137
+
138
+ if (!opts.json) {
139
+ const opLabel = result.operation === 'updated' ? chalk.yellow('updated') : chalk.green('saved');
140
+ console.log(`✓ Memory ${opLabel} [#${result.id}]`);
141
+ if (result.revisionCount > 0) {
142
+ console.log(chalk.dim(` Revision #${result.revisionCount}`));
143
+ }
144
+ }
145
+ } catch (err) {
146
+ output({ error: err.message }, opts.json);
147
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
148
+ process.exit(1);
149
+ }
150
+ });
151
+
152
+ // ─── acfm memory search ────────────────────────────────────────────────────
153
+ memory
154
+ .command('search <query>')
155
+ .description('Búsqueda full-text en memorias')
156
+ .option('-l, --limit <n>', 'Límite de resultados', '10')
157
+ .option('--type <type>', 'Filtrar por tipo')
158
+ .option('--project <path>', 'Filtrar por proyecto')
159
+ .option('--change <name>', 'Filtrar por change')
160
+ .option('--importance <level>', 'Filtrar por importancia')
161
+ .option('--since <date>', 'Desde fecha (ISO)')
162
+ .option('--min-confidence <score>', 'Confianza mínima', '0')
163
+ .option('--json', 'Output as JSON')
164
+ .action((query, opts) => {
165
+ try {
166
+ ensureInitialized();
167
+
168
+ const results = searchMemories(query, {
169
+ limit: parseInt(opts.limit),
170
+ type: opts.type,
171
+ projectPath: opts.project,
172
+ changeName: opts.change,
173
+ importance: opts.importance,
174
+ since: opts.since,
175
+ minConfidence: parseFloat(opts.minConfidence)
176
+ });
177
+
178
+ output({ query, count: results.length, results }, opts.json);
179
+
180
+ if (!opts.json) {
181
+ if (results.length === 0) {
182
+ console.log(chalk.dim('No memories found'));
183
+ return;
184
+ }
185
+
186
+ console.log(chalk.bold(`Found ${results.length} memories`));
187
+ console.log();
188
+
189
+ results.forEach((mem, i) => {
190
+ console.log(formatMemoryLine(mem, i + 1));
191
+ if (mem.tags.length > 0) {
192
+ console.log(chalk.gray(` Tags: ${mem.tags.join(', ')}`));
193
+ }
194
+ });
195
+ }
196
+ } catch (err) {
197
+ output({ error: err.message }, opts.json);
198
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
199
+ process.exit(1);
200
+ }
201
+ });
202
+
203
+ // ─── acfm memory recall ────────────────────────────────────────────────────
204
+ memory
205
+ .command('recall [task]')
206
+ .description('Recupera contexto relevante para tarea actual')
207
+ .option('-p, --project <path>', 'Proyecto actual', process.cwd())
208
+ .option('-c, --change <name>', 'Change actual')
209
+ .option('-l, --limit <n>', 'Cantidad de memorias', '5')
210
+ .option('--days <n>', 'Días hacia atrás', '30')
211
+ .option('--json', 'Output as JSON')
212
+ .action((task, opts) => {
213
+ try {
214
+ ensureInitialized();
215
+
216
+ // Si se proporciona tarea, buscar similitud
217
+ let results;
218
+ if (task) {
219
+ const keywords = extractKeywords(task, 8);
220
+ results = searchMemories(keywords.join(' OR '), {
221
+ projectPath: opts.project,
222
+ limit: parseInt(opts.limit),
223
+ minConfidence: 0.5
224
+ });
225
+ } else {
226
+ // Contexto general del proyecto
227
+ results = getContext({
228
+ projectPath: opts.project,
229
+ changeName: opts.change,
230
+ limit: parseInt(opts.limit),
231
+ lookbackDays: parseInt(opts.days)
232
+ });
233
+ }
234
+
235
+ output({
236
+ task: task || null,
237
+ project: opts.project,
238
+ count: results.length,
239
+ memories: results
240
+ }, opts.json);
241
+
242
+ if (!opts.json) {
243
+ if (results.length === 0) {
244
+ console.log(chalk.dim('No relevant memories found'));
245
+ return;
246
+ }
247
+
248
+ const header = task
249
+ ? `Relevant memories for: "${truncate(task, 50)}"`
250
+ : 'Context for current project';
251
+
252
+ console.log(chalk.bold(header));
253
+ console.log();
254
+
255
+ results.forEach((mem, i) => {
256
+ console.log(formatMemoryLine(mem, i + 1));
257
+ if (mem.changeName) {
258
+ console.log(chalk.gray(` From change: ${mem.changeName}`));
259
+ }
260
+ });
261
+
262
+ console.log();
263
+ console.log(chalk.dim('Use `acfm memory get <id>` for full details'));
264
+ }
265
+ } catch (err) {
266
+ output({ error: err.message }, opts.json);
267
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
268
+ process.exit(1);
269
+ }
270
+ });
271
+
272
+ // ─── acfm memory get ───────────────────────────────────────────────────────
273
+ memory
274
+ .command('get <id>')
275
+ .description('Muestra una memoria completa por ID')
276
+ .option('--json', 'Output as JSON')
277
+ .action((id, opts) => {
278
+ try {
279
+ ensureInitialized();
280
+
281
+ const memory = getMemory(parseInt(id));
282
+
283
+ if (!memory) {
284
+ output({ error: 'Memory not found' }, opts.json);
285
+ if (!opts.json) console.log(chalk.yellow('Memory not found'));
286
+ process.exit(1);
287
+ }
288
+
289
+ output({ memory }, opts.json);
290
+
291
+ if (!opts.json) {
292
+ const importanceColor = {
293
+ critical: chalk.red,
294
+ high: chalk.yellow,
295
+ medium: chalk.white,
296
+ low: chalk.gray
297
+ }[memory.importance] || chalk.white;
298
+
299
+ console.log(chalk.bold(`Memory #${memory.id}`));
300
+ console.log(chalk.gray('─'.repeat(50)));
301
+ console.log(`${chalk.cyan('Type:')} ${memory.type}`);
302
+ console.log(`${chalk.cyan('Importance:')} ${importanceColor(memory.importance)}`);
303
+ console.log(`${chalk.cyan('Confidence:')} ${Math.round(memory.confidence * 100)}%`);
304
+ console.log(`${chalk.cyan('Created:')} ${memory.createdAt}`);
305
+ console.log();
306
+ console.log(chalk.bold('Content:'));
307
+ console.log(memory.content);
308
+
309
+ if (memory.codeSnippet) {
310
+ console.log();
311
+ console.log(chalk.bold('Code:'));
312
+ console.log(chalk.gray(memory.codeSnippet));
313
+ }
314
+
315
+ if (memory.errorMessage) {
316
+ console.log();
317
+ console.log(chalk.bold('Error:'));
318
+ console.log(chalk.red(memory.errorMessage));
319
+ }
320
+
321
+ if (memory.solution) {
322
+ console.log();
323
+ console.log(chalk.bold('Solution:'));
324
+ console.log(chalk.green(memory.solution));
325
+ }
326
+
327
+ if (memory.tags.length > 0) {
328
+ console.log();
329
+ console.log(`${chalk.cyan('Tags:')} ${memory.tags.join(', ')}`);
330
+ }
331
+
332
+ console.log();
333
+ console.log(chalk.gray(`Accessed ${memory.accessCount} times`));
334
+ }
335
+ } catch (err) {
336
+ output({ error: err.message }, opts.json);
337
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
338
+ process.exit(1);
339
+ }
340
+ });
341
+
342
+ // ─── acfm memory timeline ──────────────────────────────────────────────────
343
+ memory
344
+ .command('timeline <id>')
345
+ .description('Muestra timeline cronológico alrededor de una memoria')
346
+ .option('-w, --window <n>', 'Ventana de memorias', '3')
347
+ .option('--json', 'Output as JSON')
348
+ .action((id, opts) => {
349
+ try {
350
+ ensureInitialized();
351
+
352
+ const timeline = getTimeline(parseInt(id), {
353
+ window: parseInt(opts.window)
354
+ });
355
+
356
+ if (!timeline) {
357
+ output({ error: 'Memory not found' }, opts.json);
358
+ if (!opts.json) console.log(chalk.yellow('Memory not found'));
359
+ process.exit(1);
360
+ }
361
+
362
+ output({ timeline }, opts.json);
363
+
364
+ if (!opts.json) {
365
+ console.log(chalk.bold(`Timeline for Memory #${timeline.base.id}`));
366
+ console.log();
367
+
368
+ timeline.before.forEach(mem => {
369
+ console.log(chalk.gray('←') + ' ' + formatMemoryLine(mem));
370
+ });
371
+
372
+ console.log();
373
+ console.log(chalk.cyan('●') + ' ' + chalk.bold(formatMemoryLine(timeline.base)));
374
+ console.log();
375
+
376
+ timeline.after.forEach(mem => {
377
+ console.log(chalk.gray('→') + ' ' + formatMemoryLine(mem));
378
+ });
379
+ }
380
+ } catch (err) {
381
+ output({ error: err.message }, opts.json);
382
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
383
+ process.exit(1);
384
+ }
385
+ });
386
+
387
+ // ─── acfm memory connections ───────────────────────────────────────────────
388
+ memory
389
+ .command('connections <id>')
390
+ .description('Muestra grafo de conexiones entre memorias')
391
+ .option('-d, --depth <n>', 'Profundidad de búsqueda', '1')
392
+ .option('--json', 'Output as JSON')
393
+ .action((id, opts) => {
394
+ try {
395
+ ensureInitialized();
396
+
397
+ const connections = getConnections(parseInt(id), parseInt(opts.depth));
398
+
399
+ output({ baseId: parseInt(id), connections }, opts.json);
400
+
401
+ if (!opts.json) {
402
+ if (connections.length === 0) {
403
+ console.log(chalk.dim('No connections found'));
404
+ return;
405
+ }
406
+
407
+ console.log(chalk.bold(`Connections for Memory #${id}`));
408
+ console.log();
409
+
410
+ connections.forEach(conn => {
411
+ const arrow = conn.depth === 1 ? '→' : '⇢';
412
+ console.log(`${' '.repeat(conn.depth - 1)}${arrow} [#${conn.to}] (depth ${conn.depth})`);
413
+ });
414
+ }
415
+ } catch (err) {
416
+ output({ error: err.message }, opts.json);
417
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
418
+ process.exit(1);
419
+ }
420
+ });
421
+
422
+ // ─── acfm memory patterns ──────────────────────────────────────────────────
423
+ memory
424
+ .command('patterns')
425
+ .description('Analiza y muestra patrones en las memorias')
426
+ .option('--type <type>', 'Filtrar por tipo')
427
+ .option('--min-frequency <n>', 'Frecuencia mínima', '2')
428
+ .option('--json', 'Output as JSON')
429
+ .action((opts) => {
430
+ try {
431
+ ensureInitialized();
432
+
433
+ const patterns = findPatterns({
434
+ type: opts.type,
435
+ minFrequency: parseInt(opts.minFrequency)
436
+ });
437
+
438
+ output({ patterns }, opts.json);
439
+
440
+ if (!opts.json) {
441
+ if (patterns.length === 0) {
442
+ console.log(chalk.dim('No patterns found'));
443
+ return;
444
+ }
445
+
446
+ console.log(chalk.bold('Detected Patterns'));
447
+ console.log();
448
+
449
+ patterns.forEach(p => {
450
+ console.log(`${chalk.cyan(p.topic_key)} ${chalk.gray(`(${p.frequency}×)`)}`);
451
+ console.log(` Type: ${p.type} | Last: ${p.last_seen.slice(0, 10)}`);
452
+ });
453
+ }
454
+ } catch (err) {
455
+ output({ error: err.message }, opts.json);
456
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
457
+ process.exit(1);
458
+ }
459
+ });
460
+
461
+ // ─── acfm memory anticipate ────────────────────────────────────────────────
462
+ memory
463
+ .command('anticipate <task>')
464
+ .description('Predice memorias relevantes para una tarea futura')
465
+ .option('-p, --project <path>', 'Proyecto', process.cwd())
466
+ .option('-l, --limit <n>', 'Límite', '5')
467
+ .option('--json', 'Output as JSON')
468
+ .action((task, opts) => {
469
+ try {
470
+ ensureInitialized();
471
+
472
+ const memories = anticipateNeeds(task, opts.project);
473
+
474
+ output({ task, suggestions: memories.slice(0, parseInt(opts.limit)) }, opts.json);
475
+
476
+ if (!opts.json) {
477
+ console.log(chalk.bold(`Anticipated memories for: "${truncate(task, 50)}"`));
478
+ console.log();
479
+
480
+ if (memories.length === 0) {
481
+ console.log(chalk.dim('No relevant memories found'));
482
+ return;
483
+ }
484
+
485
+ console.log(chalk.yellow('You should know:'));
486
+ memories.slice(0, parseInt(opts.limit)).forEach((mem, i) => {
487
+ console.log(`${i + 1}. ${truncate(mem.content, 70)}`);
488
+ });
489
+ }
490
+ } catch (err) {
491
+ output({ error: err.message }, opts.json);
492
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
493
+ process.exit(1);
494
+ }
495
+ });
496
+
497
+ // ─── acfm memory stats ─────────────────────────────────────────────────────
498
+ memory
499
+ .command('stats')
500
+ .description('Estadísticas del sistema de memoria')
501
+ .option('-p, --project <path>', 'Filtrar por proyecto')
502
+ .option('--since <date>', 'Desde fecha')
503
+ .option('--json', 'Output as JSON')
504
+ .action((opts) => {
505
+ try {
506
+ ensureInitialized();
507
+
508
+ const stats = getStats({
509
+ projectPath: opts.project,
510
+ since: opts.since
511
+ });
512
+
513
+ output(stats, opts.json);
514
+
515
+ if (!opts.json) {
516
+ console.log(chalk.bold('Memory System Statistics'));
517
+ console.log();
518
+ console.log(`${chalk.cyan('Total memories:')} ${stats.total}`);
519
+
520
+ console.log();
521
+ console.log(chalk.bold('By Type:'));
522
+ stats.byType.forEach(t => {
523
+ console.log(` ${chalk.gray(t.type.padEnd(25))} ${t.count}`);
524
+ });
525
+
526
+ console.log();
527
+ console.log(chalk.bold('By Importance:'));
528
+ stats.byImportance.forEach(i => {
529
+ const color = i.importance === 'critical' ? chalk.red :
530
+ i.importance === 'high' ? chalk.yellow :
531
+ i.importance === 'medium' ? chalk.white : chalk.gray;
532
+ console.log(` ${color(i.importance.padEnd(10))} ${i.count}`);
533
+ });
534
+
535
+ if (stats.mostAccessed.length > 0) {
536
+ console.log();
537
+ console.log(chalk.bold('Most Accessed:'));
538
+ stats.mostAccessed.forEach(m => {
539
+ console.log(` [#${m.id}] ${truncate(m.preview, 40)} ${chalk.gray(`(${m.accessCount}×)`)}`);
540
+ });
541
+ }
542
+
543
+ if (stats.commonErrors.length > 0) {
544
+ console.log();
545
+ console.log(chalk.bold('Common Errors:'));
546
+ stats.commonErrors.forEach(e => {
547
+ console.log(` ${chalk.red(truncate(e.error, 40))} ${chalk.gray(`(${e.count}×)`)}`);
548
+ });
549
+ }
550
+ }
551
+ } catch (err) {
552
+ output({ error: err.message }, opts.json);
553
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
554
+ process.exit(1);
555
+ }
556
+ });
557
+
558
+ // ─── acfm memory export ────────────────────────────────────────────────────
559
+ memory
560
+ .command('export [file]')
561
+ .description('Exporta memorias a JSON')
562
+ .option('--shareable-only', 'Solo memorias compartibles', true)
563
+ .option('--project <path>', 'Filtrar por proyecto')
564
+ .option('--since <date>', 'Desde fecha')
565
+ .option('--json', 'Output as JSON (para stdout)')
566
+ .action((file, opts) => {
567
+ try {
568
+ ensureInitialized();
569
+
570
+ const memories = exportMemories({
571
+ shareableOnly: opts.shareableOnly,
572
+ projectPath: opts.project,
573
+ since: opts.since
574
+ });
575
+
576
+ const exportData = {
577
+ exportedAt: new Date().toISOString(),
578
+ count: memories.length,
579
+ memories
580
+ };
581
+
582
+ if (file && !opts.json) {
583
+ writeFileSync(file, JSON.stringify(exportData, null, 2));
584
+ console.log(chalk.green(`✓ Exported ${memories.length} memories to ${file}`));
585
+ } else {
586
+ output(exportData, true);
587
+ }
588
+ } catch (err) {
589
+ output({ error: err.message }, opts.json);
590
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
591
+ process.exit(1);
592
+ }
593
+ });
594
+
595
+ // ─── acfm memory import ────────────────────────────────────────────────────
596
+ memory
597
+ .command('import <file>')
598
+ .description('Importa memorias desde JSON')
599
+ .option('--merge', 'Merge con existentes (upsert)', true)
600
+ .option('--json', 'Output as JSON')
601
+ .action((file, opts) => {
602
+ try {
603
+ ensureInitialized();
604
+
605
+ const data = JSON.parse(readFileSync(file, 'utf-8'));
606
+ const memories = data.memories || data;
607
+
608
+ const results = importMemories(memories, { merge: opts.merge });
609
+
610
+ const success = results.filter(r => r.success);
611
+ const failed = results.filter(r => !r.success);
612
+
613
+ output({
614
+ imported: success.length,
615
+ failed: failed.length,
616
+ results
617
+ }, opts.json);
618
+
619
+ if (!opts.json) {
620
+ console.log(chalk.green(`✓ Imported ${success.length} memories`));
621
+ if (failed.length > 0) {
622
+ console.log(chalk.red(`✗ Failed: ${failed.length}`));
623
+ }
624
+ }
625
+ } catch (err) {
626
+ output({ error: err.message }, opts.json);
627
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
628
+ process.exit(1);
629
+ }
630
+ });
631
+
632
+ // ─── acfm memory prune ─────────────────────────────────────────────────────
633
+ memory
634
+ .command('prune')
635
+ .description('Archiva memorias obsoletas')
636
+ .option('--older-than <days>', 'Días de antigüedad', '90')
637
+ .option('--dry-run', 'Mostrar sin archivar')
638
+ .option('--json', 'Output as JSON')
639
+ .action((opts) => {
640
+ try {
641
+ ensureInitialized();
642
+
643
+ if (opts.dryRun) {
644
+ // Contar sin archivar
645
+ const stats = getStats();
646
+ const cutoff = new Date();
647
+ cutoff.setDate(cutoff.getDate() - parseInt(opts.olderThan));
648
+
649
+ output({
650
+ dryRun: true,
651
+ wouldArchive: 'N/A (use actual prune)',
652
+ cutoff: cutoff.toISOString()
653
+ }, opts.json);
654
+
655
+ if (!opts.json) {
656
+ console.log(chalk.yellow('Dry run - no memories archived'));
657
+ console.log(chalk.dim(`Cutoff: ${cutoff.toISOString().slice(0, 10)}`));
658
+ }
659
+ return;
660
+ }
661
+
662
+ const result = pruneMemories({
663
+ olderThanDays: parseInt(opts.olderThan),
664
+ lowConfidence: true,
665
+ unused: true
666
+ });
667
+
668
+ output({ archived: result.archived }, opts.json);
669
+
670
+ if (!opts.json) {
671
+ console.log(chalk.green(`✓ Archived ${result.archived} memories`));
672
+ }
673
+ } catch (err) {
674
+ output({ error: err.message }, opts.json);
675
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
676
+ process.exit(1);
677
+ }
678
+ });
679
+
680
+ // ─── acfm memory delete ────────────────────────────────────────────────────
681
+ memory
682
+ .command('delete <id>')
683
+ .description('Elimina (soft-delete) una memoria')
684
+ .option('--json', 'Output as JSON')
685
+ .action((id, opts) => {
686
+ try {
687
+ ensureInitialized();
688
+
689
+ const success = deleteMemory(parseInt(id));
690
+
691
+ output({ success }, opts.json);
692
+
693
+ if (!opts.json) {
694
+ if (success) {
695
+ console.log(chalk.green(`✓ Memory #${id} deleted`));
696
+ } else {
697
+ console.log(chalk.yellow('Memory not found'));
698
+ }
699
+ }
700
+ } catch (err) {
701
+ output({ error: err.message }, opts.json);
702
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
703
+ process.exit(1);
704
+ }
705
+ });
706
+
707
+ // ─── acfm memory session ───────────────────────────────────────────────────
708
+ const sessionCmd = new Command('session')
709
+ .description('Gestión de sesiones de memoria');
710
+
711
+ sessionCmd
712
+ .command('start')
713
+ .description('Inicia nueva sesión')
714
+ .option('-p, --project <path>', 'Proyecto', process.cwd())
715
+ .option('-c, --change <name>', 'Change')
716
+ .option('--json', 'Output as JSON')
717
+ .action((opts) => {
718
+ try {
719
+ ensureInitialized();
720
+
721
+ const sessionId = startSession(opts.project, opts.change);
722
+
723
+ output({ sessionId, project: opts.project, change: opts.change }, opts.json);
724
+
725
+ if (!opts.json) {
726
+ console.log(chalk.green('✓ Session started'));
727
+ console.log(chalk.dim(` ID: ${sessionId}`));
728
+ }
729
+ } catch (err) {
730
+ output({ error: err.message }, opts.json);
731
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
732
+ process.exit(1);
733
+ }
734
+ });
735
+
736
+ sessionCmd
737
+ .command('end <sessionId>')
738
+ .description('Finaliza sesión')
739
+ .option('-s, --summary <text>', 'Resumen de la sesión')
740
+ .option('--json', 'Output as JSON')
741
+ .action((sessionId, opts) => {
742
+ try {
743
+ ensureInitialized();
744
+
745
+ endSession(sessionId, opts.summary);
746
+
747
+ output({ ended: true, sessionId }, opts.json);
748
+
749
+ if (!opts.json) {
750
+ console.log(chalk.green('✓ Session ended'));
751
+ if (opts.summary) {
752
+ console.log(chalk.dim('Summary saved as memory'));
753
+ }
754
+ }
755
+ } catch (err) {
756
+ output({ error: err.message }, opts.json);
757
+ if (!opts.json) console.error(chalk.red(`Error: ${err.message}`));
758
+ process.exit(1);
759
+ }
760
+ });
761
+
762
+ memory.addCommand(sessionCmd);
763
+
764
+ return memory;
765
+ }
766
+
767
+ // Helper
768
+ function ensureInitialized() {
769
+ if (!isDatabaseInitialized()) {
770
+ throw new Error('Memory system not initialized. Run: acfm memory init');
771
+ }
772
+ }