refacil-sdd-ai 2.7.0 → 2.8.1
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 +152 -23
- package/bin/cli.js +599 -3
- package/lib/bus/broker.js +518 -0
- package/lib/bus/client.js +67 -0
- package/lib/bus/presenter.js +97 -0
- package/lib/bus/spawn.js +121 -0
- package/lib/bus/storage.js +144 -0
- package/lib/bus/watch.js +78 -0
- package/package.json +4 -1
- package/skills/ask/SKILL.md +61 -0
- package/skills/attend/SKILL.md +68 -0
- package/skills/inbox/SKILL.md +42 -0
- package/skills/join/SKILL.md +81 -0
- package/skills/reply/SKILL.md +43 -0
- package/skills/say/SKILL.md +37 -0
package/bin/cli.js
CHANGED
|
@@ -8,6 +8,11 @@ const {
|
|
|
8
8
|
} = require('../lib/compact-guidance');
|
|
9
9
|
const compactBash = require('../lib/compact/bash');
|
|
10
10
|
const compactTelemetry = require('../lib/compact/telemetry');
|
|
11
|
+
const busBroker = require('../lib/bus/broker');
|
|
12
|
+
const busSpawn = require('../lib/bus/spawn');
|
|
13
|
+
const busClient = require('../lib/bus/client');
|
|
14
|
+
const busWatch = require('../lib/bus/watch');
|
|
15
|
+
const busPresenter = require('../lib/bus/presenter');
|
|
11
16
|
|
|
12
17
|
const SKILLS = [
|
|
13
18
|
'setup',
|
|
@@ -22,6 +27,13 @@ const SKILLS = [
|
|
|
22
27
|
'archive',
|
|
23
28
|
'bug',
|
|
24
29
|
'up-code',
|
|
30
|
+
// refacil-bus (agent chat room)
|
|
31
|
+
'join',
|
|
32
|
+
'say',
|
|
33
|
+
'ask',
|
|
34
|
+
'reply',
|
|
35
|
+
'inbox',
|
|
36
|
+
'attend',
|
|
25
37
|
];
|
|
26
38
|
|
|
27
39
|
const packageRoot = path.resolve(__dirname, '..');
|
|
@@ -113,6 +125,42 @@ function createCursorRules() {
|
|
|
113
125
|
);
|
|
114
126
|
}
|
|
115
127
|
|
|
128
|
+
const REPO_VERSION_FILES = ['.claude/.sdd-version', '.cursor/.sdd-version'];
|
|
129
|
+
|
|
130
|
+
function readRepoVersion(rootDir) {
|
|
131
|
+
for (const rel of REPO_VERSION_FILES) {
|
|
132
|
+
const p = path.join(rootDir, rel);
|
|
133
|
+
try {
|
|
134
|
+
const raw = fs.readFileSync(p, 'utf8').trim();
|
|
135
|
+
if (raw) return raw;
|
|
136
|
+
} catch (_) {
|
|
137
|
+
// continuar al siguiente
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function writeRepoVersion(rootDir, version) {
|
|
144
|
+
for (const rel of REPO_VERSION_FILES) {
|
|
145
|
+
const p = path.join(rootDir, rel);
|
|
146
|
+
const parent = path.dirname(p);
|
|
147
|
+
if (!fs.existsSync(parent)) continue;
|
|
148
|
+
try {
|
|
149
|
+
fs.writeFileSync(p, String(version) + '\n');
|
|
150
|
+
} catch (_) {
|
|
151
|
+
// tolerante
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getPackageVersion() {
|
|
157
|
+
try {
|
|
158
|
+
return require(path.join(packageRoot, 'package.json')).version;
|
|
159
|
+
} catch (_) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
116
164
|
function removeSkills() {
|
|
117
165
|
let removed = 0;
|
|
118
166
|
for (const skill of SKILLS) {
|
|
@@ -292,9 +340,39 @@ function uninstallHook() {
|
|
|
292
340
|
|
|
293
341
|
// --- Check update ---
|
|
294
342
|
|
|
343
|
+
function repoIsInitialized() {
|
|
344
|
+
return (
|
|
345
|
+
fs.existsSync(path.join(projectRoot, '.claude', 'skills')) ||
|
|
346
|
+
fs.existsSync(path.join(projectRoot, '.cursor', 'skills'))
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function syncRepoSkillsIfStale(globalVersion) {
|
|
351
|
+
// Si el paquete global ya está en globalVersion pero este repo tiene skills
|
|
352
|
+
// de una versión anterior (porque otro repo disparó el auto-upgrade), hay
|
|
353
|
+
// que re-copiarlas aquí. Idem si nunca se marcó la versión en este repo.
|
|
354
|
+
if (!repoIsInitialized()) return null;
|
|
355
|
+
const repoVersion = readRepoVersion(projectRoot);
|
|
356
|
+
if (repoVersion === globalVersion) return null;
|
|
357
|
+
|
|
358
|
+
const { execSync } = require('child_process');
|
|
359
|
+
const localCli = path.join(packageRoot, 'bin', 'cli.js');
|
|
360
|
+
try {
|
|
361
|
+
execSync(`"${process.execPath}" "${localCli}" update`, {
|
|
362
|
+
encoding: 'utf8',
|
|
363
|
+
timeout: 30000,
|
|
364
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
365
|
+
});
|
|
366
|
+
writeRepoVersion(projectRoot, globalVersion);
|
|
367
|
+
return { from: repoVersion, to: globalVersion };
|
|
368
|
+
} catch (_) {
|
|
369
|
+
return { from: repoVersion, to: globalVersion, failed: true };
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
295
373
|
function checkUpdate() {
|
|
296
374
|
const { execSync } = require('child_process');
|
|
297
|
-
const localVersion =
|
|
375
|
+
const localVersion = getPackageVersion();
|
|
298
376
|
|
|
299
377
|
// Always ensure AGENTS.md has current compact-guidance block (silent unless error)
|
|
300
378
|
try {
|
|
@@ -305,6 +383,24 @@ function checkUpdate() {
|
|
|
305
383
|
);
|
|
306
384
|
}
|
|
307
385
|
|
|
386
|
+
// Paso 1: sincronizar skills del repo actual si quedaron desfasadas respecto
|
|
387
|
+
// al paquete global (pasa cuando otro repo ya disparó el auto-upgrade).
|
|
388
|
+
const syncResult = syncRepoSkillsIfStale(localVersion);
|
|
389
|
+
if (syncResult && !syncResult.failed) {
|
|
390
|
+
const fromLabel = syncResult.from ? `v${syncResult.from}` : 'version desconocida';
|
|
391
|
+
console.log(
|
|
392
|
+
`[refacil-sdd-ai] Skills de este repo sincronizadas (${fromLabel} -> v${syncResult.to}). ` +
|
|
393
|
+
'Reinicia la sesion de Claude Code o Cursor para detectar los cambios.',
|
|
394
|
+
);
|
|
395
|
+
} else if (syncResult && syncResult.failed) {
|
|
396
|
+
console.log(
|
|
397
|
+
`[refacil-sdd-ai] Skills de este repo estan desactualizadas respecto al paquete global (v${syncResult.to}) ` +
|
|
398
|
+
'pero la sincronizacion automatica fallo. Ejecuta manualmente: refacil-sdd-ai update',
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Paso 2: chequear si hay version nueva en npm y, si la hay, actualizar
|
|
403
|
+
// global + copiar skills al repo actual.
|
|
308
404
|
try {
|
|
309
405
|
const latest = execSync('npm view refacil-sdd-ai version', {
|
|
310
406
|
encoding: 'utf8',
|
|
@@ -314,7 +410,6 @@ function checkUpdate() {
|
|
|
314
410
|
|
|
315
411
|
if (!latest || latest === localVersion) return;
|
|
316
412
|
|
|
317
|
-
// Auto-upgrade: the hook does it itself, LLM only sees the result
|
|
318
413
|
try {
|
|
319
414
|
execSync('npm update -g refacil-sdd-ai', {
|
|
320
415
|
encoding: 'utf8',
|
|
@@ -326,11 +421,11 @@ function checkUpdate() {
|
|
|
326
421
|
timeout: 30000,
|
|
327
422
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
328
423
|
});
|
|
424
|
+
writeRepoVersion(projectRoot, latest);
|
|
329
425
|
console.log(
|
|
330
426
|
`[refacil-sdd-ai] La metodologia SDD-AI se actualizo automaticamente de v${localVersion} a v${latest}. Skills y hooks sincronizados.`,
|
|
331
427
|
);
|
|
332
428
|
} catch (_) {
|
|
333
|
-
// Upgrade failed — inform so the user can do it manually
|
|
334
429
|
console.log(
|
|
335
430
|
`[refacil-sdd-ai] Hay una nueva version disponible (v${localVersion} -> v${latest}) pero la actualizacion automatica fallo. ` +
|
|
336
431
|
`Informa al usuario que ejecute manualmente: npm update -g refacil-sdd-ai && refacil-sdd-ai update`,
|
|
@@ -421,6 +516,7 @@ function init() {
|
|
|
421
516
|
// Install skills
|
|
422
517
|
const count = installSkills();
|
|
423
518
|
console.log(` ${count} skills instaladas en .claude/skills/ y .cursor/skills/`);
|
|
519
|
+
writeRepoVersion(projectRoot, getPackageVersion());
|
|
424
520
|
|
|
425
521
|
// Create or update CLAUDE.md
|
|
426
522
|
if (createClaudeMd()) {
|
|
@@ -463,6 +559,7 @@ function update() {
|
|
|
463
559
|
console.log('\n refacil-sdd-ai: Actualizando skills...\n');
|
|
464
560
|
const count = installSkills();
|
|
465
561
|
console.log(` ${count} skills actualizadas en .claude/skills/ y .cursor/skills/`);
|
|
562
|
+
writeRepoVersion(projectRoot, getPackageVersion());
|
|
466
563
|
|
|
467
564
|
// Ensure hook is installed (for users updating from versions without hook)
|
|
468
565
|
if (installHook()) {
|
|
@@ -598,6 +695,487 @@ function showCompactStats() {
|
|
|
598
695
|
console.log('');
|
|
599
696
|
}
|
|
600
697
|
|
|
698
|
+
// --- Bus subcommands (refacil-bus: broker core — fase 1) ---
|
|
699
|
+
|
|
700
|
+
async function busStart() {
|
|
701
|
+
try {
|
|
702
|
+
const { info, started } = await busSpawn.ensureBroker(packageRoot);
|
|
703
|
+
if (started) {
|
|
704
|
+
console.log(` refacil-bus broker iniciado en 127.0.0.1:${info.port} (pid ${info.pid}).`);
|
|
705
|
+
} else {
|
|
706
|
+
console.log(` refacil-bus broker ya estaba activo en 127.0.0.1:${info.port} (pid ${info.pid}).`);
|
|
707
|
+
}
|
|
708
|
+
} catch (err) {
|
|
709
|
+
console.error(` No se pudo iniciar el broker: ${err.message}`);
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function busStop() {
|
|
715
|
+
const result = busSpawn.stopBroker();
|
|
716
|
+
if (result.stopped) {
|
|
717
|
+
console.log(` refacil-bus broker detenido (pid ${result.info.pid}).`);
|
|
718
|
+
} else if (result.reason === 'no-info') {
|
|
719
|
+
console.log(' refacil-bus broker no está corriendo.');
|
|
720
|
+
} else if (result.reason === 'not-alive') {
|
|
721
|
+
console.log(' refacil-bus broker no estaba vivo; info obsoleta limpiada.');
|
|
722
|
+
} else {
|
|
723
|
+
console.error(` No se pudo detener el broker: ${result.reason}`);
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async function busStatus() {
|
|
729
|
+
const status = await busSpawn.isBrokerAlive();
|
|
730
|
+
if (!status.alive) {
|
|
731
|
+
console.log(' refacil-bus broker: INACTIVO');
|
|
732
|
+
if (status.staleInfo) {
|
|
733
|
+
console.log(` (info obsoleta encontrada: pid ${status.staleInfo.pid}, puerto ${status.staleInfo.port})`);
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
const info = status.info;
|
|
738
|
+
const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
|
|
739
|
+
const uptimeMin = Math.floor(uptimeMs / 60000);
|
|
740
|
+
console.log(' refacil-bus broker: ACTIVO');
|
|
741
|
+
console.log(` host: 127.0.0.1`);
|
|
742
|
+
console.log(` puerto: ${info.port}`);
|
|
743
|
+
console.log(` pid: ${info.pid}`);
|
|
744
|
+
console.log(` iniciado: ${info.startedAt}`);
|
|
745
|
+
console.log(` uptime: ${uptimeMin} min`);
|
|
746
|
+
console.log(` info: ${busBroker.BUS_INFO_PATH}`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function busServe() {
|
|
750
|
+
// Invocado por spawn detached — ejecuta el broker en foreground.
|
|
751
|
+
busBroker.start().catch((err) => {
|
|
752
|
+
process.stderr.write(`Error arrancando broker: ${err.message}\n`);
|
|
753
|
+
process.exit(1);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function parseBusArgs(argv) {
|
|
758
|
+
const args = {};
|
|
759
|
+
for (let i = 0; i < argv.length; i++) {
|
|
760
|
+
const token = argv[i];
|
|
761
|
+
if (!token || !token.startsWith('--')) continue;
|
|
762
|
+
const key = token.slice(2);
|
|
763
|
+
const next = argv[i + 1];
|
|
764
|
+
if (next === undefined || next.startsWith('--')) {
|
|
765
|
+
args[key] = true;
|
|
766
|
+
} else {
|
|
767
|
+
args[key] = next;
|
|
768
|
+
i++;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return args;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
function defaultSessionName() {
|
|
775
|
+
return path.basename(process.cwd()) || 'sesion';
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function connectOrDie() {
|
|
779
|
+
try {
|
|
780
|
+
const { info } = await busSpawn.ensureBroker(packageRoot);
|
|
781
|
+
const ws = await busClient.connect(info.port);
|
|
782
|
+
return { ws, info };
|
|
783
|
+
} catch (err) {
|
|
784
|
+
console.error(` No se pudo conectar al bus: ${err.message}`);
|
|
785
|
+
process.exit(1);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function formatMessage(m) {
|
|
790
|
+
const target = m.to ? ` → @${m.to}` : '';
|
|
791
|
+
return ` [${m.ts}] ${m.from}${target} (${m.kind}): ${m.text}`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
async function busJoin(args) {
|
|
795
|
+
const session = args.session || defaultSessionName();
|
|
796
|
+
const room = args.room;
|
|
797
|
+
const repo = args.repo || process.cwd();
|
|
798
|
+
let intro = args.intro;
|
|
799
|
+
if (!intro) {
|
|
800
|
+
try {
|
|
801
|
+
intro = busPresenter.buildIntro({ repoDir: repo, session });
|
|
802
|
+
} catch (_) {
|
|
803
|
+
intro = `${session} se unió a la sala`;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (!room) {
|
|
807
|
+
console.error(' Uso: refacil-sdd-ai bus join --room <sala> [--session <s>] [--intro "..."]');
|
|
808
|
+
process.exit(1);
|
|
809
|
+
}
|
|
810
|
+
const { ws } = await connectOrDie();
|
|
811
|
+
const reply = await busClient.sendAndWait(
|
|
812
|
+
ws,
|
|
813
|
+
'join',
|
|
814
|
+
{ session, room, repo, intro },
|
|
815
|
+
(d) => d.type === 'system' && d.event === 'joined',
|
|
816
|
+
3000,
|
|
817
|
+
);
|
|
818
|
+
busClient.close(ws);
|
|
819
|
+
if (!reply) {
|
|
820
|
+
console.error(' Timeout uniéndose a la sala.');
|
|
821
|
+
process.exit(1);
|
|
822
|
+
}
|
|
823
|
+
const members = (reply.detail && reply.detail.members) || [];
|
|
824
|
+
console.log(` Unido a la sala "${room}" como "${session}".`);
|
|
825
|
+
console.log(` Miembros actuales: ${members.join(', ') || '(solo tú)'}`);
|
|
826
|
+
console.log(` Para consultarte: /refacil:ask @${session} "..."`);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
async function busLeave(args) {
|
|
830
|
+
const session = args.session || defaultSessionName();
|
|
831
|
+
const { ws } = await connectOrDie();
|
|
832
|
+
const reply = await busClient.sendAndWait(
|
|
833
|
+
ws,
|
|
834
|
+
'leave',
|
|
835
|
+
{ session },
|
|
836
|
+
(d) => d.type === 'system' && (d.event === 'left' || d.event === 'error'),
|
|
837
|
+
3000,
|
|
838
|
+
);
|
|
839
|
+
busClient.close(ws);
|
|
840
|
+
if (reply && reply.event === 'left') {
|
|
841
|
+
console.log(` "${session}" salió de la sala.`);
|
|
842
|
+
} else {
|
|
843
|
+
console.log(` "${session}" no estaba en ninguna sala.`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
async function busSay(args) {
|
|
848
|
+
const session = args.session || defaultSessionName();
|
|
849
|
+
const text = args.text;
|
|
850
|
+
if (!text) {
|
|
851
|
+
console.error(' Uso: refacil-sdd-ai bus say --text "..." [--session <s>]');
|
|
852
|
+
process.exit(1);
|
|
853
|
+
}
|
|
854
|
+
const { ws } = await connectOrDie();
|
|
855
|
+
const reply = await busClient.sendAndWait(
|
|
856
|
+
ws,
|
|
857
|
+
'say',
|
|
858
|
+
{ session, text },
|
|
859
|
+
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
860
|
+
3000,
|
|
861
|
+
);
|
|
862
|
+
busClient.close(ws);
|
|
863
|
+
if (reply && reply.event === 'sent') {
|
|
864
|
+
console.log(` Mensaje enviado (id ${reply.detail.id}).`);
|
|
865
|
+
} else {
|
|
866
|
+
const detail = (reply && reply.detail) || 'sin respuesta';
|
|
867
|
+
console.error(` No se pudo enviar: ${detail}`);
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async function busAsk(args) {
|
|
873
|
+
const session = args.session || defaultSessionName();
|
|
874
|
+
const to = args.to;
|
|
875
|
+
const text = args.text;
|
|
876
|
+
const waitSec = args.wait ? parseInt(args.wait, 10) : 0;
|
|
877
|
+
if (!to || !text) {
|
|
878
|
+
console.error(' Uso: refacil-sdd-ai bus ask --to <name> --text "..." [--wait N] [--session <s>]');
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
881
|
+
const { ws } = await connectOrDie();
|
|
882
|
+
const ack = await busClient.sendAndWait(
|
|
883
|
+
ws,
|
|
884
|
+
'ask',
|
|
885
|
+
{ session, to: to.replace(/^@/, ''), text },
|
|
886
|
+
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
887
|
+
3000,
|
|
888
|
+
);
|
|
889
|
+
if (!ack || ack.event !== 'sent') {
|
|
890
|
+
busClient.close(ws);
|
|
891
|
+
const detail = (ack && ack.detail) || 'sin respuesta';
|
|
892
|
+
console.error(` No se pudo enviar la pregunta: ${detail}`);
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
const correlationId = ack.detail.correlationId;
|
|
896
|
+
console.log(` Pregunta enviada a @${to.replace(/^@/, '')} (correlationId ${correlationId}).`);
|
|
897
|
+
|
|
898
|
+
if (waitSec > 0) {
|
|
899
|
+
console.log(` Esperando respuesta hasta ${waitSec}s...`);
|
|
900
|
+
const resp = await busClient.sendAndWait(
|
|
901
|
+
ws,
|
|
902
|
+
'ping',
|
|
903
|
+
{},
|
|
904
|
+
(d) => d.type === 'msg' && d.kind === 'reply' && d.correlationId === correlationId,
|
|
905
|
+
waitSec * 1000,
|
|
906
|
+
);
|
|
907
|
+
busClient.close(ws);
|
|
908
|
+
if (!resp) {
|
|
909
|
+
console.log(` Sin respuesta en ${waitSec}s. Usa /refacil:inbox más tarde para recuperarla.`);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
console.log(` Respuesta de @${resp.from}:`);
|
|
913
|
+
console.log(` ${resp.text}`);
|
|
914
|
+
} else {
|
|
915
|
+
busClient.close(ws);
|
|
916
|
+
console.log(' Usa /refacil:inbox para ver respuestas.');
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
async function busReply(args) {
|
|
921
|
+
const session = args.session || defaultSessionName();
|
|
922
|
+
const text = args.text;
|
|
923
|
+
const correlationId = args.correlation || null;
|
|
924
|
+
const to = args.to ? args.to.replace(/^@/, '') : null;
|
|
925
|
+
if (!text) {
|
|
926
|
+
console.error(' Uso: refacil-sdd-ai bus reply --text "..." [--to <name>] [--correlation <id>]');
|
|
927
|
+
process.exit(1);
|
|
928
|
+
}
|
|
929
|
+
const { ws } = await connectOrDie();
|
|
930
|
+
const reply = await busClient.sendAndWait(
|
|
931
|
+
ws,
|
|
932
|
+
'reply',
|
|
933
|
+
{ session, text, to, correlationId },
|
|
934
|
+
(d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
|
|
935
|
+
3000,
|
|
936
|
+
);
|
|
937
|
+
busClient.close(ws);
|
|
938
|
+
if (reply && reply.event === 'sent') {
|
|
939
|
+
console.log(` Respuesta enviada (id ${reply.detail.id}).`);
|
|
940
|
+
} else {
|
|
941
|
+
const detail = (reply && reply.detail) || 'sin respuesta';
|
|
942
|
+
console.error(` No se pudo responder: ${detail}`);
|
|
943
|
+
process.exit(1);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function busHistory(args) {
|
|
948
|
+
const session = args.session || defaultSessionName();
|
|
949
|
+
const n = args.n ? parseInt(args.n, 10) : 20;
|
|
950
|
+
const { ws } = await connectOrDie();
|
|
951
|
+
const reply = await busClient.sendAndWait(
|
|
952
|
+
ws,
|
|
953
|
+
'history',
|
|
954
|
+
{ session, n },
|
|
955
|
+
(d) => d.type === 'history',
|
|
956
|
+
3000,
|
|
957
|
+
);
|
|
958
|
+
busClient.close(ws);
|
|
959
|
+
if (!reply) {
|
|
960
|
+
console.log(' Sin historial.');
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
const msgs = reply.messages || [];
|
|
964
|
+
if (msgs.length === 0) {
|
|
965
|
+
console.log(' Sin historial.');
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
console.log(` Últimos ${msgs.length} mensajes:`);
|
|
969
|
+
for (const m of msgs) console.log(formatMessage(m));
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function busInbox(args) {
|
|
973
|
+
const session = args.session || defaultSessionName();
|
|
974
|
+
const { ws } = await connectOrDie();
|
|
975
|
+
const reply = await busClient.sendAndWait(
|
|
976
|
+
ws,
|
|
977
|
+
'inbox',
|
|
978
|
+
{ session },
|
|
979
|
+
(d) => d.type === 'inbox',
|
|
980
|
+
3000,
|
|
981
|
+
);
|
|
982
|
+
busClient.close(ws);
|
|
983
|
+
if (!reply) {
|
|
984
|
+
console.log(' Sin respuesta del broker.');
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const msgs = reply.messages || [];
|
|
988
|
+
if (msgs.length === 0) {
|
|
989
|
+
console.log(' Sin mensajes nuevos.');
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
console.log(` ${msgs.length} mensaje(s) nuevo(s):`);
|
|
993
|
+
for (const m of msgs) console.log(formatMessage(m));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function findFirstUnansweredAsk(messages, session) {
|
|
997
|
+
const asks = messages.filter((m) => m.kind === 'ask' && m.to === session);
|
|
998
|
+
for (const ask of asks) {
|
|
999
|
+
const hasReply = messages.some(
|
|
1000
|
+
(m) => m.kind === 'reply' && m.correlationId === ask.correlationId,
|
|
1001
|
+
);
|
|
1002
|
+
if (!hasReply) return ask;
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function printAttendQuestion(msg) {
|
|
1008
|
+
console.log(' Pregunta recibida del bus:');
|
|
1009
|
+
console.log(` de: @${msg.from}`);
|
|
1010
|
+
console.log(` correlationId: ${msg.correlationId || '(sin id)'}`);
|
|
1011
|
+
console.log(` texto: ${msg.text}`);
|
|
1012
|
+
console.log('');
|
|
1013
|
+
console.log(' Responde con: /refacil:reply "<respuesta>"');
|
|
1014
|
+
console.log(' Luego vuelve a ejecutar /refacil:attend para seguir escuchando.');
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
async function busAttend(args) {
|
|
1018
|
+
const session = args.session || defaultSessionName();
|
|
1019
|
+
const timeoutSec = args.timeout ? parseInt(args.timeout, 10) : 540;
|
|
1020
|
+
const { ws } = await connectOrDie();
|
|
1021
|
+
|
|
1022
|
+
// 1) Revisar preguntas pendientes en el historial antes de suscribirse al push.
|
|
1023
|
+
const hist = await busClient.sendAndWait(
|
|
1024
|
+
ws,
|
|
1025
|
+
'history',
|
|
1026
|
+
{ session, n: 50 },
|
|
1027
|
+
(d) => d.type === 'history',
|
|
1028
|
+
3000,
|
|
1029
|
+
);
|
|
1030
|
+
if (hist) {
|
|
1031
|
+
const pending = findFirstUnansweredAsk(hist.messages || [], session);
|
|
1032
|
+
if (pending) {
|
|
1033
|
+
busClient.close(ws);
|
|
1034
|
+
printAttendQuestion(pending);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// 2) Suscribirse y esperar push de ask dirigido a esta sesión.
|
|
1040
|
+
const result = await new Promise((resolve) => {
|
|
1041
|
+
let done = false;
|
|
1042
|
+
const finish = (v) => {
|
|
1043
|
+
if (done) return;
|
|
1044
|
+
done = true;
|
|
1045
|
+
clearTimeout(timer);
|
|
1046
|
+
ws.removeListener('message', onMessage);
|
|
1047
|
+
resolve(v);
|
|
1048
|
+
};
|
|
1049
|
+
const onMessage = (raw) => {
|
|
1050
|
+
let data;
|
|
1051
|
+
try { data = JSON.parse(raw.toString()); } catch (_) { return; }
|
|
1052
|
+
if (data.type === 'msg' && data.kind === 'ask' && data.to === session) {
|
|
1053
|
+
finish({ kind: 'message', msg: data });
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
ws.on('message', onMessage);
|
|
1057
|
+
const timer = setTimeout(() => finish({ kind: 'timeout' }), timeoutSec * 1000);
|
|
1058
|
+
busClient.send(ws, 'attend', { session });
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
busClient.close(ws);
|
|
1062
|
+
|
|
1063
|
+
if (result.kind === 'message') {
|
|
1064
|
+
printAttendQuestion(result.msg);
|
|
1065
|
+
} else {
|
|
1066
|
+
console.log(` Sin preguntas en ${timeoutSec}s. Re-ejecuta /refacil:attend para seguir escuchando.`);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function readPersistedSessions() {
|
|
1071
|
+
try {
|
|
1072
|
+
return JSON.parse(fs.readFileSync(busBroker.SESSIONS_PATH, 'utf8'));
|
|
1073
|
+
} catch (_) {
|
|
1074
|
+
return {};
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
async function busWatchCmd(positional, args) {
|
|
1079
|
+
const session = args.session || positional || null;
|
|
1080
|
+
let room = args.room || null;
|
|
1081
|
+
if (session && !room) {
|
|
1082
|
+
const persisted = readPersistedSessions();
|
|
1083
|
+
if (persisted[session] && persisted[session].room) {
|
|
1084
|
+
room = persisted[session].room;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (!session && !room) {
|
|
1088
|
+
console.error(' Uso: refacil-sdd-ai bus watch <session> [--room <sala>]');
|
|
1089
|
+
process.exit(1);
|
|
1090
|
+
}
|
|
1091
|
+
try {
|
|
1092
|
+
const { info } = await busSpawn.ensureBroker(packageRoot);
|
|
1093
|
+
await busWatch.start({ session, room, port: info.port });
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
console.error(` No se pudo iniciar el watch: ${err.message}`);
|
|
1096
|
+
process.exit(1);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
async function busRooms() {
|
|
1101
|
+
const { ws } = await connectOrDie();
|
|
1102
|
+
const reply = await busClient.sendAndWait(
|
|
1103
|
+
ws,
|
|
1104
|
+
'status',
|
|
1105
|
+
{},
|
|
1106
|
+
(d) => d.type === 'system' && d.event === 'status',
|
|
1107
|
+
3000,
|
|
1108
|
+
);
|
|
1109
|
+
busClient.close(ws);
|
|
1110
|
+
if (!reply) {
|
|
1111
|
+
console.log(' Sin respuesta del broker.');
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
const rooms = (reply.detail && reply.detail.rooms) || {};
|
|
1115
|
+
const names = Object.keys(rooms);
|
|
1116
|
+
if (names.length === 0) {
|
|
1117
|
+
console.log(' No hay salas activas.');
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
console.log(' Salas activas:');
|
|
1121
|
+
for (const name of names) {
|
|
1122
|
+
const members = rooms[name] || [];
|
|
1123
|
+
console.log(` ${name} (${members.length}): ${members.join(', ')}`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function handleBusSubcommand(sub) {
|
|
1128
|
+
const rest = process.argv.slice(4);
|
|
1129
|
+
const positional = rest.length > 0 && !rest[0].startsWith('--') ? rest[0] : null;
|
|
1130
|
+
const args = parseBusArgs(rest);
|
|
1131
|
+
switch (sub) {
|
|
1132
|
+
case 'start':
|
|
1133
|
+
busStart();
|
|
1134
|
+
break;
|
|
1135
|
+
case 'stop':
|
|
1136
|
+
busStop();
|
|
1137
|
+
break;
|
|
1138
|
+
case 'status':
|
|
1139
|
+
busStatus();
|
|
1140
|
+
break;
|
|
1141
|
+
case 'serve':
|
|
1142
|
+
busServe();
|
|
1143
|
+
break;
|
|
1144
|
+
case 'join':
|
|
1145
|
+
busJoin(args);
|
|
1146
|
+
break;
|
|
1147
|
+
case 'leave':
|
|
1148
|
+
busLeave(args);
|
|
1149
|
+
break;
|
|
1150
|
+
case 'say':
|
|
1151
|
+
busSay(args);
|
|
1152
|
+
break;
|
|
1153
|
+
case 'ask':
|
|
1154
|
+
busAsk(args);
|
|
1155
|
+
break;
|
|
1156
|
+
case 'reply':
|
|
1157
|
+
busReply(args);
|
|
1158
|
+
break;
|
|
1159
|
+
case 'history':
|
|
1160
|
+
busHistory(args);
|
|
1161
|
+
break;
|
|
1162
|
+
case 'inbox':
|
|
1163
|
+
busInbox(args);
|
|
1164
|
+
break;
|
|
1165
|
+
case 'rooms':
|
|
1166
|
+
busRooms();
|
|
1167
|
+
break;
|
|
1168
|
+
case 'watch':
|
|
1169
|
+
busWatchCmd(positional, args);
|
|
1170
|
+
break;
|
|
1171
|
+
case 'attend':
|
|
1172
|
+
busAttend(args);
|
|
1173
|
+
break;
|
|
1174
|
+
default:
|
|
1175
|
+
console.log('Uso: refacil-sdd-ai bus <start|stop|status|serve|join|leave|say|ask|reply|history|inbox|rooms|watch|attend>');
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
601
1179
|
function help() {
|
|
602
1180
|
console.log(`
|
|
603
1181
|
refacil-sdd-ai — Metodologia SDD-AI con OpenSpec
|
|
@@ -613,6 +1191,21 @@ function help() {
|
|
|
613
1191
|
compact disable - Desactiva el rewrite temporalmente
|
|
614
1192
|
compact enable - Re-activa el rewrite
|
|
615
1193
|
compact clear-log - Borra el log historico
|
|
1194
|
+
bus Subcomandos del chat room entre agentes (refacil-bus):
|
|
1195
|
+
bus start - Arranca el broker local (auto-spawn detached)
|
|
1196
|
+
bus stop - Detiene el broker
|
|
1197
|
+
bus status - Muestra puerto, pid, uptime del broker
|
|
1198
|
+
bus serve - (interno) Ejecuta el broker en foreground
|
|
1199
|
+
bus join --room <sala> [--session <s>] [--intro "..."]
|
|
1200
|
+
bus leave [--session <s>]
|
|
1201
|
+
bus say --text "..." [--session <s>]
|
|
1202
|
+
bus ask --to <name> --text "..." [--wait N] [--session <s>]
|
|
1203
|
+
bus reply --text "..." [--correlation <id>] [--to <name>]
|
|
1204
|
+
bus history [--n N] [--session <s>]
|
|
1205
|
+
bus inbox [--session <s>]
|
|
1206
|
+
bus rooms
|
|
1207
|
+
bus watch <session> [--room <sala>] (panel en vivo, sin tokens)
|
|
1208
|
+
bus attend [--timeout N] (escucha preguntas dirigidas)
|
|
616
1209
|
clean Elimina skills y remueve hooks SDD-AI de .claude/settings.json
|
|
617
1210
|
help Muestra esta ayuda
|
|
618
1211
|
|
|
@@ -651,6 +1244,9 @@ switch (command) {
|
|
|
651
1244
|
case 'compact':
|
|
652
1245
|
handleCompactSubcommand(process.argv[3]);
|
|
653
1246
|
break;
|
|
1247
|
+
case 'bus':
|
|
1248
|
+
handleBusSubcommand(process.argv[3]);
|
|
1249
|
+
break;
|
|
654
1250
|
case 'clean':
|
|
655
1251
|
clean();
|
|
656
1252
|
break;
|