agentic-kdd 3.0.2 → 3.0.3
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/bin/akdd.js +50 -31
- package/collab-manager.cjs +403 -0
- package/package.json +1 -1
package/bin/akdd.js
CHANGED
|
@@ -62,6 +62,13 @@ const HELP = `
|
|
|
62
62
|
akdd trail <ciclo_id> Full trail of a specific cycle
|
|
63
63
|
akdd trail why <f> Why does this file/entity exist?
|
|
64
64
|
|
|
65
|
+
Collaborative Mode (Legion):
|
|
66
|
+
akdd collab init Activate collaborative mode — creates shared DB automatically
|
|
67
|
+
akdd collab join <url> <token> Join an existing team's shared memory
|
|
68
|
+
akdd collab push Push your learnings to the team
|
|
69
|
+
akdd collab pull Pull team's latest learnings
|
|
70
|
+
akdd collab status Check collaborative sync status
|
|
71
|
+
|
|
65
72
|
Intelligence v2.2:
|
|
66
73
|
akdd git-context Analyze git diff + risk assessment
|
|
67
74
|
akdd predict Predictive risk patterns from episodic memory
|
|
@@ -78,7 +85,6 @@ const HELP = `
|
|
|
78
85
|
akdd mcp Auto-configure MCP in all IDEs (recommended)
|
|
79
86
|
akdd mcp --global Configure MCP globally for all projects
|
|
80
87
|
akdd mcp status Check MCP configuration status
|
|
81
|
-
(manual fallback: imprime el JSON exacto con tu ruta real)
|
|
82
88
|
|
|
83
89
|
akdd --version / akdd --help
|
|
84
90
|
`;
|
|
@@ -99,11 +105,7 @@ function runGrafo(cmd, extra) {
|
|
|
99
105
|
function runModule(name, cmd, extra) {
|
|
100
106
|
const p = path.join(process.cwd(), '.agentic', 'grafo', name);
|
|
101
107
|
if (!fs.existsSync(p)) { console.log(`\n ${name} not found. Run: akdd update\n`); process.exit(1); }
|
|
102
|
-
const fullCmd = [
|
|
103
|
-
`node "${p}"`,
|
|
104
|
-
cmd || '',
|
|
105
|
-
extra || ''
|
|
106
|
-
].join(' ').trim().replace(/\s+/g,' ');
|
|
108
|
+
const fullCmd = [`node "${p}"`, cmd || '', extra || ''].join(' ').trim().replace(/\s+/g,' ');
|
|
107
109
|
try { execSync(fullCmd, { stdio: 'inherit', cwd: process.cwd() }); }
|
|
108
110
|
catch(e) { process.exit(e.status || 1); }
|
|
109
111
|
}
|
|
@@ -114,14 +116,14 @@ switch (command) {
|
|
|
114
116
|
case 'update': update(); break;
|
|
115
117
|
case 'analyze': analyze(); break;
|
|
116
118
|
|
|
117
|
-
// ── v3.0: Health
|
|
119
|
+
// ── v3.0: Health ──────────────────────────────────────────────────────
|
|
118
120
|
case 'health': {
|
|
119
121
|
const fixFlag = args.includes('--fix') ? '--fix' : '';
|
|
120
122
|
runModule('health-check.cjs', fixFlag);
|
|
121
123
|
break;
|
|
122
124
|
}
|
|
123
125
|
|
|
124
|
-
// ── Core memory
|
|
126
|
+
// ── Core memory ───────────────────────────────────────────────────────
|
|
125
127
|
case 'sync': runGrafo('sync'); break;
|
|
126
128
|
case 'graph': graph(); break;
|
|
127
129
|
case 'stats': runGrafo('stats'); break;
|
|
@@ -139,7 +141,7 @@ switch (command) {
|
|
|
139
141
|
runGrafo('impacto', `"${arg1}"`);
|
|
140
142
|
break;
|
|
141
143
|
|
|
142
|
-
// ── v3.0: Memory Audit
|
|
144
|
+
// ── v3.0: Memory Audit ────────────────────────────────────────────────
|
|
143
145
|
case 'audit': runModule('memory-audit.cjs', 'report'); break;
|
|
144
146
|
|
|
145
147
|
case 'forget': {
|
|
@@ -150,11 +152,11 @@ switch (command) {
|
|
|
150
152
|
break;
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
// ── v3.0: AST
|
|
155
|
+
// ── v3.0: AST ─────────────────────────────────────────────────────────
|
|
154
156
|
case 'ast': {
|
|
155
157
|
const sub = arg1 || 'index';
|
|
156
158
|
const tgt = arg2 || '';
|
|
157
|
-
if (sub === 'stats')
|
|
159
|
+
if (sub === 'stats') runModule('ast-indexer.cjs', 'stats');
|
|
158
160
|
else if (sub === 'symbols') {
|
|
159
161
|
if (!tgt) { console.log('\n Uso: akdd ast symbols <archivo>\n'); break; }
|
|
160
162
|
runModule('ast-indexer.cjs', 'symbols', `"${tgt}"`);
|
|
@@ -174,17 +176,17 @@ switch (command) {
|
|
|
174
176
|
runModule('decision-trail.cjs', 'why', `"${arg1}"`);
|
|
175
177
|
break;
|
|
176
178
|
|
|
177
|
-
// ── v3.0: Specs
|
|
179
|
+
// ── v3.0: Specs ───────────────────────────────────────────────────────
|
|
178
180
|
case 'spec': {
|
|
179
181
|
const sub = arg1;
|
|
180
182
|
const mod = arg2;
|
|
181
|
-
if (!sub || sub === 'list')
|
|
183
|
+
if (!sub || sub === 'list') runModule('spec-manager.cjs', 'list');
|
|
182
184
|
else if (sub === 'create') {
|
|
183
185
|
if (!mod) { console.log('\n Uso: akdd spec create <módulo> [--bugfix]\n'); break; }
|
|
184
186
|
runModule('spec-manager.cjs', 'create', `"${mod}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
|
|
185
187
|
}
|
|
186
|
-
else if (sub === 'waves')
|
|
187
|
-
else if (sub === 'validate')
|
|
188
|
+
else if (sub === 'waves') { if (!mod) { console.log('\n Uso: akdd spec waves <módulo>\n'); break; } runModule('spec-manager.cjs', 'waves', `"${mod}"`); }
|
|
189
|
+
else if (sub === 'validate') { if (!mod) { console.log('\n Uso: akdd spec validate <módulo>\n'); break; } runModule('spec-manager.cjs', 'validate', `"${mod}"`); }
|
|
188
190
|
else runModule('spec-manager.cjs', 'status', `"${sub}"`);
|
|
189
191
|
break;
|
|
190
192
|
}
|
|
@@ -194,7 +196,7 @@ switch (command) {
|
|
|
194
196
|
runModule('spec-manager.cjs', 'create', `"${arg1}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
|
|
195
197
|
break;
|
|
196
198
|
|
|
197
|
-
// ── v3.0: Knowledge
|
|
199
|
+
// ── v3.0: Knowledge ───────────────────────────────────────────────────
|
|
198
200
|
case 'adr':
|
|
199
201
|
runModule('adr-ingestor.cjs', 'ingest', arg1 || 'docs/adr');
|
|
200
202
|
break;
|
|
@@ -203,24 +205,45 @@ switch (command) {
|
|
|
203
205
|
runModule('knowledge-ingestor.cjs', 'ingest', arg1 || '');
|
|
204
206
|
break;
|
|
205
207
|
|
|
206
|
-
// ── v3.0: Metrics
|
|
208
|
+
// ── v3.0: Metrics ─────────────────────────────────────────────────────
|
|
207
209
|
case 'metrics':
|
|
208
210
|
runModule('metrics.cjs', arg1 || 'summary');
|
|
209
211
|
break;
|
|
210
212
|
|
|
211
|
-
// ── v3.0: Decision Trail
|
|
213
|
+
// ── v3.0: Decision Trail ──────────────────────────────────────────────
|
|
212
214
|
case 'trail': {
|
|
213
|
-
if (!arg1)
|
|
214
|
-
else if (arg1 === 'why')
|
|
215
|
-
else if (arg1 === 'timeline'){ if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
|
|
216
|
-
else
|
|
215
|
+
if (!arg1) runModule('decision-trail.cjs', 'recent', '5');
|
|
216
|
+
else if (arg1 === 'why') { if (!arg2) { console.log('\n Uso: akdd trail why <entidad>\n'); break; } runModule('decision-trail.cjs', 'why', `"${arg2}"`); }
|
|
217
|
+
else if (arg1 === 'timeline') { if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
|
|
218
|
+
else runModule('decision-trail.cjs', 'ciclo', `"${arg1}"`);
|
|
217
219
|
break;
|
|
218
220
|
}
|
|
219
221
|
|
|
220
|
-
// ──
|
|
222
|
+
// ── v3.0: Collaborative Mode (Legion) ────────────────────────────────
|
|
223
|
+
case 'collab': {
|
|
224
|
+
const sub = arg1 || 'status';
|
|
225
|
+
if (sub === 'init') {
|
|
226
|
+
runModule('collab-manager.cjs', 'init');
|
|
227
|
+
} else if (sub === 'join') {
|
|
228
|
+
if (!arg2 || !args[3]) {
|
|
229
|
+
console.log('\n Uso: akdd collab join <url> <token>\n');
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
runModule('collab-manager.cjs', 'join', `"${arg2}" "${args[3]}"`);
|
|
233
|
+
} else if (sub === 'push') {
|
|
234
|
+
runModule('collab-manager.cjs', 'push');
|
|
235
|
+
} else if (sub === 'pull') {
|
|
236
|
+
runModule('collab-manager.cjs', 'pull');
|
|
237
|
+
} else {
|
|
238
|
+
runModule('collab-manager.cjs', 'status');
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Dashboard ─────────────────────────────────────────────────────────
|
|
221
244
|
case 'dashboard': dashboard(); break;
|
|
222
245
|
|
|
223
|
-
// ── v2.2: Intelligence
|
|
246
|
+
// ── v2.2: Intelligence ────────────────────────────────────────────────
|
|
224
247
|
case 'git-context': runGrafo('git-context', args.includes('--install-hook') ? '--install-hook' : ''); break;
|
|
225
248
|
case 'predict': runGrafo('predict'); break;
|
|
226
249
|
case 'embed-status': runGrafo('embed-status'); break;
|
|
@@ -245,16 +268,12 @@ switch (command) {
|
|
|
245
268
|
break;
|
|
246
269
|
}
|
|
247
270
|
|
|
248
|
-
|
|
249
|
-
// ── v3.0: MCP Setup ────────────────────────────────────────────────────
|
|
271
|
+
// ── v3.0: MCP Setup ───────────────────────────────────────────────────
|
|
250
272
|
case 'mcp': {
|
|
251
273
|
const sub = arg1;
|
|
252
274
|
const opts = { global: args.includes('--global') };
|
|
253
|
-
if (sub === 'status')
|
|
254
|
-
|
|
255
|
-
} else {
|
|
256
|
-
mcpSetup(process.cwd(), opts);
|
|
257
|
-
}
|
|
275
|
+
if (sub === 'status') mcpStatus(process.cwd());
|
|
276
|
+
else mcpSetup(process.cwd(), opts);
|
|
258
277
|
break;
|
|
259
278
|
}
|
|
260
279
|
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic KDD — Collab Manager v2.0
|
|
3
|
+
* Modo colaborativo automático via Turso + Cloudflare Worker.
|
|
4
|
+
*
|
|
5
|
+
* El usuario solo corre: akdd collab init
|
|
6
|
+
* El sistema crea la DB en Turso automáticamente.
|
|
7
|
+
* El usuario nunca sabe que Turso existe.
|
|
8
|
+
*
|
|
9
|
+
* Uso:
|
|
10
|
+
* node .agentic/grafo/collab-manager.cjs init
|
|
11
|
+
* node .agentic/grafo/collab-manager.cjs push
|
|
12
|
+
* node .agentic/grafo/collab-manager.cjs pull
|
|
13
|
+
* node .agentic/grafo/collab-manager.cjs status
|
|
14
|
+
* node .agentic/grafo/collab-manager.cjs join <url> <token>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const { execSync } = require('child_process');
|
|
23
|
+
|
|
24
|
+
// URL de tu Cloudflare Worker — actualizar después de deploy
|
|
25
|
+
const PROVISIONER_URL = 'https://agentic-collab.adrianlpz-game.workers.dev';
|
|
26
|
+
|
|
27
|
+
const COLLAB_CONFIG_PATH = '.agentic/collab.json';
|
|
28
|
+
|
|
29
|
+
const SYNC_TABLES = [
|
|
30
|
+
'nodos',
|
|
31
|
+
'relaciones_semanticas',
|
|
32
|
+
'episodios',
|
|
33
|
+
'knowledge_docs',
|
|
34
|
+
'ast_symbols',
|
|
35
|
+
'ast_edges',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// ─── DB LOCAL ──────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
function openLocalDB(projectRoot) {
|
|
41
|
+
const dbPath = path.join(projectRoot, '.agentic/memoria.db');
|
|
42
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch {}
|
|
43
|
+
try { const { DatabaseSync } = require('node:sqlite'); return new DatabaseSync(dbPath); } catch {}
|
|
44
|
+
throw new Error('No SQLite driver disponible');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── CONFIG ───────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
function loadConfig(projectRoot) {
|
|
50
|
+
const configPath = path.join(projectRoot, COLLAB_CONFIG_PATH);
|
|
51
|
+
if (!fs.existsSync(configPath)) return null;
|
|
52
|
+
try { return JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function saveConfig(projectRoot, config) {
|
|
56
|
+
const configPath = path.join(projectRoot, COLLAB_CONFIG_PATH);
|
|
57
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
58
|
+
|
|
59
|
+
// Asegurar que collab.json está en .gitignore
|
|
60
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
61
|
+
if (fs.existsSync(gitignorePath)) {
|
|
62
|
+
const gi = fs.readFileSync(gitignorePath, 'utf8');
|
|
63
|
+
if (!gi.includes('collab.json')) {
|
|
64
|
+
fs.appendFileSync(gitignorePath, '\n# Agentic KDD collab credentials\n.agentic/collab.json\n');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── GENERAR PROJECT ID ───────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
function getProjectId(projectRoot) {
|
|
72
|
+
// Usar el nombre del directorio + un hash corto para que sea único y legible
|
|
73
|
+
const dirName = path.basename(projectRoot).toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
74
|
+
const hash = require('crypto')
|
|
75
|
+
.createHash('md5')
|
|
76
|
+
.update(projectRoot)
|
|
77
|
+
.digest('hex')
|
|
78
|
+
.substring(0, 6);
|
|
79
|
+
return `${dirName}-${hash}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─── INIT — PROVISIONAR DB AUTOMÁTICAMENTE ───────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async function collabInit(projectRoot) {
|
|
85
|
+
console.log('\n[COLLAB] Activando modo colaborativo...\n');
|
|
86
|
+
|
|
87
|
+
// Verificar si ya está configurado
|
|
88
|
+
const existing = loadConfig(projectRoot);
|
|
89
|
+
if (existing?.enabled && existing?.url) {
|
|
90
|
+
console.log('[COLLAB] Ya configurado. URL:', existing.url);
|
|
91
|
+
console.log('[COLLAB] Para re-configurar: borrar .agentic/collab.json y correr de nuevo.');
|
|
92
|
+
return existing;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const projectId = getProjectId(projectRoot);
|
|
96
|
+
console.log(`[COLLAB] Project ID: ${projectId}`);
|
|
97
|
+
console.log('[COLLAB] Creando base de datos compartida...');
|
|
98
|
+
|
|
99
|
+
// Llamar al Cloudflare Worker para provisionar la DB
|
|
100
|
+
let provisionResult;
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(PROVISIONER_URL, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({ projectId }),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const err = await response.text();
|
|
110
|
+
throw new Error(`Provisioner error: ${err}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
provisionResult = await response.json();
|
|
114
|
+
} catch (e) {
|
|
115
|
+
console.error('[COLLAB] Error conectando con el servidor de provisión:', e.message);
|
|
116
|
+
console.error('[COLLAB] Verificar que el Cloudflare Worker está activo.');
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!provisionResult.ok) {
|
|
121
|
+
console.error('[COLLAB] Error creando DB:', provisionResult.error);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Guardar credenciales localmente
|
|
126
|
+
const config = {
|
|
127
|
+
enabled: true,
|
|
128
|
+
url: provisionResult.url,
|
|
129
|
+
token: provisionResult.token,
|
|
130
|
+
db: provisionResult.db,
|
|
131
|
+
project_id: projectId,
|
|
132
|
+
member_id: os.userInfo().username,
|
|
133
|
+
sync_on_cycle_end: true,
|
|
134
|
+
last_sync: null,
|
|
135
|
+
created_at: new Date().toISOString(),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
saveConfig(projectRoot, config);
|
|
139
|
+
|
|
140
|
+
console.log('\n[COLLAB] ✅ Modo colaborativo activado.');
|
|
141
|
+
console.log(`[COLLAB] DB: ${provisionResult.db}`);
|
|
142
|
+
console.log('[COLLAB] Credenciales guardadas en .agentic/collab.json (gitignored)');
|
|
143
|
+
console.log('\n[COLLAB] Para que otros miembros del equipo se unan:');
|
|
144
|
+
console.log(` Compárteles: URL y token de .agentic/collab.json`);
|
|
145
|
+
console.log(` Ellos corren: akdd collab join <url> <token>\n`);
|
|
146
|
+
|
|
147
|
+
// Primer push
|
|
148
|
+
console.log('[COLLAB] Sincronizando memoria local → compartida...');
|
|
149
|
+
await syncUp(projectRoot);
|
|
150
|
+
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── JOIN — PARA EL DEV QUE SE UNE AL EQUIPO ─────────────────────────────────
|
|
155
|
+
|
|
156
|
+
async function collabJoin(projectRoot, url, token) {
|
|
157
|
+
console.log('\n[COLLAB] Uniéndose al equipo...\n');
|
|
158
|
+
|
|
159
|
+
const config = {
|
|
160
|
+
enabled: true,
|
|
161
|
+
url,
|
|
162
|
+
token,
|
|
163
|
+
member_id: os.userInfo().username,
|
|
164
|
+
sync_on_cycle_end: true,
|
|
165
|
+
last_sync: null,
|
|
166
|
+
joined_at: new Date().toISOString(),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
saveConfig(projectRoot, config);
|
|
170
|
+
|
|
171
|
+
console.log('[COLLAB] ✅ Conectado al equipo.');
|
|
172
|
+
console.log('[COLLAB] Descargando memoria del equipo...');
|
|
173
|
+
|
|
174
|
+
await syncDown(projectRoot);
|
|
175
|
+
|
|
176
|
+
console.log('[COLLAB] ✅ Listo. Ya tienes toda la memoria del equipo.\n');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ─── SYNC UP (local → Turso) ──────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
async function syncUp(projectRoot) {
|
|
182
|
+
const config = loadConfig(projectRoot);
|
|
183
|
+
if (!config?.enabled) {
|
|
184
|
+
console.log('[COLLAB] Modo colaborativo no activado. Correr: akdd collab init');
|
|
185
|
+
return { synced: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let client;
|
|
189
|
+
try {
|
|
190
|
+
const { createClient } = require('@libsql/client');
|
|
191
|
+
client = createClient({ url: config.url, authToken: config.token });
|
|
192
|
+
} catch {
|
|
193
|
+
console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
|
|
194
|
+
return { synced: false };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const localDB = openLocalDB(projectRoot);
|
|
198
|
+
const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
|
|
199
|
+
let totalRows = 0;
|
|
200
|
+
|
|
201
|
+
// Schema push — crear tablas en remoto si no existen
|
|
202
|
+
await pushSchema(client, localDB);
|
|
203
|
+
|
|
204
|
+
for (const table of SYNC_TABLES) {
|
|
205
|
+
try {
|
|
206
|
+
// Solo filas nuevas/modificadas desde el último sync
|
|
207
|
+
const dateField = getDateField(table);
|
|
208
|
+
const rows = localDB.prepare(
|
|
209
|
+
`SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 500`
|
|
210
|
+
).all(lastSync);
|
|
211
|
+
|
|
212
|
+
if (rows.length === 0) continue;
|
|
213
|
+
|
|
214
|
+
console.log(`[COLLAB] ${table}: ${rows.length} filas a sincronizar`);
|
|
215
|
+
|
|
216
|
+
for (const row of rows) {
|
|
217
|
+
const keys = Object.keys(row);
|
|
218
|
+
const vals = Object.values(row);
|
|
219
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
220
|
+
try {
|
|
221
|
+
await client.execute({
|
|
222
|
+
sql: `INSERT OR REPLACE INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`,
|
|
223
|
+
args: vals,
|
|
224
|
+
});
|
|
225
|
+
totalRows++;
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
228
|
+
} catch {}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Actualizar last_sync
|
|
232
|
+
config.last_sync = new Date().toISOString();
|
|
233
|
+
saveConfig(projectRoot, config);
|
|
234
|
+
|
|
235
|
+
console.log(`[COLLAB] ✅ Sync up: ${totalRows} filas enviadas.`);
|
|
236
|
+
return { synced: true, rows: totalRows };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ─── SYNC DOWN (Turso → local) ────────────────────────────────────────────────
|
|
240
|
+
|
|
241
|
+
async function syncDown(projectRoot) {
|
|
242
|
+
const config = loadConfig(projectRoot);
|
|
243
|
+
if (!config?.enabled) return { synced: false };
|
|
244
|
+
|
|
245
|
+
let client;
|
|
246
|
+
try {
|
|
247
|
+
const { createClient } = require('@libsql/client');
|
|
248
|
+
client = createClient({ url: config.url, authToken: config.token });
|
|
249
|
+
} catch {
|
|
250
|
+
console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
|
|
251
|
+
return { synced: false };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const localDB = openLocalDB(projectRoot);
|
|
255
|
+
let totalRows = 0;
|
|
256
|
+
|
|
257
|
+
for (const table of SYNC_TABLES) {
|
|
258
|
+
try {
|
|
259
|
+
const dateField = getDateField(table);
|
|
260
|
+
const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
|
|
261
|
+
|
|
262
|
+
const rs = await client.execute(
|
|
263
|
+
`SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 1000`,
|
|
264
|
+
[lastSync]
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
if (!rs.rows?.length) continue;
|
|
268
|
+
|
|
269
|
+
console.log(`[COLLAB] ${table}: recibiendo ${rs.rows.length} filas`);
|
|
270
|
+
|
|
271
|
+
for (const row of rs.rows) {
|
|
272
|
+
const keys = Object.keys(row);
|
|
273
|
+
const vals = keys.map(k => row[k]);
|
|
274
|
+
const placeholders = keys.map(() => '?').join(', ');
|
|
275
|
+
|
|
276
|
+
// Estrategia por tabla:
|
|
277
|
+
// episodios → INSERT OR IGNORE (son aditivos, no sobrescribir locales)
|
|
278
|
+
// resto → INSERT OR REPLACE (toma la versión más reciente)
|
|
279
|
+
const strategy = table === 'episodios' ? 'OR IGNORE' : 'OR REPLACE';
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
localDB.prepare(
|
|
283
|
+
`INSERT ${strategy} INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`
|
|
284
|
+
).run(vals);
|
|
285
|
+
totalRows++;
|
|
286
|
+
} catch {}
|
|
287
|
+
}
|
|
288
|
+
} catch {}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Actualizar last_sync
|
|
292
|
+
config.last_sync = new Date().toISOString();
|
|
293
|
+
saveConfig(projectRoot, config);
|
|
294
|
+
|
|
295
|
+
console.log(`[COLLAB] ✅ Sync down: ${totalRows} filas recibidas.`);
|
|
296
|
+
return { synced: true, rows: totalRows };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ─── SCHEMA PUSH ──────────────────────────────────────────────────────────────
|
|
300
|
+
// Crea las tablas en la DB remota si no existen (primera vez)
|
|
301
|
+
|
|
302
|
+
async function pushSchema(client, localDB) {
|
|
303
|
+
const tables = localDB.prepare(
|
|
304
|
+
"SELECT name, sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL"
|
|
305
|
+
).all();
|
|
306
|
+
|
|
307
|
+
for (const table of tables) {
|
|
308
|
+
if (!SYNC_TABLES.includes(table.name)) continue;
|
|
309
|
+
try {
|
|
310
|
+
await client.execute(
|
|
311
|
+
table.sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS')
|
|
312
|
+
);
|
|
313
|
+
} catch {}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ─── STATUS ───────────────────────────────────────────────────────────────────
|
|
318
|
+
|
|
319
|
+
async function status(projectRoot) {
|
|
320
|
+
const config = loadConfig(projectRoot);
|
|
321
|
+
|
|
322
|
+
console.log('\n[COLLAB] Estado del modo colaborativo:\n');
|
|
323
|
+
|
|
324
|
+
if (!config?.enabled) {
|
|
325
|
+
console.log(' ❌ No activado.');
|
|
326
|
+
console.log(' Correr: akdd collab init\n');
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(` ✅ Activado`);
|
|
331
|
+
console.log(` DB: ${config.db || config.url}`);
|
|
332
|
+
console.log(` Miembro: ${config.member_id}`);
|
|
333
|
+
console.log(` Último sync: ${config.last_sync || 'nunca'}`);
|
|
334
|
+
console.log(` Auto-sync: ${config.sync_on_cycle_end ? 'sí (al final de cada aa:)' : 'no'}`);
|
|
335
|
+
|
|
336
|
+
// Test de conexión
|
|
337
|
+
try {
|
|
338
|
+
const { createClient } = require('@libsql/client');
|
|
339
|
+
const client = createClient({ url: config.url, authToken: config.token });
|
|
340
|
+
await client.execute('SELECT 1');
|
|
341
|
+
console.log(' Conexión: ✅ OK\n');
|
|
342
|
+
} catch {
|
|
343
|
+
console.log(' Conexión: ❌ Error — verificar credenciales\n');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ─── HELPER ───────────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
function getDateField(table) {
|
|
350
|
+
const fields = {
|
|
351
|
+
nodos: 'fecha_update',
|
|
352
|
+
episodios: 'fecha',
|
|
353
|
+
relaciones_semanticas: 'valid_at',
|
|
354
|
+
knowledge_docs: 'last_indexed',
|
|
355
|
+
ast_symbols: 'last_indexed',
|
|
356
|
+
ast_edges: 'last_indexed',
|
|
357
|
+
};
|
|
358
|
+
return fields[table] || 'rowid';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
362
|
+
|
|
363
|
+
if (require.main === module) {
|
|
364
|
+
const [,, cmd, arg1, arg2] = process.argv;
|
|
365
|
+
const projectRoot = process.cwd();
|
|
366
|
+
|
|
367
|
+
switch (cmd) {
|
|
368
|
+
case 'init':
|
|
369
|
+
collabInit(projectRoot).catch(console.error);
|
|
370
|
+
break;
|
|
371
|
+
case 'join':
|
|
372
|
+
if (!arg1 || !arg2) {
|
|
373
|
+
console.error('Uso: collab-manager.cjs join <url> <token>');
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
collabJoin(projectRoot, arg1, arg2).catch(console.error);
|
|
377
|
+
break;
|
|
378
|
+
case 'push':
|
|
379
|
+
syncUp(projectRoot).then(r => {
|
|
380
|
+
process.exit(r.synced ? 0 : 1);
|
|
381
|
+
}).catch(console.error);
|
|
382
|
+
break;
|
|
383
|
+
case 'pull':
|
|
384
|
+
syncDown(projectRoot).then(r => {
|
|
385
|
+
process.exit(r.synced ? 0 : 1);
|
|
386
|
+
}).catch(console.error);
|
|
387
|
+
break;
|
|
388
|
+
case 'status':
|
|
389
|
+
status(projectRoot).catch(console.error);
|
|
390
|
+
break;
|
|
391
|
+
default:
|
|
392
|
+
console.log('Uso: node collab-manager.cjs [init | join <url> <token> | push | pull | status]');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = {
|
|
397
|
+
collabInit,
|
|
398
|
+
collabJoin,
|
|
399
|
+
syncUp,
|
|
400
|
+
syncDown,
|
|
401
|
+
status,
|
|
402
|
+
loadConfig,
|
|
403
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-kdd",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "Autonomous development pipeline — aa: · ag: · audit: · AST graph · Harness · Specs · Impact analysis · Decision trail · Metrics · MCP server. Works with Cursor and Claude Code.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|