refacil-sdd-ai 3.0.2 → 3.1.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 +398 -392
- package/bin/cli.js +400 -1407
- package/lib/commands/bus.js +499 -0
- package/lib/commands/compact.js +92 -0
- package/lib/hooks.js +107 -0
- package/lib/ignore-files.js +88 -0
- package/lib/installer.js +291 -0
- package/package.json +44 -41
- package/skills/archive/SKILL.md +191 -167
- package/skills/setup/SKILL.md +104 -91
package/bin/cli.js
CHANGED
|
@@ -1,1407 +1,400 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
console.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (!settings.hooks) return false;
|
|
402
|
-
|
|
403
|
-
let changed = false;
|
|
404
|
-
|
|
405
|
-
if (Array.isArray(settings.hooks.SessionStart)) {
|
|
406
|
-
const original = settings.hooks.SessionStart.length;
|
|
407
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
|
|
408
|
-
(h) => h._sdd !== true,
|
|
409
|
-
);
|
|
410
|
-
if (settings.hooks.SessionStart.length !== original) changed = true;
|
|
411
|
-
if (settings.hooks.SessionStart.length === 0) delete settings.hooks.SessionStart;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (Array.isArray(settings.hooks.PreToolUse)) {
|
|
415
|
-
const original = settings.hooks.PreToolUse.length;
|
|
416
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
417
|
-
(h) => h._sdd_review !== true && h._sdd_compact !== true,
|
|
418
|
-
);
|
|
419
|
-
if (settings.hooks.PreToolUse.length !== original) changed = true;
|
|
420
|
-
if (settings.hooks.PreToolUse.length === 0) delete settings.hooks.PreToolUse;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
424
|
-
|
|
425
|
-
if (!changed) return false;
|
|
426
|
-
|
|
427
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// --- Check update ---
|
|
432
|
-
|
|
433
|
-
function repoIsInitialized() {
|
|
434
|
-
return (
|
|
435
|
-
fs.existsSync(path.join(projectRoot, '.claude', 'skills')) ||
|
|
436
|
-
fs.existsSync(path.join(projectRoot, '.cursor', 'skills'))
|
|
437
|
-
);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function syncRepoSkillsIfStale(globalVersion) {
|
|
441
|
-
// Si el paquete global ya está en globalVersion pero este repo tiene skills
|
|
442
|
-
// de una versión anterior (porque otro repo disparó el auto-upgrade), hay
|
|
443
|
-
// que re-copiarlas aquí. Idem si nunca se marcó la versión en este repo.
|
|
444
|
-
if (!repoIsInitialized()) return null;
|
|
445
|
-
const repoVersion = readRepoVersion(projectRoot);
|
|
446
|
-
if (repoVersion === globalVersion) return null;
|
|
447
|
-
|
|
448
|
-
const { execSync } = require('child_process');
|
|
449
|
-
const localCli = path.join(packageRoot, 'bin', 'cli.js');
|
|
450
|
-
try {
|
|
451
|
-
execSync(`"${process.execPath}" "${localCli}" update`, {
|
|
452
|
-
encoding: 'utf8',
|
|
453
|
-
timeout: 30000,
|
|
454
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
455
|
-
});
|
|
456
|
-
writeRepoVersion(projectRoot, globalVersion);
|
|
457
|
-
return { from: repoVersion, to: globalVersion };
|
|
458
|
-
} catch (_) {
|
|
459
|
-
return { from: repoVersion, to: globalVersion, failed: true };
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
function checkUpdate() {
|
|
464
|
-
const { execSync } = require('child_process');
|
|
465
|
-
const localVersion = getPackageVersion();
|
|
466
|
-
|
|
467
|
-
// Always ensure AGENTS.md has current compact-guidance block (silent unless error)
|
|
468
|
-
try {
|
|
469
|
-
syncCompactGuidance(projectRoot, packageRoot);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
process.stderr.write(
|
|
472
|
-
`[refacil-sdd-ai] No se pudo sincronizar compact-guidance: ${err.message}\n`,
|
|
473
|
-
);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// Paso 1: sincronizar skills del repo actual si quedaron desfasadas respecto
|
|
477
|
-
// al paquete global (pasa cuando otro repo ya disparó el auto-upgrade).
|
|
478
|
-
const syncResult = syncRepoSkillsIfStale(localVersion);
|
|
479
|
-
if (syncResult && !syncResult.failed) {
|
|
480
|
-
const fromLabel = syncResult.from ? `v${syncResult.from}` : 'version desconocida';
|
|
481
|
-
console.log(
|
|
482
|
-
`[refacil-sdd-ai] Skills de este repo sincronizadas (${fromLabel} -> v${syncResult.to}). ` +
|
|
483
|
-
'Reinicia la sesion de Claude Code o Cursor para detectar los cambios.',
|
|
484
|
-
);
|
|
485
|
-
} else if (syncResult && syncResult.failed) {
|
|
486
|
-
console.log(
|
|
487
|
-
`[refacil-sdd-ai] Skills de este repo estan desactualizadas respecto al paquete global (v${syncResult.to}) ` +
|
|
488
|
-
'pero la sincronizacion automatica fallo. Ejecuta manualmente: refacil-sdd-ai update',
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Paso 2: chequear si hay version nueva en npm y, si la hay, actualizar
|
|
493
|
-
// global + copiar skills al repo actual.
|
|
494
|
-
try {
|
|
495
|
-
const latest = execSync('npm view refacil-sdd-ai version', {
|
|
496
|
-
encoding: 'utf8',
|
|
497
|
-
timeout: 10000,
|
|
498
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
499
|
-
}).trim();
|
|
500
|
-
|
|
501
|
-
if (!latest || latest === localVersion) return;
|
|
502
|
-
|
|
503
|
-
try {
|
|
504
|
-
execSync('npm update -g refacil-sdd-ai', {
|
|
505
|
-
encoding: 'utf8',
|
|
506
|
-
timeout: 60000,
|
|
507
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
508
|
-
});
|
|
509
|
-
execSync('refacil-sdd-ai update', {
|
|
510
|
-
encoding: 'utf8',
|
|
511
|
-
timeout: 30000,
|
|
512
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
513
|
-
});
|
|
514
|
-
writeRepoVersion(projectRoot, latest);
|
|
515
|
-
console.log(
|
|
516
|
-
`[refacil-sdd-ai] La metodologia SDD-AI se actualizo automaticamente de v${localVersion} a v${latest}. Skills y hooks sincronizados.`,
|
|
517
|
-
);
|
|
518
|
-
} catch (_) {
|
|
519
|
-
console.log(
|
|
520
|
-
`[refacil-sdd-ai] Hay una nueva version disponible (v${localVersion} -> v${latest}) pero la actualizacion automatica fallo. ` +
|
|
521
|
-
`Informa al usuario que ejecute manualmente: npm update -g refacil-sdd-ai && refacil-sdd-ai update`,
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
} catch (_) {
|
|
525
|
-
// Silent on error (no internet, registry unreachable, etc.)
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// --- Check review (PreToolUse hook) ---
|
|
530
|
-
|
|
531
|
-
function checkReview() {
|
|
532
|
-
// Read stdin (JSON from PreToolUse hook)
|
|
533
|
-
let input;
|
|
534
|
-
try {
|
|
535
|
-
const stdin = fs.readFileSync(0, 'utf8');
|
|
536
|
-
input = JSON.parse(stdin);
|
|
537
|
-
} catch (_) {
|
|
538
|
-
// If no stdin or invalid JSON, allow (not called from hook context)
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Only block git push commands
|
|
543
|
-
const command = (input.tool_input && input.tool_input.command) || '';
|
|
544
|
-
if (!command.match(/git\s+push/)) return;
|
|
545
|
-
|
|
546
|
-
// Find active change in openspec/changes/ (exclude archive/)
|
|
547
|
-
const changesDir = path.join(projectRoot, 'openspec', 'changes');
|
|
548
|
-
if (!fs.existsSync(changesDir)) return; // No openspec, allow
|
|
549
|
-
|
|
550
|
-
const entries = fs.readdirSync(changesDir, { withFileTypes: true });
|
|
551
|
-
const activeChanges = entries.filter(
|
|
552
|
-
(e) => e.isDirectory() && e.name !== 'archive',
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
if (activeChanges.length === 0) return; // No active changes, allow
|
|
556
|
-
|
|
557
|
-
// Check if any active change is missing .review-passed
|
|
558
|
-
const missing = activeChanges.filter(
|
|
559
|
-
(e) => !fs.existsSync(path.join(changesDir, e.name, '.review-passed')),
|
|
560
|
-
);
|
|
561
|
-
|
|
562
|
-
if (missing.length > 0) {
|
|
563
|
-
const names = missing.map((e) => e.name).join(', ');
|
|
564
|
-
const reason =
|
|
565
|
-
missing.length === 1
|
|
566
|
-
? `[refacil-sdd-ai] Review pendiente para: ${names}. ` +
|
|
567
|
-
'Deten el push y ejecuta /refacil:review sobre ese cambio antes de subir codigo. ' +
|
|
568
|
-
'Si el review aprueba, reintenta el git push. ' +
|
|
569
|
-
'Si el review requiere correcciones, informa los hallazgos al usuario y NO reintentar el push.'
|
|
570
|
-
: `[refacil-sdd-ai] Hay multiples cambios sin review aprobado: ${names}. ` +
|
|
571
|
-
'Deten el push y pide al usuario seleccionar explicitamente que cambio quiere subir. ' +
|
|
572
|
-
'Luego ejecuta /refacil:review <nombre-cambio> para ese cambio especifico y reintenta el push. ' +
|
|
573
|
-
'No ejecutes review automatico sin seleccion cuando hay mas de un cambio pendiente.';
|
|
574
|
-
console.log(
|
|
575
|
-
JSON.stringify({
|
|
576
|
-
decision: 'block',
|
|
577
|
-
reason,
|
|
578
|
-
}),
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// --- Commands ---
|
|
584
|
-
|
|
585
|
-
function init() {
|
|
586
|
-
console.log('\n refacil-sdd-ai: Inicializando metodologia SDD-AI...\n');
|
|
587
|
-
|
|
588
|
-
// Check Node version
|
|
589
|
-
const nodeOk = checkNodeVersion();
|
|
590
|
-
if (nodeOk) {
|
|
591
|
-
console.log(` Node.js ${process.version} OK`);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Check Claude Code version (for compact-bash hook)
|
|
595
|
-
const claudeCheck = checkClaudeCodeVersion();
|
|
596
|
-
if (claudeCheck.ok === true) {
|
|
597
|
-
console.log(` Claude Code ${claudeCheck.version} OK`);
|
|
598
|
-
} else if (claudeCheck.ok === false) {
|
|
599
|
-
console.log(`\n ADVERTENCIA: Claude Code ${claudeCheck.version} detectado.`);
|
|
600
|
-
console.log(' El hook compact-bash requiere Claude Code >= 2.1.89 para rewrite silencioso.');
|
|
601
|
-
console.log(' Con version inferior se instala igual pero el rewrite no tendra efecto.');
|
|
602
|
-
console.log(' Actualiza con: npm install -g @anthropic-ai/claude-code\n');
|
|
603
|
-
}
|
|
604
|
-
// ok === null: claude no esta en PATH, silencioso
|
|
605
|
-
|
|
606
|
-
// Install skills
|
|
607
|
-
const count = installSkills();
|
|
608
|
-
console.log(` ${count} skills instaladas en .claude/skills/ y .cursor/skills/`);
|
|
609
|
-
|
|
610
|
-
// Install sub-agents
|
|
611
|
-
const agentsCount = installAgents();
|
|
612
|
-
if (agentsCount > 0) {
|
|
613
|
-
console.log(` ${agentsCount} sub-agentes instalados en .claude/agents/ y .cursor/agents/`);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
writeRepoVersion(projectRoot, getPackageVersion());
|
|
617
|
-
|
|
618
|
-
// Create or update CLAUDE.md
|
|
619
|
-
if (createClaudeMd()) {
|
|
620
|
-
console.log(' CLAUDE.md OK');
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Create or update .cursorrules
|
|
624
|
-
if (createCursorRules()) {
|
|
625
|
-
console.log(' .cursorrules OK');
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Install SessionStart hook for version check
|
|
629
|
-
if (installHook()) {
|
|
630
|
-
console.log(' Hook check-update agregado a .claude/settings.json');
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Sync compact-guidance block in AGENTS.md (if it exists)
|
|
634
|
-
try {
|
|
635
|
-
const result = syncCompactGuidance(projectRoot, packageRoot);
|
|
636
|
-
if (result.status === 'appended') {
|
|
637
|
-
console.log(' Bloque compact-guidance agregado a AGENTS.md');
|
|
638
|
-
} else if (result.status === 'replaced') {
|
|
639
|
-
console.log(' Bloque compact-guidance actualizado en AGENTS.md');
|
|
640
|
-
}
|
|
641
|
-
} catch (err) {
|
|
642
|
-
console.error(` Advertencia: no se pudo sincronizar compact-guidance: ${err.message}`);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
console.log('\n Siguientes pasos:\n');
|
|
646
|
-
console.log(' 1. REINICIA tu sesion de Claude Code o Cursor');
|
|
647
|
-
console.log(' (las skills nuevas no se detectan hasta reiniciar)\n');
|
|
648
|
-
console.log(' 2. Ejecuta: /refacil:setup');
|
|
649
|
-
console.log(' (instala OpenSpec y genera AGENTS.md para tu proyecto)\n');
|
|
650
|
-
console.log(' Nota: /refacil:setup tambien instalara los comandos de OpenSpec (opsx:*).');
|
|
651
|
-
console.log(' Los comandos refacil:* y opsx:* coexisten sin conflicto.');
|
|
652
|
-
console.log(' El equipo debe usar los refacil:* como interfaz principal.\n');
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
function update() {
|
|
656
|
-
console.log('\n refacil-sdd-ai: Actualizando skills...\n');
|
|
657
|
-
const count = installSkills();
|
|
658
|
-
console.log(` ${count} skills actualizadas en .claude/skills/ y .cursor/skills/`);
|
|
659
|
-
|
|
660
|
-
const agentsCount = installAgents();
|
|
661
|
-
if (agentsCount > 0) {
|
|
662
|
-
console.log(` ${agentsCount} sub-agentes actualizados en .claude/agents/ y .cursor/agents/`);
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
writeRepoVersion(projectRoot, getPackageVersion());
|
|
666
|
-
|
|
667
|
-
// Ensure hook is installed (for users updating from versions without hook)
|
|
668
|
-
if (installHook()) {
|
|
669
|
-
console.log(' Hook check-update agregado a .claude/settings.json');
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// Sync compact-guidance block in AGENTS.md
|
|
673
|
-
try {
|
|
674
|
-
const result = syncCompactGuidance(projectRoot, packageRoot);
|
|
675
|
-
if (result.status === 'appended') {
|
|
676
|
-
console.log(' Bloque compact-guidance agregado a AGENTS.md');
|
|
677
|
-
} else if (result.status === 'replaced') {
|
|
678
|
-
console.log(' Bloque compact-guidance actualizado en AGENTS.md');
|
|
679
|
-
}
|
|
680
|
-
} catch (err) {
|
|
681
|
-
console.error(` Advertencia: no se pudo sincronizar compact-guidance: ${err.message}`);
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
console.log('\n REINICIA tu sesion de Claude Code o Cursor para aplicar los cambios.\n');
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
function clean() {
|
|
688
|
-
console.log('\n refacil-sdd-ai: Eliminando skills...\n');
|
|
689
|
-
const count = removeSkills();
|
|
690
|
-
console.log(` ${count} skills eliminadas de .claude/skills/ y .cursor/skills/`);
|
|
691
|
-
if (uninstallHook()) {
|
|
692
|
-
console.log(' Hooks SDD-AI removidos de .claude/settings.json');
|
|
693
|
-
} else {
|
|
694
|
-
console.log(' No se encontraron hooks SDD-AI para remover.');
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Remove compact-guidance block from AGENTS.md if present
|
|
698
|
-
try {
|
|
699
|
-
const result = removeCompactGuidance(projectRoot);
|
|
700
|
-
if (result.status === 'removed') {
|
|
701
|
-
console.log(' Bloque compact-guidance removido de AGENTS.md');
|
|
702
|
-
}
|
|
703
|
-
} catch (err) {
|
|
704
|
-
console.error(` Advertencia: no se pudo limpiar compact-guidance: ${err.message}`);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
console.log(' AGENTS.md, CLAUDE.md y .cursorrules no fueron eliminados.');
|
|
708
|
-
console.log('\n Nota: Los comandos opsx:* de OpenSpec no se eliminan.');
|
|
709
|
-
console.log(' Para eliminar OpenSpec: rm -rf openspec/ .claude/commands/opsx .cursor/commands/opsx\n');
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// --- Compact subcommands (stats / enable / disable / clear-log) ---
|
|
713
|
-
|
|
714
|
-
function handleCompactSubcommand(sub) {
|
|
715
|
-
switch (sub) {
|
|
716
|
-
case 'stats':
|
|
717
|
-
showCompactStats();
|
|
718
|
-
break;
|
|
719
|
-
case 'disable':
|
|
720
|
-
compactTelemetry.disable();
|
|
721
|
-
console.log(' compact-bash deshabilitado. Reactiva con: refacil-sdd-ai compact enable');
|
|
722
|
-
break;
|
|
723
|
-
case 'enable':
|
|
724
|
-
compactTelemetry.enable();
|
|
725
|
-
console.log(' compact-bash habilitado.');
|
|
726
|
-
break;
|
|
727
|
-
case 'clear-log':
|
|
728
|
-
compactTelemetry.clearLog();
|
|
729
|
-
console.log(' compact.log limpiado.');
|
|
730
|
-
break;
|
|
731
|
-
default:
|
|
732
|
-
console.log('Uso: refacil-sdd-ai compact <stats|disable|enable|clear-log>');
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function showCompactStats() {
|
|
737
|
-
const s = compactTelemetry.stats();
|
|
738
|
-
if (s.totalEvents === 0) {
|
|
739
|
-
console.log('\n No hay eventos registrados todavia. Ejecuta comandos Bash para generar telemetria de compactacion.\n');
|
|
740
|
-
return;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
const sortedRewrites = Object.entries(s.byRule)
|
|
744
|
-
.filter(([, data]) => data.rewriteCount > 0)
|
|
745
|
-
.sort((a, b) => b[1].rewriteSaved - a[1].rewriteSaved);
|
|
746
|
-
const sortedAlreadyCompact = Object.entries(s.byRule)
|
|
747
|
-
.filter(([, data]) => data.alreadyCompactCount > 0)
|
|
748
|
-
.sort((a, b) => b[1].alreadyCompactPotential - a[1].alreadyCompactPotential);
|
|
749
|
-
|
|
750
|
-
console.log(`\n compact-bash stats\n`);
|
|
751
|
-
console.log(` Rewrites por hook: ${s.totalRewrites}`);
|
|
752
|
-
console.log(` Comandos ya compactos detectados (skill/agente): ${s.totalAlreadyCompact}\n`);
|
|
753
|
-
|
|
754
|
-
if (sortedRewrites.length > 0) {
|
|
755
|
-
console.log(' Ahorro aplicado por hook (rewrite):');
|
|
756
|
-
for (const [id, data] of sortedRewrites) {
|
|
757
|
-
const kTokens = (data.rewriteSaved / 1000).toFixed(1);
|
|
758
|
-
console.log(
|
|
759
|
-
` ${id.padEnd(18)} ${String(data.rewriteCount).padStart(6)} rewrites ~${kTokens.padStart(7)}k tokens`,
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
const totalHookK = (s.totalSaved / 1000).toFixed(1);
|
|
763
|
-
const hookUsd = ((s.totalSaved / 1_000_000) * 3).toFixed(2);
|
|
764
|
-
console.log(` ${'-'.repeat(62)}`);
|
|
765
|
-
console.log(
|
|
766
|
-
` ${'Total hook'.padEnd(18)} ${String(s.totalRewrites).padStart(6)} rewrites ~${totalHookK.padStart(7)}k tokens (~$${hookUsd} USD)`,
|
|
767
|
-
);
|
|
768
|
-
console.log('');
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (sortedAlreadyCompact.length > 0) {
|
|
772
|
-
console.log(' Ahorro potencial ya capturado por skill/agente (sin rewrite):');
|
|
773
|
-
for (const [id, data] of sortedAlreadyCompact) {
|
|
774
|
-
const kTokens = (data.alreadyCompactPotential / 1000).toFixed(1);
|
|
775
|
-
console.log(
|
|
776
|
-
` ${id.padEnd(18)} ${String(data.alreadyCompactCount).padStart(6)} eventos ~${kTokens.padStart(7)}k tokens potenciales`,
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
const totalAgentK = (s.totalAlreadyCompactPotential / 1000).toFixed(1);
|
|
780
|
-
const agentUsd = ((s.totalAlreadyCompactPotential / 1_000_000) * 3).toFixed(2);
|
|
781
|
-
console.log(` ${'-'.repeat(62)}`);
|
|
782
|
-
console.log(
|
|
783
|
-
` ${'Total skill'.padEnd(18)} ${String(s.totalAlreadyCompact).padStart(6)} eventos ~${totalAgentK.padStart(7)}k tokens (~$${agentUsd} USD)`,
|
|
784
|
-
);
|
|
785
|
-
console.log('');
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
const totalK = (s.totalObservedPotential / 1000).toFixed(1);
|
|
789
|
-
const totalUsd = ((s.totalObservedPotential / 1_000_000) * 3).toFixed(2);
|
|
790
|
-
console.log(` ${'-'.repeat(62)}`);
|
|
791
|
-
console.log(
|
|
792
|
-
` ${'Total observado'.padEnd(18)} ${String(s.totalEvents).padStart(6)} eventos ~${totalK.padStart(7)}k tokens (~$${totalUsd} USD, Sonnet input)`,
|
|
793
|
-
);
|
|
794
|
-
console.log(`\n Log: ${compactTelemetry.LOG_PATH}`);
|
|
795
|
-
if (compactTelemetry.isDisabled()) {
|
|
796
|
-
console.log(' Estado: DESHABILITADO (no se registran nuevos eventos)');
|
|
797
|
-
}
|
|
798
|
-
console.log('');
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// --- Bus subcommands (refacil-bus: broker core — fase 1) ---
|
|
802
|
-
|
|
803
|
-
async function busStart() {
|
|
804
|
-
try {
|
|
805
|
-
const { info, started } = await busSpawn.ensureBroker(packageRoot);
|
|
806
|
-
if (started) {
|
|
807
|
-
console.log(` refacil-bus broker iniciado en 127.0.0.1:${info.port} (pid ${info.pid}).`);
|
|
808
|
-
} else {
|
|
809
|
-
console.log(` refacil-bus broker ya estaba activo en 127.0.0.1:${info.port} (pid ${info.pid}).`);
|
|
810
|
-
}
|
|
811
|
-
} catch (err) {
|
|
812
|
-
console.error(` No se pudo iniciar el broker: ${err.message}`);
|
|
813
|
-
process.exit(1);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
function busStop() {
|
|
818
|
-
const result = busSpawn.stopBroker();
|
|
819
|
-
if (result.stopped) {
|
|
820
|
-
console.log(` refacil-bus broker detenido (pid ${result.info.pid}).`);
|
|
821
|
-
} else if (result.reason === 'no-info') {
|
|
822
|
-
console.log(' refacil-bus broker no está corriendo.');
|
|
823
|
-
} else if (result.reason === 'not-alive') {
|
|
824
|
-
console.log(' refacil-bus broker no estaba vivo; info obsoleta limpiada.');
|
|
825
|
-
} else {
|
|
826
|
-
console.error(` No se pudo detener el broker: ${result.reason}`);
|
|
827
|
-
process.exit(1);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
async function busStatus() {
|
|
832
|
-
const status = await busSpawn.isBrokerAlive();
|
|
833
|
-
if (!status.alive) {
|
|
834
|
-
console.log(' refacil-bus broker: INACTIVO');
|
|
835
|
-
if (status.staleInfo) {
|
|
836
|
-
console.log(` (info obsoleta encontrada: pid ${status.staleInfo.pid}, puerto ${status.staleInfo.port})`);
|
|
837
|
-
}
|
|
838
|
-
return;
|
|
839
|
-
}
|
|
840
|
-
const info = status.info;
|
|
841
|
-
const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
|
|
842
|
-
const uptimeMin = Math.floor(uptimeMs / 60000);
|
|
843
|
-
console.log(' refacil-bus broker: ACTIVO');
|
|
844
|
-
console.log(` host: 127.0.0.1`);
|
|
845
|
-
console.log(` puerto: ${info.port}`);
|
|
846
|
-
console.log(` pid: ${info.pid}`);
|
|
847
|
-
console.log(` iniciado: ${info.startedAt}`);
|
|
848
|
-
console.log(` uptime: ${uptimeMin} min`);
|
|
849
|
-
console.log(` info: ${busBroker.BUS_INFO_PATH}`);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function busServe() {
|
|
853
|
-
// Invocado por spawn detached — ejecuta el broker en foreground.
|
|
854
|
-
busBroker.start().catch((err) => {
|
|
855
|
-
process.stderr.write(`Error arrancando broker: ${err.message}\n`);
|
|
856
|
-
process.exit(1);
|
|
857
|
-
});
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
function parseBusArgs(argv) {
|
|
861
|
-
const args = {};
|
|
862
|
-
for (let i = 0; i < argv.length; i++) {
|
|
863
|
-
const token = argv[i];
|
|
864
|
-
if (!token || !token.startsWith('--')) continue;
|
|
865
|
-
const key = token.slice(2);
|
|
866
|
-
const next = argv[i + 1];
|
|
867
|
-
if (next === undefined || next.startsWith('--')) {
|
|
868
|
-
args[key] = true;
|
|
869
|
-
} else {
|
|
870
|
-
args[key] = next;
|
|
871
|
-
i++;
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
return args;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function defaultSessionName() {
|
|
878
|
-
return path.basename(process.cwd()) || 'sesion';
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
async function connectOrDie() {
|
|
882
|
-
try {
|
|
883
|
-
const { info } = await busSpawn.ensureBroker(packageRoot);
|
|
884
|
-
const ws = await busClient.connect(info.port);
|
|
885
|
-
return { ws, info };
|
|
886
|
-
} catch (err) {
|
|
887
|
-
console.error(` No se pudo conectar al bus: ${err.message}`);
|
|
888
|
-
process.exit(1);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function formatMessage(m) {
|
|
893
|
-
const target = m.to ? ` → @${m.to}` : '';
|
|
894
|
-
return ` [${m.ts}] ${m.from}${target} (${m.kind}): ${m.text}`;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
async function busJoin(args) {
|
|
898
|
-
const session = args.session || defaultSessionName();
|
|
899
|
-
const room = args.room;
|
|
900
|
-
const repo = args.repo || process.cwd();
|
|
901
|
-
let intro = args.intro;
|
|
902
|
-
if (!intro) {
|
|
903
|
-
try {
|
|
904
|
-
intro = busPresenter.buildIntro({ repoDir: repo, session });
|
|
905
|
-
} catch (_) {
|
|
906
|
-
intro = `${session} se unió a la sala`;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
if (!room) {
|
|
910
|
-
console.error(' Uso: refacil-sdd-ai bus join --room <sala> [--session <s>] [--intro "..."]');
|
|
911
|
-
process.exit(1);
|
|
912
|
-
}
|
|
913
|
-
const { ws } = await connectOrDie();
|
|
914
|
-
const reply = await busClient.sendAndWait(
|
|
915
|
-
ws,
|
|
916
|
-
'join',
|
|
917
|
-
{ session, room, repo, intro },
|
|
918
|
-
(d) => d.type === 'system' && d.event === 'joined',
|
|
919
|
-
3000,
|
|
920
|
-
);
|
|
921
|
-
busClient.close(ws);
|
|
922
|
-
if (!reply) {
|
|
923
|
-
console.error(' Timeout uniéndose a la sala.');
|
|
924
|
-
process.exit(1);
|
|
925
|
-
}
|
|
926
|
-
const members = (reply.detail && reply.detail.members) || [];
|
|
927
|
-
console.log(` Unido a la sala "${room}" como "${session}".`);
|
|
928
|
-
console.log(` Miembros actuales: ${members.join(', ') || '(solo tú)'}`);
|
|
929
|
-
console.log(` Para consultarte: /refacil:ask @${session} "..."`);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
async function busLeave(args) {
|
|
933
|
-
const session = args.session || defaultSessionName();
|
|
934
|
-
const { ws } = await connectOrDie();
|
|
935
|
-
const reply = await busClient.sendAndWait(
|
|
936
|
-
ws,
|
|
937
|
-
'leave',
|
|
938
|
-
{ session },
|
|
939
|
-
(d) => d.type === 'system' && (d.event === 'left' || d.event === 'error'),
|
|
940
|
-
3000,
|
|
941
|
-
);
|
|
942
|
-
busClient.close(ws);
|
|
943
|
-
if (reply && reply.event === 'left') {
|
|
944
|
-
console.log(` "${session}" salió de la sala.`);
|
|
945
|
-
} else {
|
|
946
|
-
console.log(` "${session}" no estaba en ninguna sala.`);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
async function busSay(args) {
|
|
951
|
-
const session = args.session || defaultSessionName();
|
|
952
|
-
const text = args.text;
|
|
953
|
-
if (!text) {
|
|
954
|
-
console.error(' Uso: refacil-sdd-ai bus say --text "..." [--session <s>]');
|
|
955
|
-
process.exit(1);
|
|
956
|
-
}
|
|
957
|
-
const { ws } = await connectOrDie();
|
|
958
|
-
const reply = await busClient.sendAndWait(
|
|
959
|
-
ws,
|
|
960
|
-
'say',
|
|
961
|
-
{ session, text },
|
|
962
|
-
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
963
|
-
3000,
|
|
964
|
-
);
|
|
965
|
-
busClient.close(ws);
|
|
966
|
-
if (reply && reply.event === 'sent') {
|
|
967
|
-
console.log(` Mensaje enviado (id ${reply.detail.id}).`);
|
|
968
|
-
} else {
|
|
969
|
-
const detail = (reply && reply.detail) || 'sin respuesta';
|
|
970
|
-
console.error(` No se pudo enviar: ${detail}`);
|
|
971
|
-
process.exit(1);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
async function busAsk(args) {
|
|
976
|
-
const session = args.session || defaultSessionName();
|
|
977
|
-
const to = args.to;
|
|
978
|
-
const text = args.text;
|
|
979
|
-
const waitSec = args.wait ? parseInt(args.wait, 10) : 0;
|
|
980
|
-
if (!to || !text) {
|
|
981
|
-
console.error(' Uso: refacil-sdd-ai bus ask --to <name> --text "..." [--wait N] [--session <s>]');
|
|
982
|
-
process.exit(1);
|
|
983
|
-
}
|
|
984
|
-
const { ws } = await connectOrDie();
|
|
985
|
-
const ack = await busClient.sendAndWait(
|
|
986
|
-
ws,
|
|
987
|
-
'ask',
|
|
988
|
-
{ session, to: to.replace(/^@/, ''), text },
|
|
989
|
-
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
990
|
-
3000,
|
|
991
|
-
);
|
|
992
|
-
if (!ack || ack.event !== 'sent') {
|
|
993
|
-
busClient.close(ws);
|
|
994
|
-
const detail = (ack && ack.detail) || 'sin respuesta';
|
|
995
|
-
console.error(` No se pudo enviar la pregunta: ${detail}`);
|
|
996
|
-
process.exit(1);
|
|
997
|
-
}
|
|
998
|
-
const correlationId = ack.detail.correlationId;
|
|
999
|
-
console.log(` Pregunta enviada a @${to.replace(/^@/, '')} (correlationId ${correlationId}).`);
|
|
1000
|
-
|
|
1001
|
-
if (waitSec > 0) {
|
|
1002
|
-
console.log(` Esperando respuesta hasta ${waitSec}s...`);
|
|
1003
|
-
const resp = await busClient.sendAndWait(
|
|
1004
|
-
ws,
|
|
1005
|
-
'ping',
|
|
1006
|
-
{},
|
|
1007
|
-
(d) => d.type === 'msg' && d.kind === 'reply' && d.correlationId === correlationId,
|
|
1008
|
-
waitSec * 1000,
|
|
1009
|
-
);
|
|
1010
|
-
busClient.close(ws);
|
|
1011
|
-
if (!resp) {
|
|
1012
|
-
console.log(` Sin respuesta en ${waitSec}s. Usa /refacil:inbox más tarde para recuperarla.`);
|
|
1013
|
-
return;
|
|
1014
|
-
}
|
|
1015
|
-
console.log(` Respuesta de @${resp.from}:`);
|
|
1016
|
-
console.log(` ${resp.text}`);
|
|
1017
|
-
} else {
|
|
1018
|
-
busClient.close(ws);
|
|
1019
|
-
console.log(' Usa /refacil:inbox para ver respuestas.');
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
async function busReply(args) {
|
|
1024
|
-
const session = args.session || defaultSessionName();
|
|
1025
|
-
const text = args.text;
|
|
1026
|
-
const correlationId = args.correlation || null;
|
|
1027
|
-
const to = args.to ? args.to.replace(/^@/, '') : null;
|
|
1028
|
-
if (!text) {
|
|
1029
|
-
console.error(' Uso: refacil-sdd-ai bus reply --text "..." [--to <name>] [--correlation <id>]');
|
|
1030
|
-
process.exit(1);
|
|
1031
|
-
}
|
|
1032
|
-
const { ws } = await connectOrDie();
|
|
1033
|
-
const reply = await busClient.sendAndWait(
|
|
1034
|
-
ws,
|
|
1035
|
-
'reply',
|
|
1036
|
-
{ session, text, to, correlationId },
|
|
1037
|
-
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
1038
|
-
3000,
|
|
1039
|
-
);
|
|
1040
|
-
busClient.close(ws);
|
|
1041
|
-
if (reply && reply.event === 'sent') {
|
|
1042
|
-
console.log(` Respuesta enviada (id ${reply.detail.id}).`);
|
|
1043
|
-
} else {
|
|
1044
|
-
const detail = (reply && reply.detail) || 'sin respuesta';
|
|
1045
|
-
console.error(` No se pudo responder: ${detail}`);
|
|
1046
|
-
process.exit(1);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
async function busHistory(args) {
|
|
1051
|
-
const session = args.session || defaultSessionName();
|
|
1052
|
-
const n = args.n ? parseInt(args.n, 10) : 20;
|
|
1053
|
-
const { ws } = await connectOrDie();
|
|
1054
|
-
const reply = await busClient.sendAndWait(
|
|
1055
|
-
ws,
|
|
1056
|
-
'history',
|
|
1057
|
-
{ session, n },
|
|
1058
|
-
(d) => d.type === 'history',
|
|
1059
|
-
3000,
|
|
1060
|
-
);
|
|
1061
|
-
busClient.close(ws);
|
|
1062
|
-
if (!reply) {
|
|
1063
|
-
console.log(' Sin historial.');
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
|
-
const msgs = reply.messages || [];
|
|
1067
|
-
if (msgs.length === 0) {
|
|
1068
|
-
console.log(' Sin historial.');
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
console.log(` Últimos ${msgs.length} mensajes:`);
|
|
1072
|
-
for (const m of msgs) console.log(formatMessage(m));
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
async function busInbox(args) {
|
|
1076
|
-
const session = args.session || defaultSessionName();
|
|
1077
|
-
const { ws } = await connectOrDie();
|
|
1078
|
-
const reply = await busClient.sendAndWait(
|
|
1079
|
-
ws,
|
|
1080
|
-
'inbox',
|
|
1081
|
-
{ session },
|
|
1082
|
-
(d) => d.type === 'inbox',
|
|
1083
|
-
3000,
|
|
1084
|
-
);
|
|
1085
|
-
busClient.close(ws);
|
|
1086
|
-
if (!reply) {
|
|
1087
|
-
console.log(' Sin respuesta del broker.');
|
|
1088
|
-
return;
|
|
1089
|
-
}
|
|
1090
|
-
const msgs = reply.messages || [];
|
|
1091
|
-
if (msgs.length === 0) {
|
|
1092
|
-
console.log(' Sin mensajes nuevos.');
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
console.log(` ${msgs.length} mensaje(s) nuevo(s):`);
|
|
1096
|
-
for (const m of msgs) console.log(formatMessage(m));
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
function findFirstUnansweredAsk(messages, session) {
|
|
1100
|
-
const asks = messages.filter((m) => m.kind === 'ask' && m.to === session);
|
|
1101
|
-
for (const ask of asks) {
|
|
1102
|
-
const hasReply = messages.some(
|
|
1103
|
-
(m) => m.kind === 'reply' && m.correlationId === ask.correlationId,
|
|
1104
|
-
);
|
|
1105
|
-
if (!hasReply) return ask;
|
|
1106
|
-
}
|
|
1107
|
-
return null;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
function printAttendQuestion(msg) {
|
|
1111
|
-
console.log(' Pregunta recibida del bus:');
|
|
1112
|
-
console.log(` de: @${msg.from}`);
|
|
1113
|
-
console.log(` correlationId: ${msg.correlationId || '(sin id)'}`);
|
|
1114
|
-
console.log(` texto: ${msg.text}`);
|
|
1115
|
-
console.log('');
|
|
1116
|
-
console.log(' Responde con: /refacil:reply "<respuesta>"');
|
|
1117
|
-
console.log(' Luego vuelve a ejecutar /refacil:attend para seguir escuchando.');
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
async function busAttend(args) {
|
|
1121
|
-
const session = args.session || defaultSessionName();
|
|
1122
|
-
const timeoutSec = args.timeout ? parseInt(args.timeout, 10) : 540;
|
|
1123
|
-
const { ws } = await connectOrDie();
|
|
1124
|
-
|
|
1125
|
-
// 1) Revisar preguntas pendientes en el historial antes de suscribirse al push.
|
|
1126
|
-
const hist = await busClient.sendAndWait(
|
|
1127
|
-
ws,
|
|
1128
|
-
'history',
|
|
1129
|
-
{ session, n: 50 },
|
|
1130
|
-
(d) => d.type === 'history',
|
|
1131
|
-
3000,
|
|
1132
|
-
);
|
|
1133
|
-
if (hist) {
|
|
1134
|
-
const pending = findFirstUnansweredAsk(hist.messages || [], session);
|
|
1135
|
-
if (pending) {
|
|
1136
|
-
busClient.close(ws);
|
|
1137
|
-
printAttendQuestion(pending);
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// 2) Suscribirse y esperar push de ask dirigido a esta sesión.
|
|
1143
|
-
const result = await new Promise((resolve) => {
|
|
1144
|
-
let done = false;
|
|
1145
|
-
const finish = (v) => {
|
|
1146
|
-
if (done) return;
|
|
1147
|
-
done = true;
|
|
1148
|
-
clearTimeout(timer);
|
|
1149
|
-
ws.removeListener('message', onMessage);
|
|
1150
|
-
resolve(v);
|
|
1151
|
-
};
|
|
1152
|
-
const onMessage = (raw) => {
|
|
1153
|
-
let data;
|
|
1154
|
-
try { data = JSON.parse(raw.toString()); } catch (_) { return; }
|
|
1155
|
-
if (data.type === 'msg' && data.kind === 'ask' && data.to === session) {
|
|
1156
|
-
finish({ kind: 'message', msg: data });
|
|
1157
|
-
}
|
|
1158
|
-
};
|
|
1159
|
-
ws.on('message', onMessage);
|
|
1160
|
-
const timer = setTimeout(() => finish({ kind: 'timeout' }), timeoutSec * 1000);
|
|
1161
|
-
busClient.send(ws, 'attend', { session });
|
|
1162
|
-
});
|
|
1163
|
-
|
|
1164
|
-
busClient.close(ws);
|
|
1165
|
-
|
|
1166
|
-
if (result.kind === 'message') {
|
|
1167
|
-
printAttendQuestion(result.msg);
|
|
1168
|
-
} else {
|
|
1169
|
-
console.log(` Sin preguntas en ${timeoutSec}s. Re-ejecuta /refacil:attend para seguir escuchando.`);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
function readPersistedSessions() {
|
|
1174
|
-
try {
|
|
1175
|
-
return JSON.parse(fs.readFileSync(busBroker.SESSIONS_PATH, 'utf8'));
|
|
1176
|
-
} catch (_) {
|
|
1177
|
-
return {};
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
async function busWatchCmd(positional, args) {
|
|
1182
|
-
const session = args.session || positional || null;
|
|
1183
|
-
let room = args.room || null;
|
|
1184
|
-
if (session && !room) {
|
|
1185
|
-
const persisted = readPersistedSessions();
|
|
1186
|
-
if (persisted[session] && persisted[session].room) {
|
|
1187
|
-
room = persisted[session].room;
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
if (!session && !room) {
|
|
1191
|
-
console.error(' Uso: refacil-sdd-ai bus watch <session> [--room <sala>]');
|
|
1192
|
-
process.exit(1);
|
|
1193
|
-
}
|
|
1194
|
-
try {
|
|
1195
|
-
const { info } = await busSpawn.ensureBroker(packageRoot);
|
|
1196
|
-
await busWatch.start({ session, room, port: info.port });
|
|
1197
|
-
} catch (err) {
|
|
1198
|
-
console.error(` No se pudo iniciar el watch: ${err.message}`);
|
|
1199
|
-
process.exit(1);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
function openInBrowser(url) {
|
|
1204
|
-
const { spawn } = require('child_process');
|
|
1205
|
-
const platform = process.platform;
|
|
1206
|
-
let cmd;
|
|
1207
|
-
let args;
|
|
1208
|
-
if (platform === 'win32') {
|
|
1209
|
-
cmd = 'cmd';
|
|
1210
|
-
args = ['/c', 'start', '""', url];
|
|
1211
|
-
} else if (platform === 'darwin') {
|
|
1212
|
-
cmd = 'open';
|
|
1213
|
-
args = [url];
|
|
1214
|
-
} else {
|
|
1215
|
-
cmd = 'xdg-open';
|
|
1216
|
-
args = [url];
|
|
1217
|
-
}
|
|
1218
|
-
try {
|
|
1219
|
-
spawn(cmd, args, { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
1220
|
-
return true;
|
|
1221
|
-
} catch (_) {
|
|
1222
|
-
return false;
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
|
|
1226
|
-
async function busView() {
|
|
1227
|
-
try {
|
|
1228
|
-
const { info } = await busSpawn.ensureBroker(packageRoot);
|
|
1229
|
-
const url = `http://127.0.0.1:${info.port}/`;
|
|
1230
|
-
console.log(` refacil-bus view disponible en: ${url}`);
|
|
1231
|
-
const opened = openInBrowser(url);
|
|
1232
|
-
if (!opened) {
|
|
1233
|
-
console.log(' (no se pudo abrir el navegador automáticamente, abre la URL manualmente)');
|
|
1234
|
-
}
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
console.error(` No se pudo iniciar la vista: ${err.message}`);
|
|
1237
|
-
process.exit(1);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
async function busRooms() {
|
|
1242
|
-
const { ws } = await connectOrDie();
|
|
1243
|
-
const reply = await busClient.sendAndWait(
|
|
1244
|
-
ws,
|
|
1245
|
-
'status',
|
|
1246
|
-
{},
|
|
1247
|
-
(d) => d.type === 'system' && d.event === 'status',
|
|
1248
|
-
3000,
|
|
1249
|
-
);
|
|
1250
|
-
busClient.close(ws);
|
|
1251
|
-
if (!reply) {
|
|
1252
|
-
console.log(' Sin respuesta del broker.');
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1255
|
-
const rooms = (reply.detail && reply.detail.rooms) || {};
|
|
1256
|
-
const names = Object.keys(rooms);
|
|
1257
|
-
if (names.length === 0) {
|
|
1258
|
-
console.log(' No hay salas activas.');
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
console.log(' Salas activas:');
|
|
1262
|
-
for (const name of names) {
|
|
1263
|
-
const members = rooms[name] || [];
|
|
1264
|
-
console.log(` ${name} (${members.length}): ${members.join(', ')}`);
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
function handleBusSubcommand(sub) {
|
|
1269
|
-
const rest = process.argv.slice(4);
|
|
1270
|
-
const positional = rest.length > 0 && !rest[0].startsWith('--') ? rest[0] : null;
|
|
1271
|
-
const args = parseBusArgs(rest);
|
|
1272
|
-
switch (sub) {
|
|
1273
|
-
case 'start':
|
|
1274
|
-
busStart();
|
|
1275
|
-
break;
|
|
1276
|
-
case 'stop':
|
|
1277
|
-
busStop();
|
|
1278
|
-
break;
|
|
1279
|
-
case 'status':
|
|
1280
|
-
busStatus();
|
|
1281
|
-
break;
|
|
1282
|
-
case 'serve':
|
|
1283
|
-
busServe();
|
|
1284
|
-
break;
|
|
1285
|
-
case 'join':
|
|
1286
|
-
busJoin(args);
|
|
1287
|
-
break;
|
|
1288
|
-
case 'leave':
|
|
1289
|
-
busLeave(args);
|
|
1290
|
-
break;
|
|
1291
|
-
case 'say':
|
|
1292
|
-
busSay(args);
|
|
1293
|
-
break;
|
|
1294
|
-
case 'ask':
|
|
1295
|
-
busAsk(args);
|
|
1296
|
-
break;
|
|
1297
|
-
case 'reply':
|
|
1298
|
-
busReply(args);
|
|
1299
|
-
break;
|
|
1300
|
-
case 'history':
|
|
1301
|
-
busHistory(args);
|
|
1302
|
-
break;
|
|
1303
|
-
case 'inbox':
|
|
1304
|
-
busInbox(args);
|
|
1305
|
-
break;
|
|
1306
|
-
case 'rooms':
|
|
1307
|
-
busRooms();
|
|
1308
|
-
break;
|
|
1309
|
-
case 'watch':
|
|
1310
|
-
busWatchCmd(positional, args);
|
|
1311
|
-
break;
|
|
1312
|
-
case 'attend':
|
|
1313
|
-
busAttend(args);
|
|
1314
|
-
break;
|
|
1315
|
-
case 'view':
|
|
1316
|
-
busView();
|
|
1317
|
-
break;
|
|
1318
|
-
default:
|
|
1319
|
-
console.log('Uso: refacil-sdd-ai bus <start|stop|status|serve|join|leave|say|ask|reply|history|inbox|rooms|watch|attend|view>');
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
|
|
1323
|
-
function help() {
|
|
1324
|
-
console.log(`
|
|
1325
|
-
refacil-sdd-ai — Metodologia SDD-AI con OpenSpec
|
|
1326
|
-
|
|
1327
|
-
Comandos:
|
|
1328
|
-
init Instala skills en .claude/ y .cursor/, crea CLAUDE.md y .cursorrules
|
|
1329
|
-
update Re-copia skills (para actualizar a nueva version del paquete)
|
|
1330
|
-
check-update Verifica si hay una version mas reciente en npm y sincroniza compact-guidance en AGENTS.md
|
|
1331
|
-
check-review Verifica que el review se haya completado (usado por hook PreToolUse)
|
|
1332
|
-
compact-bash Reescribe comandos Bash bare para reducir tokens (usado por hook PreToolUse)
|
|
1333
|
-
compact Subcomandos del hook compact-bash:
|
|
1334
|
-
compact stats - Estadisticas completas (hook + ya-compacto) y ahorro estimado
|
|
1335
|
-
compact disable - Desactiva el rewrite temporalmente
|
|
1336
|
-
compact enable - Re-activa el rewrite
|
|
1337
|
-
compact clear-log - Borra el log historico
|
|
1338
|
-
bus Subcomandos del chat room entre agentes (refacil-bus):
|
|
1339
|
-
bus start - Arranca el broker local (auto-spawn detached)
|
|
1340
|
-
bus stop - Detiene el broker
|
|
1341
|
-
bus status - Muestra puerto, pid, uptime del broker
|
|
1342
|
-
bus serve - (interno) Ejecuta el broker en foreground
|
|
1343
|
-
bus join --room <sala> [--session <s>] [--intro "..."]
|
|
1344
|
-
bus leave [--session <s>]
|
|
1345
|
-
bus say --text "..." [--session <s>]
|
|
1346
|
-
bus ask --to <name> --text "..." [--wait N] [--session <s>]
|
|
1347
|
-
bus reply --text "..." [--correlation <id>] [--to <name>]
|
|
1348
|
-
bus history [--n N] [--session <s>]
|
|
1349
|
-
bus inbox [--session <s>]
|
|
1350
|
-
bus rooms
|
|
1351
|
-
bus watch <session> [--room <sala>] (panel en vivo, sin tokens)
|
|
1352
|
-
bus attend [--timeout N] (escucha preguntas dirigidas)
|
|
1353
|
-
bus view (abre la UI web en el navegador)
|
|
1354
|
-
clean Elimina skills y remueve hooks SDD-AI de .claude/settings.json
|
|
1355
|
-
help Muestra esta ayuda
|
|
1356
|
-
|
|
1357
|
-
Flujo completo:
|
|
1358
|
-
1. npm install -g refacil-sdd-ai
|
|
1359
|
-
2. refacil-sdd-ai init
|
|
1360
|
-
3. REINICIAR sesion de Claude Code o Cursor
|
|
1361
|
-
4. Ejecutar: /refacil:setup (instala OpenSpec + genera AGENTS.md)
|
|
1362
|
-
|
|
1363
|
-
Requisitos:
|
|
1364
|
-
- Node.js >= 20.19.0 (requerido por OpenSpec)
|
|
1365
|
-
- Claude Code >= 2.1.89 (requerido por compact-bash para rewrite silencioso) o Cursor
|
|
1366
|
-
`);
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
// --- Main ---
|
|
1370
|
-
|
|
1371
|
-
const command = process.argv[2] || 'help';
|
|
1372
|
-
|
|
1373
|
-
switch (command) {
|
|
1374
|
-
case 'init':
|
|
1375
|
-
init();
|
|
1376
|
-
break;
|
|
1377
|
-
case 'update':
|
|
1378
|
-
update();
|
|
1379
|
-
break;
|
|
1380
|
-
case 'check-update':
|
|
1381
|
-
checkUpdate();
|
|
1382
|
-
break;
|
|
1383
|
-
case 'check-review':
|
|
1384
|
-
checkReview();
|
|
1385
|
-
break;
|
|
1386
|
-
case 'compact-bash':
|
|
1387
|
-
compactBash.run();
|
|
1388
|
-
break;
|
|
1389
|
-
case 'compact':
|
|
1390
|
-
handleCompactSubcommand(process.argv[3]);
|
|
1391
|
-
break;
|
|
1392
|
-
case 'bus':
|
|
1393
|
-
handleBusSubcommand(process.argv[3]);
|
|
1394
|
-
break;
|
|
1395
|
-
case 'clean':
|
|
1396
|
-
clean();
|
|
1397
|
-
break;
|
|
1398
|
-
case 'help':
|
|
1399
|
-
case '--help':
|
|
1400
|
-
case '-h':
|
|
1401
|
-
help();
|
|
1402
|
-
break;
|
|
1403
|
-
default:
|
|
1404
|
-
console.error(` Comando desconocido: ${command}`);
|
|
1405
|
-
help();
|
|
1406
|
-
process.exit(1);
|
|
1407
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const {
|
|
8
|
+
syncCompactGuidance,
|
|
9
|
+
removeCompactGuidance,
|
|
10
|
+
} = require('../lib/compact-guidance');
|
|
11
|
+
const compactBash = require('../lib/compact/bash');
|
|
12
|
+
const {
|
|
13
|
+
installSkills,
|
|
14
|
+
installAgents,
|
|
15
|
+
removeSkills,
|
|
16
|
+
createClaudeMd,
|
|
17
|
+
createCursorRules,
|
|
18
|
+
readRepoVersion,
|
|
19
|
+
writeRepoVersion,
|
|
20
|
+
getPackageVersion,
|
|
21
|
+
checkNodeVersion,
|
|
22
|
+
checkClaudeCodeVersion,
|
|
23
|
+
} = require('../lib/installer');
|
|
24
|
+
const { installHooks, uninstallHooks } = require('../lib/hooks');
|
|
25
|
+
const { handleCompact } = require('../lib/commands/compact');
|
|
26
|
+
const { handleBus } = require('../lib/commands/bus');
|
|
27
|
+
const { syncIgnoreFiles } = require('../lib/ignore-files');
|
|
28
|
+
|
|
29
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
30
|
+
const projectRoot = process.cwd();
|
|
31
|
+
|
|
32
|
+
// --- Check update (SessionStart hook) ---
|
|
33
|
+
|
|
34
|
+
function repoIsInitialized() {
|
|
35
|
+
return (
|
|
36
|
+
fs.existsSync(path.join(projectRoot, '.claude', 'skills')) ||
|
|
37
|
+
fs.existsSync(path.join(projectRoot, '.cursor', 'skills'))
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function syncRepoSkillsIfStale(globalVersion) {
|
|
42
|
+
if (!repoIsInitialized()) return null;
|
|
43
|
+
const repoVersion = readRepoVersion(projectRoot);
|
|
44
|
+
if (repoVersion === globalVersion) return null;
|
|
45
|
+
|
|
46
|
+
const { execSync } = require('child_process');
|
|
47
|
+
const localCli = path.join(packageRoot, 'bin', 'cli.js');
|
|
48
|
+
try {
|
|
49
|
+
execSync(`"${process.execPath}" "${localCli}" update`, {
|
|
50
|
+
encoding: 'utf8',
|
|
51
|
+
timeout: 30000,
|
|
52
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
53
|
+
});
|
|
54
|
+
writeRepoVersion(projectRoot, globalVersion);
|
|
55
|
+
return { from: repoVersion, to: globalVersion };
|
|
56
|
+
} catch (_) {
|
|
57
|
+
return { from: repoVersion, to: globalVersion, failed: true };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function checkUpdate() {
|
|
62
|
+
const { execSync } = require('child_process');
|
|
63
|
+
const localVersion = getPackageVersion(packageRoot);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
syncCompactGuidance(projectRoot, packageRoot);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
process.stderr.write(
|
|
69
|
+
`[refacil-sdd-ai] No se pudo sincronizar compact-guidance: ${err.message}\n`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const syncResult = syncRepoSkillsIfStale(localVersion);
|
|
74
|
+
if (syncResult && !syncResult.failed) {
|
|
75
|
+
const fromLabel = syncResult.from ? `v${syncResult.from}` : 'version desconocida';
|
|
76
|
+
console.log(
|
|
77
|
+
`[refacil-sdd-ai] Skills de este repo sincronizadas (${fromLabel} -> v${syncResult.to}). ` +
|
|
78
|
+
'Reinicia la sesion de Claude Code o Cursor para detectar los cambios.',
|
|
79
|
+
);
|
|
80
|
+
} else if (syncResult && syncResult.failed) {
|
|
81
|
+
console.log(
|
|
82
|
+
`[refacil-sdd-ai] Skills de este repo estan desactualizadas respecto al paquete global (v${syncResult.to}) ` +
|
|
83
|
+
'pero la sincronizacion automatica fallo. Ejecuta manualmente: refacil-sdd-ai update',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const latest = execSync('npm view refacil-sdd-ai version', {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
timeout: 10000,
|
|
91
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
92
|
+
}).trim();
|
|
93
|
+
|
|
94
|
+
if (!latest || latest === localVersion) return;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
execSync('npm update -g refacil-sdd-ai', {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
timeout: 60000,
|
|
100
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
101
|
+
});
|
|
102
|
+
execSync('refacil-sdd-ai update', {
|
|
103
|
+
encoding: 'utf8',
|
|
104
|
+
timeout: 30000,
|
|
105
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
106
|
+
});
|
|
107
|
+
writeRepoVersion(projectRoot, latest);
|
|
108
|
+
console.log(
|
|
109
|
+
`[refacil-sdd-ai] La metodologia SDD-AI se actualizo automaticamente de v${localVersion} a v${latest}. Skills y hooks sincronizados.`,
|
|
110
|
+
);
|
|
111
|
+
} catch (_) {
|
|
112
|
+
console.log(
|
|
113
|
+
`[refacil-sdd-ai] Hay una nueva version disponible (v${localVersion} -> v${latest}) pero la actualizacion automatica fallo. ` +
|
|
114
|
+
`Informa al usuario que ejecute manualmente: npm update -g refacil-sdd-ai && refacil-sdd-ai update`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {
|
|
118
|
+
// Silent: sin internet o registry no disponible
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- Check review (PreToolUse hook) ---
|
|
123
|
+
|
|
124
|
+
function checkReview() {
|
|
125
|
+
let input;
|
|
126
|
+
try {
|
|
127
|
+
const stdin = fs.readFileSync(0, 'utf8');
|
|
128
|
+
input = JSON.parse(stdin);
|
|
129
|
+
} catch (_) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const command = (input.tool_input && input.tool_input.command) || '';
|
|
134
|
+
if (!command.match(/git\s+push/)) return;
|
|
135
|
+
|
|
136
|
+
const changesDir = path.join(projectRoot, 'openspec', 'changes');
|
|
137
|
+
if (!fs.existsSync(changesDir)) return;
|
|
138
|
+
|
|
139
|
+
const entries = fs.readdirSync(changesDir, { withFileTypes: true });
|
|
140
|
+
const activeChanges = entries.filter(
|
|
141
|
+
(e) => e.isDirectory() && e.name !== 'archive',
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (activeChanges.length === 0) return;
|
|
145
|
+
|
|
146
|
+
const missing = activeChanges.filter(
|
|
147
|
+
(e) => !fs.existsSync(path.join(changesDir, e.name, '.review-passed')),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (missing.length > 0) {
|
|
151
|
+
const names = missing.map((e) => e.name).join(', ');
|
|
152
|
+
const reason =
|
|
153
|
+
missing.length === 1
|
|
154
|
+
? `[refacil-sdd-ai] Review pendiente para: ${names}. ` +
|
|
155
|
+
'Deten el push y ejecuta /refacil:review sobre ese cambio antes de subir codigo. ' +
|
|
156
|
+
'Si el review aprueba, reintenta el git push. ' +
|
|
157
|
+
'Si el review requiere correcciones, informa los hallazgos al usuario y NO reintentar el push.'
|
|
158
|
+
: `[refacil-sdd-ai] Hay multiples cambios sin review aprobado: ${names}. ` +
|
|
159
|
+
'Deten el push y pide al usuario seleccionar explicitamente que cambio quiere subir. ' +
|
|
160
|
+
'Luego ejecuta /refacil:review <nombre-cambio> para ese cambio especifico y reintenta el push. ' +
|
|
161
|
+
'No ejecutes review automatico sin seleccion cuando hay mas de un cambio pendiente.';
|
|
162
|
+
console.log(JSON.stringify({ decision: 'block', reason }));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- High-level commands ---
|
|
167
|
+
|
|
168
|
+
function init() {
|
|
169
|
+
console.log('\n refacil-sdd-ai: Inicializando metodologia SDD-AI...\n');
|
|
170
|
+
|
|
171
|
+
const nodeOk = checkNodeVersion();
|
|
172
|
+
if (nodeOk) console.log(` Node.js ${process.version} OK`);
|
|
173
|
+
|
|
174
|
+
const claudeCheck = checkClaudeCodeVersion();
|
|
175
|
+
if (claudeCheck.ok === true) {
|
|
176
|
+
console.log(` Claude Code ${claudeCheck.version} OK`);
|
|
177
|
+
} else if (claudeCheck.ok === false) {
|
|
178
|
+
console.log(`\n ADVERTENCIA: Claude Code ${claudeCheck.version} detectado.`);
|
|
179
|
+
console.log(' El hook compact-bash requiere Claude Code >= 2.1.89 para rewrite silencioso.');
|
|
180
|
+
console.log(' Con version inferior se instala igual pero el rewrite no tendra efecto.');
|
|
181
|
+
console.log(' Actualiza con: npm install -g @anthropic-ai/claude-code\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const count = installSkills(packageRoot, projectRoot);
|
|
185
|
+
console.log(` ${count} skills instaladas en .claude/skills/ y .cursor/skills/`);
|
|
186
|
+
|
|
187
|
+
const agentsCount = installAgents(packageRoot, projectRoot);
|
|
188
|
+
if (agentsCount > 0) {
|
|
189
|
+
console.log(` ${agentsCount} sub-agentes instalados en .claude/agents/ y .cursor/agents/`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
writeRepoVersion(projectRoot, getPackageVersion(packageRoot));
|
|
193
|
+
|
|
194
|
+
if (createClaudeMd(packageRoot, projectRoot)) console.log(' CLAUDE.md OK');
|
|
195
|
+
if (createCursorRules(packageRoot, projectRoot)) console.log(' .cursorrules OK');
|
|
196
|
+
|
|
197
|
+
if (installHooks('.claude', projectRoot)) {
|
|
198
|
+
console.log(' Hook check-update agregado a .claude/settings.json');
|
|
199
|
+
}
|
|
200
|
+
if (installHooks('.cursor', projectRoot)) {
|
|
201
|
+
console.log(' Hook check-update agregado a .cursor/settings.json');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const ignoreResult = syncIgnoreFiles(projectRoot);
|
|
206
|
+
const s = ignoreResult.claude;
|
|
207
|
+
if (s.status === 'created') {
|
|
208
|
+
console.log(' .claudeignore y .cursorignore creados');
|
|
209
|
+
} else if (s.status === 'updated') {
|
|
210
|
+
console.log(` .claudeignore y .cursorignore actualizados (${s.added} entradas agregadas)`);
|
|
211
|
+
} else {
|
|
212
|
+
console.log(' .claudeignore y .cursorignore ya están al día');
|
|
213
|
+
}
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.error(` Advertencia: no se pudo sincronizar ignore files: ${err.message}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
const result = syncCompactGuidance(projectRoot, packageRoot);
|
|
220
|
+
if (result.status === 'appended') {
|
|
221
|
+
console.log(' Bloque compact-guidance agregado a AGENTS.md');
|
|
222
|
+
} else if (result.status === 'replaced') {
|
|
223
|
+
console.log(' Bloque compact-guidance actualizado en AGENTS.md');
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
console.error(` Advertencia: no se pudo sincronizar compact-guidance: ${err.message}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log('\n Siguientes pasos:\n');
|
|
230
|
+
console.log(' 1. REINICIA tu sesion de Claude Code o Cursor');
|
|
231
|
+
console.log(' (las skills nuevas no se detectan hasta reiniciar)\n');
|
|
232
|
+
console.log(' 2. Ejecuta: /refacil:setup');
|
|
233
|
+
console.log(' (instala OpenSpec y genera AGENTS.md para tu proyecto)\n');
|
|
234
|
+
console.log(' Nota: /refacil:setup tambien instalara los comandos de OpenSpec (opsx:*).');
|
|
235
|
+
console.log(' Los comandos refacil:* y opsx:* coexisten sin conflicto.');
|
|
236
|
+
console.log(' El equipo debe usar los refacil:* como interfaz principal.\n');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function update() {
|
|
240
|
+
console.log('\n refacil-sdd-ai: Actualizando skills...\n');
|
|
241
|
+
|
|
242
|
+
const count = installSkills(packageRoot, projectRoot);
|
|
243
|
+
console.log(` ${count} skills actualizadas en .claude/skills/ y .cursor/skills/`);
|
|
244
|
+
|
|
245
|
+
const agentsCount = installAgents(packageRoot, projectRoot);
|
|
246
|
+
if (agentsCount > 0) {
|
|
247
|
+
console.log(` ${agentsCount} sub-agentes actualizados en .claude/agents/ y .cursor/agents/`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
writeRepoVersion(projectRoot, getPackageVersion(packageRoot));
|
|
251
|
+
|
|
252
|
+
if (installHooks('.claude', projectRoot)) {
|
|
253
|
+
console.log(' Hook check-update agregado a .claude/settings.json');
|
|
254
|
+
}
|
|
255
|
+
if (installHooks('.cursor', projectRoot)) {
|
|
256
|
+
console.log(' Hook check-update agregado a .cursor/settings.json');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const ignoreResult = syncIgnoreFiles(projectRoot);
|
|
261
|
+
const s = ignoreResult.claude;
|
|
262
|
+
if (s.status === 'created') {
|
|
263
|
+
console.log(' .claudeignore y .cursorignore creados');
|
|
264
|
+
} else if (s.status === 'updated') {
|
|
265
|
+
console.log(` .claudeignore y .cursorignore actualizados (${s.added} entradas agregadas)`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log(' .claudeignore y .cursorignore ya están al día');
|
|
268
|
+
}
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.error(` Advertencia: no se pudo sincronizar ignore files: ${err.message}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const result = syncCompactGuidance(projectRoot, packageRoot);
|
|
275
|
+
if (result.status === 'appended') {
|
|
276
|
+
console.log(' Bloque compact-guidance agregado a AGENTS.md');
|
|
277
|
+
} else if (result.status === 'replaced') {
|
|
278
|
+
console.log(' Bloque compact-guidance actualizado en AGENTS.md');
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
console.error(` Advertencia: no se pudo sincronizar compact-guidance: ${err.message}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log('\n REINICIA tu sesion de Claude Code o Cursor para aplicar los cambios.\n');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function clean() {
|
|
288
|
+
console.log('\n refacil-sdd-ai: Eliminando skills...\n');
|
|
289
|
+
|
|
290
|
+
const count = removeSkills(projectRoot);
|
|
291
|
+
console.log(` ${count} skills eliminadas de .claude/skills/ y .cursor/skills/`);
|
|
292
|
+
|
|
293
|
+
if (uninstallHooks('.claude', projectRoot)) {
|
|
294
|
+
console.log(' Hooks SDD-AI removidos de .claude/settings.json');
|
|
295
|
+
} else {
|
|
296
|
+
console.log(' No se encontraron hooks SDD-AI para remover en .claude/settings.json.');
|
|
297
|
+
}
|
|
298
|
+
if (uninstallHooks('.cursor', projectRoot)) {
|
|
299
|
+
console.log(' Hooks SDD-AI removidos de .cursor/settings.json');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
const result = removeCompactGuidance(projectRoot);
|
|
304
|
+
if (result.status === 'removed') {
|
|
305
|
+
console.log(' Bloque compact-guidance removido de AGENTS.md');
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
console.error(` Advertencia: no se pudo limpiar compact-guidance: ${err.message}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log(' AGENTS.md, CLAUDE.md y .cursorrules no fueron eliminados.');
|
|
312
|
+
console.log('\n Nota: Los comandos opsx:* de OpenSpec no se eliminan.');
|
|
313
|
+
console.log(' Para eliminar OpenSpec: rm -rf openspec/ .claude/commands/opsx .cursor/commands/opsx\n');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function help() {
|
|
317
|
+
console.log(`
|
|
318
|
+
refacil-sdd-ai — Metodologia SDD-AI con OpenSpec
|
|
319
|
+
|
|
320
|
+
Comandos:
|
|
321
|
+
init Instala skills en .claude/ y .cursor/, crea CLAUDE.md y .cursorrules
|
|
322
|
+
update Re-copia skills (para actualizar a nueva version del paquete)
|
|
323
|
+
check-update Verifica si hay una version mas reciente en npm y sincroniza compact-guidance en AGENTS.md
|
|
324
|
+
check-review Verifica que el review se haya completado (usado por hook PreToolUse)
|
|
325
|
+
compact-bash Reescribe comandos Bash bare para reducir tokens (usado por hook PreToolUse)
|
|
326
|
+
compact Subcomandos del hook compact-bash:
|
|
327
|
+
compact stats - Estadisticas completas (hook + ya-compacto) y ahorro estimado
|
|
328
|
+
compact disable - Desactiva el rewrite temporalmente
|
|
329
|
+
compact enable - Re-activa el rewrite
|
|
330
|
+
compact clear-log - Borra el log historico
|
|
331
|
+
bus Subcomandos del chat room entre agentes (refacil-bus):
|
|
332
|
+
bus start - Arranca el broker local (auto-spawn detached)
|
|
333
|
+
bus stop - Detiene el broker
|
|
334
|
+
bus status - Muestra puerto, pid, uptime del broker
|
|
335
|
+
bus serve - (interno) Ejecuta el broker en foreground
|
|
336
|
+
bus join --room <sala> [--session <s>] [--intro "..."]
|
|
337
|
+
bus leave [--session <s>]
|
|
338
|
+
bus say --text "..." [--session <s>]
|
|
339
|
+
bus ask --to <name> --text "..." [--wait N] [--session <s>]
|
|
340
|
+
bus reply --text "..." [--correlation <id>] [--to <name>]
|
|
341
|
+
bus history [--n N] [--session <s>]
|
|
342
|
+
bus inbox [--session <s>]
|
|
343
|
+
bus rooms
|
|
344
|
+
bus watch <session> [--room <sala>] (panel en vivo, sin tokens)
|
|
345
|
+
bus attend [--timeout N] (escucha preguntas dirigidas)
|
|
346
|
+
bus view (abre la UI web en el navegador)
|
|
347
|
+
clean Elimina skills y remueve hooks SDD-AI de .claude/settings.json y .cursor/settings.json
|
|
348
|
+
help Muestra esta ayuda
|
|
349
|
+
|
|
350
|
+
Flujo completo:
|
|
351
|
+
1. npm install -g refacil-sdd-ai
|
|
352
|
+
2. refacil-sdd-ai init
|
|
353
|
+
3. REINICIAR sesion de Claude Code o Cursor
|
|
354
|
+
4. Ejecutar: /refacil:setup (instala OpenSpec + genera AGENTS.md)
|
|
355
|
+
|
|
356
|
+
Requisitos:
|
|
357
|
+
- Node.js >= 20.19.0 (requerido por OpenSpec)
|
|
358
|
+
- Claude Code >= 2.1.89 (requerido por compact-bash para rewrite silencioso) o Cursor
|
|
359
|
+
`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// --- Main ---
|
|
363
|
+
|
|
364
|
+
const command = process.argv[2] || 'help';
|
|
365
|
+
|
|
366
|
+
switch (command) {
|
|
367
|
+
case 'init':
|
|
368
|
+
init();
|
|
369
|
+
break;
|
|
370
|
+
case 'update':
|
|
371
|
+
update();
|
|
372
|
+
break;
|
|
373
|
+
case 'check-update':
|
|
374
|
+
checkUpdate();
|
|
375
|
+
break;
|
|
376
|
+
case 'check-review':
|
|
377
|
+
checkReview();
|
|
378
|
+
break;
|
|
379
|
+
case 'compact-bash':
|
|
380
|
+
compactBash.run();
|
|
381
|
+
break;
|
|
382
|
+
case 'compact':
|
|
383
|
+
handleCompact(process.argv[3]);
|
|
384
|
+
break;
|
|
385
|
+
case 'bus':
|
|
386
|
+
handleBus(process.argv[3], process.argv.slice(4), packageRoot);
|
|
387
|
+
break;
|
|
388
|
+
case 'clean':
|
|
389
|
+
clean();
|
|
390
|
+
break;
|
|
391
|
+
case 'help':
|
|
392
|
+
case '--help':
|
|
393
|
+
case '-h':
|
|
394
|
+
help();
|
|
395
|
+
break;
|
|
396
|
+
default:
|
|
397
|
+
console.error(` Comando desconocido: ${command}`);
|
|
398
|
+
help();
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|