@saulwade/swl-ses 1.4.0 β 1.4.2
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/CLAUDE.md +4 -3
- package/README.md +15 -14
- package/agentes/nemesis-auditor-swl.md +161 -0
- package/bin/swl-mcp-server.js +187 -187
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +122 -0
- package/comandos/swl/salud.md +34 -0
- package/comandos/swl/verificar.md +45 -0
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/web-fetcher-routing/SKILL.md +75 -0
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -0
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +41 -6
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +30 -9
- package/package.json +2 -2
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +10 -2
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/audit-tools/audit-history.js +330 -0
- package/scripts/audit-tools/bundle-tracker.js +290 -0
- package/scripts/audit-tools/canary-monitor.js +352 -0
- package/scripts/audit-tools/code-profiler.js +605 -0
- package/scripts/audit-tools/dep-doctor.js +320 -0
- package/scripts/audit-tools/env-validator.js +206 -0
- package/scripts/audit-tools/lib/fs-walk.js +48 -0
- package/scripts/audit-tools/lib/output.js +23 -0
- package/scripts/audit-tools/migration-checker.js +392 -0
- package/scripts/audit-tools/pentest-scanner.js +1436 -0
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -13,6 +13,7 @@ Eres el coordinador de verificaciΓ³n SWL. Tu trabajo es asegurar la calidad del
|
|
|
13
13
|
| Flag | Efecto |
|
|
14
14
|
|------|--------|
|
|
15
15
|
| (sin flag) | VerificaciΓ³n estΓ‘ndar: tests + linter + revisor-codigo-swl + revisor-seguridad-swl |
|
|
16
|
+
| `--full` | AΓ±ade 4 audits ejecutables en paralelo al flujo estΓ‘ndar: `code-profiler.js`, `pentest-scanner.js`, `dep-doctor.js` y secret-scanner. Produce un scorecard agregado 0-100 con thresholds de aprobaciΓ³n. Ver secciΓ³n "Modo `--full`". |
|
|
16
17
|
| `--ultra` | AΓ±ade tercera pasada de **revisiΓ³n profunda** con Opus 4.7 sobre los hallazgos de los dos revisores. Consolida, prioriza, detecta contradicciones entre reviewers y genera plan de acciΓ³n. Ideal para fases crΓticas (auth, pagos, migraciones de schema, endpoints pΓΊblicos). |
|
|
17
18
|
| `--solo-codigo` | Ejecuta solo revisor-codigo-swl (salta seguridad). Usar solo para refactors internos sin impacto externo. |
|
|
18
19
|
| `--solo-seguridad` | Ejecuta solo revisor-seguridad-swl (salta cΓ³digo). Usar para auditorΓas de seguridad focalizadas. |
|
|
@@ -576,6 +577,48 @@ El comando:
|
|
|
576
577
|
repetΓa 2+ veces por sesiΓ³n con alta variaciΓ³n sobre el alcance exacto. Formalizar
|
|
577
578
|
con flag elimina los 2 mensajes de fricciΓ³n y deja el scope auditable en el comando.
|
|
578
579
|
|
|
580
|
+
## Modo `--full` β Parallel Scorecard
|
|
581
|
+
|
|
582
|
+
Al pasar el flag `--full`, /swl:verificar lanza en paralelo 4 audits ejecutables ademΓ‘s del flujo secuencial estΓ‘ndar (revisor-codigo-swl + revisor-seguridad-swl). Cada audit produce score 0-100 y findings JSON. El scorecard final agrega resultados con thresholds estΓ‘ndar.
|
|
583
|
+
|
|
584
|
+
### Audits paralelos invocados
|
|
585
|
+
|
|
586
|
+
| Tool | Detecta | Comando |
|
|
587
|
+
|---|---|---|
|
|
588
|
+
| `code-profiler.js` | N+1 queries, sync I/O en async, memory leaks, ReDoS, queries unbounded | `node scripts/audit-tools/code-profiler.js .` |
|
|
589
|
+
| `pentest-scanner.js` | XSS, SQLi, SSTI, CORS misconfig, JWT vulnerabilities, prototype pollution | `node scripts/audit-tools/pentest-scanner.js <target>` |
|
|
590
|
+
| `dep-doctor.js` | Deps no usadas, outdated, heavy deps con alternativa | `node scripts/audit-tools/dep-doctor.js .` |
|
|
591
|
+
| `secret-scanner` (hook existente) | Credenciales hardcodeadas | (vΓa hook) |
|
|
592
|
+
|
|
593
|
+
### Thresholds del scorecard
|
|
594
|
+
|
|
595
|
+
| Score | Status | AcciΓ³n |
|
|
596
|
+
|---|---|---|
|
|
597
|
+
| β₯80 | READY | Listo para mergear |
|
|
598
|
+
| 60-79 | NEEDS WORK | Hallazgos menores; mergear bajo decisiΓ³n informada |
|
|
599
|
+
| <60 | NOT READY | Hallazgos crΓticos; bloquear merge |
|
|
600
|
+
|
|
601
|
+
### Salida agregada
|
|
602
|
+
|
|
603
|
+
El comando produce un reporte tipo:
|
|
604
|
+
|
|
605
|
+
```
|
|
606
|
+
SCORECARD β Audits paralelos
|
|
607
|
+
βββββββββββββββββββββββββββββββββ
|
|
608
|
+
code-profiler: 95 / 100 [READY]
|
|
609
|
+
pentest-scanner: 82 / 100 [READY]
|
|
610
|
+
dep-doctor: 70 / 100 [NEEDS WORK]
|
|
611
|
+
secret-scanner: 100 / 100 [READY]
|
|
612
|
+
βββββββββββββββββββββββββββββββββ
|
|
613
|
+
Score promedio: 86.75 [READY]
|
|
614
|
+
Total findings: 3 (0 crΓticos, 2 mayores, 1 menor)
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### CuΓ‘ndo usar `--full` vs flujo estΓ‘ndar
|
|
618
|
+
|
|
619
|
+
- **Flujo estΓ‘ndar** (`/swl:verificar`): revisiΓ³n cualitativa por agentes con criterio editorial. Adecuado para PRs pequeΓ±os/medianos donde la calidad arquitectΓ³nica importa mΓ‘s que cobertura mecΓ‘nica.
|
|
620
|
+
- **Flag `--full`**: agrega anΓ‘lisis estΓ‘tico y dinΓ‘mico ejecutable. Adecuado pre-release, audit de seguridad mensual, o features que tocan endpoints, dependencias o lΓ³gica compleja.
|
|
621
|
+
|
|
579
622
|
## Reglas de comportamiento
|
|
580
623
|
|
|
581
624
|
- NUNCA marques una fase como APROBADO si hay hallazgos CRΓTICOS de seguridad.
|
|
@@ -583,3 +626,5 @@ con flag elimina los 2 mensajes de fricciΓ³n y deja el scope auditable en el com
|
|
|
583
626
|
- Si los tests fallan, es siempre CRΓTICO, sin excepciΓ³n.
|
|
584
627
|
- Las SUGERENCIAS son de largo plazo β no bloquean el avance pero deben documentarse.
|
|
585
628
|
- Si el RESUMEN.md no existe, usa git diff para inferir el alcance. Nunca te bloquees.
|
|
629
|
+
|
|
630
|
+
<!-- El patrΓ³n parallel scorecard adaptado de Houseofmvps/ultraship /ship bajo MIT License -->
|
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* event-channel.js
|
|
5
|
-
*
|
|
6
|
-
* Pub/sub event channel para CommandRelay y otros componentes del gateway.
|
|
7
|
-
*
|
|
8
|
-
* PatrΓ³n adaptado de `temp/obsidian-agent-client-master/src/acp/acp-handler.ts`
|
|
9
|
-
* (Set<listeners> con callback unsubscribe). Diferencias:
|
|
10
|
-
* - Zero-deps Node.js (sin RxJS, sin EventEmitter de node β Set nativo).
|
|
11
|
-
* - Backward compat: si no hay listeners, comportamiento idΓ©ntico al actual.
|
|
12
|
-
* - Tipos de evento explΓcitos para evitar typos.
|
|
13
|
-
*
|
|
14
|
-
* Casos de uso en SWL:
|
|
15
|
-
* - CommandRelay emite eventos al recibir/aceptar/rechazar/procesar comandos.
|
|
16
|
-
* - MΓΊltiples adaptadores (Telegram, Discord) pueden suscribirse simultΓ‘neamente.
|
|
17
|
-
* - El consumidor /swl:inbox puede mostrar progreso sin polling.
|
|
18
|
-
*
|
|
19
|
-
* Uso:
|
|
20
|
-
* const channel = new EventChannel();
|
|
21
|
-
* const off = channel.on('cmd:queued', (event) => console.log(event));
|
|
22
|
-
* channel.emit({ type: 'cmd:queued', commandId: 'cmd-abc', userId: '123' });
|
|
23
|
-
* off(); // unsubscribe
|
|
24
|
-
*
|
|
25
|
-
* @module gateway/lib/event-channel
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
// ββ tipos de evento ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Tipos vΓ‘lidos de evento. Se usan strings para serializar fΓ‘cilmente
|
|
32
|
-
* a JSONL si se requiere persistencia (no obligatorio).
|
|
33
|
-
*/
|
|
34
|
-
const EVENTS = Object.freeze({
|
|
35
|
-
// Lifecycle de comandos en CommandRelay
|
|
36
|
-
CMD_RECEIVED: 'cmd:received', // mensaje llegΓ³ al relay
|
|
37
|
-
CMD_REJECTED: 'cmd:rejected', // rechazado por validaciΓ³n (auth/rate/dedup)
|
|
38
|
-
CMD_QUEUED: 'cmd:queued', // encolado en .planning/inbox/
|
|
39
|
-
CMD_PROCESSED: 'cmd:processed', // marcado como procesado por consumidor
|
|
40
|
-
|
|
41
|
-
// Lifecycle de notificaciones
|
|
42
|
-
NOTIFICATION_SENT: 'notification:sent',
|
|
43
|
-
NOTIFICATION_FAILED: 'notification:failed',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// ββ implementaciΓ³n ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Canal de eventos pub/sub con escucha por tipo o wildcard.
|
|
50
|
-
*
|
|
51
|
-
* Mantiene dos estructuras:
|
|
52
|
-
* - listenersByType: Map<eventType, Set<callback>>
|
|
53
|
-
* - wildcardListeners: Set<callback> (escuchan TODOS los eventos)
|
|
54
|
-
*
|
|
55
|
-
* Una excepciΓ³n en un listener NO interrumpe la propagaciΓ³n a los demΓ‘s β
|
|
56
|
-
* patrΓ³n de aislamiento del mismo origen (acp-handler.ts:47-50).
|
|
57
|
-
*/
|
|
58
|
-
class EventChannel {
|
|
59
|
-
constructor() {
|
|
60
|
-
this.listenersByType = new Map();
|
|
61
|
-
this.wildcardListeners = new Set();
|
|
62
|
-
this.errorHandler = null; // opcional: callback para errores en listeners
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Suscribe un listener a un tipo de evento (o '*' para todos).
|
|
67
|
-
*
|
|
68
|
-
* @param {string} eventType - tipo de evento o '*'
|
|
69
|
-
* @param {function} callback - recibe el objeto event
|
|
70
|
-
* @returns {function} funciΓ³n de unsubscribe (idempotente)
|
|
71
|
-
*/
|
|
72
|
-
on(eventType, callback) {
|
|
73
|
-
if (typeof callback !== 'function') {
|
|
74
|
-
throw new TypeError('callback debe ser funciΓ³n');
|
|
75
|
-
}
|
|
76
|
-
if (typeof eventType !== 'string' || !eventType) {
|
|
77
|
-
throw new TypeError('eventType debe ser string no vacΓo');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (eventType === '*') {
|
|
81
|
-
this.wildcardListeners.add(callback);
|
|
82
|
-
return () => this.wildcardListeners.delete(callback);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!this.listenersByType.has(eventType)) {
|
|
86
|
-
this.listenersByType.set(eventType, new Set());
|
|
87
|
-
}
|
|
88
|
-
const set = this.listenersByType.get(eventType);
|
|
89
|
-
set.add(callback);
|
|
90
|
-
return () => set.delete(callback);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Suscribe un listener que se invoca UNA sola vez y luego se desuscribe.
|
|
95
|
-
*/
|
|
96
|
-
once(eventType, callback) {
|
|
97
|
-
const off = this.on(eventType, (event) => {
|
|
98
|
-
off();
|
|
99
|
-
callback(event);
|
|
100
|
-
});
|
|
101
|
-
return off;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Emite un evento a todos los listeners suscriptos al tipo + wildcards.
|
|
106
|
-
* El evento debe tener al menos `type`. Se enriquece con `ts` (ISO).
|
|
107
|
-
*
|
|
108
|
-
* @param {object} event - objeto con `type` y datos arbitrarios
|
|
109
|
-
* @returns {number} cantidad de listeners notificados
|
|
110
|
-
*/
|
|
111
|
-
emit(event) {
|
|
112
|
-
if (!event || typeof event !== 'object') {
|
|
113
|
-
throw new TypeError('event debe ser objeto');
|
|
114
|
-
}
|
|
115
|
-
if (typeof event.type !== 'string' || !event.type) {
|
|
116
|
-
throw new TypeError('event.type es obligatorio');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const enriched = { ts: new Date().toISOString(), ...event };
|
|
120
|
-
let notified = 0;
|
|
121
|
-
|
|
122
|
-
// Listeners especΓficos del tipo
|
|
123
|
-
const typedSet = this.listenersByType.get(enriched.type);
|
|
124
|
-
if (typedSet) {
|
|
125
|
-
for (const cb of typedSet) {
|
|
126
|
-
notified++;
|
|
127
|
-
this._safeInvoke(cb, enriched);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Listeners wildcard
|
|
132
|
-
for (const cb of this.wildcardListeners) {
|
|
133
|
-
notified++;
|
|
134
|
-
this._safeInvoke(cb, enriched);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return notified;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Cuenta de listeners para un tipo (o total si no se pasa tipo).
|
|
142
|
-
*/
|
|
143
|
-
listenerCount(eventType) {
|
|
144
|
-
if (eventType == null) {
|
|
145
|
-
let total = this.wildcardListeners.size;
|
|
146
|
-
for (const set of this.listenersByType.values()) total += set.size;
|
|
147
|
-
return total;
|
|
148
|
-
}
|
|
149
|
-
if (eventType === '*') return this.wildcardListeners.size;
|
|
150
|
-
const set = this.listenersByType.get(eventType);
|
|
151
|
-
return (set ? set.size : 0) + this.wildcardListeners.size;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Limpia todos los listeners. Γtil para tests.
|
|
156
|
-
*/
|
|
157
|
-
clear() {
|
|
158
|
-
this.listenersByType.clear();
|
|
159
|
-
this.wildcardListeners.clear();
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Asigna un handler para errores en listeners.
|
|
164
|
-
* Si no se asigna, los errores se silencian (no rompen la propagaciΓ³n).
|
|
165
|
-
*/
|
|
166
|
-
onError(handler) {
|
|
167
|
-
if (typeof handler !== 'function') {
|
|
168
|
-
throw new TypeError('handler debe ser funciΓ³n');
|
|
169
|
-
}
|
|
170
|
-
this.errorHandler = handler;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// ββ internos ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
174
|
-
|
|
175
|
-
_safeInvoke(cb, event) {
|
|
176
|
-
try {
|
|
177
|
-
cb(event);
|
|
178
|
-
} catch (err) {
|
|
179
|
-
if (this.errorHandler) {
|
|
180
|
-
try { this.errorHandler(err, event); } catch (_) { /* silencioso */ }
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ββ exports βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
187
|
-
|
|
188
|
-
module.exports = {
|
|
189
|
-
EventChannel,
|
|
190
|
-
EVENTS,
|
|
191
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* event-channel.js
|
|
5
|
+
*
|
|
6
|
+
* Pub/sub event channel para CommandRelay y otros componentes del gateway.
|
|
7
|
+
*
|
|
8
|
+
* PatrΓ³n adaptado de `temp/obsidian-agent-client-master/src/acp/acp-handler.ts`
|
|
9
|
+
* (Set<listeners> con callback unsubscribe). Diferencias:
|
|
10
|
+
* - Zero-deps Node.js (sin RxJS, sin EventEmitter de node β Set nativo).
|
|
11
|
+
* - Backward compat: si no hay listeners, comportamiento idΓ©ntico al actual.
|
|
12
|
+
* - Tipos de evento explΓcitos para evitar typos.
|
|
13
|
+
*
|
|
14
|
+
* Casos de uso en SWL:
|
|
15
|
+
* - CommandRelay emite eventos al recibir/aceptar/rechazar/procesar comandos.
|
|
16
|
+
* - MΓΊltiples adaptadores (Telegram, Discord) pueden suscribirse simultΓ‘neamente.
|
|
17
|
+
* - El consumidor /swl:inbox puede mostrar progreso sin polling.
|
|
18
|
+
*
|
|
19
|
+
* Uso:
|
|
20
|
+
* const channel = new EventChannel();
|
|
21
|
+
* const off = channel.on('cmd:queued', (event) => console.log(event));
|
|
22
|
+
* channel.emit({ type: 'cmd:queued', commandId: 'cmd-abc', userId: '123' });
|
|
23
|
+
* off(); // unsubscribe
|
|
24
|
+
*
|
|
25
|
+
* @module gateway/lib/event-channel
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// ββ tipos de evento ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Tipos vΓ‘lidos de evento. Se usan strings para serializar fΓ‘cilmente
|
|
32
|
+
* a JSONL si se requiere persistencia (no obligatorio).
|
|
33
|
+
*/
|
|
34
|
+
const EVENTS = Object.freeze({
|
|
35
|
+
// Lifecycle de comandos en CommandRelay
|
|
36
|
+
CMD_RECEIVED: 'cmd:received', // mensaje llegΓ³ al relay
|
|
37
|
+
CMD_REJECTED: 'cmd:rejected', // rechazado por validaciΓ³n (auth/rate/dedup)
|
|
38
|
+
CMD_QUEUED: 'cmd:queued', // encolado en .planning/inbox/
|
|
39
|
+
CMD_PROCESSED: 'cmd:processed', // marcado como procesado por consumidor
|
|
40
|
+
|
|
41
|
+
// Lifecycle de notificaciones
|
|
42
|
+
NOTIFICATION_SENT: 'notification:sent',
|
|
43
|
+
NOTIFICATION_FAILED: 'notification:failed',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ββ implementaciΓ³n ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Canal de eventos pub/sub con escucha por tipo o wildcard.
|
|
50
|
+
*
|
|
51
|
+
* Mantiene dos estructuras:
|
|
52
|
+
* - listenersByType: Map<eventType, Set<callback>>
|
|
53
|
+
* - wildcardListeners: Set<callback> (escuchan TODOS los eventos)
|
|
54
|
+
*
|
|
55
|
+
* Una excepciΓ³n en un listener NO interrumpe la propagaciΓ³n a los demΓ‘s β
|
|
56
|
+
* patrΓ³n de aislamiento del mismo origen (acp-handler.ts:47-50).
|
|
57
|
+
*/
|
|
58
|
+
class EventChannel {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.listenersByType = new Map();
|
|
61
|
+
this.wildcardListeners = new Set();
|
|
62
|
+
this.errorHandler = null; // opcional: callback para errores en listeners
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Suscribe un listener a un tipo de evento (o '*' para todos).
|
|
67
|
+
*
|
|
68
|
+
* @param {string} eventType - tipo de evento o '*'
|
|
69
|
+
* @param {function} callback - recibe el objeto event
|
|
70
|
+
* @returns {function} funciΓ³n de unsubscribe (idempotente)
|
|
71
|
+
*/
|
|
72
|
+
on(eventType, callback) {
|
|
73
|
+
if (typeof callback !== 'function') {
|
|
74
|
+
throw new TypeError('callback debe ser funciΓ³n');
|
|
75
|
+
}
|
|
76
|
+
if (typeof eventType !== 'string' || !eventType) {
|
|
77
|
+
throw new TypeError('eventType debe ser string no vacΓo');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (eventType === '*') {
|
|
81
|
+
this.wildcardListeners.add(callback);
|
|
82
|
+
return () => this.wildcardListeners.delete(callback);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!this.listenersByType.has(eventType)) {
|
|
86
|
+
this.listenersByType.set(eventType, new Set());
|
|
87
|
+
}
|
|
88
|
+
const set = this.listenersByType.get(eventType);
|
|
89
|
+
set.add(callback);
|
|
90
|
+
return () => set.delete(callback);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Suscribe un listener que se invoca UNA sola vez y luego se desuscribe.
|
|
95
|
+
*/
|
|
96
|
+
once(eventType, callback) {
|
|
97
|
+
const off = this.on(eventType, (event) => {
|
|
98
|
+
off();
|
|
99
|
+
callback(event);
|
|
100
|
+
});
|
|
101
|
+
return off;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Emite un evento a todos los listeners suscriptos al tipo + wildcards.
|
|
106
|
+
* El evento debe tener al menos `type`. Se enriquece con `ts` (ISO).
|
|
107
|
+
*
|
|
108
|
+
* @param {object} event - objeto con `type` y datos arbitrarios
|
|
109
|
+
* @returns {number} cantidad de listeners notificados
|
|
110
|
+
*/
|
|
111
|
+
emit(event) {
|
|
112
|
+
if (!event || typeof event !== 'object') {
|
|
113
|
+
throw new TypeError('event debe ser objeto');
|
|
114
|
+
}
|
|
115
|
+
if (typeof event.type !== 'string' || !event.type) {
|
|
116
|
+
throw new TypeError('event.type es obligatorio');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const enriched = { ts: new Date().toISOString(), ...event };
|
|
120
|
+
let notified = 0;
|
|
121
|
+
|
|
122
|
+
// Listeners especΓficos del tipo
|
|
123
|
+
const typedSet = this.listenersByType.get(enriched.type);
|
|
124
|
+
if (typedSet) {
|
|
125
|
+
for (const cb of typedSet) {
|
|
126
|
+
notified++;
|
|
127
|
+
this._safeInvoke(cb, enriched);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Listeners wildcard
|
|
132
|
+
for (const cb of this.wildcardListeners) {
|
|
133
|
+
notified++;
|
|
134
|
+
this._safeInvoke(cb, enriched);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return notified;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cuenta de listeners para un tipo (o total si no se pasa tipo).
|
|
142
|
+
*/
|
|
143
|
+
listenerCount(eventType) {
|
|
144
|
+
if (eventType == null) {
|
|
145
|
+
let total = this.wildcardListeners.size;
|
|
146
|
+
for (const set of this.listenersByType.values()) total += set.size;
|
|
147
|
+
return total;
|
|
148
|
+
}
|
|
149
|
+
if (eventType === '*') return this.wildcardListeners.size;
|
|
150
|
+
const set = this.listenersByType.get(eventType);
|
|
151
|
+
return (set ? set.size : 0) + this.wildcardListeners.size;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Limpia todos los listeners. Γtil para tests.
|
|
156
|
+
*/
|
|
157
|
+
clear() {
|
|
158
|
+
this.listenersByType.clear();
|
|
159
|
+
this.wildcardListeners.clear();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Asigna un handler para errores en listeners.
|
|
164
|
+
* Si no se asigna, los errores se silencian (no rompen la propagaciΓ³n).
|
|
165
|
+
*/
|
|
166
|
+
onError(handler) {
|
|
167
|
+
if (typeof handler !== 'function') {
|
|
168
|
+
throw new TypeError('handler debe ser funciΓ³n');
|
|
169
|
+
}
|
|
170
|
+
this.errorHandler = handler;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ββ internos ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
174
|
+
|
|
175
|
+
_safeInvoke(cb, event) {
|
|
176
|
+
try {
|
|
177
|
+
cb(event);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (this.errorHandler) {
|
|
180
|
+
try { this.errorHandler(err, event); } catch (_) { /* silencioso */ }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ββ exports βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
EventChannel,
|
|
190
|
+
EVENTS,
|
|
191
|
+
};
|