refacil-sdd-ai 2.6.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, '..');
@@ -535,31 +547,548 @@ function handleCompactSubcommand(sub) {
535
547
 
536
548
  function showCompactStats() {
537
549
  const s = compactTelemetry.stats();
538
- if (s.totalRewrites === 0) {
539
- console.log('\n No hay rewrites registrados todavia. El hook compact-bash aun no se ha disparado o esta deshabilitado.\n');
550
+ if (s.totalEvents === 0) {
551
+ console.log('\n No hay eventos registrados todavia. Ejecuta comandos Bash para generar telemetria de compactacion.\n');
540
552
  return;
541
553
  }
542
554
 
543
- const sorted = Object.entries(s.byRule).sort((a, b) => b[1].saved - a[1].saved);
555
+ const sortedRewrites = Object.entries(s.byRule)
556
+ .filter(([, data]) => data.rewriteCount > 0)
557
+ .sort((a, b) => b[1].rewriteSaved - a[1].rewriteSaved);
558
+ const sortedAlreadyCompact = Object.entries(s.byRule)
559
+ .filter(([, data]) => data.alreadyCompactCount > 0)
560
+ .sort((a, b) => b[1].alreadyCompactPotential - a[1].alreadyCompactPotential);
561
+
562
+ console.log(`\n compact-bash stats\n`);
563
+ console.log(` Rewrites por hook: ${s.totalRewrites}`);
564
+ console.log(` Comandos ya compactos detectados (skill/agente): ${s.totalAlreadyCompact}\n`);
565
+
566
+ if (sortedRewrites.length > 0) {
567
+ console.log(' Ahorro aplicado por hook (rewrite):');
568
+ for (const [id, data] of sortedRewrites) {
569
+ const kTokens = (data.rewriteSaved / 1000).toFixed(1);
570
+ console.log(
571
+ ` ${id.padEnd(18)} ${String(data.rewriteCount).padStart(6)} rewrites ~${kTokens.padStart(7)}k tokens`,
572
+ );
573
+ }
574
+ const totalHookK = (s.totalSaved / 1000).toFixed(1);
575
+ const hookUsd = ((s.totalSaved / 1_000_000) * 3).toFixed(2);
576
+ console.log(` ${'-'.repeat(62)}`);
577
+ console.log(
578
+ ` ${'Total hook'.padEnd(18)} ${String(s.totalRewrites).padStart(6)} rewrites ~${totalHookK.padStart(7)}k tokens (~$${hookUsd} USD)`,
579
+ );
580
+ console.log('');
581
+ }
544
582
 
545
- console.log(`\n compact-bash stats (${s.totalRewrites} rewrites en total)\n`);
546
- for (const [id, data] of sorted) {
547
- const kTokens = (data.saved / 1000).toFixed(1);
583
+ if (sortedAlreadyCompact.length > 0) {
584
+ console.log(' Ahorro potencial ya capturado por skill/agente (sin rewrite):');
585
+ for (const [id, data] of sortedAlreadyCompact) {
586
+ const kTokens = (data.alreadyCompactPotential / 1000).toFixed(1);
587
+ console.log(
588
+ ` ${id.padEnd(18)} ${String(data.alreadyCompactCount).padStart(6)} eventos ~${kTokens.padStart(7)}k tokens potenciales`,
589
+ );
590
+ }
591
+ const totalAgentK = (s.totalAlreadyCompactPotential / 1000).toFixed(1);
592
+ const agentUsd = ((s.totalAlreadyCompactPotential / 1_000_000) * 3).toFixed(2);
593
+ console.log(` ${'-'.repeat(62)}`);
548
594
  console.log(
549
- ` ${id.padEnd(18)} ${String(data.count).padStart(6)} rewrites ~${kTokens.padStart(7)}k tokens estimados`,
595
+ ` ${'Total skill'.padEnd(18)} ${String(s.totalAlreadyCompact).padStart(6)} eventos ~${totalAgentK.padStart(7)}k tokens (~$${agentUsd} USD)`,
550
596
  );
597
+ console.log('');
551
598
  }
552
- const totalK = (s.totalSaved / 1000).toFixed(1);
553
- const costUsd = ((s.totalSaved / 1_000_000) * 3).toFixed(2);
599
+
600
+ const totalK = (s.totalObservedPotential / 1000).toFixed(1);
601
+ const totalUsd = ((s.totalObservedPotential / 1_000_000) * 3).toFixed(2);
554
602
  console.log(` ${'-'.repeat(62)}`);
555
603
  console.log(
556
- ` ${'Total'.padEnd(18)} ${String(s.totalRewrites).padStart(6)} rewrites ~${totalK.padStart(7)}k tokens (~$${costUsd} USD, Sonnet input)`,
604
+ ` ${'Total observado'.padEnd(18)} ${String(s.totalEvents).padStart(6)} eventos ~${totalK.padStart(7)}k tokens (~$${totalUsd} USD, Sonnet input)`,
557
605
  );
558
606
  console.log(`\n Log: ${compactTelemetry.LOG_PATH}`);
559
607
  if (compactTelemetry.isDisabled()) {
560
- console.log(' Estado: DESHABILITADO (no se registran nuevos rewrites)');
608
+ console.log(' Estado: DESHABILITADO (no se registran nuevos eventos)');
609
+ }
610
+ console.log('');
611
+ }
612
+
613
+ // --- Bus subcommands (refacil-bus: broker core — fase 1) ---
614
+
615
+ async function busStart() {
616
+ try {
617
+ const { info, started } = await busSpawn.ensureBroker(packageRoot);
618
+ if (started) {
619
+ console.log(` refacil-bus broker iniciado en 127.0.0.1:${info.port} (pid ${info.pid}).`);
620
+ } else {
621
+ console.log(` refacil-bus broker ya estaba activo en 127.0.0.1:${info.port} (pid ${info.pid}).`);
622
+ }
623
+ } catch (err) {
624
+ console.error(` No se pudo iniciar el broker: ${err.message}`);
625
+ process.exit(1);
626
+ }
627
+ }
628
+
629
+ function busStop() {
630
+ const result = busSpawn.stopBroker();
631
+ if (result.stopped) {
632
+ console.log(` refacil-bus broker detenido (pid ${result.info.pid}).`);
633
+ } else if (result.reason === 'no-info') {
634
+ console.log(' refacil-bus broker no está corriendo.');
635
+ } else if (result.reason === 'not-alive') {
636
+ console.log(' refacil-bus broker no estaba vivo; info obsoleta limpiada.');
637
+ } else {
638
+ console.error(` No se pudo detener el broker: ${result.reason}`);
639
+ process.exit(1);
640
+ }
641
+ }
642
+
643
+ async function busStatus() {
644
+ const status = await busSpawn.isBrokerAlive();
645
+ if (!status.alive) {
646
+ console.log(' refacil-bus broker: INACTIVO');
647
+ if (status.staleInfo) {
648
+ console.log(` (info obsoleta encontrada: pid ${status.staleInfo.pid}, puerto ${status.staleInfo.port})`);
649
+ }
650
+ return;
651
+ }
652
+ const info = status.info;
653
+ const uptimeMs = Date.now() - new Date(info.startedAt).getTime();
654
+ const uptimeMin = Math.floor(uptimeMs / 60000);
655
+ console.log(' refacil-bus broker: ACTIVO');
656
+ console.log(` host: 127.0.0.1`);
657
+ console.log(` puerto: ${info.port}`);
658
+ console.log(` pid: ${info.pid}`);
659
+ console.log(` iniciado: ${info.startedAt}`);
660
+ console.log(` uptime: ${uptimeMin} min`);
661
+ console.log(` info: ${busBroker.BUS_INFO_PATH}`);
662
+ }
663
+
664
+ function busServe() {
665
+ // Invocado por spawn detached — ejecuta el broker en foreground.
666
+ busBroker.start().catch((err) => {
667
+ process.stderr.write(`Error arrancando broker: ${err.message}\n`);
668
+ process.exit(1);
669
+ });
670
+ }
671
+
672
+ function parseBusArgs(argv) {
673
+ const args = {};
674
+ for (let i = 0; i < argv.length; i++) {
675
+ const token = argv[i];
676
+ if (!token || !token.startsWith('--')) continue;
677
+ const key = token.slice(2);
678
+ const next = argv[i + 1];
679
+ if (next === undefined || next.startsWith('--')) {
680
+ args[key] = true;
681
+ } else {
682
+ args[key] = next;
683
+ i++;
684
+ }
685
+ }
686
+ return args;
687
+ }
688
+
689
+ function defaultSessionName() {
690
+ return path.basename(process.cwd()) || 'sesion';
691
+ }
692
+
693
+ async function connectOrDie() {
694
+ try {
695
+ const { info } = await busSpawn.ensureBroker(packageRoot);
696
+ const ws = await busClient.connect(info.port);
697
+ return { ws, info };
698
+ } catch (err) {
699
+ console.error(` No se pudo conectar al bus: ${err.message}`);
700
+ process.exit(1);
701
+ }
702
+ }
703
+
704
+ function formatMessage(m) {
705
+ const target = m.to ? ` → @${m.to}` : '';
706
+ return ` [${m.ts}] ${m.from}${target} (${m.kind}): ${m.text}`;
707
+ }
708
+
709
+ async function busJoin(args) {
710
+ const session = args.session || defaultSessionName();
711
+ const room = args.room;
712
+ const repo = args.repo || process.cwd();
713
+ let intro = args.intro;
714
+ if (!intro) {
715
+ try {
716
+ intro = busPresenter.buildIntro({ repoDir: repo, session });
717
+ } catch (_) {
718
+ intro = `${session} se unió a la sala`;
719
+ }
720
+ }
721
+ if (!room) {
722
+ console.error(' Uso: refacil-sdd-ai bus join --room <sala> [--session <s>] [--intro "..."]');
723
+ process.exit(1);
724
+ }
725
+ const { ws } = await connectOrDie();
726
+ const reply = await busClient.sendAndWait(
727
+ ws,
728
+ 'join',
729
+ { session, room, repo, intro },
730
+ (d) => d.type === 'system' && d.event === 'joined',
731
+ 3000,
732
+ );
733
+ busClient.close(ws);
734
+ if (!reply) {
735
+ console.error(' Timeout uniéndose a la sala.');
736
+ process.exit(1);
737
+ }
738
+ const members = (reply.detail && reply.detail.members) || [];
739
+ console.log(` Unido a la sala "${room}" como "${session}".`);
740
+ console.log(` Miembros actuales: ${members.join(', ') || '(solo tú)'}`);
741
+ console.log(` Para consultarte: /refacil:ask @${session} "..."`);
742
+ }
743
+
744
+ async function busLeave(args) {
745
+ const session = args.session || defaultSessionName();
746
+ const { ws } = await connectOrDie();
747
+ const reply = await busClient.sendAndWait(
748
+ ws,
749
+ 'leave',
750
+ { session },
751
+ (d) => d.type === 'system' && (d.event === 'left' || d.event === 'error'),
752
+ 3000,
753
+ );
754
+ busClient.close(ws);
755
+ if (reply && reply.event === 'left') {
756
+ console.log(` "${session}" salió de la sala.`);
757
+ } else {
758
+ console.log(` "${session}" no estaba en ninguna sala.`);
759
+ }
760
+ }
761
+
762
+ async function busSay(args) {
763
+ const session = args.session || defaultSessionName();
764
+ const text = args.text;
765
+ if (!text) {
766
+ console.error(' Uso: refacil-sdd-ai bus say --text "..." [--session <s>]');
767
+ process.exit(1);
768
+ }
769
+ const { ws } = await connectOrDie();
770
+ const reply = await busClient.sendAndWait(
771
+ ws,
772
+ 'say',
773
+ { session, text },
774
+ (d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
775
+ 3000,
776
+ );
777
+ busClient.close(ws);
778
+ if (reply && reply.event === 'sent') {
779
+ console.log(` Mensaje enviado (id ${reply.detail.id}).`);
780
+ } else {
781
+ const detail = (reply && reply.detail) || 'sin respuesta';
782
+ console.error(` No se pudo enviar: ${detail}`);
783
+ process.exit(1);
784
+ }
785
+ }
786
+
787
+ async function busAsk(args) {
788
+ const session = args.session || defaultSessionName();
789
+ const to = args.to;
790
+ const text = args.text;
791
+ const waitSec = args.wait ? parseInt(args.wait, 10) : 0;
792
+ if (!to || !text) {
793
+ console.error(' Uso: refacil-sdd-ai bus ask --to <name> --text "..." [--wait N] [--session <s>]');
794
+ process.exit(1);
795
+ }
796
+ const { ws } = await connectOrDie();
797
+ const ack = await busClient.sendAndWait(
798
+ ws,
799
+ 'ask',
800
+ { session, to: to.replace(/^@/, ''), text },
801
+ (d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
802
+ 3000,
803
+ );
804
+ if (!ack || ack.event !== 'sent') {
805
+ busClient.close(ws);
806
+ const detail = (ack && ack.detail) || 'sin respuesta';
807
+ console.error(` No se pudo enviar la pregunta: ${detail}`);
808
+ process.exit(1);
809
+ }
810
+ const correlationId = ack.detail.correlationId;
811
+ console.log(` Pregunta enviada a @${to.replace(/^@/, '')} (correlationId ${correlationId}).`);
812
+
813
+ if (waitSec > 0) {
814
+ console.log(` Esperando respuesta hasta ${waitSec}s...`);
815
+ const resp = await busClient.sendAndWait(
816
+ ws,
817
+ 'ping',
818
+ {},
819
+ (d) => d.type === 'msg' && d.kind === 'reply' && d.correlationId === correlationId,
820
+ waitSec * 1000,
821
+ );
822
+ busClient.close(ws);
823
+ if (!resp) {
824
+ console.log(` Sin respuesta en ${waitSec}s. Usa /refacil:inbox más tarde para recuperarla.`);
825
+ return;
826
+ }
827
+ console.log(` Respuesta de @${resp.from}:`);
828
+ console.log(` ${resp.text}`);
829
+ } else {
830
+ busClient.close(ws);
831
+ console.log(' Usa /refacil:inbox para ver respuestas.');
832
+ }
833
+ }
834
+
835
+ async function busReply(args) {
836
+ const session = args.session || defaultSessionName();
837
+ const text = args.text;
838
+ const correlationId = args.correlation || null;
839
+ const to = args.to ? args.to.replace(/^@/, '') : null;
840
+ if (!text) {
841
+ console.error(' Uso: refacil-sdd-ai bus reply --text "..." [--to <name>] [--correlation <id>]');
842
+ process.exit(1);
561
843
  }
844
+ const { ws } = await connectOrDie();
845
+ const reply = await busClient.sendAndWait(
846
+ ws,
847
+ 'reply',
848
+ { session, text, to, correlationId },
849
+ (d) => d.type === 'system' && (d.event === 'sent' || d.event === 'error'),
850
+ 3000,
851
+ );
852
+ busClient.close(ws);
853
+ if (reply && reply.event === 'sent') {
854
+ console.log(` Respuesta enviada (id ${reply.detail.id}).`);
855
+ } else {
856
+ const detail = (reply && reply.detail) || 'sin respuesta';
857
+ console.error(` No se pudo responder: ${detail}`);
858
+ process.exit(1);
859
+ }
860
+ }
861
+
862
+ async function busHistory(args) {
863
+ const session = args.session || defaultSessionName();
864
+ const n = args.n ? parseInt(args.n, 10) : 20;
865
+ const { ws } = await connectOrDie();
866
+ const reply = await busClient.sendAndWait(
867
+ ws,
868
+ 'history',
869
+ { session, n },
870
+ (d) => d.type === 'history',
871
+ 3000,
872
+ );
873
+ busClient.close(ws);
874
+ if (!reply) {
875
+ console.log(' Sin historial.');
876
+ return;
877
+ }
878
+ const msgs = reply.messages || [];
879
+ if (msgs.length === 0) {
880
+ console.log(' Sin historial.');
881
+ return;
882
+ }
883
+ console.log(` Últimos ${msgs.length} mensajes:`);
884
+ for (const m of msgs) console.log(formatMessage(m));
885
+ }
886
+
887
+ async function busInbox(args) {
888
+ const session = args.session || defaultSessionName();
889
+ const { ws } = await connectOrDie();
890
+ const reply = await busClient.sendAndWait(
891
+ ws,
892
+ 'inbox',
893
+ { session },
894
+ (d) => d.type === 'inbox',
895
+ 3000,
896
+ );
897
+ busClient.close(ws);
898
+ if (!reply) {
899
+ console.log(' Sin respuesta del broker.');
900
+ return;
901
+ }
902
+ const msgs = reply.messages || [];
903
+ if (msgs.length === 0) {
904
+ console.log(' Sin mensajes nuevos.');
905
+ return;
906
+ }
907
+ console.log(` ${msgs.length} mensaje(s) nuevo(s):`);
908
+ for (const m of msgs) console.log(formatMessage(m));
909
+ }
910
+
911
+ function findFirstUnansweredAsk(messages, session) {
912
+ const asks = messages.filter((m) => m.kind === 'ask' && m.to === session);
913
+ for (const ask of asks) {
914
+ const hasReply = messages.some(
915
+ (m) => m.kind === 'reply' && m.correlationId === ask.correlationId,
916
+ );
917
+ if (!hasReply) return ask;
918
+ }
919
+ return null;
920
+ }
921
+
922
+ function printAttendQuestion(msg) {
923
+ console.log(' Pregunta recibida del bus:');
924
+ console.log(` de: @${msg.from}`);
925
+ console.log(` correlationId: ${msg.correlationId || '(sin id)'}`);
926
+ console.log(` texto: ${msg.text}`);
562
927
  console.log('');
928
+ console.log(' Responde con: /refacil:reply "<respuesta>"');
929
+ console.log(' Luego vuelve a ejecutar /refacil:attend para seguir escuchando.');
930
+ }
931
+
932
+ async function busAttend(args) {
933
+ const session = args.session || defaultSessionName();
934
+ const timeoutSec = args.timeout ? parseInt(args.timeout, 10) : 540;
935
+ const { ws } = await connectOrDie();
936
+
937
+ // 1) Revisar preguntas pendientes en el historial antes de suscribirse al push.
938
+ const hist = await busClient.sendAndWait(
939
+ ws,
940
+ 'history',
941
+ { session, n: 50 },
942
+ (d) => d.type === 'history',
943
+ 3000,
944
+ );
945
+ if (hist) {
946
+ const pending = findFirstUnansweredAsk(hist.messages || [], session);
947
+ if (pending) {
948
+ busClient.close(ws);
949
+ printAttendQuestion(pending);
950
+ return;
951
+ }
952
+ }
953
+
954
+ // 2) Suscribirse y esperar push de ask dirigido a esta sesión.
955
+ const result = await new Promise((resolve) => {
956
+ let done = false;
957
+ const finish = (v) => {
958
+ if (done) return;
959
+ done = true;
960
+ clearTimeout(timer);
961
+ ws.removeListener('message', onMessage);
962
+ resolve(v);
963
+ };
964
+ const onMessage = (raw) => {
965
+ let data;
966
+ try { data = JSON.parse(raw.toString()); } catch (_) { return; }
967
+ if (data.type === 'msg' && data.kind === 'ask' && data.to === session) {
968
+ finish({ kind: 'message', msg: data });
969
+ }
970
+ };
971
+ ws.on('message', onMessage);
972
+ const timer = setTimeout(() => finish({ kind: 'timeout' }), timeoutSec * 1000);
973
+ busClient.send(ws, 'attend', { session });
974
+ });
975
+
976
+ busClient.close(ws);
977
+
978
+ if (result.kind === 'message') {
979
+ printAttendQuestion(result.msg);
980
+ } else {
981
+ console.log(` Sin preguntas en ${timeoutSec}s. Re-ejecuta /refacil:attend para seguir escuchando.`);
982
+ }
983
+ }
984
+
985
+ function readPersistedSessions() {
986
+ try {
987
+ return JSON.parse(fs.readFileSync(busBroker.SESSIONS_PATH, 'utf8'));
988
+ } catch (_) {
989
+ return {};
990
+ }
991
+ }
992
+
993
+ async function busWatchCmd(positional, args) {
994
+ const session = args.session || positional || null;
995
+ let room = args.room || null;
996
+ if (session && !room) {
997
+ const persisted = readPersistedSessions();
998
+ if (persisted[session] && persisted[session].room) {
999
+ room = persisted[session].room;
1000
+ }
1001
+ }
1002
+ if (!session && !room) {
1003
+ console.error(' Uso: refacil-sdd-ai bus watch <session> [--room <sala>]');
1004
+ process.exit(1);
1005
+ }
1006
+ try {
1007
+ const { info } = await busSpawn.ensureBroker(packageRoot);
1008
+ await busWatch.start({ session, room, port: info.port });
1009
+ } catch (err) {
1010
+ console.error(` No se pudo iniciar el watch: ${err.message}`);
1011
+ process.exit(1);
1012
+ }
1013
+ }
1014
+
1015
+ async function busRooms() {
1016
+ const { ws } = await connectOrDie();
1017
+ const reply = await busClient.sendAndWait(
1018
+ ws,
1019
+ 'status',
1020
+ {},
1021
+ (d) => d.type === 'system' && d.event === 'status',
1022
+ 3000,
1023
+ );
1024
+ busClient.close(ws);
1025
+ if (!reply) {
1026
+ console.log(' Sin respuesta del broker.');
1027
+ return;
1028
+ }
1029
+ const rooms = (reply.detail && reply.detail.rooms) || {};
1030
+ const names = Object.keys(rooms);
1031
+ if (names.length === 0) {
1032
+ console.log(' No hay salas activas.');
1033
+ return;
1034
+ }
1035
+ console.log(' Salas activas:');
1036
+ for (const name of names) {
1037
+ const members = rooms[name] || [];
1038
+ console.log(` ${name} (${members.length}): ${members.join(', ')}`);
1039
+ }
1040
+ }
1041
+
1042
+ function handleBusSubcommand(sub) {
1043
+ const rest = process.argv.slice(4);
1044
+ const positional = rest.length > 0 && !rest[0].startsWith('--') ? rest[0] : null;
1045
+ const args = parseBusArgs(rest);
1046
+ switch (sub) {
1047
+ case 'start':
1048
+ busStart();
1049
+ break;
1050
+ case 'stop':
1051
+ busStop();
1052
+ break;
1053
+ case 'status':
1054
+ busStatus();
1055
+ break;
1056
+ case 'serve':
1057
+ busServe();
1058
+ break;
1059
+ case 'join':
1060
+ busJoin(args);
1061
+ break;
1062
+ case 'leave':
1063
+ busLeave(args);
1064
+ break;
1065
+ case 'say':
1066
+ busSay(args);
1067
+ break;
1068
+ case 'ask':
1069
+ busAsk(args);
1070
+ break;
1071
+ case 'reply':
1072
+ busReply(args);
1073
+ break;
1074
+ case 'history':
1075
+ busHistory(args);
1076
+ break;
1077
+ case 'inbox':
1078
+ busInbox(args);
1079
+ break;
1080
+ case 'rooms':
1081
+ busRooms();
1082
+ break;
1083
+ case 'watch':
1084
+ busWatchCmd(positional, args);
1085
+ break;
1086
+ case 'attend':
1087
+ busAttend(args);
1088
+ break;
1089
+ default:
1090
+ console.log('Uso: refacil-sdd-ai bus <start|stop|status|serve|join|leave|say|ask|reply|history|inbox|rooms|watch|attend>');
1091
+ }
563
1092
  }
564
1093
 
565
1094
  function help() {
@@ -573,10 +1102,25 @@ function help() {
573
1102
  check-review Verifica que el review se haya completado (usado por hook PreToolUse)
574
1103
  compact-bash Reescribe comandos Bash bare para reducir tokens (usado por hook PreToolUse)
575
1104
  compact Subcomandos del hook compact-bash:
576
- compact stats - Estadisticas de rewrites y ahorro estimado
1105
+ compact stats - Estadisticas completas (hook + ya-compacto) y ahorro estimado
577
1106
  compact disable - Desactiva el rewrite temporalmente
578
1107
  compact enable - Re-activa el rewrite
579
1108
  compact clear-log - Borra el log historico
1109
+ bus Subcomandos del chat room entre agentes (refacil-bus):
1110
+ bus start - Arranca el broker local (auto-spawn detached)
1111
+ bus stop - Detiene el broker
1112
+ bus status - Muestra puerto, pid, uptime del broker
1113
+ bus serve - (interno) Ejecuta el broker en foreground
1114
+ bus join --room <sala> [--session <s>] [--intro "..."]
1115
+ bus leave [--session <s>]
1116
+ bus say --text "..." [--session <s>]
1117
+ bus ask --to <name> --text "..." [--wait N] [--session <s>]
1118
+ bus reply --text "..." [--correlation <id>] [--to <name>]
1119
+ bus history [--n N] [--session <s>]
1120
+ bus inbox [--session <s>]
1121
+ bus rooms
1122
+ bus watch <session> [--room <sala>] (panel en vivo, sin tokens)
1123
+ bus attend [--timeout N] (escucha preguntas dirigidas)
580
1124
  clean Elimina skills y remueve hooks SDD-AI de .claude/settings.json
581
1125
  help Muestra esta ayuda
582
1126
 
@@ -615,6 +1159,9 @@ switch (command) {
615
1159
  case 'compact':
616
1160
  handleCompactSubcommand(process.argv[3]);
617
1161
  break;
1162
+ case 'bus':
1163
+ handleBusSubcommand(process.argv[3]);
1164
+ break;
618
1165
  case 'clean':
619
1166
  clean();
620
1167
  break;