@saulwade/swl-ses 1.5.0 → 1.5.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.
Files changed (134) hide show
  1. package/CLAUDE.md +19 -2
  2. package/README.md +561 -561
  3. package/agentes/arquitecto-swl.md +33 -1
  4. package/agentes/nemesis-auditor-swl.md +59 -19
  5. package/bin/swl-mcp-server.js +214 -214
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/contribuir.md +233 -233
  8. package/comandos/swl/nemesis.md +230 -56
  9. package/gateway/lib/event-channel.js +191 -191
  10. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  11. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  12. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  13. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  14. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  15. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  16. package/habilidades/eval-framework/SKILL.md +212 -212
  17. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  18. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  19. package/habilidades/harness-claude-code/SKILL.md +299 -299
  20. package/habilidades/infra-github-actions/SKILL.md +166 -166
  21. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  22. package/habilidades/manejo-errores/.evolved.json +8 -8
  23. package/habilidades/meta-skills-estandar/SKILL.md +225 -1
  24. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  25. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  26. package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
  27. package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
  28. package/habilidades/node-experto/SKILL.md +105 -4
  29. package/habilidades/patrones-python/SKILL.md +229 -229
  30. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  31. package/habilidades/planear-fase/SKILL.md +319 -319
  32. package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
  33. package/habilidades/release-semver/.evolved.json +8 -8
  34. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  35. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  36. package/habilidades/tdd-workflow/SKILL.md +150 -4
  37. package/habilidades/testing-python/SKILL.md +340 -340
  38. package/habilidades/verificar-trabajo/SKILL.md +8 -3
  39. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  40. package/hooks/check-update.js +31 -3
  41. package/hooks/claudemd-bloat-detector.js +161 -161
  42. package/hooks/lib/agent-routing.js +107 -107
  43. package/hooks/lib/auto-consolidator.js +335 -335
  44. package/hooks/lib/error-classifier.js +308 -308
  45. package/hooks/lib/merkle-audit.js +96 -96
  46. package/hooks/lib/provenance-tracker.js +191 -191
  47. package/hooks/lib/rate-limit-tracker.js +253 -253
  48. package/hooks/lib/resource-quota.js +122 -122
  49. package/hooks/lib/retry-jitter.js +165 -165
  50. package/hooks/lib/security-net.js +201 -201
  51. package/hooks/lib/skill-auditor.js +588 -588
  52. package/hooks/lib/sync-status.js +228 -228
  53. package/hooks/lib/taint-tracker.js +107 -107
  54. package/hooks/lib/text-similarity.js +241 -241
  55. package/hooks/lib/toon-compressor.js +245 -245
  56. package/hooks/registro-turnos.js +209 -209
  57. package/hooks/sugerir-regenerar-inventario.js +170 -170
  58. package/hooks/validar-formato-post-subagente.js +140 -140
  59. package/hooks/validar-memoria-hook.js +218 -218
  60. package/instintos/prompt-appendices.yaml +57 -57
  61. package/manifiestos/agent-output-schemas.json +57 -57
  62. package/manifiestos/modulos.json +1324 -1321
  63. package/manifiestos/skills-lock.json +1114 -1114
  64. package/package.json +2 -2
  65. package/plantillas/auditor-veto-template.md +105 -105
  66. package/plantillas/github-workflows/README.md +47 -47
  67. package/plantillas/github-workflows/release-please.yml +44 -44
  68. package/plantillas/github-workflows/swl-ci.yml +107 -107
  69. package/plantillas/github-workflows/swl-security.yml +51 -51
  70. package/plugin.json +353 -351
  71. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  72. package/reglas/arreglar-al-detectar.md +147 -147
  73. package/reglas/fragmentos-compartidos.md +152 -152
  74. package/reglas/harness-claude-code.md +213 -213
  75. package/reglas/registro-componentes-nuevos.md +192 -0
  76. package/reglas/usar-context7.md +226 -226
  77. package/schemas/diary-entry.schema.json +80 -80
  78. package/scripts/actualizar.js +110 -1
  79. package/scripts/audit-tools/audit-history.js +330 -330
  80. package/scripts/audit-tools/bundle-tracker.js +290 -290
  81. package/scripts/audit-tools/canary-monitor.js +352 -352
  82. package/scripts/audit-tools/code-profiler.js +605 -605
  83. package/scripts/audit-tools/dep-doctor.js +320 -320
  84. package/scripts/audit-tools/env-validator.js +206 -206
  85. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  86. package/scripts/audit-tools/lib/output.js +23 -23
  87. package/scripts/audit-tools/migration-checker.js +392 -392
  88. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  89. package/scripts/benchmark-memoria.js +167 -167
  90. package/scripts/configurar-branch-protection.js +418 -418
  91. package/scripts/derivar-feature-list.js +489 -489
  92. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  93. package/scripts/doctor.js +58 -4
  94. package/scripts/field-report.js +199 -199
  95. package/scripts/generar-checklists-consolidados.js +273 -273
  96. package/scripts/generar-inventario.js +420 -420
  97. package/scripts/generar-matriz-lenguajes.js +271 -271
  98. package/scripts/lib/artefactos-python.js +43 -43
  99. package/scripts/lib/benchmark-metrics.js +160 -160
  100. package/scripts/lib/budget-enforcer.js +252 -252
  101. package/scripts/lib/configurar-ci.js +380 -380
  102. package/scripts/lib/contadores-inventario.js +217 -217
  103. package/scripts/lib/detectar-stack-detallado.js +307 -307
  104. package/scripts/lib/diary-entry.js +234 -234
  105. package/scripts/lib/eval-metrics-store.js +218 -218
  106. package/scripts/lib/eval-quality.js +171 -171
  107. package/scripts/lib/eval-schemas.js +144 -144
  108. package/scripts/lib/eval-self-correct.js +106 -106
  109. package/scripts/lib/eval-validator.js +185 -185
  110. package/scripts/lib/expandir-targets.js +71 -71
  111. package/scripts/lib/jaccard-similarity.js +98 -98
  112. package/scripts/lib/longmemeval-runner.js +125 -125
  113. package/scripts/lib/mcp_config.py +127 -0
  114. package/scripts/lib/npm-version.js +261 -261
  115. package/scripts/lib/paquetes-conocidos.js +50 -50
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/lib/toml-merge.js +204 -204
  121. package/scripts/lib/transformadores/codex.js +375 -375
  122. package/scripts/lib/transformadores/cursor.js +359 -359
  123. package/scripts/limpiar-artefactos-python.js +131 -131
  124. package/scripts/mcp-orchestrator.py +8 -18
  125. package/scripts/mcp-pool-manager.py +12 -23
  126. package/scripts/mcp-server/README.md +170 -170
  127. package/scripts/mcp-server/auth.js +105 -105
  128. package/scripts/mcp-server/cache.js +106 -106
  129. package/scripts/mcp-server/telemetry.js +78 -78
  130. package/scripts/migrar-csv-a-array.js +168 -168
  131. package/scripts/migrar-fase-dominio.js +201 -201
  132. package/scripts/publicar.js +511 -511
  133. package/scripts/run-eval.js +141 -141
  134. package/scripts/validar-userland-vacio.js +110 -110
@@ -0,0 +1,341 @@
1
+ ---
2
+ name: nemesis-redistribuir
3
+ description: >
4
+ Protocolo de redistribución del scope cuando una auditoría nemesis supera
5
+ los umbrales que saturan la context window del agente evaluator (>1500 LOC
6
+ o >5 archivos en módulos distintos). Divide el scope en sub-scopes coherentes,
7
+ ejecuta el evaluator una vez por sub-scope con ventana fresca, y consolida
8
+ los reportes parciales en un veredicto unificado. Cargar desde el comando
9
+ /swl:nemesis cuando la fase 0 detecta scope grande, antes de invocar al
10
+ agente nemesis-auditor-swl. NO cargar para scope pequeño — el bucle iterativo
11
+ Feynman+State del agente es más profundo que la redistribución.
12
+ ---
13
+
14
+ # Redistribución de scope para Nemesis
15
+
16
+ Este skill implementa el patrón "divide & consolidate" cuando una auditoría
17
+ nemesis no cabe en una sola invocación del agente evaluator. El bug que
18
+ originó esta solución es el incidente SIGM 2026-05-16 documentado en
19
+ ADR-0021: el agente saturó Sonnet 4.6 (200K context) en la pasada 4-5 de 6
20
+ con scope de ~3170 LOC.
21
+
22
+ El skill **NO sustituye** al protocolo Feynman+State del agente. Lo aplica
23
+ en porciones coherentes del codebase con context fresca por porción.
24
+
25
+ ---
26
+
27
+ ## Cuándo cargar
28
+
29
+ - El comando `/swl:nemesis` en su Fase 0 detectó que el scope supera el
30
+ umbral de redistribución.
31
+ - El usuario invocó explícitamente `/swl:nemesis --redistribuir` (forzado).
32
+ - Un componente downstream (orquestador, hook de telemetría) necesita
33
+ conocer el formato del plan de redistribución para coordinar.
34
+
35
+ ## Cuándo NO cargar
36
+
37
+ - Scope ≤ 1500 LOC y ≤ 5 archivos: el bucle iterativo normal del agente
38
+ evaluator cabe sin problema. Redistribuir aquí degrada profundidad.
39
+ - Auditoría dirigida con `--modulo` ya acotado por el usuario: respetar la
40
+ decisión explícita. NO sobre-dividir lo que el usuario ya delimitó.
41
+ - Modo `--pass1` o `--pass2` sin loop: una sola pasada cabe en cualquier
42
+ contexto razonable. La redistribución agregaría overhead sin ganancia.
43
+
44
+ ---
45
+
46
+ ## Umbrales canónicos
47
+
48
+ | Métrica | Umbral | Justificación |
49
+ |---------|--------|---------------|
50
+ | LOC totales del scope | > 1500 | El bucle Feynman+State con feed-forward llega a ~3-4× del scope en tokens hacia la pasada 4. 1500 LOC × 4 ≈ 80K tokens cabe; >1500 LOC tiende a saturar 200K en pasadas finales. |
51
+ | Archivos en módulos distintos | > 5 | Cada módulo introduce vocabulario y contratos propios; >5 dominios distintos hacen que el agente pierda foco entre pasadas. |
52
+ | Profundidad de directorios | > 4 niveles desde root | Heurística secundaria — codebases profundos suelen tener acoplamiento implícito que el agente no captura en una sola invocación. |
53
+
54
+ **Activar redistribución si CUALQUIERA de los tres umbrales se supera.** No
55
+ requerir los tres simultáneamente. Es defensa conservadora.
56
+
57
+ Los umbrales son heurísticos iniciales basados en el incidente SIGM. La
58
+ auto-evolución del skill ajustará valores con telemetría real.
59
+
60
+ ---
61
+
62
+ ## Protocolo de redistribución
63
+
64
+ ### Fase 1 — Análisis del scope
65
+
66
+ ```bash
67
+ # Contar LOC reales (excluyendo blank lines y comentarios single-line)
68
+ find <scope> -type f \( -name "*.py" -o -name "*.ts" -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.cs" -o -name "*.sql" \) | \
69
+ xargs grep -cv "^[[:space:]]*$\|^[[:space:]]*#\|^[[:space:]]*//" | \
70
+ awk -F: '{sum += $2} END {print sum}'
71
+
72
+ # Listar archivos por módulo (primer subdirectorio bajo backend/, frontend/, database/, etc.)
73
+ find <scope> -type f -name "*.py" | awk -F/ '{print $1"/"$2"/"$3}' | sort -u
74
+ ```
75
+
76
+ Producir un análisis estructurado:
77
+
78
+ ```json
79
+ {
80
+ "loc_total": 3170,
81
+ "archivos": 14,
82
+ "modulos": [
83
+ { "ruta": "backend/app/auth/", "archivos": 5, "loc": 627 },
84
+ { "ruta": "backend/app/db/", "archivos": 1, "loc": 345 },
85
+ { "ruta": "database/schemas/", "archivos": 3, "loc": 1057 },
86
+ { "ruta": "database/policies/", "archivos": 4, "loc": 775 },
87
+ { "ruta": "database/functions/", "archivos": 1, "loc": 166 }
88
+ ],
89
+ "supera_umbrales": ["loc_total", "archivos_modulos_distintos"]
90
+ }
91
+ ```
92
+
93
+ ### Fase 2 — División en sub-scopes coherentes
94
+
95
+ Reglas de división, en orden de prioridad:
96
+
97
+ 1. **Por capa arquitectónica** cuando aplica: separar `backend/`, `frontend/`,
98
+ `database/`, `mobile/`. Estas capas tienen vocabularios diferentes y el
99
+ agente trabaja mejor con una a la vez.
100
+
101
+ 2. **Por dominio de negocio** dentro de una capa: si `backend/app/` tiene
102
+ `auth/`, `pagos/`, `usuarios/` y cada uno excede 500 LOC, dividir por
103
+ dominio. Los acoplamientos cross-dominio son input para una pasada de
104
+ consolidación posterior, no para la pasada inicial.
105
+
106
+ 3. **Por archivo de tamaño excepcional**: si un solo archivo supera 1000 LOC
107
+ (caso típico de `repository.py` o `schema.sql` monolíticos), tratarlo
108
+ como sub-scope independiente. El agente puede dedicarle una pasada
109
+ completa sin distracción.
110
+
111
+ 4. **Por relación contractual**: schema SQL + funciones PL/pgSQL + policies
112
+ RLS de un mismo dominio van juntas (no se divide schema de su policy
113
+ correspondiente — el agente perdería la correlación).
114
+
115
+ Plan resultante en `.planning/audit/nemesis-plan.json`:
116
+
117
+ ```json
118
+ {
119
+ "modo": "redistribuido",
120
+ "fecha_generacion": "2026-05-16T20:00:00Z",
121
+ "scope_original": ["backend/app/auth/", "database/schemas/002-schema-usuario.sql", "database/policies/"],
122
+ "sub_scopes": [
123
+ {
124
+ "id": "sub-1-auth-backend",
125
+ "archivos": ["backend/app/auth/router.py", "backend/app/auth/service.py", "backend/app/auth/dependencies.py", "backend/app/auth/schemas.py"],
126
+ "loc": 627,
127
+ "dominio": "Autenticación backend Python",
128
+ "orden_ejecucion": 1
129
+ },
130
+ {
131
+ "id": "sub-2-auth-schema",
132
+ "archivos": ["database/schemas/002-schema-usuario.sql", "database/schemas/010-schema-token-revocacion.sql"],
133
+ "loc": 955,
134
+ "dominio": "Schema SQL usuario + tokens",
135
+ "orden_ejecucion": 2
136
+ },
137
+ {
138
+ "id": "sub-3-rls-policies",
139
+ "archivos": ["database/policies/rls.sql", "database/policies/rls-fase5.sql"],
140
+ "loc": 499,
141
+ "dominio": "Row Level Security policies",
142
+ "orden_ejecucion": 3
143
+ }
144
+ ],
145
+ "consolidacion_requerida": true,
146
+ "razon_redistribuir": "loc_total=3170 (>1500) + archivos_modulos_distintos=5 (>5)"
147
+ }
148
+ ```
149
+
150
+ ### Fase 3 — Ejecución secuencial de sub-scopes
151
+
152
+ El comando `/swl:nemesis` invoca al agente `nemesis-auditor-swl` una vez por
153
+ sub-scope, en el orden declarado en `nemesis-plan.json`. Cada invocación:
154
+
155
+ - Recibe scope acotado al sub-scope correspondiente.
156
+ - Recibe **`sub_id`** (el campo `id` del sub-scope en el plan, ej: `"sub-1-auth-backend"`).
157
+ El agente USA `sub_id` para construir la ruta de output. **Sin `sub_id`, todos
158
+ los sub-scopes escribirían al mismo `iter-N/` y se sobreescribirían entre sí.**
159
+ - Recibe `iter` (1, 2 o 3 según iteración del loop).
160
+ - Produce `.planning/audit/findings/iter-N/<sub_id>/evaluacion.json` + reporte markdown.
161
+ - Es invocación independiente con context window fresca (no acumula del
162
+ sub-scope anterior).
163
+
164
+ **Contrato del comando ↔ agente**:
165
+
166
+ ```
167
+ invocar(nemesis-auditor-swl,
168
+ scope=sub_scope.archivos,
169
+ iter=<1..3>,
170
+ sub_id=sub_scope.id)
171
+ ```
172
+
173
+ El agente DEBE respetar `sub_id` al escribir. Si recibe `null` o `undefined`,
174
+ opera en modo monolítico (escribe a `iter-N/` sin subdirectorio).
175
+
176
+ **Importante**: ejecutar **secuencialmente**, no en paralelo. La razón:
177
+
178
+ - Permite que el agente del sub-scope N+1 cargue `Skill("memoria-busqueda")`
179
+ y consulte los hallazgos del sub-scope N para detectar correlaciones.
180
+ - Evita race conditions sobre `.planning/audit/findings/`.
181
+ - Suaviza el costo de tokens (no se acumulan N invocaciones simultáneas).
182
+
183
+ Si el costo de secuencial es prohibitivo, considerar paralelo solo cuando
184
+ los sub-scopes son **arquitectónicamente independientes** (capas separadas
185
+ sin acoplamiento esperado).
186
+
187
+ ### Fase 4 — Consolidación
188
+
189
+ Tras completar todos los sub-scopes, el comando genera un `evaluacion.json`
190
+ consolidado en `.planning/audit/findings/iter-N/evaluacion.json`:
191
+
192
+ ```json
193
+ {
194
+ "schema_version": "1.0.0",
195
+ "metadata": {
196
+ "iteracion": 1,
197
+ "modo": "redistribuido",
198
+ "sub_scopes_consolidados": ["sub-1-auth-backend", "sub-2-auth-schema", "sub-3-rls-policies"]
199
+ },
200
+ "veredicto": {
201
+ "status": "NEEDS_IMPROVEMENT",
202
+ "puede_remediar_automaticamente": true
203
+ },
204
+ "hallazgos": [
205
+ /* concatenación de hallazgos de todos los sub-scopes, con prefijo de origen */
206
+ /* ej: hallazgos del sub-1 tienen id "S1-F-04", del sub-2 tienen "S2-F-04" */
207
+ ],
208
+ "correlaciones_cross_subscope": [
209
+ {
210
+ "descripcion": "F-04 (sub-1, backend no incrementa intentos_fallidos) correlaciona con S2-X (schema declara la columna). Ambos hallazgos comparten causa raíz.",
211
+ "hallazgos_ligados": ["S1-F-04", "S2-X"],
212
+ "accion_unificada": "Una sola implementación cierra ambos."
213
+ }
214
+ ]
215
+ }
216
+ ```
217
+
218
+ La consolidación es responsabilidad del comando, **no** del agente. El skill
219
+ provee el formato; el comando lo materializa con datos reales de los sub-scopes.
220
+
221
+ ### Detección de correlaciones cross-subscope
222
+
223
+ Tras consolidar, el comando puede invocar al agente `nemesis-auditor-swl` con
224
+ **scope mínimo** (~50 LOC seleccionados estratégicamente) y skills
225
+ `memoria-busqueda` + el reporte consolidado como input, para que detecte
226
+ correlaciones cross-subscope que ninguna pasada individual encontró.
227
+
228
+ Esta "pasada de consolidación" es opcional. Se activa cuando:
229
+
230
+ - Hay 2+ sub-scopes con hallazgos.
231
+ - Los sub-scopes comparten al menos un identificador (tabla, función, módulo
232
+ importado) — detectable con grep simple.
233
+ - El presupuesto de tokens lo permite.
234
+
235
+ ---
236
+
237
+ ## Formato del plan persistido
238
+
239
+ `.planning/audit/nemesis-plan.json` — schema:
240
+
241
+ ```typescript
242
+ interface NemesisPlan {
243
+ schema_version: "1.0.0";
244
+ modo: "redistribuido";
245
+ fecha_generacion: string; // ISO 8601
246
+ scope_original: string[]; // paths
247
+ sub_scopes: SubScope[];
248
+ consolidacion_requerida: boolean;
249
+ razon_redistribuir: string; // formato "metrica1=valor1 (>umbral1) + ..."
250
+ }
251
+
252
+ interface SubScope {
253
+ id: string; // "sub-N-<dominio-kebab>"
254
+ archivos: string[]; // paths relativos
255
+ loc: number;
256
+ dominio: string; // descripción humana
257
+ orden_ejecucion: number; // 1-indexed
258
+ consolidacion_inputs?: string[]; // ids de sub-scopes cuyos hallazgos
259
+ // este sub-scope debe considerar como contexto
260
+ }
261
+ ```
262
+
263
+ ---
264
+
265
+ ## Reglas estrictas
266
+
267
+ ### Trazabilidad
268
+
269
+ Cada hallazgo del reporte consolidado DEBE incluir prefijo del sub-scope de
270
+ origen (`S1-F-04`, `S2-F-12`). Permite al implementador:
271
+
272
+ - Localizar el reporte parcial original.
273
+ - Reproducir el sub-scope con `/swl:nemesis --pass1 --modulo <archivos del sub-scope>`.
274
+ - Auditar qué porción del codebase produjo qué hallazgos.
275
+
276
+ ### Registro en nudges.jsonl
277
+
278
+ El comando DEBE registrar la activación del modo redistribuido en
279
+ `.planning/nudges.jsonl`:
280
+
281
+ ```json
282
+ {
283
+ "timestamp": "2026-05-16T20:00:00Z",
284
+ "tipo": "nemesis-redistribuido",
285
+ "mutation_category": "scope-adaptation",
286
+ "risk_level": "low",
287
+ "razon": "loc_total=3170 (>1500)",
288
+ "sub_scopes": 3,
289
+ "archivo_plan": ".planning/audit/nemesis-plan.json"
290
+ }
291
+ ```
292
+
293
+ Esto alimenta la telemetría de auto-evolución (regla `memoria-consolidada.md
294
+ § Estado del sistema y observabilidad`) y permite ajustar umbrales con datos
295
+ reales.
296
+
297
+ ### Idempotencia
298
+
299
+ Si `.planning/audit/nemesis-plan.json` ya existe y `--continue` fue pasado al
300
+ comando, reutilizar el plan existente. NO regenerar — la división puede
301
+ diferir si el filesystem cambió entre invocaciones y eso rompe la trazabilidad.
302
+
303
+ Para forzar regeneración del plan: `/swl:nemesis --redistribuir --reset-plan`.
304
+
305
+ ---
306
+
307
+ ## Anti-patrones
308
+
309
+ - **Dividir scope ya pequeño**: aplicar redistribución a < 1500 LOC introduce
310
+ overhead sin ganancia. Verificar umbrales antes de cargar este skill.
311
+
312
+ - **Dividir por número de archivos sin coherencia**: 10 archivos del mismo
313
+ módulo NO requieren división — el agente puede manejarlos juntos. El criterio
314
+ es **dominios diferentes**, no archivos totales.
315
+
316
+ - **Paralelizar sub-scopes con acoplamiento implícito**: ejecutar `auth/` y
317
+ `db/tenant.py` en paralelo pierde la correlación que el agente vería en
318
+ ejecución secuencial. Solo paralelo cuando los sub-scopes son
319
+ arquitectónicamente independientes.
320
+
321
+ - **Olvidar la pasada de consolidación**: si la auditoría es de un sistema
322
+ con alto acoplamiento entre módulos (auth + RLS + tokens), omitir la
323
+ consolidación deja hallazgos cross-módulo sin detectar. Tablar la decisión
324
+ en el plan.
325
+
326
+ - **Trazabilidad perdida**: emitir hallazgos consolidados sin prefijo `SN-`.
327
+ Cuando el implementador quiera reproducir un hallazgo específico, no sabrá
328
+ qué sub-scope re-auditar.
329
+
330
+ ---
331
+
332
+ ## Referencias
333
+
334
+ - ADR-0021: `nemesis-evaluator-optimizer` — define el modo redistribuido como
335
+ parte del patrón completo.
336
+ - `reglas/analisis-previo-tareas-grandes.md`: aplica el mismo principio
337
+ "dividir antes de implementar" pero a auditoría en lugar de implementación.
338
+ - `reglas/seguridad-agentes.md § Anti-fallback silencioso`: justifica registrar
339
+ la activación de redistribución como nudge explícito, no como decisión
340
+ silenciosa.
341
+ - Incidente SIGM 2026-05-16: caso real que motivó los umbrales canónicos.
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: node-experto
3
3
  description: Node.js y TypeScript backend moderno. Cubre patterns de Express/Fastify/NestJS, error handling middleware, streams y buffers, worker threads y clustering, Prisma/Drizzle ORM, validación con Zod, graceful shutdown y anti-patrones críticos como callback hell y event loop blocking.
4
- version: "1.0.1"
4
+ version: "1.0.2"
5
5
  evolved: true
6
- evolved-from: "1.0.0"
7
- evolved-at: "2026-05-14"
6
+ evolved-from: "1.0.1"
7
+ evolved-at: "2026-05-16"
8
8
  evolved-by: "aprender"
9
- evolved-note: "Gotcha req.destroy() vs req.pause() en handlers HTTP nativos origen PR #13 webhook-server"
9
+ evolved-note: "v1.0.1: gotcha req.destroy() + CRLF + TOML literal. v1.0.2: gotchas `undefined<N` (NaN comparison), first-wins vs deep merge en config (PR #27 #30 v1.5.2), `assert.notMatch` no existe."
10
10
  herramientasPermitidas: [Read, Write, Glob]
11
11
  exclusiones:
12
12
  - "No cargar para aplicaciones Next.js con App Router — Next.js tiene patrones de Server Components, SSR y caché que difieren del backend Node puro; cargar `nextjs-experto`."
@@ -121,6 +121,9 @@ export class NotFoundError extends AppError {
121
121
  | `promise.then()` sin `.catch()` | asyncHandler o try/catch |
122
122
  | `require()` dinámico en hot path | Importar al inicio |
123
123
  | Operaciones síncronas de fs en requests | `fs.promises.*` |
124
+ | **Comparación `undefined < N`** evalúa `false` silenciosamente (NaN) | `(x \|\| 0) < N` con fallback explícito |
125
+ | **First-wins en config merging** cuando el consumidor hace deep merge | Replicar deep merge del consumidor (override por key, env mergeado por sub-clave) |
126
+ | **`assert.notMatch`** no existe en `node:assert` | Usar `assert.doesNotMatch` (con minúscula en "Match") |
124
127
 
125
128
  ```typescript
126
129
  // MAL: bloquea el event loop
@@ -130,6 +133,93 @@ const hash = crypto.pbkdf2Sync(pass, salt, 100000, 64, 'sha512');
130
133
  const hash = await pool.run({ pass, salt });
131
134
  ```
132
135
 
136
+ ### Gotcha: `undefined < N` evalúa a false (NaN comparison)
137
+
138
+ ```javascript
139
+ // MAL — el branch nunca dispara porque `notificado` puede ser undefined
140
+ const data = JSON.parse(readFileSync(flagPath, 'utf8'));
141
+ if (data.hayNueva && data.notificado < 2) {
142
+ emitirAviso();
143
+ data.notificado = (data.notificado || 0) + 1; // ← inconsistente: defensive aquí pero no arriba
144
+ writeFileSync(flagPath, JSON.stringify(data));
145
+ }
146
+
147
+ // BIEN — defense-in-depth con fallback explícito en ambos sitios
148
+ const data = JSON.parse(readFileSync(flagPath, 'utf8'));
149
+ const actual = data.notificado || 0; // fallback explícito al leer
150
+ if (data.hayNueva && actual < 2) {
151
+ emitirAviso();
152
+ data.notificado = actual + 1;
153
+ writeFileSync(flagPath, JSON.stringify(data));
154
+ }
155
+
156
+ // MEJOR aún — inicializar el campo al escribir para que nunca sea undefined
157
+ function registrarCheck(local, remota, hayNueva) {
158
+ writeFileSync(flagPath, JSON.stringify({
159
+ timestamp: Date.now(),
160
+ local,
161
+ remota,
162
+ hayNueva,
163
+ notificado: 0, // ← inicialización explícita
164
+ }));
165
+ }
166
+ ```
167
+
168
+ **Por qué importa**: bugs de comparación con `undefined` son **silenciosos** (no
169
+ tiran error). El código se ve sintácticamente correcto y los tests sin
170
+ escenario adversarial pasan. Detectados típicamente solo cuando un usuario
171
+ reporta que "el aviso nunca aparece" — investigación cuesta horas.
172
+
173
+ ### Gotcha: first-wins vs deep merge en config
174
+
175
+ Cuando un sistema carga config desde múltiples archivos (`base.json` +
176
+ `overrides.json` + `local.json`) y el **consumidor** del config hace deep
177
+ merge (ejemplo: Claude Code con `settings.json` + `settings.local.json`), el
178
+ loader interno **DEBE** replicar la misma semántica:
179
+
180
+ ```javascript
181
+ // MAL — first-wins descarta info silenciosamente
182
+ function cargarConfig(paths) {
183
+ for (const p of paths) {
184
+ if (fs.existsSync(p)) {
185
+ const data = JSON.parse(fs.readFileSync(p, 'utf8'));
186
+ if (data.servers && Object.keys(data.servers).length) {
187
+ return data.servers; // ← primer archivo con servers !== {} gana, resto invisible
188
+ }
189
+ }
190
+ }
191
+ return {};
192
+ }
193
+
194
+ // BIEN — deep merge replicando el consumidor canónico
195
+ function cargarConfig(paths) {
196
+ // Orden: base → override por key. Último gana en escalares;
197
+ // merge profundo en sub-objects (env, etc.).
198
+ const merged = {};
199
+ for (const p of paths) {
200
+ if (!fs.existsSync(p)) continue;
201
+ const data = JSON.parse(fs.readFileSync(p, 'utf8'));
202
+ for (const [nombre, cfg] of Object.entries(data.servers || {})) {
203
+ if (merged[nombre]) {
204
+ merged[nombre] = {
205
+ ...merged[nombre],
206
+ ...cfg,
207
+ env: { ...(merged[nombre].env || {}), ...(cfg.env || {}) },
208
+ };
209
+ } else {
210
+ merged[nombre] = cfg;
211
+ }
212
+ }
213
+ }
214
+ return merged;
215
+ }
216
+ ```
217
+
218
+ **Regla**: si el archivo de config tiene múltiples capas que el consumidor
219
+ mergea, el loader interno NUNCA usa first-wins. Replicar la semántica del
220
+ consumidor evita confusiones donde override parcial (solo `env` en local)
221
+ "rompe" la config completa.
222
+
133
223
  ---
134
224
 
135
225
  ## Checklist Node.js
@@ -272,6 +362,17 @@ if (require.main === module) {
272
362
 
273
363
  **`req.destroy()` en un handler `http` cierra el socket antes de poder enviar la respuesta**: cuando se aborta la lectura del body (por ejemplo al exceder `maxPayloadBytes`), llamar `req.destroy()` en el listener `data` cierra el socket inmediatamente. El subsiguiente `res.writeHead(413); res.end()` del handler falla porque ya no hay socket — el cliente recibe `ECONNRESET` en lugar de 413. Causa: `destroy()` no espera al flush de la respuesta pendiente; equivale a un `RST` TCP. Fix: usar `req.pause()` + un flag `abortado = true` + rechazar la promesa de lectura; dejar que el handler envíe `res.writeHead(413); res.end(...)` normalmente. Node cierra el socket por sí solo tras `res.end()`. Aplica a servidores HTTP nativos sin Express/Fastify, donde el control del body es manual.
274
364
 
365
+ **Regex multi-line con `\n` falla con archivos CRLF de Windows**: un parser que usa `/^---\n([\s\S]*?)\n---\n/` para detectar frontmatter YAML rompe con archivos commiteados en Windows (donde git aplica `core.autocrlf=true` y convierte LF↔CRLF al checkout). Caso real (Sub-fase 11.5 v1.5.1, swl-ses): `parsearFrontmatter` en `scripts/lib/transformadores/base.js` devolvía `frontmatter:{}` y `cuerpo:<archivo entero>` para los 60 agentes `agentes/*.md` cuando se ejecutaba en Windows. Resultado downstream: `transformarAgente` de Codex generaba TOML con `description = ""` y `developer_instructions` con el frontmatter duplicado dentro del body. Bug latente que solo aparece al ejecutar contra archivos reales con CRLF. Fix: normalizar antes del match — `const norm = String(contenido).replace(/\r\n/g, '\n').replace(/\r/g, '\n');` y aplicar regex sobre `norm`. Siempre asumir que cualquier archivo leído de filesystem en Windows puede tener CRLF, incluso si git lo "almacena" como LF. Aplica a TODA función con regex multi-line que reciba contenido de filesystem.
366
+
367
+ **Serializar TOML zero-deps: preferir `'''literal'''` sobre `"""basic"""` para multi-line con backslashes**: TOML soporta dos sintaxis multi-line — basic `"""..."""` que procesa escapes (`\n`, `\t`, `\\`) y literal `'''...'''` que preserva el contenido tal cual. Caso real (Sub-fase 11 v1.5.1, swl-ses): al serializar el cuerpo Markdown de agentes SWL (con regex `\n`, paths Windows con `\`, etc.) a `developer_instructions` de `~/.codex/agents/*.toml`, usar basic `"""..."""` exigía escapar cada `\` a `\\` (laborioso y propenso a bugs). Solución: usar literal `'''...'''` por default (preserva Markdown crudo) y caer a basic `"""..."""` con escape SOLO si el cuerpo contiene `'''` literal (raro). Helper en `scripts/lib/transformadores/codex.js`:
368
+ ```js
369
+ _tomlMultiline(s) {
370
+ if (!s.includes("'''")) return `'''\n${s}\n'''`;
371
+ return `"""\n${s.replace(/\\/g, '\\\\').replace(/"""/g, '\\"""')}\n"""`;
372
+ }
373
+ ```
374
+ Aplica a cualquier serialización TOML manual sin librería (`@iarna/toml`, etc.) donde el contenido es texto rico con caracteres especiales.
375
+
275
376
  ```js
276
377
  // MAL — ECONNRESET en cliente, nunca recibe 413
277
378
  req.on('data', chunk => {