@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
@@ -0,0 +1,7 @@
1
+ {
2
+ "evolved": true,
3
+ "evolved-from": "1.8.0",
4
+ "evolved-at": "2026-06-04",
5
+ "evolved-by": "evolucionar",
6
+ "evolved-note": "PE-010 sección nueva 'Tablas append-only — excepción documentada a audit columns nullable=False'. Patrón portable a logs estructurados, audit trails, event sourcing, telemetría. Origen: OIC v1.5 Slice 1 2026-06-04 (BitacoraError)."
7
+ }
@@ -274,6 +274,71 @@ Documentar en un ADR qué patrón se usa y por qué.
274
274
 
275
275
  ---
276
276
 
277
+ ## Tablas append-only — excepción documentada a "audit columns nullable=False"
278
+
279
+ Algunas tablas son **append-only por diseño**: solo se crean filas (INSERT) y
280
+ eventualmente se eliminan por purga (DELETE). Nunca se actualizan. Ejemplos
281
+ típicos: logs de errores (`bitacora_error`, `error_log`), audit trails inmutables,
282
+ telemetría, métricas históricas, event sourcing event store, outbox pattern.
283
+
284
+ Para estas tablas la regla general del proyecto "`created_by`/`updated_by`
285
+ `nullable=False` en tablas transaccionales" **NO aplica**. Documentar la
286
+ excepción explícitamente:
287
+
288
+ 1. En el **docstring de la clase modelo** (Python/Java/C#):
289
+
290
+ ```python
291
+ class BitacoraError(Base):
292
+ """
293
+ Registro inmutable de un error backend o frontend.
294
+
295
+ Tabla append-only: solo se crean filas (INSERT) y se purgan por antigüedad
296
+ (DELETE). Nunca se actualizan. Esta invariante es fundamental para que el
297
+ log de errores sea un audit trail confiable.
298
+
299
+ Excepción a regla "audit columns nullable=False":
300
+ La convención del proyecto exige `created_by` y `updated_by` NOT NULL
301
+ en tablas transaccionales. BitacoraError queda exenta porque:
302
+ (a) Es un log del sistema, no una entidad de negocio modificable.
303
+ (b) No tiene `updated_at` ni `updated_by` — no hay concepto de
304
+ "última modificación" en una tabla append-only.
305
+ (c) `usuario_id` es nullable de forma intencional: los errores de
306
+ sesiones no autenticadas (401 antes del login) no tienen usuario.
307
+ """
308
+ ```
309
+
310
+ 2. En el **comentario del DDL SQL**:
311
+
312
+ ```sql
313
+ COMMENT ON TABLE bitacora_error IS
314
+ 'Errores backend y frontend persistidos para diagnóstico. '
315
+ 'Append-only: solo INSERT y DELETE (purga). '
316
+ 'Sin updated_at/updated_by por diseño (excepción documentada en CONTEXTO).';
317
+ ```
318
+
319
+ 3. En el **GRANT** del rol de aplicación, omitir explícitamente `UPDATE`:
320
+
321
+ ```sql
322
+ -- INSERT: para que el sistema cree entradas.
323
+ -- SELECT: para que el admin las consulte.
324
+ -- DELETE: para que el job de purga elimine antiguas.
325
+ -- NO UPDATE: la tabla es append-only por diseño.
326
+ GRANT SELECT, INSERT, DELETE ON bitacora_error TO ine_user;
327
+ ```
328
+
329
+ **Criterios para considerar una tabla append-only legítima**:
330
+
331
+ - Toda fila representa un evento ocurrido en un momento específico (immutable in time).
332
+ - No hay caso de negocio que requiera modificar la fila después de creada.
333
+ - La única forma de "corregir" un dato erróneo es insertar una fila correctora
334
+ posterior (event sourcing) o purgar y re-insertar (telemetría).
335
+ - El borrado se hace por política de retención (antigüedad), no por revocación.
336
+
337
+ **Origen del patrón**: OIC v1.5 Slice 1 (2026-06-04). Patrón portable a cualquier
338
+ proyecto con logs estructurados, audit trails o event sourcing.
339
+
340
+ ---
341
+
277
342
  ## Split modular con compositor por herencia múltiple
278
343
 
279
344
  Cuando un módulo backend (router, service, repository) supera ~1500 LOC en un
@@ -40,7 +40,7 @@ Los skills generados por `auto-evolucion-swl` o `/swl:evolucionar`:
40
40
  `agentes/auto-evolucion-swl.md` sección "Gate G8". Origen ADR 0013
41
41
  sección 3C.
42
42
  - La promoción se registra en `.planning/AUDITORIA.md` con justificación
43
- Y en `.planning/evolucion/evoluciones.jsonl` con evento
43
+ Y en `.planning/evolution/evoluciones.jsonl` con evento
44
44
  `tipo: "promocion-skill"` y `score`.
45
45
 
46
46
  ### Reglas nuevas obligatorias
@@ -15,7 +15,7 @@ regla clara, un mismo dato puede terminar en el lugar incorrecto o duplicado.
15
15
  | 2 | **`.planning/APRENDIZAJES.md`** | Markdown con entradas `### [YYYY-MM-DD] Título` agrupadas por sección | Conocimiento del dominio (anti-patrones, patrones, decisiones, gotchas) |
16
16
  | 3 | **`instintos/proyecto.yaml` / `global.yaml` / `perfil-usuario.yaml`** | YAML estructurado con confidence, scope, evidence_count | Patrones aprendidos inductivamente, con promoción/degradación |
17
17
  | 4 | **`.planning/sessions/search-index.json`** | Índice FTS de sesiones pasadas | Búsqueda histórica de trabajo (qué se hizo, cuándo) |
18
- | 5 | **`.planning/evolucion/*`** | JSONL (nudges, agentes) + JSON (métricas, alertas, evoluciones) | Estado del ciclo de auto-evolución |
18
+ | 5 | **`.planning/evolution/*`** | JSONL (nudges, agentes) + JSON (métricas, alertas, evoluciones) | Estado del ciclo de auto-evolución |
19
19
 
20
20
  ---
21
21
 
@@ -44,11 +44,11 @@ regla clara, un mismo dato puede terminar en el lugar incorrecto o duplicado.
44
44
 
45
45
  | Tipo de dato | Canal correcto |
46
46
  |---|---|
47
- | Nudge emitido por un hook | 5 (`.planning/evolucion/nudges.jsonl`) |
48
- | Evento de subagente terminado | 5 (`.planning/auto-evolucion/agentes.jsonl`) |
49
- | Evolución aplicada/revertida a skill | 5 (`.planning/evolucion/evoluciones.jsonl`) |
50
- | Alerta persistente (nudges ignorados) | 5 (`.planning/evolucion/alertas-persistentes.json`) |
51
- | Métricas agregadas (health score) | 5 (`.planning/evolucion/metricas.json`) |
47
+ | Nudge emitido por un hook | 5 (`.planning/evolution/nudges.jsonl`) |
48
+ | Evento de subagente terminado | 5 (`.planning/auto-evolution/agentes.jsonl`) |
49
+ | Evolución aplicada/revertida a skill | 5 (`.planning/evolution/evoluciones.jsonl`) |
50
+ | Alerta persistente (nudges ignorados) | 5 (`.planning/evolution/alertas-persistentes.json`) |
51
+ | Métricas agregadas (health score) | 5 (`.planning/evolution/metricas.json`) |
52
52
  | Traza de ejecución de agente | `.planning/traces/` (OTLP-lite) |
53
53
  | Audit trail inmutable | `.planning/audit.jsonl` + Merkle |
54
54
 
@@ -125,7 +125,7 @@ conocido, datos en canales deprecados.
125
125
 
126
126
  - **Corrección del usuario escrita a APRENDIZAJES.md** → debe ir al perfil
127
127
  - **Anti-patrón técnico escrito al perfil del usuario** → debe ir a APRENDIZAJES
128
- - **Métrica persistida en APRENDIZAJES.md** → debe ir a `.planning/evolucion/`
128
+ - **Métrica persistida en APRENDIZAJES.md** → debe ir a `.planning/evolution/`
129
129
  - **Secreto/credencial en cualquier canal** → bloqueo inmediato (`privacy-memoria`)
130
130
  - **Dato duplicado en proyecto.yaml y global.yaml** → uno debe borrarse
131
131
  (promover a global SOLO cuando confidence > 0.8 Y validado cross-proyecto)
@@ -0,0 +1,7 @@
1
+ {
2
+ "evolved": true,
3
+ "evolved-from": "1.8.0",
4
+ "evolved-at": "2026-06-04",
5
+ "evolved-by": "evolucionar",
6
+ "evolved-note": "PE-003 (verificación de flags antes de asumir servicios externos LDAP/Redis/S3) + PE-004 (sanitización PII centralizada en handlers) + PE-007 (generación de passwords legibles sin chars ambiguos). Origen: OIC v1.5 2026-06-04, bug histórico manuel.monteagudo + Slice 2 v1.5 Observabilidad."
7
+ }
@@ -141,6 +141,147 @@ vulnerabilidad del OWASP Top 10. Las más frecuentes en este stack:
141
141
 
142
142
  ---
143
143
 
144
+ ## Verificación obligatoria de flags de configuración antes de asumir servicios externos
145
+
146
+ Toda rama de código que asuma un servicio externo opcional (LDAP, Redis, S3, SMTP,
147
+ OIDC, Auth0, etc.) DEBE verificar el flag de configuración del proyecto ANTES de
148
+ tomar la decisión. Sin esta verificación, una configuración no esperada deja a los
149
+ usuarios atrapados en flujos rotos sin error visible.
150
+
151
+ **Anti-patrón** (caso real OIC 2026-06-04, dos bugs históricos en el mismo flujo):
152
+
153
+ ```python
154
+ # MAL — asume LDAP siempre habilitado
155
+ if rol == "ADMIN":
156
+ hashed = hash_password(body.password)
157
+ else:
158
+ # "Para usuarios LDAP, no se almacena contraseña funcional local."
159
+ hashed = hash_password(uuid.uuid4().hex)
160
+ # ↑ Si AUTH_LDAP_ENABLED=false (caso real), el usuario queda con un hash
161
+ # UUID dummy que ninguna password real puede igualar → 401 perpetuo.
162
+ ```
163
+
164
+ ```python
165
+ # MAL — rechaza no-ADMIN sin verificar si LDAP existe siquiera
166
+ if rol != "ADMIN":
167
+ raise HTTPException(400, "Los usuarios no ADMIN gestionan su contraseña en LDAP")
168
+ # ↑ Sin LDAP habilitado los usuarios no tienen forma de cambiar password.
169
+ ```
170
+
171
+ **Patrón correcto**:
172
+
173
+ ```python
174
+ # BIEN — siempre verifica el flag antes de asumir el servicio
175
+ if rol == "ADMIN":
176
+ hashed = hash_password(body.password)
177
+ else:
178
+ if body.password:
179
+ hashed = hash_password(body.password)
180
+ elif not settings.AUTH_LDAP_ENABLED:
181
+ # Sin LDAP: generar password funcional para que el usuario pueda entrar
182
+ temp = _generar_temp_password()
183
+ hashed = hash_password(temp)
184
+ # devolver `temp` en la respuesta del POST UNA SOLA VEZ
185
+ else:
186
+ # LDAP habilitado: dummy OK porque el usuario se autentica fuera
187
+ hashed = hash_password(uuid.uuid4().hex)
188
+ ```
189
+
190
+ **Detección temprana en code review**: cualquier comentario `"para LDAP"`,
191
+ `"para Redis"`, `"para S3"` debe acompañarse del check del flag correspondiente.
192
+ Si el comentario está solo, es bug latente — el código asume un mundo que el
193
+ deployment puede no tener.
194
+
195
+ ---
196
+
197
+ ## Sanitización PII centralizada en handlers vs refactorear N sitios
198
+
199
+ Cuando un sistema persiste logs estructurados (audit trails, error logs,
200
+ telemetría) y existen N sitios donde el código emite `logger.error(..., extra={...})`
201
+ con potencial PII en los valores, **siempre preferir sanitización centralizada en
202
+ el handler** sobre refactorear cada sitio individualmente.
203
+
204
+ **Decisión arquitectónica** (caso OIC v1.5 Slice 2, 2026-06-04):
205
+
206
+ | Opción | Esfuerzo | Cobertura futura | Mantenimiento |
207
+ |---|---|---|---|
208
+ | Refactorear N sitios uno por uno | O(N) turnos | Solo lo refactorizado | Cada `logger.error` nuevo requiere auditoría |
209
+ | Sanitización centralizada en handler | O(1) turno | Todos los sitios + futuros automáticamente | Cero auditoría manual al agregar nuevos `logger.error` |
210
+
211
+ **Patrón portable**:
212
+
213
+ ```python
214
+ import re
215
+
216
+ _PATRON_SENSIBLE = re.compile(
217
+ r'(password|token|secret|jwt|authorization|cookie|api[_-]?key)([=:])([^&\s"\']+)',
218
+ re.IGNORECASE,
219
+ )
220
+
221
+ def sanitizar_url(url: str | None) -> str | None:
222
+ """Redacta query-params sensibles preservando key y separador."""
223
+ if not url:
224
+ return url
225
+ return _PATRON_SENSIBLE.sub(r"\1\2<redacted>", url)
226
+
227
+ def sanitizar_valor(valor: Any) -> Any:
228
+ """Sanitiza recursivamente strings dentro de dict/list/tuple."""
229
+ if isinstance(valor, str):
230
+ return _PATRON_SENSIBLE.sub(r"\1\2<redacted>", valor)
231
+ if isinstance(valor, dict):
232
+ return {k: sanitizar_valor(v) for k, v in valor.items()}
233
+ if isinstance(valor, (list, tuple)):
234
+ return [sanitizar_valor(item) for item in valor]
235
+ return valor
236
+ ```
237
+
238
+ **Regla del regex**:
239
+ - Grupo 1 (key) y grupo 2 (`=` o `:`) se preservan → mantiene legibilidad
240
+ (`password=<redacted>` en vez de `<redacted>`).
241
+ - Grupo 3 (valor) se reemplaza por `<redacted>`.
242
+ - Boundary del valor: `&`, whitespace, comilla, paréntesis.
243
+ - `api_key` y `api-key` ambos matchean con `api[_-]?key`.
244
+
245
+ **Aplicabilidad**: handlers de logging custom, middleware HTTP que redactan headers,
246
+ sanitización de cookies en logs, redactor de stack traces, error reporters
247
+ (Sentry-style). Defensa en profundidad sin tocar el código del caller.
248
+
249
+ **Limitación documentada**: el regex opera sobre strings; NO sanitiza claves cuyo
250
+ nombre no esté en la lista (no detecta `contraseña` en español, `pwd`, `mot_de_passe`).
251
+ Mantener el regex extensible vía configuración si el proyecto opera en múltiples idiomas.
252
+
253
+ ---
254
+
255
+ ## Generación de tokens y passwords legibles (sin caracteres ambiguos)
256
+
257
+ Para passwords temporales (reset por admin, primer login, recuperación) que el
258
+ usuario debe transcribir verbalmente o desde papel, usar alfabeto SIN caracteres
259
+ ambiguos: `0` (cero) vs `O` (o mayúscula), `1` (uno) vs `l` (L minúscula) vs `I`
260
+ (i mayúscula).
261
+
262
+ ```python
263
+ import secrets
264
+
265
+ # 54 chars del alfabeto seguro (sin 0, O, 1, l, I)
266
+ _ALFABETO_LEGIBLE = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789"
267
+
268
+ def generar_password_temporal(longitud: int = 12) -> str:
269
+ """12 chars → ~71 bits de entropía. Suficiente para temporal que debe
270
+ cambiarse en primer login. NUNCA persistir en texto plano; el caller
271
+ la hashea con bcrypt y la devuelve UNA SOLA VEZ en la respuesta del POST."""
272
+ return "".join(secrets.choice(_ALFABETO_LEGIBLE) for _ in range(longitud))
273
+ ```
274
+
275
+ **NO usar** `secrets.token_urlsafe()` para passwords visibles al humano: incluye
276
+ `-` y `_` que pueden confundir al transcribir. Reservar `token_urlsafe()` para
277
+ tokens de sesión, CSRF, magic links — strings que no se leen ni transcriben.
278
+
279
+ **Para tokens de uso programático** (API keys, session IDs, refresh tokens):
280
+ `secrets.token_urlsafe(32)` (32 bytes → 256 bits) o `secrets.token_hex(32)`. Sin
281
+ restricción de alfabeto porque el usuario nunca los teclea manualmente.
282
+
283
+ ---
284
+
144
285
  ## Checklist antes de merge a main
145
286
 
146
287
  - [ ] No hay secrets hardcodeados (revisar con `git grep -i "password\|secret\|token\|key"`)
@@ -149,3 +290,6 @@ vulnerabilidad del OWASP Top 10. Las más frecuentes en este stack:
149
290
  - [ ] Sin uso de `eval`, `exec`, `shell=True`
150
291
  - [ ] Logs sin datos sensibles
151
292
  - [ ] Dependencias nuevas auditadas
293
+ - [ ] Cualquier rama que asuma servicio externo (LDAP/Redis/S3) verifica su flag de config
294
+ - [ ] Sanitización PII centralizada en handlers (no diseminada por N sitios)
295
+ - [ ] Passwords temporales generadas con alfabeto sin chars ambiguos
@@ -96,7 +96,7 @@ node scripts/auditar-claudemd.js
96
96
  - **Auditor síncrono**: `scripts/auditar-claudemd.js` (dimensión 7,
97
97
  severidad WARN, no bloquea).
98
98
  - **Hook async**: `hooks/claudemd-duplicacion-detector.js` (PostToolUse,
99
- emite nudge a `.planning/evolucion/nudges.jsonl`).
99
+ emite nudge a `.planning/evolution/nudges.jsonl`).
100
100
  - **Comando**: `/swl:claudemd audit` reporta el conteo; `/swl:claudemd
101
101
  refactor` propone el reemplazo concreto.
102
102
 
@@ -34,7 +34,7 @@ try {
34
34
 
35
35
  var RAIZ_AGENTES = path.resolve(__dirname, '..', 'agentes');
36
36
  var DESTINO_PLANNING = path.resolve(
37
- __dirname, '..', '.planning', 'evolucion', 'audit-agentes-gaps.json'
37
+ __dirname, '..', '.planning', 'evolution', 'audit-agentes-gaps.json'
38
38
  );
39
39
 
40
40
  // Patrones - soportan LF y CRLF (Windows).
@@ -11,7 +11,7 @@
11
11
  * Uso:
12
12
  * node scripts/auditar-cobertura-frameworks.js # JSON a stdout
13
13
  * node scripts/auditar-cobertura-frameworks.js --resumen # tabla a stderr
14
- * node scripts/auditar-cobertura-frameworks.js --save # persiste en .planning/evolucion/
14
+ * node scripts/auditar-cobertura-frameworks.js --save # persiste en .planning/evolution/
15
15
  * node scripts/auditar-cobertura-frameworks.js --framework=nist_csf # solo un framework
16
16
  *
17
17
  * No falla si un skill no tiene mapeos — solo los skills del dominio de
@@ -35,7 +35,7 @@ try {
35
35
 
36
36
  const RAIZ_SKILLS = path.resolve(__dirname, '..', 'habilidades');
37
37
  const DESTINO = path.resolve(
38
- __dirname, '..', '.planning', 'evolucion', 'cobertura-frameworks.json'
38
+ __dirname, '..', '.planning', 'evolution', 'cobertura-frameworks.json'
39
39
  );
40
40
 
41
41
  const CAMPOS_FRAMEWORK = Object.freeze([
@@ -10,7 +10,7 @@
10
10
  * Uso:
11
11
  * node scripts/auditar-skills-gaps.js # JSON a stdout
12
12
  * node scripts/auditar-skills-gaps.js --resumen # solo tabla a stderr
13
- * node scripts/auditar-skills-gaps.js --save # persiste en .planning/evolucion/
13
+ * node scripts/auditar-skills-gaps.js --save # persiste en .planning/evolution/
14
14
  * node scripts/auditar-skills-gaps.js --incluir-excepciones # incluye gaps excusados en el total
15
15
  *
16
16
  * Excepciones documentadas: lista inline de skills con gaps justificados por ADR.
@@ -30,7 +30,7 @@ try {
30
30
 
31
31
  const RAIZ_SKILLS = path.resolve(__dirname, '..', 'habilidades');
32
32
  const DESTINO_PLANNING = path.resolve(
33
- __dirname, '..', '.planning', 'evolucion', 'audit-skills-gaps.json'
33
+ __dirname, '..', '.planning', 'evolution', 'audit-skills-gaps.json'
34
34
  );
35
35
 
36
36
  // Patrones de detección
@@ -26,7 +26,7 @@
26
26
  * 2 - Argumentos inválidos
27
27
  *
28
28
  * Persistencia opcional: si se setea SWL_BENCHMARK_PERSIST=1, escribe
29
- * el resultado agregado a `.planning/evolucion/benchmark-memoria.jsonl`
29
+ * el resultado agregado a `.planning/evolution/benchmark-memoria.jsonl`
30
30
  * para tracking histórico.
31
31
  */
32
32
 
@@ -36,7 +36,7 @@ const path = require('path');
36
36
  const { ejecutarDataset } = require('./lib/longmemeval-runner');
37
37
 
38
38
  const DATASET_DEFAULT = '.planning/benchmark/dataset.jsonl';
39
- const HISTORICO_PATH = '.planning/evolucion/benchmark-memoria.jsonl';
39
+ const HISTORICO_PATH = '.planning/evolution/benchmark-memoria.jsonl';
40
40
 
41
41
  function uso() {
42
42
  console.error('Uso: node scripts/benchmark-memoria.js [--dataset <ruta>] [--limit <n>] [--json] [--verbose]');
@@ -64,7 +64,7 @@ function parseArgs(argv) {
64
64
  function persistirHistorico(baseDir, resumen) {
65
65
  if (process.env.SWL_BENCHMARK_PERSIST !== '1') return;
66
66
  try {
67
- const dirEvolucion = path.join(baseDir, '.planning', 'evolucion');
67
+ const dirEvolucion = path.join(baseDir, '.planning', 'evolution');
68
68
  if (!fs.existsSync(dirEvolucion)) fs.mkdirSync(dirEvolucion, { recursive: true });
69
69
  const linea = JSON.stringify({
70
70
  timestamp: new Date().toISOString(),
@@ -33,7 +33,8 @@ const args = new Set(process.argv.slice(2));
33
33
  const DRY = args.has('--dry-run');
34
34
  const SOLO_INV = args.has('--inventario');
35
35
  const SOLO_SAL = args.has('--salud');
36
- const AMBOS = !SOLO_INV && !SOLO_SAL;
36
+ const SOLO_LLMS = args.has('--llms');
37
+ const AMBOS = !SOLO_INV && !SOLO_SAL && !SOLO_LLMS;
37
38
 
38
39
  // ── Helpers ────────────────────────────────────────────────────────────
39
40
 
@@ -427,6 +428,57 @@ function generarSalud() {
427
428
  return lines.join('\n');
428
429
  }
429
430
 
431
+ // ── llms.txt (convención llmstxt.org) ─────────────────────────────────
432
+ // Índice raíz LLM-legible para descubrimiento externo por herramientas/agentes
433
+ // que esperan el estándar. Cifras sincronizadas con INVENTARIO (mismo cómputo)
434
+ // para que el gate de release no detecte drift. Adoptado de obsidian-second-brain
435
+ // (análisis temp/ 2026-06-05); el resto de ese repo era redundante con swl-ses.
436
+ function generarLlmsTxt() {
437
+ const pkg = JSON.parse(fs.readFileSync(path.join(RAIZ, 'package.json'), 'utf8'));
438
+ const agentes = recolectarAgentes().length;
439
+ const skills = recolectarSkills().length;
440
+ const hooks = recolectarHooks().length;
441
+ const comandos = contarArchivos(path.join(RAIZ, 'comandos', 'swl'), '.md');
442
+ const reglasBase = contarArchivos(path.join(RAIZ, 'reglas'), '.md');
443
+ const reglasLang = fs.readdirSync(path.join(RAIZ, 'reglas', 'lenguajes'))
444
+ .filter(d => fs.statSync(path.join(RAIZ, 'reglas', 'lenguajes', d)).isDirectory())
445
+ .reduce((acc, d) => acc + contarArchivos(path.join(RAIZ, 'reglas', 'lenguajes', d), '.md'), 0);
446
+
447
+ const L = [];
448
+ L.push('# swl-ses (@saulwade/swl-ses)');
449
+ L.push('');
450
+ L.push(`> Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. ${agentes} agentes, ${skills} habilidades, ${comandos} comandos, ${reglasBase} reglas base y ${hooks} hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión ${pkg.version}.`);
451
+ L.push('');
452
+ L.push('Archivo generado por `node scripts/generar-inventario.js` — no editar a mano. Las cifras se sincronizan con INVENTARIO.md en cada regeneración.');
453
+ L.push('');
454
+ L.push('## Documentación');
455
+ L.push('');
456
+ L.push('- [README](README.md): overview público y quickstart');
457
+ L.push('- [Manual de uso](MANUAL_USO.md): manual operacional completo');
458
+ L.push('- [Comandos](COMANDOS.md): referencia detallada de cada comando `/swl:*`');
459
+ L.push('- [Agentes](AGENTS.md): catálogo de agentes con capacidades');
460
+ L.push('- [Inventario](INVENTARIO.md): conteos oficiales de todos los componentes');
461
+ L.push('- [Instalación](INSTALACION.md): instalación, perfiles y configuración');
462
+ L.push('- [Instrucciones del proyecto](CLAUDE.md): convenciones, stack y reglas');
463
+ L.push('');
464
+ L.push('## Componentes');
465
+ L.push('');
466
+ L.push(`- ${agentes} agentes especializados en \`agentes/\` (orquestación, implementación por stack, revisión, calidad, diseño)`);
467
+ L.push(`- ${skills} habilidades cargables bajo demanda en \`habilidades/\` (conocimiento operacional con divulgación progresiva)`);
468
+ L.push(`- ${comandos} comandos \`/swl:*\` en \`comandos/swl/\` (ciclo GSD, calidad, release, diagnóstico)`);
469
+ L.push(`- ${reglasBase} reglas base + ${reglasLang} reglas por lenguaje en \`reglas/\` (políticas obligatorias por matcher)`);
470
+ L.push(`- ${hooks} hooks en \`hooks/\` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)`);
471
+ L.push('');
472
+ L.push('## Opcional');
473
+ L.push('');
474
+ L.push('- [CHANGELOG](CHANGELOG.md): historial de versiones');
475
+ L.push('- [Índice de ADRs](.planning/adrs/README.md): decisiones de arquitectura');
476
+ L.push('- [Variables de entorno](docs/variables-entorno.md): configuración opt-in');
477
+ L.push('');
478
+
479
+ return L.join('\n');
480
+ }
481
+
430
482
  // ── Ejecutar ──────────────────────────────────────────────────────────
431
483
 
432
484
  if (AMBOS || SOLO_INV) {
@@ -450,3 +502,14 @@ if (AMBOS || SOLO_SAL) {
450
502
  console.log('SALUD.md generado correctamente.');
451
503
  }
452
504
  }
505
+
506
+ if (AMBOS || SOLO_LLMS) {
507
+ const llms = generarLlmsTxt();
508
+ if (DRY) {
509
+ console.log('\n=== llms.txt (dry-run) ===\n');
510
+ console.log(llms);
511
+ } else {
512
+ atomicWriteSync(path.join(RAIZ, 'llms.txt'), llms, 'utf8');
513
+ console.log('llms.txt generado correctamente.');
514
+ }
515
+ }
@@ -14,7 +14,7 @@
14
14
  * node scripts/inferir-herramientas-permitidas.js --dry-run --skill=fastapi-experto
15
15
  * node scripts/inferir-herramientas-permitidas.js --aplicar
16
16
  * node scripts/inferir-herramientas-permitidas.js --aplicar --solo-faltantes
17
- * node scripts/inferir-herramientas-permitidas.js --aplicar --log=.planning/evolucion/infer-herramientas.log
17
+ * node scripts/inferir-herramientas-permitidas.js --aplicar --log=.planning/evolution/infer-herramientas.log
18
18
  *
19
19
  * @module scripts/inferir-herramientas-permitidas
20
20
  */
@@ -210,10 +210,30 @@ async function install(opciones) {
210
210
  // 2b. Filtrar reglas de lenguaje por stack detectado
211
211
  // El stack se detecta siempre que: hay reglas de lenguaje en el perfil, O
212
212
  // se ejecuta con --force (actualización) para poder limpiar las ya instaladas.
213
+ // FIX (thrashing de contexto en subagentes): en scope GLOBAL las reglas
214
+ // por-lenguaje NO se instalan en ~/.claude/rules/. Global no tiene un proyecto
215
+ // que detectar, así que se instalarían los 8 lenguajes (~69K tokens) y
216
+ // saturarían la ventana de todo subagente que herede ~/.claude/rules/. Las
217
+ // reglas por-lenguaje viven project-scoped, donde el stack-filter sí aplica.
218
+ if (esGlobal) {
219
+ const antesGlobal = resolucion.archivos.length;
220
+ resolucion.archivos = resolucion.archivos.filter(
221
+ a => !(a.rutaRelativa && a.rutaRelativa.startsWith('reglas/lenguajes/'))
222
+ );
223
+ const excluidasGlobal = antesGlobal - resolucion.archivos.length;
224
+ if (excluidasGlobal > 0) {
225
+ console.log(
226
+ `\n[stack] Scope global: ${excluidasGlobal} regla(s) por-lenguaje excluidas ` +
227
+ '(se instalan project-scoped, filtradas por stack; no en ~/.claude/rules/ ' +
228
+ 'para no saturar el contexto de subagentes).'
229
+ );
230
+ }
231
+ }
232
+
213
233
  const tieneReglasLenguaje = resolucion.archivos.some(
214
234
  a => a.rutaRelativa && a.rutaRelativa.startsWith('reglas/lenguajes/')
215
235
  );
216
- const necesitaStack = (tieneReglasLenguaje || force) && !allLangs;
236
+ const necesitaStack = (tieneReglasLenguaje || force) && !allLangs && !esGlobal;
217
237
 
218
238
  let stackDetectado = null;
219
239
 
@@ -224,7 +244,7 @@ async function install(opciones) {
224
244
  // FIX v1.6.6: si el usuario pasa --all-langs pero el perfil actual no incluye
225
245
  // reglas/lenguajes/, el flag se ignora silenciosamente. Antes la única pista
226
246
  // era el conteo final de archivos. Ahora emitimos warning explícito.
227
- if (allLangs && !tieneReglasLenguaje) {
247
+ if (allLangs && !tieneReglasLenguaje && !esGlobal) {
228
248
  console.log(
229
249
  '\n[stack] Aviso: --all-langs ignorado — el perfil actual (' +
230
250
  (resolucion.perfil || 'core') +
@@ -269,6 +289,16 @@ async function install(opciones) {
269
289
  }
270
290
  }
271
291
 
292
+ // Scope global: purgar TODAS las reglas por-lenguaje preexistentes de
293
+ // ~/.claude/rules/ (instalaciones previas dejaban los 8 lenguajes always-loaded,
294
+ // saturando el contexto de subagentes). Corre en cada install global, no solo --force.
295
+ if (esGlobal && rutas.reglas) {
296
+ const purgaGlobal = limpiarReglasSinStack(rutas.reglas, new Set());
297
+ if (purgaGlobal.eliminados > 0) {
298
+ console.log(`\n[stack] Scope global: ${purgaGlobal.eliminados} subdir(s) de reglas por-lenguaje purgadas de ~/.claude/rules/ (${purgaGlobal.lenguajes.join(', ')}) — viven project-scoped.`);
299
+ }
300
+ }
301
+
272
302
  // 4. Detectar _userland/
273
303
  const userlandAgentes = path.join(process.cwd(), '_userland', 'agentes');
274
304
  const userlandHabilidades = path.join(process.cwd(), '_userland', 'habilidades');
@@ -416,6 +446,54 @@ async function install(opciones) {
416
446
  console.log(` [migración] ${migracionesAplicadas} archivo(s) de .planning/ migrado(s) de inglés a español`);
417
447
  }
418
448
 
449
+ // 6c-bis. Migración de DIRECTORIOS runtime de .planning/ español → inglés.
450
+ // Unificación: los paths runtime/técnicos van en inglés (telemetría, perfil,
451
+ // logs archivados). NO incluye fases/ (vocabulario GSD de cara al usuario, se
452
+ // mantiene español por coherencia con los comandos *-fase). Ver ADR-0031.
453
+ const MIGRACIONES_DIRS_PLANNING = [
454
+ { viejo: 'evolucion', nuevo: 'evolution' },
455
+ { viejo: 'auto-evolucion', nuevo: 'auto-evolution' },
456
+ { viejo: 'perfil-usuario', nuevo: 'user-profile' },
457
+ { viejo: 'archivo', nuevo: 'archive' },
458
+ ];
459
+
460
+ let dirsMigrados = 0;
461
+ for (const m of MIGRACIONES_DIRS_PLANNING) {
462
+ const dirViejo = path.join(process.cwd(), '.planning', m.viejo);
463
+ const dirNuevo = path.join(process.cwd(), '.planning', m.nuevo);
464
+
465
+ if (!fs.existsSync(dirViejo)) continue;
466
+
467
+ try {
468
+ if (!fs.existsSync(dirNuevo)) {
469
+ // Caso 1: solo existe el viejo → renombrar el directorio completo
470
+ fs.renameSync(dirViejo, dirNuevo);
471
+ console.log(` ✓ Migración dir: .planning/${m.viejo}/ → .planning/${m.nuevo}/`);
472
+ } else {
473
+ // Caso 2: ambos existen → mover entradas del viejo al nuevo (sin sobreescribir)
474
+ let saltadas = 0;
475
+ for (const entry of fs.readdirSync(dirViejo)) {
476
+ const src = path.join(dirViejo, entry);
477
+ const dst = path.join(dirNuevo, entry);
478
+ if (!fs.existsSync(dst)) fs.renameSync(src, dst);
479
+ else saltadas++; // conflicto de nombre: se deja en origen (no destructivo)
480
+ }
481
+ try { fs.rmdirSync(dirViejo); } catch { /* no vacío: se deja, no destructivo */ }
482
+ const sufijo = saltadas > 0
483
+ ? ` (${saltadas} entrada(s) con conflicto dejadas en .planning/${m.viejo}/)`
484
+ : '';
485
+ console.log(` ⊕ Migración dir: .planning/${m.viejo}/ fusionado en .planning/${m.nuevo}/${sufijo}`);
486
+ }
487
+ dirsMigrados++;
488
+ } catch (err) {
489
+ console.log(` ! Migración dir ${m.viejo}: ${err.message}`);
490
+ }
491
+ }
492
+
493
+ if (dirsMigrados > 0) {
494
+ console.log(` [migración] ${dirsMigrados} directorio(s) runtime de .planning/ migrado(s) español → inglés`);
495
+ }
496
+
419
497
  // 6d. Filtrar skills por targets declarados en SKILL.md frontmatter (patrón skillshare)
420
498
  // Si un skill declara targets: [claude, copilot], solo se instala en esos runtimes.
421
499
  // Si no declara targets, se instala en todos (comportamiento por defecto).
@@ -122,7 +122,7 @@ const _WIDGET_CATALOG = [
122
122
  {
123
123
  id: 'nudges-recientes',
124
124
  label: 'Nudges Recientes',
125
- descripcion: 'Últimos nudges emitidos por hooks de observabilidad (.planning/evolucion/nudges.jsonl)',
125
+ descripcion: 'Últimos nudges emitidos por hooks de observabilidad (.planning/evolution/nudges.jsonl)',
126
126
  categoria: 'evolucion',
127
127
  modos: ['offline'],
128
128
  tamanoDefault: 'md',
@@ -130,7 +130,7 @@ const _WIDGET_CATALOG = [
130
130
  {
131
131
  id: 'evoluciones-aplicadas',
132
132
  label: 'Evoluciones Aplicadas',
133
- descripcion: 'Historial de evoluciones de skills y agentes (.planning/evolucion/evoluciones.jsonl)',
133
+ descripcion: 'Historial de evoluciones de skills y agentes (.planning/evolution/evoluciones.jsonl)',
134
134
  categoria: 'evolucion',
135
135
  modos: ['offline'],
136
136
  tamanoDefault: 'md',
@@ -138,7 +138,7 @@ const _WIDGET_CATALOG = [
138
138
  {
139
139
  id: 'alertas-persistentes',
140
140
  label: 'Alertas Persistentes',
141
- descripcion: 'Alertas activas sin resolver del ciclo AGP (.planning/evolucion/alertas-persistentes.json)',
141
+ descripcion: 'Alertas activas sin resolver del ciclo AGP (.planning/evolution/alertas-persistentes.json)',
142
142
  categoria: 'evolucion',
143
143
  modos: ['offline'],
144
144
  tamanoDefault: 'md',
@@ -17,7 +17,7 @@
17
17
  * - Fuente de datos: JSONL en disco, no SQLite
18
18
  * - Sin dependencias externas (solo Node stdlib: fs, path)
19
19
  * - Métricas configurables via parámetro, no hardcodeadas
20
- * - Emite nudge a .planning/evolucion/nudges.jsonl en drift crítico
20
+ * - Emite nudge a .planning/evolution/nudges.jsonl en drift crítico
21
21
  *
22
22
  * @module scripts/lib/drift-detector
23
23
  */
@@ -50,7 +50,7 @@ const METRICAS_DEFAULT = [
50
50
 
51
51
  // Ruta raíz del proyecto (dos niveles arriba de scripts/lib/)
52
52
  const RAIZ_PROYECTO = path.resolve(__dirname, '..', '..');
53
- const RUTA_NUDGES = path.join(RAIZ_PROYECTO, '.planning', 'evolucion', 'nudges.jsonl');
53
+ const RUTA_NUDGES = path.join(RAIZ_PROYECTO, '.planning', 'evolution', 'nudges.jsonl');
54
54
 
55
55
  // ── helpers internos ──────────────────────────────────────────────────────────
56
56
 
@@ -202,7 +202,7 @@ function contarNudgesPrevios(metrica, agente) {
202
202
  }
203
203
 
204
204
  /**
205
- * Emite un nudge a .planning/evolucion/nudges.jsonl cuando se detecta
205
+ * Emite un nudge a .planning/evolution/nudges.jsonl cuando se detecta
206
206
  * drift crítico. Usa fs.appendFileSync (JSONL, no sobreescribe).
207
207
  *
208
208
  * Aplica repair-loop detection: si el mismo (metrica, agente) ya fue
@@ -5,8 +5,8 @@
5
5
  *
6
6
  * Patrón adoptado de `temp/agentmemory-main/src/eval/metrics-store.ts`.
7
7
  * Adaptado a swl-ses: file-based en lugar de SQLite. Persiste:
8
- * - JSONL append-only: `.planning/evolucion/eval-results.jsonl` (cada eval individual)
9
- * - JSON agregado: `.planning/evolucion/eval-metrics.json` (totales por functionId)
8
+ * - JSONL append-only: `.planning/evolution/eval-results.jsonl` (cada eval individual)
9
+ * - JSON agregado: `.planning/evolution/eval-metrics.json` (totales por functionId)
10
10
  *
11
11
  * El JSONL preserva el detalle histórico (un evento por evaluación). El JSON
12
12
  * agregado se recalcula incrementalmente: avg latency, avg quality, success
@@ -31,7 +31,7 @@ try {
31
31
 
32
32
  // ── constantes ────────────────────────────────────────────────────────────────
33
33
 
34
- const DIR_EVOLUCION = path.join('.planning', 'evolucion');
34
+ const DIR_EVOLUCION = path.join('.planning', 'evolution');
35
35
  const RUTA_JSONL = path.join(DIR_EVOLUCION, 'eval-results.jsonl');
36
36
  const RUTA_AGREGADO = path.join(DIR_EVOLUCION, 'eval-metrics.json');
37
37