@saulwade/swl-ses 1.7.4 → 1.9.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.
Files changed (97) hide show
  1. package/CLAUDE.md +196 -196
  2. package/README.md +579 -579
  3. package/agentes/auto-evolucion-swl.md +7 -7
  4. package/agentes/disenador-ui-swl.md +12 -0
  5. package/agentes/investigador-ux-swl.md +9 -0
  6. package/agentes/orquestador-swl.md +89 -1
  7. package/agentes/perfilador-usuario-swl.md +2 -2
  8. package/agentes/revisor-codigo-swl.md +34 -10
  9. package/agentes/revisor-seguridad-swl.md +7 -0
  10. package/agentes/tdd-qa-swl.md +23 -2
  11. package/agentes/ux-disenador-swl.md +6 -0
  12. package/comandos/swl/autoresearch.md +102 -6
  13. package/comandos/swl/evaluar-skill.md +1 -1
  14. package/comandos/swl/evolucion-estado.md +5 -5
  15. package/comandos/swl/evolucionar.md +2 -2
  16. package/comandos/swl/inbox.md +1 -1
  17. package/comandos/swl/metricas.md +34 -0
  18. package/comandos/swl/nemesis.md +42 -1
  19. package/comandos/swl/planear-fase.md +8 -0
  20. package/comandos/swl/predecir.md +139 -0
  21. package/comandos/swl/reflect-skills.md +2 -2
  22. package/comandos/swl/salud.md +1 -1
  23. package/comandos/swl/verificar.md +50 -7
  24. package/habilidades/ai-runtime-security/SKILL.md +2 -2
  25. package/habilidades/angular-moderno/SKILL.md +44 -1
  26. package/habilidades/auto-evolucion-protocolo/SKILL.md +2 -2
  27. package/habilidades/autoresearch/SKILL.md +15 -1
  28. package/habilidades/benchmark-memoria/SKILL.md +2 -2
  29. package/habilidades/calidad-mutation-testing/SKILL.md +170 -0
  30. package/habilidades/changelog-generator/scripts/parse-commits.js +2 -1
  31. package/habilidades/checklist-seguridad/SKILL.md +29 -1
  32. package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -0
  33. package/habilidades/css-moderno/SKILL.md +3 -1
  34. package/habilidades/drift-detection/SKILL.md +3 -3
  35. package/habilidades/eval-framework/SKILL.md +1 -1
  36. package/habilidades/fastapi-experto/SKILL.md +56 -5
  37. package/habilidades/guardrail-semantico/SKILL.md +4 -4
  38. package/habilidades/patrones-python/SKILL.md +8 -5
  39. package/habilidades/proceso-ddia-streaming/SKILL.md +4 -4
  40. package/habilidades/proceso-debate-adversarial/SKILL.md +164 -0
  41. package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -0
  42. package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -0
  43. package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -0
  44. package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -0
  45. package/habilidades/swl-claudemd/SKILL.md +2 -2
  46. package/habilidades/tdd-workflow/SKILL.md +14 -1
  47. package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -0
  48. package/habilidades/testing-python/SKILL.md +1 -1
  49. package/habilidades/tracing-processor/SKILL.md +1 -1
  50. package/hooks/actualizar-perfil-usuario.js +2 -2
  51. package/hooks/aiisms-detector.js +2 -2
  52. package/hooks/auto-evolucion.js +1 -1
  53. package/hooks/captura-feedback-usuario.js +2 -2
  54. package/hooks/claudemd-bloat-detector.js +2 -2
  55. package/hooks/claudemd-duplicacion-detector.js +1 -1
  56. package/hooks/contexto-iteracion.js +144 -0
  57. package/hooks/guardrail-modelo.js +2 -2
  58. package/hooks/lib/loop-telemetry.js +321 -0
  59. package/hooks/lib/memory-search.js +1 -1
  60. package/hooks/lib/nudge-tracker.js +1 -1
  61. package/hooks/metricas-evolucion.js +3 -3
  62. package/hooks/notificacion-telegram.js +11 -3
  63. package/hooks/rotar-audit-auto.js +2 -2
  64. package/hooks/validar-formato-post-subagente.js +2 -2
  65. package/hooks/validar-intent-spec.js +1 -1
  66. package/hooks/validar-planning-paths.js +134 -0
  67. package/llms.txt +29 -0
  68. package/manifiestos/hooks-config.json +30 -12
  69. package/manifiestos/modulos.json +1358 -1351
  70. package/manifiestos/planning-paths.json +44 -0
  71. package/manifiestos/skills-lock.json +1275 -1254
  72. package/package.json +93 -92
  73. package/plugin.json +375 -372
  74. package/reglas/arquitectura.evolved.json +7 -0
  75. package/reglas/arquitectura.md +65 -0
  76. package/reglas/gobernanza.md +1 -1
  77. package/reglas/memoria-consolidada.md +7 -7
  78. package/reglas/seguridad.evolved.json +7 -0
  79. package/reglas/seguridad.md +144 -0
  80. package/reglas/sin-duplicacion-reglas-globales.md +1 -1
  81. package/scripts/auditar-agentes-gaps.js +1 -1
  82. package/scripts/auditar-cobertura-frameworks.js +2 -2
  83. package/scripts/auditar-skills-gaps.js +2 -2
  84. package/scripts/benchmark-memoria.js +3 -3
  85. package/scripts/generar-inventario.js +64 -1
  86. package/scripts/inferir-herramientas-permitidas.js +1 -1
  87. package/scripts/instalador.js +80 -2
  88. package/scripts/lib/dashboard-widgets.js +3 -3
  89. package/scripts/lib/drift-detector.js +3 -3
  90. package/scripts/lib/eval-metrics-store.js +3 -3
  91. package/scripts/lib/gitignore-manifest.js +3 -3
  92. package/scripts/mcp-server/README.md +1 -1
  93. package/scripts/mcp-server/telemetry.js +2 -2
  94. package/scripts/reflect-skills.js +4 -4
  95. package/scripts/rotar-audit-logs.js +2 -2
  96. package/scripts/run-skill-evals.js +2 -2
  97. package/scripts/smoke-test.js +24 -2
@@ -51,7 +51,7 @@ const { execSync } = require('node:child_process');
51
51
  * refactor(hooks/lib): split evolution-tracker
52
52
  * chore(release): bump version
53
53
  */
54
- const RE_CONVENTIONAL = /^(feat|fix|perf|refactor|docs|style|test|ci|build|chore|revert|evolucion)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
54
+ const RE_CONVENTIONAL = /^(feat|fix|perf|refactor|docs|style|test|ci|build|chore|revert|evolucion|evolve)(?:\(([^)]+)\))?(!)?:\s*(.+)$/;
55
55
 
56
56
  /** Mapa tipo CC → categoría Keep a Changelog en es-MX. */
57
57
  const CATEGORIAS = Object.freeze({
@@ -61,6 +61,7 @@ const CATEGORIAS = Object.freeze({
61
61
  refactor: 'Cambios internos',
62
62
  revert: 'Reversiones',
63
63
  evolucion: 'Evoluciones de skills/agentes',
64
+ evolve: 'Evoluciones de skills/agentes',
64
65
  docs: 'Mantenimiento',
65
66
  style: 'Mantenimiento',
66
67
  test: 'Mantenimiento',
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: checklist-seguridad
3
3
  description: Checklist de seguridad basado en OWASP Top 10 + seguridad de agentes autónomos (A11). Cubre inyección, autenticación, exposición de datos, control de acceso, configuración insegura, XSS, deserialización, componentes vulnerables, logging y agencia excesiva de IA. Produce reporte con hallazgos y remediaciones.
4
- version: "1.1.1"
4
+ version: "1.2.0"
5
5
  evolved: true
6
6
  evolved-from: "1.1.0"
7
7
  evolved-at: "2026-05-04"
@@ -297,6 +297,34 @@ grep -rn "\.env\|credentials\|secret" --include="*.md" agentes/ | head -10
297
297
 
298
298
  ---
299
299
 
300
+ ## Modo cobertura — STRIDE + score compuesto (auditorías iterativas)
301
+
302
+ Para auditorías en profundidad (no el checklist de un solo PR), complementar
303
+ el barrido OWASP con el modelo **STRIDE** y medir la auditoría con un **score
304
+ compuesto de cobertura** — convierte "auditamos seguridad" en un número
305
+ verificable y permite usar la auditoría como loop iterativo:
306
+
307
+ ```
308
+ score = (categorías_OWASP_auditadas / 10) × 50
309
+ + (categorías_STRIDE_auditadas / 6) × 30
310
+ + min(hallazgos_únicos, 20)
311
+ ```
312
+
313
+ Score perfecto = 100 (OWASP completo + STRIDE completo + 20 hallazgos). Cada
314
+ hallazgo se etiqueta con AMBAS taxonomías (`OWASP: A03, STRIDE: T`). Reportar
315
+ la cobertura cada 5 iteraciones:
316
+
317
+ ```
318
+ OWASP: [A01✓ A02✓ A03✗ ...] 4/10 | STRIDE: [S✓ T✓ R✗ I✓ D✗ E✗] 3/6 | Score: 48
319
+ ```
320
+
321
+ Tabla STRIDE→OWASP, qué buscar por categoría, y rotación de personas red-team
322
+ (Adversario, Supply Chain, Insider, Infra — definidas en
323
+ `habilidades/proceso-debate-adversarial/recursos/personas.md`) en
324
+ [recursos/stride-cobertura.md](recursos/stride-cobertura.md). Registrar las
325
+ iteraciones de la auditoría con `hooks/lib/loop-telemetry.js` (tipo
326
+ `seguridad`, direccion `higher_is_better`, métrica = score compuesto).
327
+
300
328
  ## Gotchas / Errores comunes no obvios
301
329
 
302
330
  **El grep de búsqueda de secrets hardcodeados devuelve cero resultados pero hay credenciales en archivos de configuración YAML**: el patrón de búsqueda usa `password\s*=` (sintaxis Python), pero los archivos YAML usan `password:` (sin signo igual). Causa: las búsquedas de código están optimizadas para un lenguaje y pierden variantes de otro formato. Fix: expandir el patrón de búsqueda a `password[\s=:]+['\"][^'\"]{4,}` para capturar asignaciones en Python, YAML y JSON simultáneamente. Verificar también `docker-compose.yml`, `.env.example` y archivos de configuración de CI.
@@ -0,0 +1,60 @@
1
+ # STRIDE — modelo de amenazas para el modo cobertura
2
+
3
+ Complemento del checklist OWASP de `SKILL.md`. STRIDE clasifica por **tipo de
4
+ amenaza** (qué quiere lograr el atacante); OWASP por **tipo de debilidad**
5
+ (qué error del código lo permite). Auditar con ambas taxonomías cierra los
6
+ huecos que cada una deja sola: repudio y DoS casi no aparecen en OWASP Top 10;
7
+ componentes desactualizados (A06) no mapean limpio a STRIDE.
8
+
9
+ ## Las 6 categorías
10
+
11
+ | Letra | Amenaza | Qué buscar | OWASP relacionadas |
12
+ |-------|---------|-----------|--------------------|
13
+ | **S**poofing | Suplantación de identidad | Auth débil, tokens predecibles, session fixation, falta de MFA | A07 |
14
+ | **T**ampering | Modificación de datos | Input sin validar, falta de checks de integridad, inyección SQL/NoSQL, deserialización insegura | A03, A08 |
15
+ | **R**epudiation | Acciones negables | Audit logs faltantes, transacciones sin firma, logs mutables, cambios de privilegio sin registro | A09 |
16
+ | **I**nformation Disclosure | Fuga de información | Stack traces al cliente, logging verboso con PII, env vars expuestas, mensajes de error que revelan existencia de recursos | A01, A02, A05 |
17
+ | **D**enial of Service | Ataques a disponibilidad | Queries sin cota ni paginación, rate limits faltantes, regex DoS (ReDoS), uploads sin límite de tamaño | A04 |
18
+ | **E**levation of Privilege | Acceso no autorizado | Checks de authz faltantes, IDOR, rutas de escalación admin, confusión autenticación/autorización | A01, A04 |
19
+
20
+ ## Protocolo de auditoría iterativa con cobertura
21
+
22
+ 1. **Reconocimiento (una vez)**: mapear superficie de ataque — manifest de
23
+ dependencias, `.env.example`, Dockerfile, rutas de API, módulos de auth,
24
+ schemas de BD, configuración de CI/CD. Producir threat model inicial:
25
+ qué activos, qué trust boundaries, qué entry points.
26
+ 2. **Por iteración**: elegir el vector MENOS cubierto (OWASP sin auditar →
27
+ STRIDE sin auditar → profundizar en hallazgos existentes). Adoptar la
28
+ persona red-team que corresponde al vector (rotación: Adversario de
29
+ Seguridad → Supply Chain → Insider → Infraestructura — definiciones en
30
+ `habilidades/proceso-debate-adversarial/recursos/personas.md`).
31
+ 3. **Validar cada hallazgo**: evidencia archivo:línea + escenario de ataque
32
+ concreto + reproducción. Sin escenario de ataque no es hallazgo, es
33
+ especulación (regla `verificar-citas-normativas.md § Familia 2`).
34
+ 4. **Registrar**: fila en la corrida de `loop-telemetry` (columnas sugeridas:
35
+ `iteracion, timestamp, hallazgo, severidad, owasp, stride, archivo_linea`)
36
+ y recalcular el score compuesto.
37
+ 5. **Salida**: OWASP 10/10 + STRIDE 6/6 cubiertos, o max iteraciones (default
38
+ 15), o plateau de hallazgos (3 iteraciones sin hallazgo nuevo).
39
+
40
+ ## Formato de hallazgo (obligatorio, 7 campos)
41
+
42
+ ```markdown
43
+ ### [Título de una línea]
44
+ - **Severidad**: Crítico | Alto | Medio | Bajo | Informativo
45
+ - **OWASP**: A01-A10
46
+ - **STRIDE**: S | T | R | I | D | E
47
+ - **Evidencia**: archivo:línea + escenario de ataque (sin especulación teórica)
48
+ - **Reproducción**: pasos para disparar el problema
49
+ - **Remediación**: fix concreto para ESTE código
50
+ ```
51
+
52
+ ## Severidad — criterios
53
+
54
+ | Severidad | Criterio | Ejemplos |
55
+ |-----------|----------|----------|
56
+ | Crítico | Explotable remoto, sin auth, brecha de datos | RCE, SQL injection, bypass de auth |
57
+ | Alto | Requiere algún acceso, impacto severo | XSS almacenado, IDOR, escalación de privilegios |
58
+ | Medio | Impacto limitado o requiere interacción | CSRF, XSS reflejado, divulgación de info |
59
+ | Bajo | Impacto mínimo | Headers faltantes, errores verbosos |
60
+ | Informativo | Recomendación de hardening | Defensa en profundidad |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: css-moderno
3
3
  description: CSS moderno 2024+. Cubre Container Queries, CSS Layers (@layer), Nesting nativo, Custom Properties avanzadas, funciones min/max/clamp/color-mix, propiedades lógicas (block/inline), View Transitions API, animaciones performantes solo-compositor, dark mode patterns y anti-patrones críticos.
4
- version: "1.0.0"
4
+ version: "1.1.0"
5
5
  herramientasPermitidas: [Read, Grep]
6
6
  exclusiones:
7
7
  - "No cargar para Tailwind CSS (clases utilitarias, @theme, cva, responsive) — para Tailwind cargar `tailwind-experto`."
@@ -164,3 +164,5 @@ Para ejemplos completos de Container Queries, CSS Layers, Nesting, Custom Proper
164
164
  **`container-type: inline-size` en un elemento con `position: absolute/fixed` puede no funcionar como contenedor de queries en algunos navegadores**: el contenedor crea un nuevo stacking context y en ciertos casos la query no se propaga correctamente a hijos con posicionamiento absoluto en Safari <17. Causa: bug de implementación en Safari relacionado con contenedores posicionados. Fix: en elementos con posicionamiento absoluto dentro de container queries, probar en Safari y usar un wrapper div sin posicionamiento como contenedor si hay problemas.
165
165
 
166
166
  **`clamp()` con `vw` en la función intermedia no escala correctamente en pantallas muy anchas si no se acota con `max()`**: `font-size: clamp(1rem, 2vw, 2rem)` en un monitor de 2560px da `2vw = 51.2px`, que es mayor que el máximo de `2rem = 32px` — el clamp lo atrapa pero el cálculo puede ser confuso al depurar. Causa: el valor de `vw` crece sin límite. Fix: entender que `clamp(min, preferido, max)` garantiza que el resultado siempre está entre min y max — el valor preferido puede ser cualquier expresión, incluso mayor que max, y el clamp simplemente retorna max en ese caso. Verificar los tres valores en los tamaños de viewport objetivo.
167
+
168
+ **`var(--token)` NO resuelve cuando el valor lo consume JavaScript pintando a `<canvas>`**: las librerías que renderizan a canvas (Chart.js, OpenLayers, three.js/WebGL, o `ctx.fillStyle`/`strokeStyle`/`ctx.font` directos) NO reciben el contexto CSS del documento → el color sale negro o invisible **sin error en consola**. Causa: el canvas es un buffer de píxeles fuera del árbol CSS; la cascada y `getComputedStyle` no aplican al contexto 2D/WebGL — `var()` queda como string literal que el motor de canvas no interpreta. La frontera es: un color es `var(--x)` SOLO si lo consume CSS (`.css`/`.module.scss` o `style={{}}` sobre HTML); si lo consume JS pintando a canvas, debe ser hex/rgb concreto. Fix: pasar hex/rgb literal a esos objetos, con comentario anti-recurrencia (`// canvas: NO resuelve var()`). Si el color debe ser theme-aware, resolverlo primero en JS — `getComputedStyle(document.documentElement).getPropertyValue('--color-x').trim()` — y pasar el valor ya resuelto al canvas, re-leyéndolo al cambiar de tema.
@@ -135,7 +135,7 @@ const resultado = ejecutarDriftReflect(
135
135
  ```
136
136
 
137
137
  Cuando `estado_global === 'critico'`, el módulo emite automáticamente un nudge
138
- a `.planning/evolucion/nudges.jsonl`:
138
+ a `.planning/evolution/nudges.jsonl`:
139
139
 
140
140
  ```json
141
141
  {
@@ -167,7 +167,7 @@ El agente `auto-evolucion-swl` consume estos nudges para proponer mejoras.
167
167
 
168
168
  - **Timestamps inválidos en eventos JSONL provocan líneas ignoradas silenciosamente**: el módulo es permisivo con los campos pero si ningún campo de timestamp (`timestamp`, `ts`, `inicio`, `created_at`) tiene una fecha ISO válida, el evento se descarta del cálculo. Causa: eventos generados con timestamps de formato local (ej. `"19/04/2026"`) no pasan la validación. Solución: verificar que todos los eventos JSONL del trace tengan al menos un campo de timestamp en formato ISO 8601 — si el baseline aparece como 0, es el primer síntoma de eventos descartados.
169
169
  - **Estado `critico` emitido por baseline con muy pocos eventos**: si el baseline de 4 semanas tiene solo 3 eventos y la ventana de 7 días tiene 8, el `driftPct` de tokens se dispara al 166% cuando en realidad el agente está más activo, no degradado. Causa: el módulo no valida que el baseline tenga suficientes eventos para ser estadísticamente válido. Solución: antes de interpretar un estado `critico`, verificar que el baseline tiene al menos 20 eventos — si no, marcar el resultado como `insufficient-data` en lugar de accionar.
170
- - **Nudge duplicado en `.planning/evolucion/nudges.jsonl` por falta de deduplicación**: el hook se ejecuta dos veces en el mismo `SubagentStop` (por bug de throttle) y emite el mismo nudge dos veces. Causa: el throttle `SWL_DRIFT_THROTTLE_H` no validó correctamente el timestamp del último run. Solución: el archivo `nudges.jsonl` es append-only — antes de emitir un nudge, verificar si el último evento del mismo `agente_o_skill` y `metrica` tiene un timestamp dentro de la ventana de throttle.
170
+ - **Nudge duplicado en `.planning/evolution/nudges.jsonl` por falta de deduplicación**: el hook se ejecuta dos veces en el mismo `SubagentStop` (por bug de throttle) y emite el mismo nudge dos veces. Causa: el throttle `SWL_DRIFT_THROTTLE_H` no validó correctamente el timestamp del último run. Solución: el archivo `nudges.jsonl` es append-only — antes de emitir un nudge, verificar si el último evento del mismo `agente_o_skill` y `metrica` tiene un timestamp dentro de la ventana de throttle.
171
171
  - **`atomicWriteJSON` usado para escribir en nudges.jsonl**: escribir el archivo completo en lugar de hacer append corrompe el historial de nudges previos. Causa: confusión entre archivos de estado mutable (usan `atomicWriteJSON`) y archivos de eventos de alta frecuencia (usan `fs.appendFileSync`). Solución: `nudges.jsonl` es un JSONL de alta frecuencia — siempre usar `fs.appendFileSync(ruta, JSON.stringify(nudge) + '\n')`, nunca reescribir el archivo completo.
172
172
 
173
173
  ## Referencia cruzada
@@ -175,5 +175,5 @@ El agente `auto-evolucion-swl` consume estos nudges para proponer mejoras.
175
175
  - Módulo: `scripts/lib/drift-detector.js`
176
176
  - Tests: `tests/lib/drift-detector.test.js`
177
177
  - Operador Reflect: `hooks/lib/reflect-classifier.js`
178
- - Ciclo AGP: `.planning/evolucion/nudges.jsonl`
178
+ - Ciclo AGP: `.planning/evolution/nudges.jsonl`
179
179
  - Origen (adaptado de): `temp/mission-control-main/src/lib/agent-evals.ts` — MIT
@@ -24,7 +24,7 @@ evolvable: true # default para skill estandar
24
24
  resultado de búsqueda) cuando se quiera puntuar su calidad antes de
25
25
  persistir.
26
26
  - Para auditar histórico de calidad de una función crítica (ver métricas
27
- agregadas en `.planning/evolucion/eval-metrics.json`).
27
+ agregadas en `.planning/evolution/eval-metrics.json`).
28
28
  - En tests/CI cuando el contrato del output tenga campos obligatorios y
29
29
  quality thresholds.
30
30
  - En loops de auto-corrección donde un output inválido debe regenerarse
@@ -5,12 +5,12 @@ description: >
5
5
  testing con httpx. Incluye el anti-patrón crítico MissingGreenlet (lazy loading
6
6
  en async). Cargar cuando se implementen endpoints FastAPI, schemas Pydantic v2,
7
7
  queries SQLAlchemy async, WebSockets, SSE o tests de integración con httpx.
8
- version: "1.3.0"
8
+ version: "1.3.1"
9
9
  evolved: true
10
- evolved-from: "1.2.0"
11
- evolved-at: "2026-05-20"
12
- evolved-by: "aprender"
13
- evolved-note: "2 gotchas nuevos SIGAF 2026-05-15: setattr con strings inventados en whitelist bypassea persistencia silenciosamente (extiende gotcha getattr); ClassVar[frozenset] como patrón positivo para whitelists de PATCH endpoints"
10
+ evolved-from: "1.3.0"
11
+ evolved-at: "2026-06-04"
12
+ evolved-by: "evolucionar"
13
+ evolved-note: "3 patrones nuevos OIC v1.5 2026-06-04: handler logging defensivo con sesión SQLAlchemy desacoplada (PE-001); tests pytest cuelgan si handler abre engine con pool_pre_ping al startup sin BD + receta conftest (PE-002); tests de endpoint sin BD con dependency_overrides[get_db]=lambda:None + monkeypatch del service (PE-008)"
14
14
  herramientasPermitidas: [Read]
15
15
  exclusiones:
16
16
  - "No cargar para proyectos Django o Flask — los patrones de ORM sync, Class-Based Views y middleware difieren fundamentalmente; cargar `django-experto` o el skill del framework correspondiente."
@@ -267,6 +267,57 @@ Beneficios:
267
267
 
268
268
  Regla: para cualquier whitelist usada en `setattr` ciego (PATCH endpoints, factory methods, deserializadores manuales), declarar como `ClassVar[frozenset[str]]` de la clase del service. NUNCA como variable local del método. NUNCA como módulo-level constant fuera de la clase (pierde el namespacing).
269
269
 
270
+ - **Handler logging que persiste en BD durante manejo de excepciones — SIEMPRE usar sesión SQLAlchemy desacoplada + silenciar fallos** (patrón portable; caso real OIC v1.5 BitacoraErrorHandler 2026-06-04): si un `logging.Handler` custom (audit trail, Sentry-style, métricas async, observabilidad) usa la sesión del request para persistir el evento, durante una excepción HTTP la sesión está en estado roto y `session.add()` falla, perdiendo la excepción original. Patrón correcto:
271
+ ```python
272
+ # Engine PROPIO independiente del pool del request (pool=2 suficiente).
273
+ engine = create_engine(settings.DATABASE_URL, pool_pre_ping=True, pool_size=2, max_overflow=2)
274
+ session_factory = sessionmaker(bind=engine, autocommit=False, autoflush=False)
275
+
276
+ class MiHandler(logging.Handler):
277
+ def emit(self, record):
278
+ try:
279
+ self._emit_unsafe(record)
280
+ except Exception: # noqa: BLE001 — silencio defensivo intencional
281
+ # NUNCA loggear con `logging` desde aquí (riesgo de recursión infinita).
282
+ pass
283
+
284
+ def _emit_unsafe(self, record):
285
+ sess = session_factory() # sesión NUEVA, no la del request
286
+ try:
287
+ sess.add(entry); sess.commit()
288
+ finally:
289
+ sess.close()
290
+ ```
291
+ Reglas clave: (a) engine propio; (b) `try/except: pass` agresivo en `emit()`; (c) NUNCA emitir a logger propio; (d) filtrar loggers ruidosos por nombre (`sqlalchemy.*`, `uvicorn.access`, `httpx`, `httpcore`, `asyncio`, `urllib3`); (e) idempotente: verificar si ya está en `root.handlers` antes de registrar. Aplicable a Sentry-style integrations, audit handlers, métricas async, alertas.
292
+
293
+ - **Tests pytest se cuelgan al importar `app.main` cuando un handler abre engine BD con `pool_pre_ping=True` al startup** (gotcha crítico OIC v1.5 2026-06-04): si un handler de logging custom se instala en `setup_logging()` y construye un engine con `pool_pre_ping=True`, sin PostgreSQL corriendo SQLAlchemy intenta una query de verificación en `socket.connect()` infinito. El test no falla — se cuelga sin output (ni siquiera el header de pytest). Causa: el bloqueo ocurre en la **importación** del módulo `app.main` (que llama `setup_logging()`), no en el test mismo. Síntoma típico: 3+ invocaciones de `pytest` en background sin output, requiere matar `python.exe` manualmente. Solución obligatoria en `backend/tests/conftest.py`:
294
+ ```python
295
+ # ANTES de importar app.main
296
+ os.environ.setdefault("ENV", "test")
297
+ os.environ.setdefault("SKIP_DB_HEALTHCHECK", "true")
298
+ # Por CADA handler del proyecto que abre engine al startup:
299
+ os.environ.setdefault("SWL_BITACORA_ERRORES_ENABLED", "false")
300
+ # (sustituir por la env var real del proyecto)
301
+ ```
302
+ Regla: cualquier handler con `pool_pre_ping=True` en su engine debe tener un kill-switch env var, y el conftest debe deshabilitarlo. Sin esto, los tests del proyecto serán inviables sin BD real.
303
+
304
+ - **Tests de endpoint sin BD con `dependency_overrides[get_db]=lambda:None` + monkeypatch del service** (patrón portable validado OIC v1.5 Slice 4 2026-06-04): para tests de endpoint que verifican contrato HTTP (auth, validación de query params, estructura de respuesta, rate-limit, headers de export CSV) sin requerir PostgreSQL. 25 tests en 0.23s.
305
+ ```python
306
+ @pytest.fixture
307
+ def client_admin(monkeypatch: pytest.MonkeyPatch) -> TestClient:
308
+ app.dependency_overrides[requiere_admin] = _fake_admin # stub user
309
+ app.dependency_overrides[get_db] = lambda: None # type: ignore[return-value]
310
+ # Mock del service: el endpoint llama service.metodo(db, ...) pero `db` es None;
311
+ # el service no debe ejecutarse — se reemplaza con fake que devuelve schema válido.
312
+ from app.services import mi_service
313
+ monkeypatch.setattr(mi_service, "listar_paginado",
314
+ lambda db, filtros, page, per_page: SchemaPage(items=[], total=0, ...))
315
+ with TestClient(app) as c:
316
+ yield c
317
+ app.dependency_overrides.clear()
318
+ ```
319
+ Aplicabilidad: cualquier endpoint que dependa de `get_db` + service mockeable. NO requiere BD real ni fixtures de datos. Tests de integración con BD viven aparte (`@pytest.mark.integration`).
320
+
270
321
  ## Referencias especializadas
271
322
 
272
323
  | Tema | Archivo |
@@ -38,7 +38,7 @@ si se justifica el costo del modelo seleccionado.
38
38
  - Al diseñar hooks `PreToolUse` que evalúan prompts antes de invocar un subagente.
39
39
  - Al implementar degradación de modelo basada en complejidad de tarea.
40
40
  - Al revisar si `guardrail-modelo.js` necesita nuevos criterios de tripwire.
41
- - Al analizar logs de `.planning/evolucion/guardrail-observaciones.jsonl`.
41
+ - Al analizar logs de `.planning/evolution/guardrail-observaciones.jsonl`.
42
42
 
43
43
  ---
44
44
 
@@ -199,7 +199,7 @@ Pasar de modo `observational` a `blocking` sin revisar las observaciones acumula
199
199
  es una fuente garantizada de falsos positivos. El ciclo correcto:
200
200
 
201
201
  1. Deploy en modo `observational` (exit 0 siempre).
202
- 2. Revisar `.planning/evolucion/guardrail-observaciones.jsonl` tras ≥50 activaciones.
202
+ 2. Revisar `.planning/evolution/guardrail-observaciones.jsonl` tras ≥50 activaciones.
203
203
  3. Calcular tasa de falsos positivos. Si < 5%, considerar `run_in_parallel`.
204
204
  4. Solo con tasa < 2% y aprobación manual, activar `blocking`.
205
205
 
@@ -240,14 +240,14 @@ if (tripwire) {
240
240
  process.stderr.write(
241
241
  `[guardrail-modelo] El agente '${agenteNombre}' podría ejecutarse con 'haiku' ` +
242
242
  `(menos costoso). Razón: prompt corto sin keywords críticas (${prompt.length} chars). ` +
243
- `Ver .planning/evolucion/guardrail-observaciones.jsonl\n`
243
+ `Ver .planning/evolution/guardrail-observaciones.jsonl\n`
244
244
  );
245
245
  }
246
246
  // exit 0 siempre — modo observacional
247
247
  process.exit(0);
248
248
  ```
249
249
 
250
- **Evento JSONL resultante** en `.planning/evolucion/guardrail-observaciones.jsonl`:
250
+ **Evento JSONL resultante** en `.planning/evolution/guardrail-observaciones.jsonl`:
251
251
 
252
252
  ```jsonl
253
253
  {"timestamp":"2026-04-19T14:23:01.000Z","agente":"notificador-swl","modelo_actual":"sonnet","modelo_sugerido":"haiku","razon_tripwire":"prompt-corto-sin-keywords-criticas","prompt_length":87}
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: patrones-python
3
3
  description: Idiomas pythonicos, PEP 8, type hints modernos, dataclasses, async/await, context managers, decorators y generators. Patrones de código limpio en Python.
4
- version: "1.3.1"
4
+ version: "1.4.2"
5
5
  evolved: true
6
- evolved-from: "1.3.0"
7
- evolved-at: "2026-05-04"
8
- evolved-by: "aprender"
9
- evolved-note: "+1 gotcha: assert se elimina con PYTHONOPTIMIZE=1 usar if/raise para invariantes (sync desde global tras sesión SIGM Fase 5b)"
6
+ evolved-from: "1.4.1"
7
+ evolved-at: "2026-06-05"
8
+ evolved-by: "evolucionar"
9
+ evolved-note: "+gotcha PEP 758: except A,B: sin paréntesis es válido en Python >=3.14, no SyntaxError; cubre efecto colateral de ruff format (target py314) reformateando archivo completo. Origen: falso positivo reproducible en sistema-verificacion-oic (requires-python >=3.14)"
10
10
  herramientasPermitidas: [Read, Glob, Grep]
11
11
  exclusiones:
12
12
  - "No cargar para patrones de un framework específico (FastAPI, Django, Celery) — los idiomas generales de este skill aplican, pero los patrones de framework tienen restricciones adicionales; cargar el skill del framework correspondiente."
@@ -205,6 +205,9 @@ ver [recursos/referencia-completa.md](recursos/referencia-completa.md).
205
205
  - **`__slots__` en clase Python produce `TypeError: multiple bases have instance lay-out conflict`** al heredar de otra clase con `__slots__`: las subclases con `__slots__` requieren que todos los ancestros también tengan `__slots__`, o que el ancestro directo sea `object`. Causa: si `ClaseBase` no tiene `__slots__`, tiene un `__dict__` implícito; si `ClaseHija` tiene `__slots__`, hay conflicto de layout de memoria. Solución: o agregar `__slots__ = ()` vacío a la clase base, o eliminar `__slots__` de la subclase — no mezclar clases con y sin `__slots__` en la misma jerarquía.
206
206
  - **`property` setter que modifica un campo privado no refleja el cambio en `__repr__` generado por dataclass**: el `@property` en un dataclass crea un campo de clase que conflictúa con el campo de instancia del dataclass. Causa: `@dataclass` genera `__repr__` basado en los campos declarados en `__init__` — si el setter modifica un atributo con nombre diferente (ej: `_valor`), `__repr__` muestra el campo original sin la modificación. Solución: usar `field(init=False, repr=False)` para el campo interno y exponer solo la `property` en la interfaz pública.
207
207
  - **`assert` no es guard de invariantes en producción con `PYTHONOPTIMIZE=1` o `python -O`**: el bytecode optimizado **elimina** todos los `assert` del módulo, por lo que `assert x is not None; return x` puede retornar `None` violando el contrato `-> dict` en producción aunque pase tests en desarrollo. El test runner por defecto NO usa `-O`, por lo que el bug es invisible hasta que alguien despliega con `PYTHONOPTIMIZE=1` (configuración común para reducir memoria en imágenes Docker production). Causa: `assert` está documentado en Python como herramienta de **debugging**, no de validación. Solución: para invariantes que DEBEN cumplirse en producción, usar guard explícito con raise: `if x is None: raise HTTPException(500, "Invariante violado")` o `if x is None: raise RuntimeError(...)`. Reservar `assert` solo para tests, scripts, o pre-condiciones triviales en código de desarrollo. Regla rápida: si el assert protege un caso que activa una respuesta del usuario o un side-effect, NO es assert — es validación y debe ser `if/raise`.
208
+ - **Pasar `None` explícito a un parámetro NO activa su valor por defecto en el callee**: si una función declara `def listar(activa: bool = True)` y el caller hace `listar(activa=valor)` donde `valor` puede ser `None` (típico con parámetros opcionales que viajan entre capas: `activa: bool | None = None`), el callee recibe `None`, NO `True`. Python aplica el default SOLO cuando el argumento se **omite literalmente** en la llamada, no cuando recibe `None` explícito. Causa: el binding del parámetro usa el valor pasado, sea cual sea — el default es fallback de *ausencia*, no de *nulidad*. El bug es silencioso: pasa los tests donde el caller omite el parámetro y falla en producción cuando el cliente omite el valor y una capa intermedia lo traduce a `None` antes de propagarlo. Caso real: un endpoint que documentaba "omitir devuelve solo activos" retornaba inactivos porque el router pasaba `activa=None` al service cuyo default era `True`. Solución: normalizar en la frontera — en el caller (`listar(activa=True if valor is None else valor)`) o tratando `None` como sentinel en el callee (`if activa is None: activa = True`). Regla: cuando un valor opcional cruza de una capa a otra y el callee tiene un default distinto de `None`, convertir `None → default` explícitamente en el punto de cruce.
209
+ - **`getattr(obj, "attr", default)` tampoco aplica `default` si el atributo existe con valor `None`**: misma raíz que el gotcha anterior, otra manifestación. `getattr` usa `default` SOLO cuando el atributo **no existe**; si existe con valor `None` (caso típico: campos opcionales de Pydantic/SQLAlchemy inicializados a `None`), devuelve `None`, no `default`. Así `getattr(modelo, "campo_opcional", "X")` retorna `None` cuando `campo_opcional is None`, no `"X"`. Solución sin ambigüedad: `raw = getattr(modelo, "campo_opcional", None); valor = raw if raw is not None else "X"` (el atajo `getattr(...) or "X"` solo sirve si `""` y `0` son aceptables como falsy). Regla unificada para ambos casos: en Python el `default` —de parámetro o de `getattr`— es fallback de **ausencia**, nunca de **nulidad**; si `None` es un valor posible, normalízalo en la frontera.
210
+ - **`except A, B:` sin paréntesis es sintaxis válida en Python ≥3.14 (PEP 758), NO un SyntaxError**: desde 3.14 los paréntesis en `except`/`except*` con múltiples tipos son opcionales (`except ValueError, TypeError:` ≡ `except (ValueError, TypeError):`). Bajo intérpretes <3.14 esa forma SÍ es SyntaxError, lo que confunde a herramientas y revisores que juzgan la sintaxis contra su conocimiento general del lenguaje en vez de contra la versión objetivo del proyecto (`requires-python`/`tool.ruff.target-version` en `pyproject.toml`). Causa: la validez de la sintaxis depende de la versión, no es absoluta. Efecto colateral a vigilar: `ruff format` con `target-version = "py314"` **quita** los paréntesis redundantes y, además, reformatea el archivo COMPLETO — puede tocar `except` ajenos a tu cambio (churn no intencional); `ruff check` (lo único que suele correr el CI) conserva la forma que encuentre. Solución: antes de marcar cualquier `except A, B:` como error, leer la versión objetivo del proyecto y verificar con evidencia ejecutable (`python -c "import <módulo>"` + `ruff check`). Si se prefieren los paréntesis por portabilidad/Pyright, restaurarlos a mano y NO correr `ruff format` sobre esas líneas. Caso real (2026-06-05): un proyecto con `requires-python >=3.14` recibió un falso positivo de SyntaxError CRÍTICO por esta forma; refutado con import + ruff + CI verde.
208
211
 
209
212
  ---
210
213
 
@@ -22,7 +22,7 @@ streams locales. Los patrones del Cap 11 aplican directamente.
22
22
  ## Cuándo cargar
23
23
 
24
24
  - Crear un hook que persiste eventos en JSONL (telemetría, audit, evolución).
25
- - Diseñar un consumidor de `.planning/evolucion/*.jsonl`,
25
+ - Diseñar un consumidor de `.planning/evolution/*.jsonl`,
26
26
  `.planning/audit.jsonl`, `.planning/comms/nudges.jsonl`.
27
27
  - Diagnosticar duplicación de eventos en JSONL.
28
28
  - Decidir si un hook debe ser idempotente o si basta con append.
@@ -80,9 +80,9 @@ fuente).
80
80
 
81
81
  | Archivo | Productor | Consumidores | ¿Es event source? |
82
82
  |---|---|---|---|
83
- | `.planning/evolucion/evoluciones.jsonl` | `/swl:evolucionar`, hook `evolucion-detector` | `/swl:evolucion-estado`, dashboard | Sí |
84
- | `.planning/evolucion/nudges.jsonl` | hooks varios | `/swl:salud`, `red-team-swl` | Sí |
85
- | `.planning/evolucion/agentes.jsonl` | hook `telemetria-agentes` | `/swl:metricas` | Sí |
83
+ | `.planning/evolution/evoluciones.jsonl` | `/swl:evolucionar`, hook `evolucion-detector` | `/swl:evolucion-estado`, dashboard | Sí |
84
+ | `.planning/evolution/nudges.jsonl` | hooks varios | `/swl:salud`, `red-team-swl` | Sí |
85
+ | `.planning/evolution/agentes.jsonl` | hook `telemetria-agentes` | `/swl:metricas` | Sí |
86
86
  | `.planning/audit.jsonl` | hook `audit-trail` | auditorías, post-mortems | Sí (con Merkle) |
87
87
  | `.planning/comms/*.jsonl` | `notificador-swl` | inbox, gateway | Sí |
88
88
 
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: proceso-debate-adversarial
3
+ description: >
4
+ Protocolo de debate adversarial con jueces ciegos para decisiones técnicas
5
+ subjetivas: dos autores en frío generan candidatos, un crítico forzosamente
6
+ adversarial los ataca, un sintetizador produce un híbrido y un panel de jueces
7
+ ciegos con etiquetas aleatorizadas elige al ganador hasta convergencia.
8
+ Cargar cuando una decisión de arquitectura, diseño o estrategia no tiene
9
+ métrica objetiva y el riesgo de sycophancy (auto-aprobarse) es alto; también
10
+ cuando arquitecto-swl evalúa alternativas o /swl:predecir necesita el
11
+ protocolo de personas en frío.
12
+ version: "1.0.0"
13
+ herramientasPermitidas: [Read, Agent, Skill]
14
+ exclusiones:
15
+ - "No cargar para decisiones con métrica objetiva verificable — ahí aplica el loop de autoresearch (Verify numérico), no un debate."
16
+ - "No cargar para revisión de código post-implementación — eso es revisor-codigo-swl / nemesis-auditor-swl."
17
+ - "No cargar para decisiones triviales o de preferencia personal sin impacto técnico — el costo de 5+ invocaciones de agente no se justifica."
18
+ evolvable: true
19
+ ---
20
+ # Debate Adversarial con Jueces Ciegos
21
+
22
+ Protocolo para decidir entre alternativas técnicas **sin métrica objetiva**
23
+ eliminando los dos sesgos que arruinan las auto-evaluaciones de un LLM:
24
+ **sycophancy** (el agente aprueba lo que él mismo generó) y **position bias**
25
+ (el juez prefiere la opción presentada primero). Patrón adoptado del análisis
26
+ de autoresearch v2.1 (`reason-judge-protocol`), adaptado al ecosistema SWL.
27
+
28
+ **Principio**: el que genera nunca juzga, el que juzga nunca sabe quién generó.
29
+
30
+ ## Cuándo cargar este skill
31
+
32
+ - Decisión de arquitectura con 2+ alternativas viables sin benchmark objetivo
33
+ (ej: event sourcing vs CRUD+audit, monolito modular vs microservicios).
34
+ - `arquitecto-swl` necesita justificar un ADR con alternativas evaluadas de
35
+ forma no sesgada.
36
+ - `/swl:predecir` requiere el protocolo de aislamiento de personas.
37
+ - Decisión de producto/estrategia donde el usuario pide "dame la mejor opción"
38
+ y una sola pasada produciría la primera idea plausible, no la mejor.
39
+
40
+ ## Los 5 roles — aislamiento obligatorio (COLD START)
41
+
42
+ | Rol | Recibe | Produce | Regla dura |
43
+ |-----|--------|---------|------------|
44
+ | **Autor-A** | tarea | candidato A | No ve crítica ni candidato B |
45
+ | **Crítico** | tarea + candidato A | ≥3 debilidades con evidencia + qué haría un candidato superior | NUNCA elogia — rol puramente adversarial |
46
+ | **Autor-B** | tarea + candidato A + crítica | candidato B que resuelve la crítica preservando fortalezas de A | No ve al sintetizador |
47
+ | **Sintetizador** | tarea + A + B | candidato híbrido AB | Fusiona lo mejor de ambos, no promedia |
48
+ | **Panel de jueces** (3 default) | tarea + 3 candidatos con **etiquetas aleatorizadas** (X, Y, Z) | ranking 1°/2°/3° + justificación de un párrafo c/u | "Todos están bien" NO es veredicto válido |
49
+
50
+ **COLD START**: cada rol se ejecuta como invocación independiente del Agent
51
+ tool **sin contexto compartido de sesión** — recibe SOLO los insumos de su fila.
52
+ Pasar el historial completo a un juez invalida el protocolo (sabría quién
53
+ escribió qué).
54
+
55
+ **Aleatorización de etiquetas**: antes de invocar a los jueces, mapear
56
+ A/B/AB → X/Y/Z con orden aleatorio distinto por juez. Previene position bias.
57
+
58
+ ## El loop de convergencia
59
+
60
+ ```
61
+ Ronda N:
62
+ 1. Autor-A genera candidato (ronda 1) o presenta al incumbente (ronda N>1)
63
+ 2. Crítico ataca → ≥3 debilidades con evidencia
64
+ 3. Autor-B genera candidato alternativo
65
+ 4. Sintetizador produce híbrido AB
66
+ 5. Panel ciego vota → ganador por mayoría (empate → gana el híbrido)
67
+ 6. ¿Ganador == incumbente? → convergencia++ ; si no → convergencia = 1,
68
+ el ganador se vuelve incumbente
69
+ ```
70
+
71
+ | Condición | Acción |
72
+ |-----------|--------|
73
+ | Mismo incumbente gana 3 rondas consecutivas | **CONVERGIDO** — terminar |
74
+ | Ronda ≥ max (default 8) | **ACOTADO** — reportar mejor candidato actual |
75
+ | Incumbente cambió >5 veces en las últimas 8 rondas | **OSCILACIÓN** — detener y reportar: la tarea está mal planteada o las alternativas son equivalentes |
76
+
77
+ Registrar cada ronda con `hooks/lib/loop-telemetry.js` (tipo `debate`,
78
+ columnas `ronda, timestamp, etiqueta_ganadora, veredicto, convergencia,
79
+ descripcion`) para que la trayectoria sea auditable y `/swl:metricas` la lea.
80
+
81
+ ## Anti-herd check — obligatorio
82
+
83
+ Si TODOS los jueces coinciden en la primera ronda, el sintetizador DEBE
84
+ producir al menos un contraargumento antes de aceptar el consenso. Unanimidad
85
+ inmediata en un debate de alternativas reales es señal de herd bias, no de
86
+ calidad — las alternativas genuinas siempre tienen tradeoffs defendibles.
87
+
88
+ ## Criterios de juez por dominio
89
+
90
+ | Dominio | Criterios de evaluación |
91
+ |---------|------------------------|
92
+ | Arquitectura de software | escalabilidad, mantenibilidad, rendimiento, seguridad, simplicidad |
93
+ | Estrategia de producto | encaje de mercado, factibilidad, diferenciación, riesgo, tiempos |
94
+ | Decisión de negocio | ROI, riesgo, alineación, recursos requeridos, reversibilidad |
95
+ | Enfoque de seguridad | cobertura, tasa de falsos positivos, practicidad, cumplimiento |
96
+ | Hipótesis de investigación | testabilidad, novedad, soporte de evidencia, poder explicativo |
97
+
98
+ El juez evalúa CADA candidato contra TODOS los criterios del dominio y
99
+ produce ranking con justificación — nunca un score suelto sin comparación.
100
+
101
+ ## Personas para análisis predictivo
102
+
103
+ Cuando el objetivo es **predecir problemas de un cambio propuesto** (no
104
+ elegir entre alternativas), usar el modo personas: 5 expertos analizan EN FRÍO
105
+ el mismo cambio y un sintetizador deduplica y rankea. Definiciones completas,
106
+ preguntas guía y red flags por persona en
107
+ [recursos/personas.md](recursos/personas.md). Set default: Arquitecto de
108
+ Software, Analista de Seguridad, Ingeniero de Rendimiento, Ingeniero de
109
+ Confiabilidad, Abogado del Diablo. Set adversarial (`--adversarial`): El
110
+ Rompedor, El Tramposo, El Escalador, El Novato, El Insider Malicioso.
111
+
112
+ Ranking de hallazgos del sintetizador:
113
+ `severidad × confianza promedio × número de personas que coinciden`.
114
+
115
+ ## Integración con el ecosistema SWL
116
+
117
+ | Componente | Uso del protocolo |
118
+ |-----------|-------------------|
119
+ | `arquitecto-swl` | Debate para la sección "Alternativas consideradas" de un ADR |
120
+ | `/swl:predecir` | Modo personas pre-implementación |
121
+ | `/swl:nemesis` | El evaluator puede pedir un debate cuando dos remediaciones compiten |
122
+ | `hooks/lib/loop-telemetry.js` | Registro de rondas + handoff para encadenar |
123
+ | `/swl:metricas` | Lectura de trayectorias de debates en `.planning/loops/` |
124
+
125
+ ## Cuándo NO cargar
126
+
127
+ - La decisión tiene métrica objetiva (latencia, cobertura, bundle size) — usar
128
+ el loop autoresearch con Verify numérico; un debate es más caro y menos
129
+ preciso que medir.
130
+ - El usuario ya tomó la decisión y está documentada en ADR/vault — aplicar
131
+ `consultar-vault-primero`, no reabrir con un debate.
132
+ - Hay restricción dura que elimina las alternativas (compliance, presupuesto,
133
+ stack fijo) — verificar restricciones ANTES de armar el debate.
134
+
135
+ ## Gotchas / Errores comunes no obvios
136
+
137
+ - **Jueces con contexto contaminado**: invocar a los jueces en la misma
138
+ conversación donde se generaron los candidatos les revela la autoría por el
139
+ historial. Causa: usar el contexto principal en vez del Agent tool con
140
+ prompt acotado. Solución: cada juez es una invocación Agent independiente
141
+ cuyo prompt contiene SOLO tarea + candidatos etiquetados.
142
+ - **Crítico que "equilibra"**: el crítico señala 3 debilidades pero cierra con
143
+ "en general es un buen enfoque" — eso re-introduce sycophancy y debilita al
144
+ Autor-B. Solución: el prompt del crítico prohíbe explícitamente elogios y
145
+ exige proponer qué haría un candidato superior.
146
+ - **Sintetizador que promedia en vez de fusionar**: produce un candidato
147
+ "tibio" que toma la mitad de cada uno y pierde la coherencia interna de
148
+ ambos. Solución: el híbrido debe tener una tesis propia — tomar la
149
+ arquitectura dominante de uno e injertar mecanismos puntuales del otro.
150
+ - **Convergencia falsa por candidatos idénticos**: si Autor-B produce
151
+ esencialmente el mismo candidato que A, el panel "converge" en ronda 2 sin
152
+ exploración real. Solución: el prompt de Autor-B exige divergencia
153
+ estructural, no cosmética; si la crítica fue débil, regenerar la crítica.
154
+
155
+ ## Anti-patrones
156
+
157
+ - **Debate de 1 ronda presentado como consenso** — sin convergencia ×3 no hay
158
+ veredicto, hay una primera impresión cara.
159
+ - **Saltarse la aleatorización de etiquetas** "porque los jueces son
160
+ imparciales" — el position bias es estadístico, no intencional.
161
+ - **Usar el debate para decisiones ya cerradas** — teatro de proceso que
162
+ quema tokens para justificar lo decidido.
163
+ - **Panel de 1 juez** — un juez único reintroduce el sesgo individual que el
164
+ panel existe para diluir; mínimo 3, número impar.
@@ -0,0 +1,105 @@
1
+ # Personas para análisis predictivo y debate adversarial
2
+
3
+ Definiciones operativas de las personas que consume `proceso-debate-adversarial`
4
+ (modo personas) y `/swl:predecir`. Cada persona se invoca EN FRÍO (COLD START):
5
+ recibe la descripción del cambio + conocimiento del codebase + sus criterios —
6
+ nunca el análisis de otra persona.
7
+
8
+ Formato de hallazgo obligatorio por persona:
9
+
10
+ ```markdown
11
+ | # | Hallazgo | Severidad | Confianza (0-100%) | archivo:línea | Recomendación |
12
+ ```
13
+
14
+ Presupuesto: `max_hallazgos_por_persona = presupuesto_total / num_personas`
15
+ (default presupuesto 40 → 8 por persona). Obliga a priorizar, no a enumerar.
16
+
17
+ ---
18
+
19
+ ## Set default (análisis predictivo estándar)
20
+
21
+ ### 1. Arquitecto de Software
22
+ - **Enfoque**: diseño sistémico, fronteras de componentes, flujo de datos, escalabilidad.
23
+ - **Preguntas guía**: ¿Escala? ¿Los límites entre módulos son limpios? ¿El acoplamiento está minimizado? ¿Sobrevive un crecimiento 10x?
24
+ - **Evidencia exigida**: archivo:línea, grafo de dependencias, métricas de acoplamiento.
25
+ - **Red flags**: god classes, dependencias circulares, abstracciones con fugas, estado mutable compartido.
26
+
27
+ ### 2. Analista de Seguridad
28
+ - **Enfoque**: superficies de ataque, autenticación/autorización, protección de datos, vectores de inyección.
29
+ - **Preguntas guía**: ¿Es explotable? ¿Los trust boundaries se aplican? ¿El input se sanitiza? ¿Los secretos están protegidos?
30
+ - **Evidencia exigida**: archivo:línea + escenario de ataque concreto (no especulación teórica).
31
+ - **Red flags**: SQL crudo, authz faltante, secretos hardcodeados, input sin sanitizar.
32
+
33
+ ### 3. Ingeniero de Rendimiento
34
+ - **Enfoque**: latencia, throughput, uso de recursos, complejidad algorítmica.
35
+ - **Preguntas guía**: ¿Es suficientemente rápido? ¿Cuál es el peor caso? ¿Dónde están los cuellos de botella? ¿El caching es efectivo?
36
+ - **Evidencia exigida**: archivo:línea, análisis de complejidad, estimaciones de recursos.
37
+ - **Red flags**: queries N+1, loops sin cota, índices faltantes, I/O síncrono en hot paths.
38
+
39
+ ### 4. Ingeniero de Confiabilidad
40
+ - **Enfoque**: manejo de errores, modos de falla, observabilidad, recuperación.
41
+ - **Preguntas guía**: ¿Qué pasa cuando falla? ¿Podemos detectarlo? ¿Hay camino de recuperación? ¿Es observable?
42
+ - **Evidencia exigida**: archivo:línea, escenarios de falla, rutas de recuperación.
43
+ - **Red flags**: errores tragados, retries faltantes, sin circuit breakers, fallas silenciosas.
44
+
45
+ ### 5. Abogado del Diablo
46
+ - **Enfoque**: asunciones, casos límite, complejidad oculta, mantenibilidad.
47
+ - **Preguntas guía**: ¿Qué asunciones son falsas? ¿Qué rompe esto? ¿Está sobre-ingenierizado?
48
+ - **Evidencia exigida**: contraejemplos concretos, escenarios de caso límite.
49
+ - **Red flags**: diseño solo-happy-path, asunciones sin probar, complejidad sin justificación.
50
+
51
+ ---
52
+
53
+ ## Set adversarial (`--adversarial`)
54
+
55
+ Reemplaza al set default cuando el objetivo es estresar el cambio como atacante,
56
+ no como revisor.
57
+
58
+ ### 1. El Rompedor
59
+ Intenta crashear o corromper el sistema. Busca: estados imposibles, inputs
60
+ malformados, condiciones de carrera, límites de recursos.
61
+
62
+ ### 2. El Tramposo
63
+ Busca formas de saltarse reglas y abusar de features. Busca: validaciones solo
64
+ en frontend, límites evadibles, flujos alternos sin guards.
65
+
66
+ ### 3. El Escalador
67
+ Imagina carga 1000x y encuentra qué se rompe primero. Busca: queries sin
68
+ paginación, locks globales, colas sin backpressure, costos lineales ocultos.
69
+
70
+ ### 4. El Novato
71
+ Usa mal cada API esperando que funcione. Busca: defaults peligrosos, errores
72
+ crípticos, documentación que asume contexto, footguns de la interfaz.
73
+
74
+ ### 5. El Insider Malicioso
75
+ Tiene credenciales válidas y quiere exfiltrar. Busca: permisos excesivos,
76
+ auditoría faltante, datos sensibles accesibles lateralmente.
77
+
78
+ ---
79
+
80
+ ## Set red-team de seguridad (para `checklist-seguridad` modo cobertura)
81
+
82
+ Rotación de mentalidades para auditoría STRIDE/OWASP iterativa:
83
+
84
+ | Persona | Foco | Mentalidad |
85
+ |---------|------|-----------|
86
+ | Adversario de Seguridad | auth, crypto, inyección | atacante externo con browser + proxy de intercepción |
87
+ | Atacante de Supply Chain | dependencias, CI/CD, build pipeline | comprometer vía código de terceros |
88
+ | Amenaza Interna | acceso a datos, abuso de privilegios, exfiltración | usuario autenticado con intención maliciosa |
89
+ | Atacante de Infraestructura | red, configuración cloud, contenedores | apuntar a misconfigurations de infra |
90
+
91
+ ---
92
+
93
+ ## Protocolo de síntesis (tras el análisis individual)
94
+
95
+ 1. **Deduplicar**: mismo archivo:línea + mismo problema → fusionar, conservar la severidad más alta.
96
+ 2. **Resolver conflictos**: si dos personas discrepan → registrar el disenso, no silenciarlo.
97
+ 3. **Anti-herd check**: si TODAS las personas coinciden → el sintetizador DEBE producir ≥1 contraargumento.
98
+ 4. **Rankear**: `severidad × confianza promedio × número de personas que coinciden`.
99
+
100
+ Output del sintetizador:
101
+
102
+ ```markdown
103
+ ### Consenso — [N hallazgos tras dedup]
104
+ | # | Hallazgo | Severidad | Acuerdo | Personas origen | Acción |
105
+ ```