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,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
|
+
}
|