@saulwade/swl-ses 1.9.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 (108) hide show
  1. package/CLAUDE.md +8 -8
  2. package/README.md +12 -12
  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 +7 -7
  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/tdd-qa-swl.md +17 -1
  16. package/comandos/swl/actualizar.md +1 -1
  17. package/comandos/swl/aprender.md +2 -2
  18. package/comandos/swl/aprobar-plan.md +152 -0
  19. package/comandos/swl/ayuda.md +3 -3
  20. package/comandos/swl/discutir-fase.md +20 -2
  21. package/comandos/swl/ejecutar-fase.md +53 -6
  22. package/comandos/swl/evolucionar.md +1 -1
  23. package/comandos/swl/inbox.md +1 -1
  24. package/comandos/swl/instalar.md +1 -1
  25. package/comandos/swl/nemesis.md +1 -1
  26. package/comandos/swl/planear-fase.md +17 -1
  27. package/comandos/swl/plugins.md +1 -1
  28. package/comandos/swl/release.md +1 -1
  29. package/comandos/swl/status.md +279 -0
  30. package/comandos/swl/verificar.md +26 -1
  31. package/habilidades/ai-runtime-security/SKILL.md +1 -1
  32. package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
  33. package/habilidades/benchmark-memoria/SKILL.md +1 -1
  34. package/habilidades/calidad-contract-testing/SKILL.md +165 -0
  35. package/habilidades/changelog-generator/SKILL.md +9 -2
  36. package/habilidades/changelog-generator/scripts/parse-commits.js +11 -1
  37. package/habilidades/diagrama-arquitectura/SKILL.md +1 -1
  38. package/habilidades/drift-detection/SKILL.md +179 -179
  39. package/habilidades/ejecutar-fase/SKILL.md +64 -14
  40. package/habilidades/estructura-proyecto-claude/SKILL.md +17 -14
  41. package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +34 -23
  42. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +70 -53
  43. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -77
  44. package/habilidades/extractor-de-aprendizajes/SKILL.md +9 -5
  45. package/habilidades/harness-claude-code/SKILL.md +10 -7
  46. package/{reglas/harness-claude-code.md → habilidades/harness-claude-code/recursos/disciplina-harness-regla.md} +2 -2
  47. package/habilidades/instalar-sistema/SKILL.md +3 -3
  48. package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +1 -1
  49. package/habilidades/perfil-usuario/SKILL.md +200 -200
  50. package/habilidades/planear-fase/SKILL.md +25 -4
  51. package/habilidades/proceso-ddia-fundamentos/SKILL.md +1 -1
  52. package/habilidades/proceso-ddia-streaming/SKILL.md +4 -4
  53. package/habilidades/proceso-debate-adversarial/SKILL.md +2 -2
  54. package/habilidades/protocolo-revision-swl/SKILL.md +1 -1
  55. package/habilidades/seguridad-skills-ia/SKILL.md +1 -1
  56. package/habilidades/swl-claudemd/SKILL.md +50 -210
  57. package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -0
  58. package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -0
  59. package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -0
  60. package/habilidades/swl-dashboard/SKILL.md +9 -9
  61. package/habilidades/swl-revisar-impacto/SKILL.md +1 -1
  62. package/habilidades/tdd-workflow/SKILL.md +45 -5
  63. package/habilidades/validacion-ci-sistema/SKILL.md +3 -3
  64. package/hooks/calidad-pre-commit.js +340 -3
  65. package/hooks/ciclo-evolucion-subagente.js +26 -0
  66. package/hooks/ciclo-evolucion.js +26 -0
  67. package/hooks/extraccion-aprendizajes.js +13 -0
  68. package/hooks/lib/ciclo-evolucion.js +47 -0
  69. package/hooks/{auto-evolucion.js → lib/etapa-auto-evolucion.js} +701 -700
  70. package/hooks/{metricas-evolucion.js → lib/etapa-metricas.js} +388 -376
  71. package/hooks/{actualizar-perfil-usuario.js → lib/etapa-perfil-usuario.js} +376 -364
  72. package/hooks/lib/evolution-tracker.js +24 -3
  73. package/hooks/spec-gate.js +211 -0
  74. package/hooks/tdd-gate.js +241 -0
  75. package/hooks/validar-intent-spec.js +30 -10
  76. package/llms.txt +6 -6
  77. package/manifiestos/hooks-config.json +26 -17
  78. package/manifiestos/modulos.json +17 -14
  79. package/manifiestos/skills-lock.json +63 -56
  80. package/package.json +2 -2
  81. package/plugin.json +6 -10
  82. package/reglas/accesibilidad.md +10 -0
  83. package/reglas/api-diseno.md +9 -0
  84. package/reglas/auditorias-documentales-estructurales.md +7 -0
  85. package/reglas/cloud-infra.md +8 -0
  86. package/reglas/fragmentos-compartidos.md +5 -0
  87. package/reglas/gobernanza.md +4 -4
  88. package/reglas/hooks.md +6 -0
  89. package/reglas/intent-engineering.md +4 -0
  90. package/reglas/markitdown.md +8 -0
  91. package/reglas/memoria-consolidada.md +1 -1
  92. package/reglas/patrones.md +6 -0
  93. package/reglas/registro-componentes-nuevos.md +10 -1
  94. package/reglas/seguridad-agentes.md +1 -1
  95. package/reglas/skills-estandar.md +6 -0
  96. package/reglas/testing.md +7 -0
  97. package/reglas/tests-cleanup.md +4 -0
  98. package/reglas/usar-sistema-swl.md +1 -1
  99. package/scripts/lib/gitignore-manifest.js +29 -1
  100. package/scripts/lib/plan-lock.js +275 -0
  101. package/scripts/migrar-fase-dominio.js +0 -1
  102. package/scripts/verificar-trazabilidad.js +292 -0
  103. package/agentes/ux-disenador-swl.md +0 -503
  104. package/comandos/swl/dashboard.md +0 -146
  105. package/comandos/swl/evolucion-estado.md +0 -191
  106. package/comandos/swl/metricas.md +0 -376
  107. package/comandos/swl/salud.md +0 -481
  108. package/reglas/verificar-citas-temporales.md +0 -139
@@ -64,6 +64,7 @@ const EXCLUDED_FILENAME_PATTERNS = [
64
64
  /\.rej$/, // patch reject
65
65
  /\.merge_file_/, // merge tools (kdiff3, etc.)
66
66
  /~$/, // editores tipo Emacs/Vim
67
+ /\.evolved-diff\.(md|txt)$/, // diffs de merge (no son componentes; .md legacy)
67
68
  ];
68
69
 
69
70
  /**
@@ -407,7 +408,13 @@ const DIFF_NOISY_THRESHOLD = 50;
407
408
  *
408
409
  * Estrategia: toma el archivo nuevo como base y re-aplica los campos de
409
410
  * evolución (frontmatter evolved-*). Las mutaciones de contenido se preservan
410
- * generando un archivo .evolved-diff.md que Claude puede re-aplicar.
411
+ * generando un archivo `.evolved-diff.txt` que Claude puede re-aplicar.
412
+ *
413
+ * Extensión `.txt` (no `.md`) deliberada: el diff vive junto al componente
414
+ * evolucionado (incluyendo `commands/`), pero el harness de Claude Code indexa
415
+ * todo `.md` dentro de `commands/` como slash-command — un `aprender.evolved-diff.md`
416
+ * aparecería como `/swl:aprender.evolved-diff`. Con `.txt` el harness no lo indexa
417
+ * y `scanEvolved` (que solo recorre `.md`) tampoco lo confunde con un componente.
411
418
  *
412
419
  * Comparación: solo el body (post-frontmatter) se compara línea-a-línea.
413
420
  * El frontmatter SIEMPRE diverge (el destino tiene campos `evolved-*` que el
@@ -415,7 +422,8 @@ const DIFF_NOISY_THRESHOLD = 50;
415
422
  * contarlo como mutación genera ruido por desplazamiento.
416
423
  *
417
424
  * Limpieza: cuando un merge posterior elimina la divergencia (diffs vacíos),
418
- * borra el `.evolved-diff.md` huérfano de sesiones previas si existe.
425
+ * borra el `.evolved-diff.txt` huérfano de sesiones previas si existe (y el
426
+ * `.evolved-diff.md` legacy de versiones anteriores a esta corrección).
419
427
  *
420
428
  * Cap defensivo: si tras alinear correctamente el body aún hay más de
421
429
  * `DIFF_NOISY_THRESHOLD` líneas distintas, genera un resumen estadístico
@@ -470,7 +478,17 @@ function mergeEvolved(destino, origen, versionNueva) {
470
478
  }
471
479
  }
472
480
 
473
- const diffPath = destino.replace(/\.md$/, '.evolved-diff.md');
481
+ const diffPath = destino.replace(/\.md$/, '.evolved-diff.txt');
482
+ // Legacy: versiones previas escribían el diff como `.evolved-diff.md`, que
483
+ // el harness indexaba como slash-command. Se limpia siempre que se toca el
484
+ // componente, exista o no divergencia nueva.
485
+ const diffPathLegacy = destino.replace(/\.md$/, '.evolved-diff.md');
486
+ const limpiarLegacy = () => {
487
+ if (fs.existsSync(diffPathLegacy)) {
488
+ try { fs.unlinkSync(diffPathLegacy); return true; } catch { /* best-effort */ }
489
+ }
490
+ return false;
491
+ };
474
492
 
475
493
  if (diffs.length === 0) {
476
494
  // Sin diferencias reales — limpiar diff huérfano si existe (de sesión
@@ -486,6 +504,7 @@ function mergeEvolved(destino, origen, versionNueva) {
486
504
  // el merge sigue siendo válido.
487
505
  }
488
506
  }
507
+ if (limpiarLegacy()) cleanedDiff = true;
489
508
 
490
509
  // force: true — `mergeEvolved` solo se invoca en contexto de update
491
510
  // intencional. El skip de isPackageRoot() aplica a la primera marca
@@ -557,6 +576,8 @@ function mergeEvolved(destino, origen, versionNueva) {
557
576
  ].join('\n');
558
577
 
559
578
  atomicWriteSync(diffPath, diffContent, 'utf8');
579
+ // Si existía el `.evolved-diff.md` legacy, eliminarlo: el `.txt` lo reemplaza.
580
+ limpiarLegacy();
560
581
 
561
582
  return { merged: true, diffPath, diffsCount: diffs.length, truncated };
562
583
  } catch (err) {
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: spec-gate.js (Gate G0 — SDD)
6
+ * Tipo: PreToolUse (matcher: Write|Edit|MultiEdit)
7
+ *
8
+ * Detecta escrituras de CÓDIGO FUENTE sin fase activa con PLAN aprobado.
9
+ * Implementa el gate G0 del flujo SPEC→PLAN→TEST→CODE→VERIFY (Revisión
10
+ * Evolutiva 03 §5.2): "ningún Write/Edit a código fuente sin fase activa
11
+ * con PLAN aprobado".
12
+ *
13
+ * Modo actual: WARN-ONLY (ADR-0034, decisión D-07 de 10-CONTEXTO.md).
14
+ * - Emite nudge `kind: spec-gate` a .planning/evolution/nudges.jsonl.
15
+ * - NUNCA exit 2. La promoción a blocking se decide vía ADR tras ~2
16
+ * semanas de calibración sin falsos positivos (patrón ADR-0027→0033).
17
+ *
18
+ * Concepto "fase activa": `.planning/locks/fase-activa.json`, escrito por
19
+ * /swl:aprobar-plan al firmar un plan y eliminado por /swl:ejecutar-fase al
20
+ * cerrar la fase. Formato:
21
+ * { numero, planPath, sha256, aprobadoEn, aprobadoPor }
22
+ * El hook re-computa el SHA256 del plan referenciado y lo compara con el del
23
+ * archivo de fase activa — verificación autocontenida (sin dependencia de
24
+ * scripts/lib/, que puede no estar distribuido en proyectos destino).
25
+ *
26
+ * Zero-config: en proyectos sin `.planning/` el hook sale 0 sin ruido.
27
+ * Opt-out: SWL_SPEC_GATE=0 desactiva completamente el hook.
28
+ *
29
+ * Modelo de amenaza (heredado de plan-lock.js): detecta omisión accidental
30
+ * del ciclo GSD y mutación post-firma; NO resiste a un adversario con
31
+ * escritura al repo (puede regenerar fase-activa.json).
32
+ */
33
+
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const crypto = require('crypto');
37
+
38
+ const RUTAS_EXCLUIDAS = [
39
+ '/temp/',
40
+ '/node_modules/',
41
+ '/respositorios-git/',
42
+ '/_userland/',
43
+ '/.planning/',
44
+ '/.claude/',
45
+ ];
46
+
47
+ /**
48
+ * Clasificador de código fuente. Reutiliza esArchivoFuente() de
49
+ * calidad-pre-commit.js (misma definición que el gate G3) con fallback
50
+ * defensivo si el módulo no está disponible en el destino.
51
+ */
52
+ function cargarClasificador() {
53
+ try {
54
+ const calidad = require(path.join(__dirname, 'calidad-pre-commit.js'));
55
+ if (typeof calidad.esArchivoFuente === 'function') return calidad.esArchivoFuente;
56
+ } catch (_) {
57
+ // Fallback mínimo: extensiones de código de los lenguajes soportados,
58
+ // excluyendo tests por sufijo/directorio.
59
+ }
60
+ return function esArchivoFuenteFallback(ruta) {
61
+ const normal = ruta.replace(/\\/g, '/');
62
+ if (/\.(test|spec)\.[jt]sx?$/.test(normal)) return false;
63
+ if (/(^|\/)(tests?|__tests__|spec)\//.test(normal)) return false;
64
+ if (/\.(md|json|ya?ml|toml|ini|cfg|conf|txt|svg|lock)$/i.test(normal)) return false;
65
+ return /\.(jsx?|tsx?|py|go|rs|java|kt|cs|rb|php|swift|c|cc|cpp|h|hpp)$/i.test(normal);
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Evalúa el estado de la fase activa del proyecto en `cwd`.
71
+ * @returns {{ ok: boolean, motivo: string|null, numero?: number }}
72
+ * ok=true → hay fase activa con plan íntegro (no advertir)
73
+ * ok=false → motivo describe por qué no hay cobertura de spec
74
+ */
75
+ function evaluarFaseActiva(cwd) {
76
+ const rutaFaseActiva = path.join(cwd, '.planning', 'locks', 'fase-activa.json');
77
+ if (!fs.existsSync(rutaFaseActiva)) {
78
+ return { ok: false, motivo: 'sin fase activa (no existe .planning/locks/fase-activa.json)' };
79
+ }
80
+
81
+ let faseActiva;
82
+ try {
83
+ faseActiva = JSON.parse(fs.readFileSync(rutaFaseActiva, 'utf-8'));
84
+ } catch (_) {
85
+ return { ok: false, motivo: 'fase-activa.json corrupto (JSON inválido)' };
86
+ }
87
+
88
+ if (!faseActiva || typeof faseActiva.planPath !== 'string' || typeof faseActiva.sha256 !== 'string') {
89
+ return { ok: false, motivo: 'fase-activa.json sin planPath/sha256' };
90
+ }
91
+
92
+ const planAbs = path.isAbsolute(faseActiva.planPath)
93
+ ? faseActiva.planPath
94
+ : path.join(cwd, faseActiva.planPath);
95
+
96
+ if (!fs.existsSync(planAbs)) {
97
+ return { ok: false, motivo: `el plan de la fase activa no existe (${faseActiva.planPath})` };
98
+ }
99
+
100
+ let hashActual;
101
+ try {
102
+ hashActual = crypto.createHash('sha256').update(fs.readFileSync(planAbs)).digest('hex');
103
+ } catch (_) {
104
+ return { ok: false, motivo: 'no se pudo leer el plan de la fase activa' };
105
+ }
106
+
107
+ if (hashActual !== faseActiva.sha256) {
108
+ return {
109
+ ok: false,
110
+ motivo: `el plan de la fase activa fue mutado tras la firma (${faseActiva.planPath})`,
111
+ numero: faseActiva.numero,
112
+ };
113
+ }
114
+
115
+ return { ok: true, motivo: null, numero: faseActiva.numero };
116
+ }
117
+
118
+ /**
119
+ * Decide si la escritura amerita advertencia G0.
120
+ * @returns {null | { motivo: string }} null = silencio
121
+ */
122
+ function evaluarEscritura({ cwd, toolName, filePath, esArchivoFuente }) {
123
+ if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) return null;
124
+ if (!filePath) return null;
125
+
126
+ // Zero-config: sin .planning/ no hay ciclo GSD que vigilar.
127
+ if (!fs.existsSync(path.join(cwd, '.planning'))) return null;
128
+
129
+ const normal = filePath.replace(/\\/g, '/');
130
+ if (RUTAS_EXCLUIDAS.some((ex) => normal.includes(ex))) return null;
131
+ if (!esArchivoFuente(normal)) return null;
132
+
133
+ const fase = evaluarFaseActiva(cwd);
134
+ if (fase.ok) return null;
135
+
136
+ return { motivo: fase.motivo };
137
+ }
138
+
139
+ function emitirNudge({ cwd, filePath, motivo }) {
140
+ const rutaRelativa = path.relative(cwd, filePath).replace(/\\/g, '/');
141
+ let emit;
142
+ try {
143
+ ({ emit } = require(path.join(__dirname, 'lib', 'nudge-tracker.js')));
144
+ } catch (_) {
145
+ return;
146
+ }
147
+ try {
148
+ emit({
149
+ kind: 'spec-gate',
150
+ target: rutaRelativa,
151
+ source: 'hooks/spec-gate.js',
152
+ message:
153
+ `Gate G0 (warn): escritura de código fuente ${rutaRelativa} ${motivo}. ` +
154
+ `El flujo SDD espera una fase activa con PLAN aprobado (/swl:aprobar-plan N) ` +
155
+ `antes de escribir código. Si es un fix trivial o exploración, ignora este aviso ` +
156
+ `— se usa para calibrar la promoción a blocking (ADR-0034). Opt-out: SWL_SPEC_GATE=0.`,
157
+ data: { archivo: rutaRelativa, motivo, gate: 'G0' },
158
+ mutation_category: 'optimize',
159
+ risk_level: 'low',
160
+ });
161
+ } catch (_) {
162
+ // La observabilidad nunca bloquea el trabajo productivo.
163
+ }
164
+ }
165
+
166
+ function main() {
167
+ if (process.env.SWL_SPEC_GATE === '0') process.exit(0);
168
+
169
+ let hookInput = '';
170
+ try {
171
+ hookInput = fs.readFileSync(0, 'utf-8');
172
+ } catch (_) {
173
+ process.exit(0);
174
+ }
175
+
176
+ let evento;
177
+ try {
178
+ evento = JSON.parse(hookInput);
179
+ } catch (_) {
180
+ process.exit(0);
181
+ }
182
+
183
+ const cwd = process.cwd();
184
+ const toolName = evento?.tool_name;
185
+ const filePath = evento?.tool_input?.file_path;
186
+
187
+ let resultado = null;
188
+ try {
189
+ resultado = evaluarEscritura({
190
+ cwd,
191
+ toolName,
192
+ filePath,
193
+ esArchivoFuente: cargarClasificador(),
194
+ });
195
+ } catch (_) {
196
+ process.exit(0);
197
+ }
198
+
199
+ if (resultado) {
200
+ emitirNudge({ cwd, filePath, motivo: resultado.motivo });
201
+ }
202
+
203
+ // WARN-ONLY (ADR-0034): jamás exit 2 en este modo.
204
+ process.exit(0);
205
+ }
206
+
207
+ if (require.main === module) {
208
+ main();
209
+ }
210
+
211
+ module.exports = { evaluarFaseActiva, evaluarEscritura, RUTAS_EXCLUIDAS };
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: tdd-gate.js (Gate G2 — TDD con evidencia RED)
6
+ * Tipo: PreToolUse (matcher: Bash)
7
+ *
8
+ * Ante un `git commit` de feature (código fuente + tests en el diff staged),
9
+ * verifica que exista evidencia de ciclo RED en la telemetría de loops
10
+ * (`iteraciones.tsv` dentro de `.planning/loops/tdd-…`, escrita por
11
+ * ejecutar-fase / tdd-workflow). Cierra F-TDD-6 de la Revisión Evolutiva 03:
12
+ * "TDD declarativo sin evidencia — el RED no deja rastro".
13
+ *
14
+ * Modo actual: WARN-ONLY (ADR-0035, decisión D-07 de 10-CONTEXTO.md).
15
+ * - Emite nudge `kind: tdd-red-evidence`; NUNCA exit 2 en este modo.
16
+ *
17
+ * Alcance deliberado (sin doble nudge ni falsos dominios):
18
+ * - Solo aplica con FASE ACTIVA (`.planning/locks/fase-activa.json`) — el
19
+ * enforcement TDD es parte del ciclo GSD, no de commits sueltos.
20
+ * - Solo cuando el diff staged toca fuente Y tests: el caso "fuente sin
21
+ * tests" es dominio del gate G3 (calidad-pre-commit) y ya tiene nudge.
22
+ * - Respeta el opt-out declarado del CONTEXTO (`**TDD**: off — razón: ...`).
23
+ * - Prefijos exentos: docs:/chore:/style: (mismos que G3).
24
+ *
25
+ * Evidencia RED válida: corrida `tdd-*` cuya `iteraciones.tsv` fue modificada
26
+ * dentro de la ventana (default 24h, `SWL_TDD_GATE_VENTANA_H`) con una fila
27
+ * de estado `baseline` con métrica > 0, o cuya descripción contenga "RED".
28
+ *
29
+ * Opt-out: SWL_TDD_GATE=0 desactiva completamente el hook.
30
+ */
31
+
32
+ const fs = require('fs');
33
+ const path = require('path');
34
+ const { execFileSync } = require('child_process');
35
+
36
+ const VENTANA_HORAS_DEFAULT = 24;
37
+
38
+ /** ¿El comando es un git commit? (ignora git status/log/diff y comandos no-git) */
39
+ function esGitCommit(comando) {
40
+ if (typeof comando !== 'string') return false;
41
+ // Cinturón: un comando legítimo de commit no excede unos KB. Acotar evita
42
+ // costo lineal patológico del regex ante input gigante (hardening, no ReDoS).
43
+ if (comando.length > 10000) return false;
44
+ return /\bgit\b[^|&;]*\bcommit\b/.test(comando);
45
+ }
46
+
47
+ /** Lee el numero de fase activa y verifica el opt-out del CONTEXTO. */
48
+ function estadoTddDeFaseActiva(cwd) {
49
+ const rutaFaseActiva = path.join(cwd, '.planning', 'locks', 'fase-activa.json');
50
+ if (!fs.existsSync(rutaFaseActiva)) return { aplica: false, motivo: 'sin-fase-activa' };
51
+
52
+ let faseActiva;
53
+ try {
54
+ faseActiva = JSON.parse(fs.readFileSync(rutaFaseActiva, 'utf-8'));
55
+ } catch (_) {
56
+ return { aplica: false, motivo: 'fase-activa-corrupta' };
57
+ }
58
+
59
+ const numero = Number(faseActiva?.numero);
60
+ if (!Number.isFinite(numero)) return { aplica: false, motivo: 'fase-activa-sin-numero' };
61
+
62
+ const nn = String(numero).padStart(2, '0');
63
+ const contextoPath = path.join(cwd, '.planning', 'fases', `${nn}-CONTEXTO.md`);
64
+ if (fs.existsSync(contextoPath)) {
65
+ try {
66
+ const contexto = fs.readFileSync(contextoPath, 'utf-8');
67
+ if (/\*\*TDD\*\*:\s*off/i.test(contexto)) {
68
+ return { aplica: false, motivo: 'tdd-off-declarado' };
69
+ }
70
+ } catch (_) {
71
+ // CONTEXTO ilegible: asumir TDD on (default)
72
+ }
73
+ }
74
+ return { aplica: true, numero: nn };
75
+ }
76
+
77
+ /** Archivos staged del repo en cwd (lista vacía si no es repo o falla git). */
78
+ function archivosStaged(cwd) {
79
+ try {
80
+ const out = execFileSync('git', ['diff', '--cached', '--name-only'], {
81
+ cwd,
82
+ encoding: 'utf8',
83
+ timeout: 5000,
84
+ });
85
+ return out.split('\n').map((l) => l.trim()).filter(Boolean);
86
+ } catch (_) {
87
+ return [];
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Busca evidencia RED en `.planning/loops/tdd-*` dentro de la ventana.
93
+ * @returns {boolean}
94
+ */
95
+ function hayEvidenciaRed(cwd, ventanaHoras = VENTANA_HORAS_DEFAULT) {
96
+ const loopsDir = path.join(cwd, '.planning', 'loops');
97
+ if (!fs.existsSync(loopsDir)) return false;
98
+
99
+ let corridas;
100
+ try {
101
+ corridas = fs.readdirSync(loopsDir).filter((d) => d.startsWith('tdd-'));
102
+ } catch (_) {
103
+ return false;
104
+ }
105
+
106
+ const limite = Date.now() - ventanaHoras * 3600 * 1000;
107
+
108
+ for (const corrida of corridas) {
109
+ const tsvPath = path.join(loopsDir, corrida, 'iteraciones.tsv');
110
+ let stat;
111
+ try {
112
+ stat = fs.statSync(tsvPath);
113
+ } catch (_) {
114
+ continue;
115
+ }
116
+ if (stat.mtimeMs < limite) continue;
117
+
118
+ let contenido;
119
+ try {
120
+ contenido = fs.readFileSync(tsvPath, 'utf-8');
121
+ } catch (_) {
122
+ continue;
123
+ }
124
+
125
+ for (const linea of contenido.split('\n')) {
126
+ if (linea.startsWith('#') || linea.startsWith('iteracion')) continue;
127
+ const cols = linea.split('\t');
128
+ if (cols.length < 6) continue;
129
+ const metrica = Number(cols[2]);
130
+ const estado = (cols[4] || '').trim();
131
+ const descripcion = cols[5] || '';
132
+ if ((estado === 'baseline' && metrica > 0) || /\bRED\b/.test(descripcion)) {
133
+ return true;
134
+ }
135
+ }
136
+ }
137
+ return false;
138
+ }
139
+
140
+ /**
141
+ * Decide si el commit amerita advertencia G2.
142
+ * @returns {null | { fuentes: string[], tests: string[] }}
143
+ */
144
+ function evaluarCommit({ cwd, comando, helpers }) {
145
+ if (!esGitCommit(comando)) return null;
146
+
147
+ const mensaje = helpers.extraerMensajeCommit(comando);
148
+ if (helpers.esPrefijoExentoDeTests(mensaje)) return null;
149
+
150
+ const fase = estadoTddDeFaseActiva(cwd);
151
+ if (!fase.aplica) return null;
152
+
153
+ const staged = archivosStaged(cwd);
154
+ const fuentes = staged.filter(helpers.esArchivoFuente);
155
+ const tests = staged.filter(helpers.esArchivoTest);
156
+ // Solo el caso feature-con-tests: "fuente sin tests" es dominio de G3.
157
+ if (fuentes.length === 0 || tests.length === 0) return null;
158
+
159
+ const ventana = Number(process.env.SWL_TDD_GATE_VENTANA_H) || VENTANA_HORAS_DEFAULT;
160
+ if (hayEvidenciaRed(cwd, ventana)) return null;
161
+
162
+ return { fuentes, tests };
163
+ }
164
+
165
+ function emitirNudge({ cwd, hallazgo }) {
166
+ let emit;
167
+ try {
168
+ ({ emit } = require(path.join(__dirname, 'lib', 'nudge-tracker.js')));
169
+ } catch (_) {
170
+ return;
171
+ }
172
+ try {
173
+ const muestra = hallazgo.tests.slice(0, 2).join(', ');
174
+ emit({
175
+ kind: 'tdd-red-evidence',
176
+ target: hallazgo.tests[0],
177
+ source: 'hooks/tdd-gate.js',
178
+ message:
179
+ `Gate G2 (warn): commit de feature con tests (${muestra}) sin evidencia de ciclo ` +
180
+ `RED en .planning/loops/tdd-*/ — el TDD exige registrar el test fallando ANTES de ` +
181
+ `implementar (Skill("tdd-workflow") § Evidencia RED en telemetría). Este aviso ` +
182
+ `calibra la promoción a blocking (ADR-0035). Opt-out: SWL_TDD_GATE=0 o ` +
183
+ `'**TDD**: off — razón: ...' en el CONTEXTO de la fase.`,
184
+ data: { fuentes: hallazgo.fuentes, tests: hallazgo.tests, gate: 'G2' },
185
+ mutation_category: 'optimize',
186
+ risk_level: 'low',
187
+ });
188
+ } catch (_) {
189
+ // La observabilidad nunca bloquea el trabajo productivo.
190
+ }
191
+ }
192
+
193
+ function main() {
194
+ if (process.env.SWL_TDD_GATE === '0') process.exit(0);
195
+
196
+ let hookInput = '';
197
+ try {
198
+ hookInput = fs.readFileSync(0, 'utf-8');
199
+ } catch (_) {
200
+ process.exit(0);
201
+ }
202
+
203
+ let evento;
204
+ try {
205
+ evento = JSON.parse(hookInput);
206
+ } catch (_) {
207
+ process.exit(0);
208
+ }
209
+
210
+ if (evento?.tool_name !== 'Bash') process.exit(0);
211
+ const comando = evento?.tool_input?.command;
212
+ const cwd = process.cwd();
213
+
214
+ // Zero-config: sin .planning/ no hay ciclo GSD que vigilar.
215
+ if (!fs.existsSync(path.join(cwd, '.planning'))) process.exit(0);
216
+
217
+ let helpers;
218
+ try {
219
+ helpers = require(path.join(__dirname, 'calidad-pre-commit.js'));
220
+ } catch (_) {
221
+ process.exit(0);
222
+ }
223
+
224
+ let hallazgo = null;
225
+ try {
226
+ hallazgo = evaluarCommit({ cwd, comando, helpers });
227
+ } catch (_) {
228
+ process.exit(0);
229
+ }
230
+
231
+ if (hallazgo) emitirNudge({ cwd, hallazgo });
232
+
233
+ // WARN-ONLY (ADR-0035): jamás exit 2 en este modo.
234
+ process.exit(0);
235
+ }
236
+
237
+ if (require.main === module) {
238
+ main();
239
+ }
240
+
241
+ module.exports = { esGitCommit, estadoTddDeFaseActiva, hayEvidenciaRed, evaluarCommit };
@@ -14,9 +14,12 @@
14
14
  * - fragmentos: [_intent-spec]
15
15
  * - maxTurnos (Parte 8 — Stop Rules)
16
16
  *
17
- * Comportamiento:
18
- * - NUNCA bloquea (exit code 0 siempre blocking:false en hooks-config)
19
- * - Solo emite nudge cuando faltan campos en agente ALTO ruido mínimo
17
+ * Comportamiento (modo blocking desde ADR-0033):
18
+ * - BLOQUEA (exit 2) cuando un agente ALTO tiene frontmatter incompleto.
19
+ * El Write/Edit ya ocurrió (PostToolUse); el exit 2 surface el error a
20
+ * Claude para que complete el intent spec. Sigue emitiendo el nudge para
21
+ * trazabilidad antes de bloquear.
22
+ * - Sale 0 (sin ruido) cuando el agente ALTO está completo o no es ALTO.
20
23
  * - Solo se dispara con archivos `agentes/*.md` (no fragmentos `_*.md`)
21
24
  * - Respeta exclusiones: temp/, node_modules/, respositorios-git/, _userland/
22
25
  *
@@ -34,9 +37,10 @@
34
37
  * accionado: false
35
38
  * }
36
39
  *
37
- * Origen: ADR-0027 (F3 Opción B integración Lead Agents + DDIA).
38
- * Plan de promoción a `blocking:true`: tras 2 semanas sin falsos positivos
39
- * documentados, con nudges accionados, considerar bloqueo activo.
40
+ * Origen: ADR-0027 (warn-only) ADR-0033 (promoción a blocking, 2026-06-11).
41
+ * Promoción justificada: 24 días estable en warn-only, 8/8 agentes ALTO con
42
+ * frontmatter completo al momento de la promoción (cero falsos positivos sobre
43
+ * el estado actual). Opt-out preservado: SWL_INTENT_SPEC=0.
40
44
  */
41
45
 
42
46
  const fs = require('fs');
@@ -216,7 +220,23 @@ try {
216
220
  }
217
221
  fs.appendFileSync(nudgesFile, JSON.stringify(nudge) + '\n');
218
222
  } catch (_) {
219
- // Si falla persistir, no romper el flujo del usuario
220
- }
221
-
222
- process.exit(0);
223
+ // Si falla persistir, no romper el flujo del bloqueo
224
+ }
225
+
226
+ // ─── Modo blocking (ADR-0033) ─────────────────────────────────────────────
227
+ // El agente ALTO tiene frontmatter incompleto: bloquear (exit 2) y surface el
228
+ // detalle a Claude por stderr para que complete el intent spec. Opt-out global
229
+ // SWL_INTENT_SPEC=0 ya cortó arriba; aquí el bloqueo es activo.
230
+ const razon = [
231
+ `Intent spec incompleto en agente ALTO riesgo: ${nombreAgente}`,
232
+ `Faltan campos obligatorios: ${faltan.join(', ')}.`,
233
+ ``,
234
+ `Completa el frontmatter según reglas/intent-engineering.md:`,
235
+ ` strategy, healthMetrics, steering, hardGuardrails, maxTurnos,`,
236
+ ` y fragmentos: [_intent-spec].`,
237
+ `Carga Skill("proceso-intent-engineering") para la guía completa.`,
238
+ ``,
239
+ `Para omitir esta verificación: SWL_INTENT_SPEC=0 (registra la excepción).`,
240
+ ].join('\n');
241
+ process.stderr.write(razon);
242
+ process.exit(2);
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # swl-ses (@saulwade/swl-ses)
2
2
 
3
- > Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. 61 agentes, 181 habilidades, 45 comandos, 31 reglas base y 45 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 1.9.0.
3
+ > Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo), distribuido como paquete npm y plugin de Claude Code. 60 agentes, 182 habilidades, 43 comandos, 29 reglas base y 46 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 2.0.0.
4
4
 
5
5
  Archivo generado por `node scripts/generar-inventario.js` — no editar a mano. Las cifras se sincronizan con INVENTARIO.md en cada regeneración.
6
6
 
@@ -16,11 +16,11 @@ Archivo generado por `node scripts/generar-inventario.js` — no editar a mano.
16
16
 
17
17
  ## Componentes
18
18
 
19
- - 61 agentes especializados en `agentes/` (orquestación, implementación por stack, revisión, calidad, diseño)
20
- - 181 habilidades cargables bajo demanda en `habilidades/` (conocimiento operacional con divulgación progresiva)
21
- - 45 comandos `/swl:*` en `comandos/swl/` (ciclo GSD, calidad, release, diagnóstico)
22
- - 31 reglas base + 40 reglas por lenguaje en `reglas/` (políticas obligatorias por matcher)
23
- - 45 hooks en `hooks/` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)
19
+ - 60 agentes especializados en `agentes/` (orquestación, implementación por stack, revisión, calidad, diseño)
20
+ - 182 habilidades cargables bajo demanda en `habilidades/` (conocimiento operacional con divulgación progresiva)
21
+ - 43 comandos `/swl:*` en `comandos/swl/` (ciclo GSD, calidad, release, diagnóstico)
22
+ - 29 reglas base + 40 reglas por lenguaje en `reglas/` (políticas obligatorias por matcher)
23
+ - 46 hooks en `hooks/` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)
24
24
 
25
25
  ## Opcional
26
26
 
@@ -168,19 +168,10 @@
168
168
  "maxConsecutiveFailures": 5,
169
169
  "degradeOnFailure": "skip"
170
170
  },
171
- "actualizar-perfil-usuario.js": {
171
+ "ciclo-evolucion.js": {
172
172
  "event": "Stop",
173
173
  "matcher": "",
174
- "description": "Detecta señales de corrección/preferencia del usuario y acumula un dirty-bit en .planning/user-profile/. Emite nudge a perfilador-usuario-swl cuando cruza el umbral (default 3 señales).",
175
- "blocking": false,
176
- "async": true,
177
- "maxConsecutiveFailures": 5,
178
- "degradeOnFailure": "skip"
179
- },
180
- "metricas-evolucion.js": {
181
- "event": "Stop",
182
- "matcher": "",
183
- "description": "Consolida métricas del ciclo de evolución (nudges accionados/pendientes, instintos, fallos de agentes, evoluciones aplicadas/revertidas) y actualiza .planning/evolution/metricas.json con health_score compuesto. Dispara escalamiento a alertas persistentes si >=5 nudges del mismo tipo ignorados en 14d.",
174
+ "description": "Ciclo de evolución (evento Stop): en un solo proceso ejecuta las sub-etapas de métricas (consolida .planning/evolution/metricas.json con health_score; escala alertas persistentes si >=5 nudges del mismo tipo ignorados en 14d) y perfil de usuario (acumula dirty-bit en .planning/user-profile/; nudge a perfilador-usuario-swl al cruzar umbral). Fusión de metricas-evolucion.js + actualizar-perfil-usuario.js (Fase 11, D-12). Lógica en hooks/lib/etapa-{metricas,perfil-usuario}.js.",
184
175
  "blocking": false,
185
176
  "async": true,
186
177
  "maxConsecutiveFailures": 5,
@@ -195,10 +186,10 @@
195
186
  "maxConsecutiveFailures": 5,
196
187
  "degradeOnFailure": "skip"
197
188
  },
198
- "auto-evolucion.js": {
189
+ "ciclo-evolucion-subagente.js": {
199
190
  "event": "SubagentStop",
200
191
  "matcher": "",
201
- "description": "Registra cada terminación de subagente SWL en .planning/auto-evolution/agentes.jsonl y emite nudge a /swl:evolucionar cuando un agente acumula fallos o volumen de runs (ventana 14 días, throttle 24h).",
192
+ "description": "Ciclo de evolución (evento SubagentStop): registra cada terminación de subagente SWL en .planning/auto-evolution/agentes.jsonl y emite nudge a /swl:evolucionar cuando un agente acumula fallos, volumen de runs, loop o drift (ventana 14 días, throttle 24h). Renombrado de auto-evolucion.js (Fase 11, D-12). Lógica en hooks/lib/etapa-auto-evolucion.js.",
202
193
  "blocking": false,
203
194
  "async": true,
204
195
  "maxConsecutiveFailures": 5,
@@ -374,14 +365,32 @@
374
365
  "maxConsecutiveFailures": 5,
375
366
  "degradeOnFailure": "skip"
376
367
  },
368
+ "tdd-gate.js": {
369
+ "event": "PreToolUse",
370
+ "matcher": "Bash",
371
+ "description": "Gate G2 (TDD): ante git commit de feature (fuente+tests staged) verifica evidencia de ciclo RED en .planning/loops/tdd-* (telemetria escrita por ejecutar-fase/tdd-workflow). Sin evidencia emite nudge kind:tdd-red-evidence (warn-only, NUNCA exit 2 en este modo). Solo aplica con fase activa (ciclo GSD); 'fuente sin tests' es dominio de G3 (sin doble nudge). Respeta opt-out declarado del CONTEXTO (**TDD**: off) y prefijos docs:/chore:/style:. Ventana de evidencia: 24h (SWL_TDD_GATE_VENTANA_H). Opt-out: SWL_TDD_GATE=0. Promocion a blocking via ADR-0035 (D-07).",
372
+ "blocking": false,
373
+ "async": false,
374
+ "maxConsecutiveFailures": 5,
375
+ "degradeOnFailure": "warn"
376
+ },
377
+ "spec-gate.js": {
378
+ "event": "PreToolUse",
379
+ "matcher": "Write|Edit|MultiEdit",
380
+ "description": "Gate G0 (SDD): detecta Write/Edit a codigo fuente sin fase activa con PLAN aprobado. Lee .planning/locks/fase-activa.json (escrito por /swl:aprobar-plan) y re-computa SHA256 del plan referenciado; sin fase activa o plan mutado emite nudge kind:spec-gate (warn-only, NUNCA exit 2 en este modo). Zero-config: sin .planning/ sale silencioso. Excluye tests/generados/docs (clasificador esArchivoFuente de calidad-pre-commit) y temp/, node_modules/, _userland/, respositorios-git/. Opt-out: SWL_SPEC_GATE=0. Promocion a blocking via ADR-0034 tras ~2 semanas de calibracion (D-07).",
381
+ "blocking": false,
382
+ "async": false,
383
+ "maxConsecutiveFailures": 5,
384
+ "degradeOnFailure": "warn"
385
+ },
377
386
  "validar-intent-spec.js": {
378
387
  "event": "PostToolUse",
379
388
  "matcher": "Write|Edit|MultiEdit",
380
- "description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolution/nudges.jsonl si faltan campos. No bloquea (modo warn-only inicial; promocion a blocking:true tras 2 semanas estables). Excluye temp/, node_modules/, respositorios-git/, _userland/, .planning/. Opt-out: SWL_INTENT_SPEC=0. ADR-0027.",
381
- "blocking": false,
382
- "async": true,
389
+ "description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolution/nudges.jsonl y BLOQUEA (exit 2) si faltan campos. Excluye temp/, node_modules/, respositorios-git/, _userland/, .planning/. Opt-out: SWL_INTENT_SPEC=0. ADR-0027 (warn-only) -> ADR-0033 (blocking, 2026-06-11).",
390
+ "blocking": true,
391
+ "async": false,
383
392
  "maxConsecutiveFailures": 5,
384
- "degradeOnFailure": "skip"
393
+ "degradeOnFailure": "warn"
385
394
  },
386
395
  "notificacion-telegram.js": {
387
396
  "event": "Stop",