@saulwade/swl-ses 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/CLAUDE.md +19 -2
  2. package/README.md +561 -561
  3. package/agentes/arquitecto-swl.md +33 -1
  4. package/agentes/nemesis-auditor-swl.md +59 -19
  5. package/bin/swl-mcp-server.js +214 -214
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/contribuir.md +233 -233
  8. package/comandos/swl/nemesis.md +230 -56
  9. package/gateway/lib/event-channel.js +191 -191
  10. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  11. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  12. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  13. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  14. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  15. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
  16. package/habilidades/eval-framework/SKILL.md +212 -212
  17. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  18. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  19. package/habilidades/harness-claude-code/SKILL.md +299 -299
  20. package/habilidades/infra-github-actions/SKILL.md +166 -166
  21. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  22. package/habilidades/manejo-errores/.evolved.json +8 -8
  23. package/habilidades/meta-skills-estandar/SKILL.md +225 -1
  24. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  25. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  26. package/habilidades/nemesis-evaluacion-json/SKILL.md +266 -0
  27. package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
  28. package/habilidades/node-experto/SKILL.md +105 -4
  29. package/habilidades/patrones-python/SKILL.md +229 -229
  30. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  31. package/habilidades/planear-fase/SKILL.md +319 -319
  32. package/habilidades/protocolo-revision-swl/SKILL.md +350 -276
  33. package/habilidades/release-semver/.evolved.json +8 -8
  34. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  35. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  36. package/habilidades/tdd-workflow/SKILL.md +150 -4
  37. package/habilidades/testing-python/SKILL.md +340 -340
  38. package/habilidades/verificar-trabajo/SKILL.md +8 -3
  39. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  40. package/hooks/check-update.js +31 -3
  41. package/hooks/claudemd-bloat-detector.js +161 -161
  42. package/hooks/lib/agent-routing.js +107 -107
  43. package/hooks/lib/auto-consolidator.js +335 -335
  44. package/hooks/lib/error-classifier.js +308 -308
  45. package/hooks/lib/merkle-audit.js +96 -96
  46. package/hooks/lib/provenance-tracker.js +191 -191
  47. package/hooks/lib/rate-limit-tracker.js +253 -253
  48. package/hooks/lib/resource-quota.js +122 -122
  49. package/hooks/lib/retry-jitter.js +165 -165
  50. package/hooks/lib/security-net.js +201 -201
  51. package/hooks/lib/skill-auditor.js +588 -588
  52. package/hooks/lib/sync-status.js +228 -228
  53. package/hooks/lib/taint-tracker.js +107 -107
  54. package/hooks/lib/text-similarity.js +241 -241
  55. package/hooks/lib/toon-compressor.js +245 -245
  56. package/hooks/registro-turnos.js +209 -209
  57. package/hooks/sugerir-regenerar-inventario.js +170 -170
  58. package/hooks/validar-formato-post-subagente.js +140 -140
  59. package/hooks/validar-memoria-hook.js +218 -218
  60. package/instintos/prompt-appendices.yaml +57 -57
  61. package/manifiestos/agent-output-schemas.json +57 -57
  62. package/manifiestos/modulos.json +1324 -1321
  63. package/manifiestos/skills-lock.json +1114 -1114
  64. package/package.json +2 -2
  65. package/plantillas/auditor-veto-template.md +105 -105
  66. package/plantillas/github-workflows/README.md +47 -47
  67. package/plantillas/github-workflows/release-please.yml +44 -44
  68. package/plantillas/github-workflows/swl-ci.yml +107 -107
  69. package/plantillas/github-workflows/swl-security.yml +51 -51
  70. package/plugin.json +353 -351
  71. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  72. package/reglas/arreglar-al-detectar.md +147 -147
  73. package/reglas/fragmentos-compartidos.md +152 -152
  74. package/reglas/harness-claude-code.md +213 -213
  75. package/reglas/registro-componentes-nuevos.md +192 -0
  76. package/reglas/usar-context7.md +226 -226
  77. package/schemas/diary-entry.schema.json +80 -80
  78. package/scripts/actualizar.js +110 -1
  79. package/scripts/audit-tools/audit-history.js +330 -330
  80. package/scripts/audit-tools/bundle-tracker.js +290 -290
  81. package/scripts/audit-tools/canary-monitor.js +352 -352
  82. package/scripts/audit-tools/code-profiler.js +605 -605
  83. package/scripts/audit-tools/dep-doctor.js +320 -320
  84. package/scripts/audit-tools/env-validator.js +206 -206
  85. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  86. package/scripts/audit-tools/lib/output.js +23 -23
  87. package/scripts/audit-tools/migration-checker.js +392 -392
  88. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  89. package/scripts/benchmark-memoria.js +167 -167
  90. package/scripts/configurar-branch-protection.js +418 -418
  91. package/scripts/derivar-feature-list.js +489 -489
  92. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  93. package/scripts/doctor.js +58 -4
  94. package/scripts/field-report.js +199 -199
  95. package/scripts/generar-checklists-consolidados.js +273 -273
  96. package/scripts/generar-inventario.js +420 -420
  97. package/scripts/generar-matriz-lenguajes.js +271 -271
  98. package/scripts/lib/artefactos-python.js +43 -43
  99. package/scripts/lib/benchmark-metrics.js +160 -160
  100. package/scripts/lib/budget-enforcer.js +252 -252
  101. package/scripts/lib/configurar-ci.js +380 -380
  102. package/scripts/lib/contadores-inventario.js +217 -217
  103. package/scripts/lib/detectar-stack-detallado.js +307 -307
  104. package/scripts/lib/diary-entry.js +234 -234
  105. package/scripts/lib/eval-metrics-store.js +218 -218
  106. package/scripts/lib/eval-quality.js +171 -171
  107. package/scripts/lib/eval-schemas.js +144 -144
  108. package/scripts/lib/eval-self-correct.js +106 -106
  109. package/scripts/lib/eval-validator.js +185 -185
  110. package/scripts/lib/expandir-targets.js +71 -71
  111. package/scripts/lib/jaccard-similarity.js +98 -98
  112. package/scripts/lib/longmemeval-runner.js +125 -125
  113. package/scripts/lib/mcp_config.py +127 -0
  114. package/scripts/lib/npm-version.js +261 -261
  115. package/scripts/lib/paquetes-conocidos.js +50 -50
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/lib/toml-merge.js +204 -204
  121. package/scripts/lib/transformadores/codex.js +375 -375
  122. package/scripts/lib/transformadores/cursor.js +359 -359
  123. package/scripts/limpiar-artefactos-python.js +131 -131
  124. package/scripts/mcp-orchestrator.py +8 -18
  125. package/scripts/mcp-pool-manager.py +12 -23
  126. package/scripts/mcp-server/README.md +170 -170
  127. package/scripts/mcp-server/auth.js +105 -105
  128. package/scripts/mcp-server/cache.js +106 -106
  129. package/scripts/mcp-server/telemetry.js +78 -78
  130. package/scripts/migrar-csv-a-array.js +168 -168
  131. package/scripts/migrar-fase-dominio.js +201 -201
  132. package/scripts/publicar.js +511 -511
  133. package/scripts/run-eval.js +141 -141
  134. package/scripts/validar-userland-vacio.js +110 -110
@@ -1,170 +1,170 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Hook: sugerir-regenerar-inventario.js
6
- * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
7
- *
8
- * Cuando el agente edita o crea un componente registrado en INVENTARIO.md
9
- * (agentes, habilidades, comandos, hooks o reglas), sugiere — sin bloquear —
10
- * regenerar el inventario antes del próximo commit.
11
- *
12
- * Motivado por el drift recurrente detectado el 2026-04-29: tras agregar el
13
- * skill `harness-claude-code` el inventario quedó stale en 5 archivos canónicos
14
- * porque nadie regeneró `INVENTARIO.md` después de la edición. La regeneración
15
- * es 1 comando (`node scripts/generar-inventario.js`) pero es fácil olvidarla.
16
- *
17
- * Cooldown: una vez por sesión por carpeta afectada para no spammear cuando
18
- * el agente edita docenas de archivos seguidos. Estado persistente en
19
- * `.planning/inventario-aviso-state.json`.
20
- *
21
- * Resultado:
22
- * - Edición sobre carpeta inventariada y sin cooldown → sugerencia en stdout (exit 0)
23
- * - Edición sobre carpeta no inventariada → sin output (exit 0)
24
- * - Error interno → sin output (nunca bloquear)
25
- *
26
- * Activo por defecto. Para silenciar: SWL_SUGERIR_REGEN_INVENTARIO=0.
27
- */
28
-
29
- const fs = require('fs');
30
- const path = require('path');
31
-
32
- // Carpetas cuyo cambio invalida los contadores de INVENTARIO.md
33
- const CARPETAS_INVENTARIADAS = [
34
- { ruta: 'agentes/', etiqueta: 'agentes' },
35
- { ruta: 'habilidades/', etiqueta: 'habilidades' },
36
- { ruta: 'comandos/swl/', etiqueta: 'comandos' },
37
- { ruta: 'hooks/', etiqueta: 'hooks', excluir: ['hooks/lib/'] },
38
- { ruta: 'reglas/', etiqueta: 'reglas' },
39
- ];
40
-
41
- const STATE_FILE = path.join(process.cwd(), '.planning', 'inventario-aviso-state.json');
42
- const COOLDOWN_MS = 30 * 60 * 1000; // 30 min
43
-
44
- function normalizarSeparadores(p) {
45
- return String(p).replace(/\\/g, '/');
46
- }
47
-
48
- function detectarCarpeta(rutaRaw) {
49
- if (!rutaRaw) return null;
50
- let normalizada = normalizarSeparadores(rutaRaw);
51
-
52
- // Solo nos interesan rutas relativas al proyecto. Si es absoluta, intentamos
53
- // hacerla relativa al CWD actual.
54
- if (path.isAbsolute(normalizada)) {
55
- const cwd = normalizarSeparadores(process.cwd());
56
- if (normalizada.toLowerCase().startsWith(cwd.toLowerCase())) {
57
- normalizada = normalizada.slice(cwd.length).replace(/^\/+/, '');
58
- } else {
59
- return null;
60
- }
61
- } else {
62
- normalizada = normalizada.replace(/^\.\//, '');
63
- }
64
-
65
- for (const c of CARPETAS_INVENTARIADAS) {
66
- if (normalizada.startsWith(c.ruta)) {
67
- if (c.excluir && c.excluir.some(ex => normalizada.startsWith(ex))) continue;
68
- return c.etiqueta;
69
- }
70
- }
71
- return null;
72
- }
73
-
74
- function leerEstado() {
75
- try {
76
- return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
77
- } catch {
78
- return {};
79
- }
80
- }
81
-
82
- function escribirEstado(estado) {
83
- try {
84
- fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
85
- const tmp = STATE_FILE + '.tmp';
86
- fs.writeFileSync(tmp, JSON.stringify(estado, null, 2), 'utf8');
87
- fs.renameSync(tmp, STATE_FILE);
88
- } catch {
89
- // Falla silenciosa — el cooldown no es crítico
90
- }
91
- }
92
-
93
- function dentroDeCooldown(estado, etiqueta) {
94
- const ultima = estado[etiqueta];
95
- if (!ultima) return false;
96
- return Date.now() - ultima < COOLDOWN_MS;
97
- }
98
-
99
- function marcarAviso(estado, etiqueta) {
100
- estado[etiqueta] = Date.now();
101
- escribirEstado(estado);
102
- }
103
-
104
- function construirMensaje(etiqueta) {
105
- return (
106
- '\n[swl-ses] Tocaste un archivo en `' + etiqueta + '/` — los contadores ' +
107
- 'de INVENTARIO.md pueden quedar stale.\n' +
108
- ' Antes del próximo commit, ejecuta:\n' +
109
- ' node scripts/generar-inventario.js\n' +
110
- ' y verifica con:\n' +
111
- ' node scripts/verificar-release.js\n' +
112
- ' (silenciar con SWL_SUGERIR_REGEN_INVENTARIO=0)\n'
113
- );
114
- }
115
-
116
- function extraerRuta(toolInput) {
117
- if (!toolInput || typeof toolInput !== 'object') return null;
118
- return (
119
- toolInput.file_path ||
120
- toolInput.path ||
121
- toolInput.notebook_path ||
122
- null
123
- );
124
- }
125
-
126
- function ejecutarComoHook() {
127
- let inputRaw = '';
128
-
129
- process.stdin.on('data', chunk => { inputRaw += chunk; });
130
-
131
- process.stdin.on('end', () => {
132
- try {
133
- if (process.env.SWL_SUGERIR_REGEN_INVENTARIO === '0') return;
134
-
135
- const data = JSON.parse(inputRaw || '{}');
136
- const toolName = String(data.tool_name || data.tool?.name || '');
137
- const toolInput = data.tool_input || data.tool?.input || {};
138
-
139
- if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) return;
140
-
141
- const ruta = extraerRuta(toolInput);
142
- if (!ruta) return;
143
-
144
- const etiqueta = detectarCarpeta(ruta);
145
- if (!etiqueta) return;
146
-
147
- const estado = leerEstado();
148
- if (dentroDeCooldown(estado, etiqueta)) return;
149
-
150
- process.stdout.write(construirMensaje(etiqueta));
151
- marcarAviso(estado, etiqueta);
152
- } catch {
153
- // Errores internos nunca bloquean
154
- }
155
- });
156
- }
157
-
158
- // Solo escuchar stdin cuando se ejecuta como entrypoint (no al importar para tests)
159
- if (require.main === module) {
160
- ejecutarComoHook();
161
- }
162
-
163
- // Para tests unitarios
164
- module.exports = {
165
- CARPETAS_INVENTARIADAS,
166
- detectarCarpeta,
167
- construirMensaje,
168
- dentroDeCooldown,
169
- COOLDOWN_MS,
170
- };
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: sugerir-regenerar-inventario.js
6
+ * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
7
+ *
8
+ * Cuando el agente edita o crea un componente registrado en INVENTARIO.md
9
+ * (agentes, habilidades, comandos, hooks o reglas), sugiere — sin bloquear —
10
+ * regenerar el inventario antes del próximo commit.
11
+ *
12
+ * Motivado por el drift recurrente detectado el 2026-04-29: tras agregar el
13
+ * skill `harness-claude-code` el inventario quedó stale en 5 archivos canónicos
14
+ * porque nadie regeneró `INVENTARIO.md` después de la edición. La regeneración
15
+ * es 1 comando (`node scripts/generar-inventario.js`) pero es fácil olvidarla.
16
+ *
17
+ * Cooldown: una vez por sesión por carpeta afectada para no spammear cuando
18
+ * el agente edita docenas de archivos seguidos. Estado persistente en
19
+ * `.planning/inventario-aviso-state.json`.
20
+ *
21
+ * Resultado:
22
+ * - Edición sobre carpeta inventariada y sin cooldown → sugerencia en stdout (exit 0)
23
+ * - Edición sobre carpeta no inventariada → sin output (exit 0)
24
+ * - Error interno → sin output (nunca bloquear)
25
+ *
26
+ * Activo por defecto. Para silenciar: SWL_SUGERIR_REGEN_INVENTARIO=0.
27
+ */
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ // Carpetas cuyo cambio invalida los contadores de INVENTARIO.md
33
+ const CARPETAS_INVENTARIADAS = [
34
+ { ruta: 'agentes/', etiqueta: 'agentes' },
35
+ { ruta: 'habilidades/', etiqueta: 'habilidades' },
36
+ { ruta: 'comandos/swl/', etiqueta: 'comandos' },
37
+ { ruta: 'hooks/', etiqueta: 'hooks', excluir: ['hooks/lib/'] },
38
+ { ruta: 'reglas/', etiqueta: 'reglas' },
39
+ ];
40
+
41
+ const STATE_FILE = path.join(process.cwd(), '.planning', 'inventario-aviso-state.json');
42
+ const COOLDOWN_MS = 30 * 60 * 1000; // 30 min
43
+
44
+ function normalizarSeparadores(p) {
45
+ return String(p).replace(/\\/g, '/');
46
+ }
47
+
48
+ function detectarCarpeta(rutaRaw) {
49
+ if (!rutaRaw) return null;
50
+ let normalizada = normalizarSeparadores(rutaRaw);
51
+
52
+ // Solo nos interesan rutas relativas al proyecto. Si es absoluta, intentamos
53
+ // hacerla relativa al CWD actual.
54
+ if (path.isAbsolute(normalizada)) {
55
+ const cwd = normalizarSeparadores(process.cwd());
56
+ if (normalizada.toLowerCase().startsWith(cwd.toLowerCase())) {
57
+ normalizada = normalizada.slice(cwd.length).replace(/^\/+/, '');
58
+ } else {
59
+ return null;
60
+ }
61
+ } else {
62
+ normalizada = normalizada.replace(/^\.\//, '');
63
+ }
64
+
65
+ for (const c of CARPETAS_INVENTARIADAS) {
66
+ if (normalizada.startsWith(c.ruta)) {
67
+ if (c.excluir && c.excluir.some(ex => normalizada.startsWith(ex))) continue;
68
+ return c.etiqueta;
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+
74
+ function leerEstado() {
75
+ try {
76
+ return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
77
+ } catch {
78
+ return {};
79
+ }
80
+ }
81
+
82
+ function escribirEstado(estado) {
83
+ try {
84
+ fs.mkdirSync(path.dirname(STATE_FILE), { recursive: true });
85
+ const tmp = STATE_FILE + '.tmp';
86
+ fs.writeFileSync(tmp, JSON.stringify(estado, null, 2), 'utf8');
87
+ fs.renameSync(tmp, STATE_FILE);
88
+ } catch {
89
+ // Falla silenciosa — el cooldown no es crítico
90
+ }
91
+ }
92
+
93
+ function dentroDeCooldown(estado, etiqueta) {
94
+ const ultima = estado[etiqueta];
95
+ if (!ultima) return false;
96
+ return Date.now() - ultima < COOLDOWN_MS;
97
+ }
98
+
99
+ function marcarAviso(estado, etiqueta) {
100
+ estado[etiqueta] = Date.now();
101
+ escribirEstado(estado);
102
+ }
103
+
104
+ function construirMensaje(etiqueta) {
105
+ return (
106
+ '\n[swl-ses] Tocaste un archivo en `' + etiqueta + '/` — los contadores ' +
107
+ 'de INVENTARIO.md pueden quedar stale.\n' +
108
+ ' Antes del próximo commit, ejecuta:\n' +
109
+ ' node scripts/generar-inventario.js\n' +
110
+ ' y verifica con:\n' +
111
+ ' node scripts/verificar-release.js\n' +
112
+ ' (silenciar con SWL_SUGERIR_REGEN_INVENTARIO=0)\n'
113
+ );
114
+ }
115
+
116
+ function extraerRuta(toolInput) {
117
+ if (!toolInput || typeof toolInput !== 'object') return null;
118
+ return (
119
+ toolInput.file_path ||
120
+ toolInput.path ||
121
+ toolInput.notebook_path ||
122
+ null
123
+ );
124
+ }
125
+
126
+ function ejecutarComoHook() {
127
+ let inputRaw = '';
128
+
129
+ process.stdin.on('data', chunk => { inputRaw += chunk; });
130
+
131
+ process.stdin.on('end', () => {
132
+ try {
133
+ if (process.env.SWL_SUGERIR_REGEN_INVENTARIO === '0') return;
134
+
135
+ const data = JSON.parse(inputRaw || '{}');
136
+ const toolName = String(data.tool_name || data.tool?.name || '');
137
+ const toolInput = data.tool_input || data.tool?.input || {};
138
+
139
+ if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) return;
140
+
141
+ const ruta = extraerRuta(toolInput);
142
+ if (!ruta) return;
143
+
144
+ const etiqueta = detectarCarpeta(ruta);
145
+ if (!etiqueta) return;
146
+
147
+ const estado = leerEstado();
148
+ if (dentroDeCooldown(estado, etiqueta)) return;
149
+
150
+ process.stdout.write(construirMensaje(etiqueta));
151
+ marcarAviso(estado, etiqueta);
152
+ } catch {
153
+ // Errores internos nunca bloquean
154
+ }
155
+ });
156
+ }
157
+
158
+ // Solo escuchar stdin cuando se ejecuta como entrypoint (no al importar para tests)
159
+ if (require.main === module) {
160
+ ejecutarComoHook();
161
+ }
162
+
163
+ // Para tests unitarios
164
+ module.exports = {
165
+ CARPETAS_INVENTARIADAS,
166
+ detectarCarpeta,
167
+ construirMensaje,
168
+ dentroDeCooldown,
169
+ COOLDOWN_MS,
170
+ };
@@ -1,140 +1,140 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Hook: validar-formato-post-subagente.js
6
- * Tipo: SubagentStop (async: true — fire-and-forget)
7
- *
8
- * Cuando un subagente SWL termina, valida que su output cumple con el
9
- * schema declarado en `manifiestos/agent-output-schemas.json`. Si el
10
- * agente no tiene schema declarado, el hook no hace nada — no asume
11
- * que todo agente debe seguir el formato compacto.
12
- *
13
- * Registra el resultado en `.planning/evolucion/formato-violaciones.jsonl`
14
- * para que `metricas-evolucion.js` calcule la tasa de violación por
15
- * agente y la incluya en el dashboard de calidad conductual.
16
- *
17
- * Schema simple: lista de patrones regex que DEBEN aparecer en el output.
18
- * Si todos matchean → conforme. Si falta alguno → violación con detalle.
19
- *
20
- * Opt-out: SWL_FORMATO_VALIDACION=0 desactiva el hook.
21
- *
22
- * El hook nunca bloquea — solo registra para análisis posterior.
23
- */
24
-
25
- const fs = require('fs');
26
- const path = require('path');
27
-
28
- const CWD = process.cwd();
29
- const SCHEMAS_PATH = path.join(CWD, 'manifiestos', 'agent-output-schemas.json');
30
- const LOG_PATH = path.join(CWD, '.planning', 'evolucion', 'formato-violaciones.jsonl');
31
-
32
- // ---------------------------------------------------------------------------
33
- // Carga del schema (una vez)
34
- // ---------------------------------------------------------------------------
35
-
36
- function cargarSchemas() {
37
- try {
38
- const raw = fs.readFileSync(SCHEMAS_PATH, 'utf8');
39
- const parsed = JSON.parse(raw);
40
- return parsed.schemas || {};
41
- } catch {
42
- return {};
43
- }
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Validación
48
- // ---------------------------------------------------------------------------
49
-
50
- /**
51
- * Compara el output contra el schema. Devuelve { violation, errors }.
52
- * @param {string} output - Texto del output del subagente
53
- * @param {object} schema - { patronesRequeridos: string[] }
54
- */
55
- function validarOutput(output, schema) {
56
- const errors = [];
57
- const patrones = Array.isArray(schema.patronesRequeridos)
58
- ? schema.patronesRequeridos
59
- : [];
60
- for (const patron of patrones) {
61
- // El schema puede declarar inline flags estilo (?im) por compatibilidad
62
- // con regex POSIX. JavaScript no los soporta inline — los stripeamos
63
- // y aplicamos 'im' siempre (case-insensitive + multiline).
64
- const limpio = patron.replace(/^\(\?[imsxu]+\)/, '');
65
- let regex;
66
- try { regex = new RegExp(limpio, 'im'); }
67
- catch { errors.push(`Patrón inválido en schema: ${patron}`); continue; }
68
- if (!regex.test(output)) {
69
- errors.push(`Falta patrón requerido: ${patron}`);
70
- }
71
- }
72
- return { violation: errors.length > 0, errors };
73
- }
74
-
75
- /**
76
- * Extrae el output textual del subagente del payload del hook.
77
- * El payload del SubagentStop expone el resultado en distintos lugares
78
- * según versión de Claude Code; intentamos varias rutas.
79
- */
80
- function extraerOutput(data) {
81
- const r = data.tool_response || data.tool_result || data.response || {};
82
- // Posibles ubicaciones del output del subagente
83
- if (typeof r.text === 'string') return r.text;
84
- if (typeof r.content === 'string') return r.content;
85
- if (Array.isArray(r.messages)) {
86
- return r.messages
87
- .map(m => (typeof m.content === 'string' ? m.content : ''))
88
- .filter(Boolean)
89
- .join('\n');
90
- }
91
- if (typeof r.output === 'string') return r.output;
92
- return '';
93
- }
94
-
95
- function ensureDir() {
96
- try { fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true }); } catch { /* ignore */ }
97
- }
98
-
99
- // ---------------------------------------------------------------------------
100
- // Entrypoint
101
- // ---------------------------------------------------------------------------
102
-
103
- let inputRaw = '';
104
- process.stdin.on('data', chunk => { inputRaw += chunk; });
105
-
106
- process.stdin.on('end', () => {
107
- try {
108
- if (process.env.SWL_FORMATO_VALIDACION === '0') return;
109
-
110
- const data = JSON.parse(inputRaw);
111
- const subagentType =
112
- data.tool_input?.subagent_type ||
113
- data.tool?.input?.subagent_type ||
114
- data.subagent_type ||
115
- null;
116
- if (!subagentType) return;
117
-
118
- const schemas = cargarSchemas();
119
- const schema = schemas[subagentType];
120
- if (!schema) return; // agente sin schema → no se evalúa
121
-
122
- const output = extraerOutput(data);
123
- if (!output || output.length < 10) return; // output trivial — no evaluar
124
-
125
- const { violation, errors } = validarOutput(output, schema);
126
- const entry = {
127
- ts: new Date().toISOString(),
128
- sessionId: String(data.session_id || 'default'),
129
- agente: subagentType,
130
- violation,
131
- errors,
132
- outputLen: output.length,
133
- };
134
-
135
- ensureDir();
136
- fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + '\n', 'utf8');
137
- } catch {
138
- // SubagentStop hook nunca bloquea por error interno.
139
- }
140
- });
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: validar-formato-post-subagente.js
6
+ * Tipo: SubagentStop (async: true — fire-and-forget)
7
+ *
8
+ * Cuando un subagente SWL termina, valida que su output cumple con el
9
+ * schema declarado en `manifiestos/agent-output-schemas.json`. Si el
10
+ * agente no tiene schema declarado, el hook no hace nada — no asume
11
+ * que todo agente debe seguir el formato compacto.
12
+ *
13
+ * Registra el resultado en `.planning/evolucion/formato-violaciones.jsonl`
14
+ * para que `metricas-evolucion.js` calcule la tasa de violación por
15
+ * agente y la incluya en el dashboard de calidad conductual.
16
+ *
17
+ * Schema simple: lista de patrones regex que DEBEN aparecer en el output.
18
+ * Si todos matchean → conforme. Si falta alguno → violación con detalle.
19
+ *
20
+ * Opt-out: SWL_FORMATO_VALIDACION=0 desactiva el hook.
21
+ *
22
+ * El hook nunca bloquea — solo registra para análisis posterior.
23
+ */
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const CWD = process.cwd();
29
+ const SCHEMAS_PATH = path.join(CWD, 'manifiestos', 'agent-output-schemas.json');
30
+ const LOG_PATH = path.join(CWD, '.planning', 'evolucion', 'formato-violaciones.jsonl');
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Carga del schema (una vez)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ function cargarSchemas() {
37
+ try {
38
+ const raw = fs.readFileSync(SCHEMAS_PATH, 'utf8');
39
+ const parsed = JSON.parse(raw);
40
+ return parsed.schemas || {};
41
+ } catch {
42
+ return {};
43
+ }
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Validación
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Compara el output contra el schema. Devuelve { violation, errors }.
52
+ * @param {string} output - Texto del output del subagente
53
+ * @param {object} schema - { patronesRequeridos: string[] }
54
+ */
55
+ function validarOutput(output, schema) {
56
+ const errors = [];
57
+ const patrones = Array.isArray(schema.patronesRequeridos)
58
+ ? schema.patronesRequeridos
59
+ : [];
60
+ for (const patron of patrones) {
61
+ // El schema puede declarar inline flags estilo (?im) por compatibilidad
62
+ // con regex POSIX. JavaScript no los soporta inline — los stripeamos
63
+ // y aplicamos 'im' siempre (case-insensitive + multiline).
64
+ const limpio = patron.replace(/^\(\?[imsxu]+\)/, '');
65
+ let regex;
66
+ try { regex = new RegExp(limpio, 'im'); }
67
+ catch { errors.push(`Patrón inválido en schema: ${patron}`); continue; }
68
+ if (!regex.test(output)) {
69
+ errors.push(`Falta patrón requerido: ${patron}`);
70
+ }
71
+ }
72
+ return { violation: errors.length > 0, errors };
73
+ }
74
+
75
+ /**
76
+ * Extrae el output textual del subagente del payload del hook.
77
+ * El payload del SubagentStop expone el resultado en distintos lugares
78
+ * según versión de Claude Code; intentamos varias rutas.
79
+ */
80
+ function extraerOutput(data) {
81
+ const r = data.tool_response || data.tool_result || data.response || {};
82
+ // Posibles ubicaciones del output del subagente
83
+ if (typeof r.text === 'string') return r.text;
84
+ if (typeof r.content === 'string') return r.content;
85
+ if (Array.isArray(r.messages)) {
86
+ return r.messages
87
+ .map(m => (typeof m.content === 'string' ? m.content : ''))
88
+ .filter(Boolean)
89
+ .join('\n');
90
+ }
91
+ if (typeof r.output === 'string') return r.output;
92
+ return '';
93
+ }
94
+
95
+ function ensureDir() {
96
+ try { fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true }); } catch { /* ignore */ }
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Entrypoint
101
+ // ---------------------------------------------------------------------------
102
+
103
+ let inputRaw = '';
104
+ process.stdin.on('data', chunk => { inputRaw += chunk; });
105
+
106
+ process.stdin.on('end', () => {
107
+ try {
108
+ if (process.env.SWL_FORMATO_VALIDACION === '0') return;
109
+
110
+ const data = JSON.parse(inputRaw);
111
+ const subagentType =
112
+ data.tool_input?.subagent_type ||
113
+ data.tool?.input?.subagent_type ||
114
+ data.subagent_type ||
115
+ null;
116
+ if (!subagentType) return;
117
+
118
+ const schemas = cargarSchemas();
119
+ const schema = schemas[subagentType];
120
+ if (!schema) return; // agente sin schema → no se evalúa
121
+
122
+ const output = extraerOutput(data);
123
+ if (!output || output.length < 10) return; // output trivial — no evaluar
124
+
125
+ const { violation, errors } = validarOutput(output, schema);
126
+ const entry = {
127
+ ts: new Date().toISOString(),
128
+ sessionId: String(data.session_id || 'default'),
129
+ agente: subagentType,
130
+ violation,
131
+ errors,
132
+ outputLen: output.length,
133
+ };
134
+
135
+ ensureDir();
136
+ fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + '\n', 'utf8');
137
+ } catch {
138
+ // SubagentStop hook nunca bloquea por error interno.
139
+ }
140
+ });