@saulwade/swl-ses 1.8.0 → 2.0.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 (135) hide show
  1. package/CLAUDE.md +8 -8
  2. package/README.md +13 -13
  3. package/agentes/accesibilidad-wcag-swl.md +3 -3
  4. package/agentes/auto-evolucion-swl.md +908 -908
  5. package/agentes/disenador-ui-swl.md +6 -5
  6. package/agentes/frontend-angular-swl.md +2 -2
  7. package/agentes/frontend-css-swl.md +2 -2
  8. package/agentes/frontend-react-swl.md +4 -4
  9. package/agentes/frontend-swl.md +6 -6
  10. package/agentes/investigador-ux-swl.md +5 -5
  11. package/agentes/orquestador-swl.md +96 -8
  12. package/agentes/perfilador-usuario-swl.md +308 -308
  13. package/agentes/producto-prd-swl.md +1 -1
  14. package/agentes/red-team-swl.md +218 -218
  15. package/agentes/revisor-codigo-swl.md +34 -10
  16. package/agentes/revisor-seguridad-swl.md +7 -0
  17. package/agentes/tdd-qa-swl.md +39 -2
  18. package/comandos/swl/actualizar.md +1 -1
  19. package/comandos/swl/aprender.md +2 -2
  20. package/comandos/swl/aprobar-plan.md +152 -0
  21. package/comandos/swl/autoresearch.md +102 -6
  22. package/comandos/swl/ayuda.md +3 -3
  23. package/comandos/swl/discutir-fase.md +20 -2
  24. package/comandos/swl/ejecutar-fase.md +53 -6
  25. package/comandos/swl/evolucionar.md +1 -1
  26. package/comandos/swl/inbox.md +1 -1
  27. package/comandos/swl/instalar.md +1 -1
  28. package/comandos/swl/nemesis.md +42 -1
  29. package/comandos/swl/planear-fase.md +25 -1
  30. package/comandos/swl/plugins.md +1 -1
  31. package/comandos/swl/predecir.md +139 -0
  32. package/comandos/swl/release.md +1 -1
  33. package/comandos/swl/status.md +279 -0
  34. package/comandos/swl/verificar.md +75 -7
  35. package/habilidades/ai-runtime-security/SKILL.md +1 -1
  36. package/habilidades/angular-moderno/SKILL.md +44 -1
  37. package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
  38. package/habilidades/autoresearch/SKILL.md +15 -1
  39. package/habilidades/benchmark-memoria/SKILL.md +1 -1
  40. package/habilidades/calidad-contract-testing/SKILL.md +165 -0
  41. package/habilidades/calidad-mutation-testing/SKILL.md +170 -0
  42. package/habilidades/changelog-generator/SKILL.md +9 -2
  43. package/habilidades/changelog-generator/scripts/parse-commits.js +12 -1
  44. package/habilidades/checklist-seguridad/SKILL.md +29 -1
  45. package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -0
  46. package/habilidades/css-moderno/SKILL.md +3 -1
  47. package/habilidades/diagrama-arquitectura/SKILL.md +1 -1
  48. package/habilidades/drift-detection/SKILL.md +179 -179
  49. package/habilidades/ejecutar-fase/SKILL.md +64 -14
  50. package/habilidades/estructura-proyecto-claude/SKILL.md +17 -14
  51. package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +34 -23
  52. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +70 -53
  53. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -77
  54. package/habilidades/extractor-de-aprendizajes/SKILL.md +9 -5
  55. package/habilidades/fastapi-experto/SKILL.md +56 -5
  56. package/habilidades/harness-claude-code/SKILL.md +10 -7
  57. package/{reglas/harness-claude-code.md → habilidades/harness-claude-code/recursos/disciplina-harness-regla.md} +2 -2
  58. package/habilidades/instalar-sistema/SKILL.md +3 -3
  59. package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +1 -1
  60. package/habilidades/patrones-python/SKILL.md +8 -5
  61. package/habilidades/perfil-usuario/SKILL.md +200 -200
  62. package/habilidades/planear-fase/SKILL.md +25 -4
  63. package/habilidades/proceso-ddia-fundamentos/SKILL.md +1 -1
  64. package/habilidades/proceso-ddia-streaming/SKILL.md +4 -4
  65. package/habilidades/proceso-debate-adversarial/SKILL.md +164 -0
  66. package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -0
  67. package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -0
  68. package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -0
  69. package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -0
  70. package/habilidades/protocolo-revision-swl/SKILL.md +1 -1
  71. package/habilidades/seguridad-skills-ia/SKILL.md +1 -1
  72. package/habilidades/swl-claudemd/SKILL.md +50 -210
  73. package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -0
  74. package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -0
  75. package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -0
  76. package/habilidades/swl-dashboard/SKILL.md +9 -9
  77. package/habilidades/swl-revisar-impacto/SKILL.md +1 -1
  78. package/habilidades/tdd-workflow/SKILL.md +58 -5
  79. package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -0
  80. package/habilidades/validacion-ci-sistema/SKILL.md +3 -3
  81. package/hooks/calidad-pre-commit.js +340 -3
  82. package/hooks/ciclo-evolucion-subagente.js +26 -0
  83. package/hooks/ciclo-evolucion.js +26 -0
  84. package/hooks/contexto-iteracion.js +144 -0
  85. package/hooks/extraccion-aprendizajes.js +13 -0
  86. package/hooks/lib/ciclo-evolucion.js +47 -0
  87. package/hooks/{auto-evolucion.js → lib/etapa-auto-evolucion.js} +701 -700
  88. package/hooks/{metricas-evolucion.js → lib/etapa-metricas.js} +388 -376
  89. package/hooks/{actualizar-perfil-usuario.js → lib/etapa-perfil-usuario.js} +376 -364
  90. package/hooks/lib/evolution-tracker.js +24 -3
  91. package/hooks/lib/loop-telemetry.js +321 -0
  92. package/hooks/notificacion-telegram.js +11 -3
  93. package/hooks/spec-gate.js +211 -0
  94. package/hooks/tdd-gate.js +241 -0
  95. package/hooks/validar-intent-spec.js +30 -10
  96. package/llms.txt +29 -0
  97. package/manifiestos/hooks-config.json +36 -18
  98. package/manifiestos/modulos.json +23 -14
  99. package/manifiestos/skills-lock.json +100 -72
  100. package/package.json +4 -3
  101. package/plugin.json +9 -10
  102. package/reglas/accesibilidad.md +10 -0
  103. package/reglas/api-diseno.md +9 -0
  104. package/reglas/arquitectura.evolved.json +7 -0
  105. package/reglas/arquitectura.md +65 -0
  106. package/reglas/auditorias-documentales-estructurales.md +7 -0
  107. package/reglas/cloud-infra.md +8 -0
  108. package/reglas/fragmentos-compartidos.md +5 -0
  109. package/reglas/gobernanza.md +4 -4
  110. package/reglas/hooks.md +6 -0
  111. package/reglas/intent-engineering.md +4 -0
  112. package/reglas/markitdown.md +8 -0
  113. package/reglas/memoria-consolidada.md +1 -1
  114. package/reglas/patrones.md +6 -0
  115. package/reglas/registro-componentes-nuevos.md +10 -1
  116. package/reglas/seguridad-agentes.md +1 -1
  117. package/reglas/seguridad.evolved.json +7 -0
  118. package/reglas/seguridad.md +144 -0
  119. package/reglas/skills-estandar.md +6 -0
  120. package/reglas/testing.md +7 -0
  121. package/reglas/tests-cleanup.md +4 -0
  122. package/reglas/usar-sistema-swl.md +1 -1
  123. package/scripts/generar-inventario.js +64 -1
  124. package/scripts/instalador.js +32 -2
  125. package/scripts/lib/gitignore-manifest.js +29 -1
  126. package/scripts/lib/plan-lock.js +275 -0
  127. package/scripts/migrar-fase-dominio.js +0 -1
  128. package/scripts/smoke-test.js +24 -2
  129. package/scripts/verificar-trazabilidad.js +292 -0
  130. package/agentes/ux-disenador-swl.md +0 -503
  131. package/comandos/swl/dashboard.md +0 -146
  132. package/comandos/swl/evolucion-estado.md +0 -191
  133. package/comandos/swl/metricas.md +0 -342
  134. package/comandos/swl/salud.md +0 -481
  135. package/reglas/verificar-citas-temporales.md +0 -139
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: tdd-workflow
3
3
  description: Flujo completo de Test-Driven Development. Ciclo RED (el test falla) → GREEN (implementación mínima) → REFACTOR (limpieza). Incluye cobertura mínima obligatoria, tests de frontera, factories, fixtures y estrategias para diferentes tipos de código (APIs, services, componentes Angular).
4
- version: "1.0.6"
4
+ version: "1.2.0"
5
5
  evolved: true
6
- evolved-from: "1.0.4"
7
- evolved-at: "2026-05-16"
8
- evolved-by: "aprender"
9
- evolved-note: "v1.0.5: gotcha 'Tests E2E de CLIs interactivos sin PTY real'. Origen M2 sesión 2026-05-16 — harness TTY mockeado en swl-ses v1.6.0 que evita instalar node-pty. v1.0.4: silenced tests por race en path único compartido + anti-patrón `if (X) assert(Y)` sin else. v1.0.3: gotcha cwd cacheado al require()."
6
+ evolved-from: "1.1.0"
7
+ evolved-at: "2026-06-11"
8
+ evolved-by: "fase-10-slice-2"
9
+ evolved-note: "v1.2.0: sección 'Evidencia RED en telemetría' (gate G2, ADR-0035, cierra F-TDD-6) — registro de corridas tdd-* en loop-telemetry. v1.0.5: gotcha 'Tests E2E de CLIs interactivos sin PTY real'. Origen M2 sesión 2026-05-16. v1.0.4: silenced tests por race en path único compartido. v1.0.3: gotcha cwd cacheado al require()."
10
10
  herramientasPermitidas: [Read, Bash]
11
11
  evolvable: true # default para skill estandar
12
12
  exclusiones:
@@ -40,6 +40,19 @@ que los tests exigen — ni más, ni menos.
40
40
 
41
41
  ---
42
42
 
43
+ ## Etapa opcional previa: Gherkin (BDD) y gate de mutación
44
+
45
+ Dos extensiones opt-in del ciclo, ambas con guía completa en recursos:
46
+
47
+ - **Antes del ciclo** — si la fase tiene criterios de aceptación de negocio,
48
+ convertirlos en escenarios Given–When–Then validados por el usuario ANTES de
49
+ implementar; cada escenario es el test RED de su criterio. Guía, runners por
50
+ stack y anti-patrones en [recursos/gherkin-bdd.md](recursos/gherkin-bdd.md).
51
+ - **Después del ciclo** — en módulos críticos, verificar la calidad de los
52
+ asserts con mutation testing incremental sobre el diff:
53
+ `Skill("calidad-mutation-testing")`. La cobertura mide ejecución; los
54
+ mutantes sobrevivientes miden si los tests detectarían un bug.
55
+
43
56
  ## El ciclo fundamental RED → GREEN → REFACTOR
44
57
 
45
58
  ### Fase RED — El test debe fallar por la razón correcta
@@ -62,6 +75,46 @@ def test_descuento_cliente_premium_es_15_porciento():
62
75
 
63
76
  **NUNCA avanzar a GREEN si el test pasa en RED.**
64
77
 
78
+ #### Evidencia RED en telemetría (gate G2 — proyectos con SWL)
79
+
80
+ El RED debe dejar rastro verificable (cierra F-TDD-6: "TDD declarativo sin
81
+ evidencia"). En proyectos con `.planning/`, registrar la corrida en
82
+ `hooks/lib/loop-telemetry.js` ANTES de pasar a GREEN:
83
+
84
+ ```bash
85
+ # Una vez por fase/tarea — abre la corrida
86
+ node -e "const lt=require('./hooks/lib/loop-telemetry');const r=lt.iniciarCorrida({tipo:'tdd',direccion:'lower_is_better',config:{fase:'0N',tarea:'T-NN'}});console.log(r.dir)"
87
+
88
+ # Al confirmar el RED — métrica = número de tests fallando, descripción = fallo exacto
89
+ node -e "const lt=require('./hooks/lib/loop-telemetry');lt.registrarIteracion('<dir>',{iteracion:0,metrica:N,delta:0,estado:'baseline',descripcion:'RED T-NN: <error textual del runner>'})"
90
+
91
+ # Al llegar a GREEN
92
+ node -e "const lt=require('./hooks/lib/loop-telemetry');lt.registrarIteracion('<dir>',{iteracion:1,metrica:0,delta:-N,estado:'keep',descripcion:'GREEN T-NN: suite verde'})"
93
+ ```
94
+
95
+ `hooks/tdd-gate.js` (warn-only, ADR-0035) busca la fila RED en
96
+ `.planning/loops/tdd-*/iteraciones.tsv` al commitear un feature con tests; sin
97
+ evidencia emite nudge `tdd-red-evidence`. Sin `.planning/` no aplica.
98
+
99
+ #### Marker de trazabilidad REQ en tests (proyectos con REQ-IDs)
100
+
101
+ Cuando la fase tiene criterios `REQ-NN` en el CONTEXTO, cada test que verifica un
102
+ criterio lleva el marker en comentario — `scripts/verificar-trazabilidad.js` lo usa
103
+ para cerrar la cadena REQ→T→commit→test:
104
+
105
+ ```python
106
+ def test_descuento_cliente_premium():
107
+ # verifica: REQ-03
108
+ ...
109
+ ```
110
+
111
+ ```javascript
112
+ test('descuento cliente premium', () => {
113
+ // verifica: REQ-03
114
+ ...
115
+ });
116
+ ```
117
+
65
118
  ### Fase GREEN — Implementación mínima
66
119
 
67
120
  **Regla de oro**: Implementar solo lo que hace pasar el test. Nada más.
@@ -0,0 +1,111 @@
1
+ # Etapa Gherkin (BDD) — de criterios de aceptación a tests ejecutables
2
+
3
+ Etapa opt-in previa al ciclo RED→GREEN→REFACTOR que convierte los criterios de
4
+ aceptación del CONTEXTO.md/PRD en escenarios **Given–When–Then** ejecutables.
5
+ Cierra el hueco entre "lo que el negocio pidió" y "lo que los tests verifican":
6
+ cada escenario es a la vez especificación legible por el usuario y esqueleto
7
+ del test.
8
+
9
+ Inspirada en el flujo de Robert C. Martin (spec → hard spec → Gherkin → TDD →
10
+ mutation testing): el Gherkin es la "hard spec" verificable; el ciclo TDD la
11
+ implementa; el mutation testing (`Skill("calidad-mutation-testing")`) verifica
12
+ la suite resultante.
13
+
14
+ ## Cuándo usar la etapa (y cuándo no)
15
+
16
+ **Usar cuando**: la fase tiene criterios de aceptación de negocio (PRD o
17
+ CONTEXTO.md con decisiones cerradas), el comportamiento tiene reglas con casos
18
+ distinguibles (descuentos, permisos, estados), o el usuario validará la spec
19
+ sin leer código.
20
+
21
+ **NO usar cuando**: la fase es técnica pura (refactor, migración, infra), los
22
+ criterios son triviales (CRUD sin reglas), o no hay quien lea los escenarios —
23
+ Gherkin sin lector de negocio es ceremonia que duplica los tests.
24
+
25
+ ## Formato
26
+
27
+ ```gherkin
28
+ # language: es
29
+ Característica: Descuento por nivel de cliente
30
+ Como cliente premium quiero recibir mi descuento automático
31
+ para no capturar cupones manualmente.
32
+
33
+ Escenario: Cliente premium recibe 15% en compras normales
34
+ Dado un cliente con nivel "premium"
35
+ Cuando realiza una compra de $100.00 MXN
36
+ Entonces el descuento aplicado es de $15.00 MXN
37
+
38
+ Esquema del escenario: Descuento por nivel
39
+ Dado un cliente con nivel "<nivel>"
40
+ Cuando realiza una compra de $<monto> MXN
41
+ Entonces el descuento aplicado es de $<descuento> MXN
42
+
43
+ Ejemplos:
44
+ | nivel | monto | descuento |
45
+ | normal | 100.00 | 0.00 |
46
+ | premium | 100.00 | 15.00 |
47
+ | mayorista| 100.00 | 22.00 |
48
+ ```
49
+
50
+ Reglas de redacción:
51
+
52
+ - **Un comportamiento por escenario** — si necesitas "Y cuando..." encadenados,
53
+ son dos escenarios.
54
+ - **Lenguaje del dominio, no de la implementación**: "Dado un cliente premium",
55
+ NUNCA "Dado un row en la tabla clientes con tipo=2".
56
+ - **Valores concretos** en los ejemplos — los criterios vagos ("un monto
57
+ válido") no son verificables.
58
+ - **Esquema del escenario** para reglas con tabla de casos — es la forma
59
+ natural de los tests de frontera de `pruebas.md`.
60
+ - Español de México (`# language: es`) — los escenarios los lee el usuario.
61
+
62
+ ## Derivación desde CONTEXTO.md / PRD
63
+
64
+ 1. Tomar cada criterio de aceptación cerrado del CONTEXTO.md (o historia del PRD).
65
+ 2. Reescribirlo como 1-N escenarios: el caso feliz + las fronteras + el caso de error.
66
+ 3. Presentar los escenarios al usuario para validación ANTES de implementar —
67
+ este es el checkpoint humano del flujo: corregir una spec cuesta una
68
+ conversación; corregir la implementación cuesta un refactor.
69
+ 4. Los escenarios validados se guardan en `tests/features/<dominio>.feature`
70
+ y el PLAN.md referencia qué tarea implementa cada escenario.
71
+
72
+ ## Runners por stack
73
+
74
+ | Stack | Runner | Binding típico |
75
+ |-------|--------|----------------|
76
+ | Python | `pytest-bdd` (o `behave`) | `@scenario("features/descuento.feature", "Cliente premium...")` + steps con `@given/@when/@then` |
77
+ | JS/TS | `@cucumber/cucumber` | steps en `features/steps/*.ts` con `Given/When/Then` |
78
+ | C#/.NET | Reqnroll (sucesor de SpecFlow) | bindings `[Given]/[When]/[Then]` |
79
+ | Java | Cucumber-JVM | anotaciones `@Given/@When/@Then` |
80
+
81
+ Verificar versión vigente con Context7 antes de instalar (regla `usar-context7.md`).
82
+
83
+ Los steps son **pegamento delgado**: parsean el Gherkin y llaman al mismo
84
+ código de test que usaría el ciclo TDD (factories incluidas). La lógica de
85
+ verificación vive en los asserts, no en los steps.
86
+
87
+ ## Integración con el ciclo TDD
88
+
89
+ ```
90
+ CONTEXTO.md/PRD → escenarios Gherkin → validación del usuario (checkpoint)
91
+ → por cada escenario: RED (step sin implementar falla)
92
+ → GREEN (implementación mínima) → REFACTOR
93
+ → al cierre de fase: mutation testing opcional sobre el diff
94
+ ```
95
+
96
+ Cada escenario Gherkin ES un test RED al inicio: el runner reporta steps sin
97
+ implementar como fallos — exactamente la fase RED del ciclo. No escribir tests
98
+ unitarios duplicados del mismo criterio: el escenario cubre el comportamiento
99
+ de negocio; los tests unitarios cubren los detalles internos que el Gherkin
100
+ no expresa (errores de infraestructura, edge cases técnicos).
101
+
102
+ ## Anti-patrones
103
+
104
+ - **Gherkin imperativo de UI**: "Cuando hago clic en el botón #submit" — eso
105
+ es un script de Selenium disfrazado. Los escenarios describen comportamiento
106
+ de dominio; la UI cambia sin que la regla de negocio cambie.
107
+ - **Escenarios escritos DESPUÉS de implementar** para "documentar" — pierde el
108
+ checkpoint de validación, que es el valor de la etapa.
109
+ - **Steps con lógica de negocio** — la duplican; los steps solo traducen.
110
+ - **Un .feature gigante por módulo** — un archivo por característica, como el
111
+ código.
@@ -6,7 +6,7 @@ herramientasPermitidas: [Read, Bash]
6
6
  exclusiones:
7
7
  - "No cargar para validar el código de aplicación del usuario (tests, linting de Python/TS, cobertura) — este skill valida la integridad del sistema SWL (frontmatter de agentes, SKILL.md presentes, exit codes de hooks); para validación de código cargar `reglas/pruebas.md` o invocar `tdd-qa-swl`."
8
8
  - "No cargar para diagnosticar por qué un agente específico falló en una tarea — este skill detecta problemas estructurales del sistema SWL, no bugs en la lógica de un agente; para diagnóstico de agente usar `evaluacion-agentes`."
9
- - "No cargar si `/swl:salud` acaba de ejecutarse sin errores — la validación CI es un subconjunto de lo que salud corre; no tiene sentido duplicarla en la misma sesión."
9
+ - "No cargar si `/swl:status salud` acaba de ejecutarse sin errores — la validación CI es un subconjunto de lo que salud corre; no tiene sentido duplicarla en la misma sesión."
10
10
  - "No cargar para agregar un componente nuevo al sistema — primero crear el componente (agente, skill, hook) y luego ejecutar validación CI para verificarlo; no como guía de creación."
11
11
  evolvable: true # default para skill estandar
12
12
  ---
@@ -124,7 +124,7 @@ Para código fuente completo de todos los validadores (agentes, skills, hooks, c
124
124
  ## Cuándo NO cargar
125
125
 
126
126
  - Se valida código de aplicación del usuario (tests Python/TS, cobertura, linting) — este skill valida la integridad del sistema SWL; para código de aplicación cargar `reglas/pruebas.md` o invocar `tdd-qa-swl`.
127
- - `/swl:salud` acaba de ejecutarse sin errores en esta sesión — la validación CI es un subconjunto de lo que salud ejecuta; duplicarla no aporta información nueva.
127
+ - `/swl:status salud` acaba de ejecutarse sin errores en esta sesión — la validación CI es un subconjunto de lo que salud ejecuta; duplicarla no aporta información nueva.
128
128
  - El objetivo es diagnosticar por qué un agente específico falló en su tarea de dominio — este skill detecta problemas estructurales (frontmatter faltante, exit code incorrecto), no bugs en la lógica de un agente; para eso usar `evaluacion-agentes`.
129
129
  - Se está creando un componente nuevo y se necesita orientación sobre la estructura — este skill verifica componentes existentes, no guía la creación; usar `estructura-proyecto-claude` para crear con la estructura correcta desde el inicio.
130
130
 
@@ -132,5 +132,5 @@ Para código fuente completo de todos los validadores (agentes, skills, hooks, c
132
132
 
133
133
  - **Hook de observabilidad que usa `process.exit(1)` detectado como error crítico**: el validador marca el hook como inválido pero el hook fue escrito intencionalmente para bloquear en ciertos escenarios. Causa: el validador distingue entre hooks de observación (deben usar `exit(0)` siempre) y hooks de bloqueo (pueden usar `exit(1)` para rechazar acciones); si el hook no está categorizado correctamente, el validador aplica la regla incorrecta. Solución: verificar si el hook es bloqueante o de observación antes de corregir — un guardrail de seguridad que usa `exit(1)` es correcto; un hook de telemetría que usa `exit(1)` es un bug.
134
134
  - **Agente con `name` diferente al nombre de archivo pasa validación en local pero falla en CI**: el validador local compara el campo `name` del frontmatter con el nombre del directorio, pero CI usa la ruta del archivo. Causa: un agente renombrado actualiza el directorio pero no el campo `name` en el frontmatter. Solución: el checklist dice "name debe coincidir con nombre del archivo (kebab-case)" — al renombrar un agente o skill, actualizar el campo `name` del frontmatter en el mismo commit.
135
- - **`npm run validate` pasa sin advertencias pero `/swl:salud` reporta inconsistencias**: el script de validación CI cubre un subconjunto de las verificaciones de salud. Causa: las validaciones de grafo de dependencias, métricas de evolución y consistencia de versiones solo corren en `/swl:salud`, no en el validador CI básico. Solución: antes de un release o merge importante, ejecutar `/swl:salud` completo, no solo la validación CI — la validación CI es el mínimo para commits diarios.
135
+ - **`npm run validate` pasa sin advertencias pero `/swl:status salud` reporta inconsistencias**: el script de validación CI cubre un subconjunto de las verificaciones de salud. Causa: las validaciones de grafo de dependencias, métricas de evolución y consistencia de versiones solo corren en `/swl:status salud`, no en el validador CI básico. Solución: antes de un release o merge importante, ejecutar `/swl:status salud` completo, no solo la validación CI — la validación CI es el mínimo para commits diarios.
136
136
  - **Skills con `description: >` YAML multiline registrados como sin frontmatter**: el validador usa una regex basada solo en LF (`/^---\n[\s\S]*?\n---/`) que no parsea correctamente archivos con terminaciones CRLF. Causa: archivos creados en Windows con CRLF en el frontmatter. Solución: si el validador reporta `sin-frontmatter` en un skill que claramente tiene frontmatter, verificar las terminaciones de línea con `file skill.md` — convertir CRLF→LF con `git config core.autocrlf input` o `sed -i 's/\r//'`.
@@ -44,7 +44,7 @@
44
44
 
45
45
  const fs = require('fs');
46
46
  const path = require('path');
47
- const { execSync } = require('child_process');
47
+ const { execSync, execFileSync } = require('child_process');
48
48
 
49
49
  const { normalizeForDetection } = require('./lib/normalize-input');
50
50
 
@@ -151,7 +151,9 @@ function obtenerArchivosStagedGit() {
151
151
  */
152
152
  function obtenerContenidoStaged(rutaRelativa) {
153
153
  try {
154
- const contenido = execSync(`git show :${rutaRelativa}`, {
154
+ // execFileSync (sin shell): el nombre de archivo se pasa como argumento
155
+ // directo, no se interpreta por el shell (defensa S-recom verificación F09).
156
+ const contenido = execFileSync('git', ['show', `:${rutaRelativa}`], {
155
157
  encoding: 'buffer',
156
158
  stdio: ['pipe', 'pipe', 'pipe'],
157
159
  });
@@ -172,7 +174,7 @@ function obtenerContenidoStaged(rutaRelativa) {
172
174
  */
173
175
  function obtenerTamanoStaged(rutaRelativa) {
174
176
  try {
175
- const salida = execSync(`git cat-file -s :${rutaRelativa}`, {
177
+ const salida = execFileSync('git', ['cat-file', '-s', `:${rutaRelativa}`], {
176
178
  encoding: 'utf8',
177
179
  stdio: ['pipe', 'pipe', 'pipe'],
178
180
  });
@@ -824,12 +826,190 @@ function verificarRiskScore() {
824
826
  }
825
827
  }
826
828
 
829
+ // ---------------------------------------------------------------------------
830
+ // Gate G3 — tests-presence (warn-only). Fase 09, Slice 2.
831
+ // ---------------------------------------------------------------------------
832
+
833
+ /**
834
+ * ¿La ruta es un archivo de test? Consolida los patrones de test antes
835
+ * dispersos en este hook (verificarConsoleLog, verificarPrintPython) más
836
+ * convenciones multi-lenguaje. git entrega rutas relativas con separador `/`.
837
+ *
838
+ * @param {string} ruta - ruta relativa al repo
839
+ * @returns {boolean}
840
+ */
841
+ function esArchivoTest(ruta) {
842
+ return (
843
+ /\.(test|spec)\.[jt]sx?$/.test(ruta) || // foo.test.ts, foo.spec.jsx
844
+ /(^|[/\\])__tests__[/\\]/.test(ruta) || // __tests__/
845
+ /(^|[/\\])tests?[/\\]/.test(ruta) || // test/ o tests/
846
+ /(^|[/\\])spec[/\\]/.test(ruta) || // spec/
847
+ /(^|[/\\])test_[^/\\]*\.py$/.test(ruta) || // test_foo.py
848
+ /test.*\.py$/.test(ruta) || // pytest naming laxo
849
+ /_test\.(py|go)$/.test(ruta) || // foo_test.go, foo_test.py
850
+ /Tests?\.(java|kt|cs)$/.test(ruta) || // FooTest.java, FooTests.cs
851
+ /_spec\.rb$/.test(ruta) || // foo_spec.rb
852
+ /Test\.php$/.test(ruta) // FooTest.php
853
+ );
854
+ }
855
+
856
+ /**
857
+ * ¿La ruta es un archivo generado/derivado/lock que NO debe exigir tests?
858
+ *
859
+ * @param {string} ruta - ruta relativa al repo
860
+ * @returns {boolean}
861
+ */
862
+ function esArchivoGenerado(ruta) {
863
+ return (
864
+ /(^|[/\\])(INVENTARIO|SALUD)\.md$/.test(ruta) ||
865
+ /(^|[/\\])llms\.txt$/.test(ruta) ||
866
+ /\.lock$/.test(ruta) ||
867
+ /(^|[/\\])(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|poetry\.lock|Cargo\.lock|go\.sum)$/.test(ruta) ||
868
+ /(^|[/\\])(dist|build|coverage|node_modules|__pycache__|\.next|target)[/\\]/.test(ruta) ||
869
+ /\.min\.(js|css)$/.test(ruta) ||
870
+ /(^|[/\\])\.planning[/\\]/.test(ruta)
871
+ );
872
+ }
873
+
874
+ /**
875
+ * ¿La ruta es código fuente (lenguaje soportado), excluyendo test/generado/
876
+ * docs/config? Lo que dispara el gate cuando se toca sin tests.
877
+ *
878
+ * @param {string} ruta - ruta relativa al repo
879
+ * @returns {boolean}
880
+ */
881
+ function esArchivoFuente(ruta) {
882
+ if (esArchivoTest(ruta) || esArchivoGenerado(ruta)) return false;
883
+ // Excluir docs/config/datos por extensión.
884
+ if (/\.(md|json|ya?ml|toml|ini|cfg|conf|txt|svg|png|jpe?g|gif|ico|csv|html?|xml|env)$/i.test(ruta)) {
885
+ return false;
886
+ }
887
+ return /\.(jsx?|tsx?|py|go|rs|java|kt|cs|rb|php|swift|c|cc|cpp|h|hpp|vue|svelte)$/i.test(ruta);
888
+ }
889
+
890
+ /**
891
+ * Extrae el mensaje del flag -m de un comando `git commit`. Soporta comillas
892
+ * dobles, simples o sin comillas (primera palabra). Devuelve '' si no hay -m.
893
+ *
894
+ * @param {string} comando
895
+ * @returns {string}
896
+ */
897
+ function extraerMensajeCommit(comando) {
898
+ // Soporta: -m "x" | -m 'x' | -m x | -m"x" | -m=x | --message="x" | --message x.
899
+ // Multi -m: captura el primero (el subject, donde vive el prefijo Conventional).
900
+ // -F/--file: no hay mensaje en la línea → retorna '' → NO se exenta → advierte
901
+ // (conservador y correcto para un gate warn-only). Verificación Fase 09, S-3.
902
+ const m = comando.match(/(?:-m|--message)\s*=?\s*(?:"([^"]*)"|'([^']*)'|(\S+))/);
903
+ if (!m) return '';
904
+ return m[1] || m[2] || m[3] || '';
905
+ }
906
+
907
+ /**
908
+ * ¿El prefijo Conventional Commit exime del gate de tests? (docs/chore/style)
909
+ *
910
+ * @param {string} mensaje
911
+ * @returns {boolean}
912
+ */
913
+ function esPrefijoExentoDeTests(mensaje) {
914
+ return /^\s*(docs|chore|style)(\([^)]*\))?!?:/i.test(mensaje);
915
+ }
916
+
917
+ /**
918
+ * Gate G3: detecta un diff staged que toca código fuente SIN tocar tests.
919
+ *
920
+ * @param {string[]} archivosStaged - rutas relativas staged
921
+ * @param {string} comandoCommit - el comando bash `git commit ...`
922
+ * @returns {{ fuentes: string[] } | null} datos del warning, o null si no aplica
923
+ */
924
+ function verificarTestPresence(archivosStaged, comandoCommit) {
925
+ const mensaje = extraerMensajeCommit(comandoCommit);
926
+ if (esPrefijoExentoDeTests(mensaje)) return null; // docs:/chore:/style: exentos
927
+
928
+ const fuentes = archivosStaged.filter(esArchivoFuente);
929
+ if (fuentes.length === 0) return null; // no toca código fuente
930
+
931
+ const tieneTests = archivosStaged.some(esArchivoTest);
932
+ if (tieneTests) return null; // hay al menos un test
933
+
934
+ return { fuentes };
935
+ }
936
+
937
+ /**
938
+ * Ejecutor real de cobertura (default de coverageDiferencial en el hook).
939
+ * Corre el runner detectado con timeout y parsea su reporte JSON al mapa
940
+ * { archivoRelativo: pct }. Best-effort: lanza si el runner falla o el reporte
941
+ * no es parseable, y coverageDiferencial captura el throw devolviendo null.
942
+ * @param {string[]} fuentes
943
+ * @param {{ runner: string, comando: string[], reporte: string }} runner
944
+ * @returns {Object<string, number>}
945
+ */
946
+ function ejecutarCoverage(fuentes, runner) {
947
+ const { execFileSync } = require('child_process');
948
+ const path = require('path');
949
+ const cwd = process.cwd();
950
+ // Correr el runner con coverage (timeout duro — un commit no debe colgarse).
951
+ execFileSync(runner.comando[0], runner.comando.slice(1), {
952
+ cwd,
953
+ stdio: 'ignore',
954
+ timeout: Number(process.env.SWL_COVERAGE_DIFF_TIMEOUT_MS) || 120000,
955
+ });
956
+ const reporteAbs = path.join(cwd, runner.reporte);
957
+ const raw = fs.readFileSync(reporteAbs, 'utf8');
958
+
959
+ // pytest-cov coverage.json: { files: { "src/a.py": { summary: { percent_covered } } } }
960
+ if (runner.runner === 'pytest') {
961
+ const data = JSON.parse(raw);
962
+ const out = {};
963
+ for (const [f, info] of Object.entries(data.files || {})) {
964
+ const pct = info?.summary?.percent_covered;
965
+ if (typeof pct === 'number') out[f.replace(/\\/g, '/')] = pct;
966
+ }
967
+ return out;
968
+ }
969
+ // istanbul coverage-final.json (jest/vitest): { "/abs/path": { s: {...}, statementMap } }
970
+ if (runner.runner === 'vitest' || runner.runner === 'jest') {
971
+ const data = JSON.parse(raw);
972
+ const out = {};
973
+ for (const [abs, info] of Object.entries(data)) {
974
+ const stmts = info?.s ? Object.values(info.s) : [];
975
+ if (!stmts.length) continue;
976
+ const cubiertos = stmts.filter((n) => n > 0).length;
977
+ const rel = path.relative(cwd, abs).replace(/\\/g, '/');
978
+ out[rel] = (cubiertos / stmts.length) * 100;
979
+ }
980
+ return out;
981
+ }
982
+ // go coverprofile: líneas "file.go:from.col,to.col stmts count"
983
+ if (runner.runner === 'go') {
984
+ const porArchivo = {};
985
+ for (const linea of raw.split('\n')) {
986
+ const m = linea.match(/^(.+\.go):\d+\.\d+,\d+\.\d+\s+(\d+)\s+(\d+)$/);
987
+ if (!m) continue;
988
+ const [, f, stmts, count] = m;
989
+ porArchivo[f] = porArchivo[f] || { total: 0, cub: 0 };
990
+ porArchivo[f].total += Number(stmts);
991
+ if (Number(count) > 0) porArchivo[f].cub += Number(stmts);
992
+ }
993
+ const out = {};
994
+ for (const [f, v] of Object.entries(porArchivo)) {
995
+ out[f.replace(/\\/g, '/')] = v.total ? (v.cub / v.total) * 100 : 0;
996
+ }
997
+ return out;
998
+ }
999
+ return {};
1000
+ }
1001
+
827
1002
  // ---------------------------------------------------------------------------
828
1003
  // Entrypoint principal
829
1004
  // ---------------------------------------------------------------------------
830
1005
 
831
1006
  let inputRaw = '';
832
1007
 
1008
+ // Solo enganchar stdin cuando el hook se ejecuta directamente (no al hacer
1009
+ // `require()` desde un test). Permite testear las funciones puras sin disparar
1010
+ // el flujo de stdin.
1011
+ if (require.main === module) {
1012
+
833
1013
  process.stdin.on('data', chunk => {
834
1014
  inputRaw += chunk;
835
1015
  });
@@ -861,6 +1041,76 @@ process.stdin.on('end', () => {
861
1041
  return;
862
1042
  }
863
1043
 
1044
+ // --- Gate G3: tests-presence (warn-only) ---
1045
+ // Detecta diffs que tocan código fuente sin tocar tests y emite un nudge.
1046
+ // NO bloquea (no exit 2): esta iteración es warn-only para calibrar falsos
1047
+ // positivos (renames, refactors puros). Exenciones: docs:/chore:/style: y
1048
+ // archivos generados/lock.
1049
+ //
1050
+ // PROMOCIÓN A BLOCKING: tras ~2 semanas de calibración sin falsos positivos
1051
+ // recurrentes, documentar la promoción en un ADR (patrón ADR-0027) y
1052
+ // convertir este bloque en `process.exit(2)` con la razón en stdout/stderr.
1053
+ // Hasta entonces: solo nudge.
1054
+ try {
1055
+ const g3 = verificarTestPresence(archivos, comando);
1056
+ if (g3) {
1057
+ const tracker = require('./lib/nudge-tracker');
1058
+ const muestra = g3.fuentes.slice(0, 3).join(', ');
1059
+ const sufijo = g3.fuentes.length > 3 ? ` (+${g3.fuentes.length - 3} más)` : '';
1060
+ tracker.emit({
1061
+ kind: 'tests-presence',
1062
+ target: g3.fuentes[0],
1063
+ source: 'hooks/calidad-pre-commit.js',
1064
+ message:
1065
+ `Gate G3 (warn): el commit toca ${g3.fuentes.length} archivo(s) de ` +
1066
+ `código fuente sin tocar ningún test: ${muestra}${sufijo}. ` +
1067
+ `Considera agregar o actualizar tests. Exenta el commit con prefijo ` +
1068
+ `docs:/chore:/style: si no aplica.`,
1069
+ data: { fuentes: g3.fuentes, gate: 'G3' },
1070
+ // 'optimize' = mejora de cobertura (no corrige un bug observado).
1071
+ // El criterio del plan mencionaba "warn", pero el enum válido del
1072
+ // tracker es repair|optimize|innovate; 'warn' se descartaría.
1073
+ mutation_category: 'optimize',
1074
+ risk_level: 'low',
1075
+ });
1076
+ }
1077
+ } catch (_) {
1078
+ // El gate G3 nunca rompe el commit (warn-only, best-effort).
1079
+ }
1080
+
1081
+ // --- Gate G2-cov: coverage diferencial (opt-in SWL_COVERAGE_DIFF=1) ---
1082
+ // Default OFF → cero latencia. Solo cuando el usuario lo activa: detecta el
1083
+ // runner, mide cobertura de los módulos tocados y emite nudge warn-only si
1084
+ // alguno cae bajo el umbral. Nunca bloquea (D-08).
1085
+ if (process.env.SWL_COVERAGE_DIFF === '1') {
1086
+ try {
1087
+ const fuentes = archivos.filter(esArchivoFuente);
1088
+ const runner = fuentes.length ? detectarRunnerCoverage(process.cwd()) : null;
1089
+ if (runner) {
1090
+ const umbral = Number(process.env.SWL_COVERAGE_DIFF_UMBRAL) || 80;
1091
+ const res = coverageDiferencial(fuentes, runner, { ejecutor: ejecutarCoverage, umbral });
1092
+ if (res && res.bajos.length) {
1093
+ const tracker = require('./lib/nudge-tracker');
1094
+ const muestra = res.bajos.slice(0, 3).join(', ');
1095
+ tracker.emit({
1096
+ kind: 'coverage-diff',
1097
+ target: res.bajos[0],
1098
+ source: 'hooks/calidad-pre-commit.js',
1099
+ message:
1100
+ `Coverage diferencial (warn): ${res.bajos.length} módulo(s) tocado(s) ` +
1101
+ `bajo ${umbral}% de cobertura: ${muestra}. Runner: ${runner.runner}. ` +
1102
+ `Agrega tests antes de mergear. Desactivar: SWL_COVERAGE_DIFF=0.`,
1103
+ data: { bajos: res.bajos, runner: runner.runner, umbral, gate: 'G2-cov' },
1104
+ mutation_category: 'optimize',
1105
+ risk_level: 'low',
1106
+ });
1107
+ }
1108
+ }
1109
+ } catch (_) {
1110
+ // Coverage diferencial es best-effort opt-in: nunca rompe el commit.
1111
+ }
1112
+ }
1113
+
864
1114
  // Verificar cada archivo
865
1115
  const todasLasViolaciones = [];
866
1116
 
@@ -927,3 +1177,90 @@ process.stdin.on('end', () => {
927
1177
  // El hook nunca bloquea por errores internos propios
928
1178
  }
929
1179
  });
1180
+
1181
+ } // fin if (require.main === module)
1182
+
1183
+ // ─── Coverage diferencial opt-in (gate G2-cov, Fase 11) ──────────────────────
1184
+ //
1185
+ // Opt-in estricto: solo corre con SWL_COVERAGE_DIFF=1 (default off → cero
1186
+ // latencia). Mide la cobertura de los módulos tocados en el diff staged y emite
1187
+ // nudge warn-only `coverage-diff` si alguno cae bajo el umbral (default 80%).
1188
+ // Decisión D-08 del 11-CONTEXTO.md.
1189
+
1190
+ /**
1191
+ * Detecta el runner de cobertura del proyecto por marcadores de archivo.
1192
+ * Detección barata (sin ejecutar nada) y segura ante ausencia.
1193
+ * @param {string} cwd
1194
+ * @returns {{ runner: string, comando: string[], reporte: string }|null}
1195
+ */
1196
+ function detectarRunnerCoverage(cwd) {
1197
+ const existe = (rel) => {
1198
+ try { return fs.existsSync(require('path').join(cwd, rel)); } catch { return false; }
1199
+ };
1200
+ const path = require('path');
1201
+
1202
+ // Node: leer package.json para distinguir vitest/jest.
1203
+ let pkg = null;
1204
+ try {
1205
+ pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
1206
+ } catch { /* sin package.json */ }
1207
+ const deps = pkg ? { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) } : {};
1208
+
1209
+ if (deps.vitest || existe('vitest.config.ts') || existe('vitest.config.js')) {
1210
+ return { runner: 'vitest', comando: ['npx', 'vitest', 'run', '--coverage'], reporte: 'coverage/coverage-final.json' };
1211
+ }
1212
+ if (deps.jest || existe('jest.config.js') || existe('jest.config.ts')) {
1213
+ return { runner: 'jest', comando: ['npx', 'jest', '--coverage', '--coverageReporters=json'], reporte: 'coverage/coverage-final.json' };
1214
+ }
1215
+ // Python: pytest-cov.
1216
+ if (existe('pyproject.toml') || existe('pytest.ini') || existe('setup.cfg') || existe('tox.ini')) {
1217
+ return { runner: 'pytest', comando: ['pytest', '--cov', '--cov-report=json'], reporte: 'coverage.json' };
1218
+ }
1219
+ // Go: cobertura nativa.
1220
+ if (existe('go.mod')) {
1221
+ return { runner: 'go', comando: ['go', 'test', '-coverprofile=coverage.out', './...'], reporte: 'coverage.out' };
1222
+ }
1223
+ return null;
1224
+ }
1225
+
1226
+ /**
1227
+ * Mide la cobertura de los módulos tocados y devuelve los que están bajo umbral.
1228
+ * El cómputo real de cobertura se delega a `opts.ejecutor` (inyectable para
1229
+ * tests; por defecto no implementado para evitar correr runners pesados en el
1230
+ * hook sin un parser robusto por formato). Best-effort: si el ejecutor falla,
1231
+ * devuelve null y el hook no emite nada.
1232
+ *
1233
+ * @param {string[]} fuentes Archivos de código fuente del diff staged.
1234
+ * @param {object} runner Descriptor de detectarRunnerCoverage.
1235
+ * @param {object} [opts]
1236
+ * @param {function} [opts.ejecutor] (fuentes, runner) => { [archivo]: pct }.
1237
+ * @param {number} [opts.umbral=80]
1238
+ * @returns {{ bajos: string[], cobertura: object }|null}
1239
+ */
1240
+ function coverageDiferencial(fuentes, runner, opts = {}) {
1241
+ const { ejecutor, umbral = 80 } = opts;
1242
+ if (typeof ejecutor !== 'function') return null;
1243
+ let cobertura;
1244
+ try {
1245
+ cobertura = ejecutor(fuentes, runner);
1246
+ } catch (_) {
1247
+ return null; // runner no instalado / reporte ilegible — best-effort.
1248
+ }
1249
+ if (!cobertura || typeof cobertura !== 'object') return null;
1250
+ const bajos = fuentes.filter(
1251
+ (f) => typeof cobertura[f] === 'number' && cobertura[f] < umbral,
1252
+ );
1253
+ return { bajos, cobertura };
1254
+ }
1255
+
1256
+ // Exportar funciones puras del gate G3 + coverage diferencial para testing.
1257
+ module.exports = {
1258
+ esArchivoTest,
1259
+ esArchivoGenerado,
1260
+ esArchivoFuente,
1261
+ extraerMensajeCommit,
1262
+ esPrefijoExentoDeTests,
1263
+ verificarTestPresence,
1264
+ detectarRunnerCoverage,
1265
+ coverageDiferencial,
1266
+ };
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: ciclo-evolucion-subagente.js
6
+ * Tipo: SubagentStop (async: true — fire-and-forget)
7
+ *
8
+ * Entry point del ciclo de evolución en el evento SubagentStop. Delega a la
9
+ * sub-etapa de auto-evolución (antes `auto-evolucion.js`), que analiza el
10
+ * payload del subagente, registra en el log y emite nudges de evolución
11
+ * (fallos/volumen/loop/drift). Fase 11, D-12.
12
+ *
13
+ * Nunca bloquea (siempre exit 0). Zero-deps fuera de hooks/lib/.
14
+ */
15
+
16
+ const cicloEvolucion = require('./lib/ciclo-evolucion');
17
+
18
+ let inputRaw = '';
19
+ process.stdin.on('data', chunk => { inputRaw += chunk; });
20
+ process.stdin.on('end', () => {
21
+ try {
22
+ cicloEvolucion.ejecutarSubagentStop(inputRaw);
23
+ } catch {
24
+ // El hook nunca bloquea por error interno.
25
+ }
26
+ });
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: ciclo-evolucion.js
6
+ * Tipo: Stop (async: true — fire-and-forget)
7
+ *
8
+ * Entry point del ciclo de evolución en el evento Stop. Ejecuta en UN solo
9
+ * proceso las sub-etapas que antes eran dos hooks separados
10
+ * (`metricas-evolucion.js` + `actualizar-perfil-usuario.js`), reduciendo de
11
+ * dos arranques de Node por sesión a uno (Fase 11, D-12).
12
+ *
13
+ * Nunca bloquea (siempre exit 0). Zero-deps fuera de hooks/lib/.
14
+ */
15
+
16
+ const cicloEvolucion = require('./lib/ciclo-evolucion');
17
+
18
+ let inputRaw = '';
19
+ process.stdin.on('data', chunk => { inputRaw += chunk; });
20
+ process.stdin.on('end', () => {
21
+ try {
22
+ cicloEvolucion.ejecutarStop(inputRaw);
23
+ } catch {
24
+ // El hook nunca bloquea por error interno.
25
+ }
26
+ });