@saulwade/swl-ses 1.9.0 → 2.1.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 (142) hide show
  1. package/CLAUDE.md +196 -196
  2. package/README.md +579 -579
  3. package/agentes/_propose-step.md +90 -0
  4. package/agentes/accesibilidad-wcag-swl.md +3 -3
  5. package/agentes/auto-evolucion-swl.md +908 -908
  6. package/agentes/disenador-ui-swl.md +6 -5
  7. package/agentes/frontend-angular-swl.md +2 -2
  8. package/agentes/frontend-css-swl.md +2 -2
  9. package/agentes/frontend-react-swl.md +4 -4
  10. package/agentes/frontend-swl.md +6 -6
  11. package/agentes/implementador-swl.md +2 -0
  12. package/agentes/investigador-ux-swl.md +5 -5
  13. package/agentes/orquestador-swl.md +9 -7
  14. package/agentes/perfilador-usuario-swl.md +321 -308
  15. package/agentes/producto-prd-swl.md +1 -1
  16. package/agentes/red-team-swl.md +218 -218
  17. package/agentes/tdd-qa-swl.md +17 -1
  18. package/bin/swl-ses.js +1 -1
  19. package/comandos/swl/actualizar.md +1 -1
  20. package/comandos/swl/aprender.md +2 -2
  21. package/comandos/swl/aprobar-plan.md +153 -0
  22. package/comandos/swl/ayuda.md +3 -3
  23. package/comandos/swl/briefing.md +122 -0
  24. package/comandos/swl/compactar.md +29 -2
  25. package/comandos/swl/discutir-fase.md +23 -2
  26. package/comandos/swl/ejecutar-fase.md +59 -6
  27. package/comandos/swl/evolucionar.md +1 -1
  28. package/comandos/swl/inbox.md +1 -1
  29. package/comandos/swl/instalar.md +1 -1
  30. package/comandos/swl/nemesis.md +1 -1
  31. package/comandos/swl/planear-fase.md +19 -1
  32. package/comandos/swl/plugins.md +1 -1
  33. package/comandos/swl/release.md +47 -1
  34. package/comandos/swl/status.md +348 -0
  35. package/comandos/swl/verificar.md +27 -1
  36. package/habilidades/ai-runtime-security/SKILL.md +1 -1
  37. package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
  38. package/habilidades/benchmark-memoria/SKILL.md +1 -1
  39. package/habilidades/calidad-contract-testing/SKILL.md +165 -0
  40. package/habilidades/changelog-generator/SKILL.md +9 -2
  41. package/habilidades/changelog-generator/scripts/parse-commits.js +13 -1
  42. package/habilidades/diagrama-arquitectura/SKILL.md +1 -1
  43. package/habilidades/drift-detection/SKILL.md +179 -179
  44. package/habilidades/ejecutar-fase/SKILL.md +541 -468
  45. package/habilidades/estructura-proyecto-claude/SKILL.md +17 -14
  46. package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +34 -23
  47. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +70 -53
  48. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -77
  49. package/habilidades/extractor-de-aprendizajes/SKILL.md +9 -5
  50. package/habilidades/harness-claude-code/SKILL.md +10 -7
  51. package/{reglas/harness-claude-code.md → habilidades/harness-claude-code/recursos/disciplina-harness-regla.md} +2 -2
  52. package/habilidades/instalar-sistema/SKILL.md +3 -3
  53. package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +1 -1
  54. package/habilidades/perfil-usuario/SKILL.md +200 -200
  55. package/habilidades/planear-fase/SKILL.md +26 -4
  56. package/habilidades/proceso-ddia-fundamentos/SKILL.md +1 -1
  57. package/habilidades/proceso-ddia-streaming/SKILL.md +4 -4
  58. package/habilidades/proceso-debate-adversarial/SKILL.md +2 -2
  59. package/habilidades/protocolo-revision-swl/SKILL.md +1 -1
  60. package/habilidades/seguridad-skills-ia/SKILL.md +1 -1
  61. package/habilidades/swl-claudemd/SKILL.md +50 -210
  62. package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -0
  63. package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -0
  64. package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -0
  65. package/habilidades/swl-dashboard/SKILL.md +9 -9
  66. package/habilidades/swl-revisar-impacto/SKILL.md +1 -1
  67. package/habilidades/tdd-workflow/SKILL.md +715 -673
  68. package/habilidades/validacion-ci-sistema/SKILL.md +20 -4
  69. package/hooks/calidad-pre-commit.js +344 -3
  70. package/hooks/check-update.js +39 -1
  71. package/hooks/ciclo-evolucion-subagente.js +26 -0
  72. package/hooks/ciclo-evolucion.js +26 -0
  73. package/hooks/extraccion-aprendizajes.js +13 -0
  74. package/hooks/lib/autonomia.js +208 -0
  75. package/hooks/lib/briefing.js +474 -0
  76. package/hooks/lib/ciclo-evolucion.js +47 -0
  77. package/hooks/{auto-evolucion.js → lib/etapa-auto-evolucion.js} +701 -700
  78. package/hooks/{metricas-evolucion.js → lib/etapa-metricas.js} +388 -376
  79. package/hooks/{actualizar-perfil-usuario.js → lib/etapa-perfil-usuario.js} +376 -364
  80. package/hooks/lib/evolution-tracker.js +24 -3
  81. package/hooks/lib/propose-step.js +357 -0
  82. package/hooks/session-briefing.js +98 -0
  83. package/hooks/spec-gate.js +211 -0
  84. package/hooks/tdd-gate.js +241 -0
  85. package/hooks/telemetria-skill-routing.js +100 -0
  86. package/hooks/validar-intent-spec.js +30 -10
  87. package/instintos/autonomia.yaml +27 -0
  88. package/llms.txt +6 -6
  89. package/manifiestos/hooks-config.json +44 -17
  90. package/manifiestos/modulos.json +40 -15
  91. package/manifiestos/skills-lock.json +64 -57
  92. package/package.json +93 -93
  93. package/plugin.json +371 -375
  94. package/reglas/accesibilidad.md +10 -0
  95. package/reglas/analizar-directorios-antes-de-escribir.md +228 -0
  96. package/reglas/api-diseno.md +9 -0
  97. package/reglas/auditorias-documentales-estructurales.md +7 -0
  98. package/reglas/cloud-infra.md +8 -0
  99. package/reglas/consultar-vault-primero.md +195 -0
  100. package/reglas/debatir-antes-de-aceptar.md +158 -0
  101. package/reglas/fragmentos-compartidos.md +5 -0
  102. package/reglas/git-coauthor.md +100 -0
  103. package/reglas/gobernanza.md +4 -4
  104. package/reglas/hooks.md +6 -0
  105. package/reglas/intent-engineering.md +4 -0
  106. package/reglas/markitdown.md +8 -0
  107. package/reglas/memoria-consolidada.md +1 -1
  108. package/reglas/monitor-ci.md +309 -0
  109. package/reglas/patrones.md +6 -0
  110. package/reglas/registro-componentes-nuevos.md +39 -2
  111. package/reglas/seguridad-agentes.md +1 -1
  112. package/reglas/sesiones-paralelas.md +180 -0
  113. package/reglas/skills-estandar.md +6 -0
  114. package/reglas/testing.md +7 -0
  115. package/reglas/tests-cleanup.md +4 -0
  116. package/reglas/usar-code-review-graph.md +155 -0
  117. package/reglas/usar-sistema-swl.md +1 -1
  118. package/reglas/verificar-citas-normativas.md +548 -0
  119. package/scripts/instalador.js +52 -6
  120. package/scripts/lib/ci-reader.js +193 -0
  121. package/scripts/lib/detectar-host-swl.js +175 -0
  122. package/scripts/lib/evidencia-release.js +322 -0
  123. package/scripts/lib/gate-hooks-requires.js +249 -0
  124. package/scripts/lib/gate-licencias.js +212 -0
  125. package/scripts/lib/git-metricas.js +257 -0
  126. package/scripts/lib/gitignore-manifest.js +29 -1
  127. package/scripts/lib/metricas-dora.js +204 -0
  128. package/scripts/lib/plan-lock.js +275 -0
  129. package/scripts/migrar-fase-dominio.js +0 -1
  130. package/scripts/tui/ejecutores.js +1 -1
  131. package/scripts/validar-manifest.js +92 -1
  132. package/scripts/verificar-evolucion.js +54 -4
  133. package/scripts/verificar-release.js +102 -0
  134. package/scripts/verificar-trazabilidad.js +298 -0
  135. package/agentes/ux-disenador-swl.md +0 -503
  136. package/comandos/swl/dashboard.md +0 -146
  137. package/comandos/swl/evolucion-estado.md +0 -191
  138. package/comandos/swl/metricas.md +0 -376
  139. package/comandos/swl/salud.md +0 -481
  140. package/reglas/arquitectura.evolved.json +0 -7
  141. package/reglas/seguridad.evolved.json +0 -7
  142. package/reglas/verificar-citas-temporales.md +0 -139
@@ -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 };
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: telemetria-skill-routing.js
6
+ * Tipo: PostToolUse (matcher: "Skill")
7
+ *
8
+ * Cablea la lib huérfana `scripts/lib/skill-metrics.js`: por cada invocación
9
+ * del tool Skill registra la frecuencia/estado en `.planning/skill-metrics.json`
10
+ * vía `registrarUso(cwd, skillName, estado, duracionMs)`.
11
+ *
12
+ * Propósito (Revisión Evolutiva 04, P-2 "bloat y precisión de routing"):
13
+ * acumular telemetría de qué skills se invocan para alimentar la futura
14
+ * auditoría de bloat (skills muertos / solapados). Es la instrumentación que
15
+ * el roadmap permite adelantar como "tarea suelta" antes de la fase de auditoría.
16
+ *
17
+ * Distinto de telemetria-agentes.js (spans OTLP a traces/) y de audit-trail.js
18
+ * (todos los tool calls a audit.jsonl): este hook produce el agregado por-skill
19
+ * que la auditoría de bloat necesita y que ningún otro hook genera.
20
+ *
21
+ * NUNCA bloquea (siempre exit 0). Async: corre en background.
22
+ * Opt-out: SWL_SKILL_TELEMETRIA=0.
23
+ *
24
+ * Origen: punto P-2 de la Revisión Evolutiva 04 (instrumentación adelantada).
25
+ */
26
+
27
+ /**
28
+ * Extrae el evento de skill de un payload PostToolUse.
29
+ * Pura y testeable — no toca el filesystem.
30
+ *
31
+ * @param {object} data payload PostToolUse
32
+ * @returns {{skillName: string, estado: 'ok'|'error'}|null} null si no aplica
33
+ */
34
+ function extraerEvento(data) {
35
+ if (!data || typeof data !== 'object') return null;
36
+ const toolName = String(data.tool_name || (data.tool && data.tool.name) || '');
37
+ if (toolName !== 'Skill') return null;
38
+
39
+ const input = data.tool_input || {};
40
+ const skillName = String(input.skill || input.name || '').trim();
41
+ if (!skillName) return null;
42
+
43
+ const esError = Boolean(data.error_message || data.error);
44
+ return { skillName, estado: esError ? 'error' : 'ok' };
45
+ }
46
+
47
+ /**
48
+ * Carga defensiva de la lib de métricas. Si no está disponible (destino
49
+ * aplanado, instalación parcial), retorna null y el hook degrada en silencio.
50
+ * @returns {Function|null} registrarUso o null
51
+ */
52
+ function cargarRegistrar() {
53
+ try {
54
+ return require('../scripts/lib/skill-metrics').registrarUso;
55
+ } catch (_) {
56
+ try {
57
+ return require('./lib/skill-metrics').registrarUso;
58
+ } catch (_2) {
59
+ return null;
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Procesa un payload y registra el uso del skill si aplica.
66
+ * @param {object} data payload PostToolUse
67
+ * @param {string} cwd directorio del proyecto
68
+ * @param {Function} [registrar] inyectable para tests; default carga skill-metrics
69
+ * @returns {boolean} true si registró
70
+ */
71
+ function procesar(data, cwd, registrar) {
72
+ const evento = extraerEvento(data);
73
+ if (!evento) return false;
74
+ const fn = registrar || cargarRegistrar();
75
+ if (!fn) return false;
76
+ fn(cwd, evento.skillName, evento.estado, 0);
77
+ return true;
78
+ }
79
+
80
+ module.exports = { extraerEvento, procesar };
81
+
82
+ // --- Entrypoint -------------------------------------------------------------
83
+ // Claude Code invoca el hook vía `node -e "require('./hooks/...')"`, donde
84
+ // `require.main` es undefined. Adjuntar stdin SOLO en ese caso (o invocación
85
+ // directa), nunca cuando un test requiere el módulo (ahí require.main es el
86
+ // runner, definido y ≠ este módulo) — así los tests no cuelgan en stdin.
87
+ if (!require.main || require.main === module) {
88
+ // Opt-out: no adjuntar el listener (sin process.exit, seguro de requerir).
89
+ if (process.env.SWL_SKILL_TELEMETRIA !== '0') {
90
+ let inputRaw = '';
91
+ process.stdin.on('data', (chunk) => { inputRaw += chunk; });
92
+ process.stdin.on('end', () => {
93
+ try {
94
+ procesar(JSON.parse(inputRaw), process.cwd());
95
+ } catch (_) {
96
+ // Silencioso — nunca interrumpir la sesión por un fallo de telemetría.
97
+ }
98
+ });
99
+ }
100
+ }
@@ -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);
@@ -0,0 +1,27 @@
1
+ # Dial de autonomía — SWL-SES (Fase 13, ADR-0037)
2
+ #
3
+ # Presupuesto de autonomía por clase de riesgo. Consumido por hooks/lib/autonomia.js
4
+ # (orquestador y agentes ALTO leen el dial como GUÍA; no es un gate bloqueante en v1).
5
+ #
6
+ # Defaults = los controles vigentes de reglas/seguridad-agentes.md. Este archivo
7
+ # NO relaja ningún control: lo hace visible y ajustable. El dial sube SOLO por
8
+ # decisión explícita del usuario, nunca por conveniencia del agente.
9
+ #
10
+ # Niveles válidos por clase:
11
+ # total -> acción autónoma sin checkpoint (lectura/análisis).
12
+ # con_auto_checkpoint -> autónoma, pero registra un auto-checkpoint antes de actuar.
13
+ # hitl -> requiere confirmación humana siempre.
14
+ # Cualquier valor desconocido se degrada a 'hitl' (nunca relaja).
15
+ #
16
+ # Estructura PLANA obligatoria (1 nivel bajo `clases:`) — ver 13-PLAN.md.
17
+
18
+ version: "1.0"
19
+ generado: "2026-06-11"
20
+ defaults: seguridad-agentes.md
21
+ clases:
22
+ lectura_analisis: total
23
+ # Subido a `total` por decisión explícita del usuario (2026-06-11, cierre Fase 13):
24
+ # cambios reversibles autónomos sin auto-checkpoint. Los commits atómicos siguen
25
+ # siendo el mecanismo de rollback. Default de seguridad-agentes.md era con_auto_checkpoint.
26
+ cambio_reversible: total
27
+ migracion_auth_push_publish: hitl
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, 44 comandos, 37 reglas base y 48 hooks. Soporta 11 lenguajes y 7 runtimes (Claude Code, OpenClaude, OpenCode, Gemini, Cursor, Codex, Copilot). Versión 2.1.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
+ - 44 comandos `/swl:*` en `comandos/swl/` (ciclo GSD, calidad, release, diagnóstico)
22
+ - 37 reglas base + 40 reglas por lenguaje en `reglas/` (políticas obligatorias por matcher)
23
+ - 48 hooks en `hooks/` (telemetría, validación, seguridad; zero-deps, escrituras atómicas)
24
24
 
25
25
  ## Opcional
26
26