@saulwade/swl-ses 1.3.8 → 1.4.1

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 (148) hide show
  1. package/CLAUDE.md +15 -6
  2. package/README.md +15 -14
  3. package/agentes/nemesis-auditor-swl.md +161 -0
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/bin/swl-webhook-server.js +198 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/adoptar-proyecto.md +21 -1
  8. package/comandos/swl/claudemd.md +14 -1
  9. package/comandos/swl/contribuir.md +233 -233
  10. package/comandos/swl/exportar-vault.md +108 -0
  11. package/comandos/swl/nemesis.md +122 -0
  12. package/comandos/swl/nuevo-proyecto.md +24 -2
  13. package/comandos/swl/salud.md +34 -0
  14. package/comandos/swl/verificar.md +45 -0
  15. package/gateway/adapters/base.js +109 -0
  16. package/gateway/adapters/discord.js +167 -0
  17. package/gateway/adapters/email.js +221 -0
  18. package/gateway/adapters/slack.js +192 -0
  19. package/gateway/adapters/telegram.js +183 -0
  20. package/gateway/adapters/webhook.js +113 -0
  21. package/gateway/adapters/whatsapp.js +214 -0
  22. package/gateway/agent-executor.js +322 -0
  23. package/gateway/command-relay.js +271 -0
  24. package/gateway/cron/jobs.js +263 -0
  25. package/gateway/cron/scheduler.js +322 -0
  26. package/gateway/cron/store.js +335 -0
  27. package/gateway/index.js +320 -0
  28. package/gateway/lib/event-channel.js +191 -0
  29. package/gateway/session.js +131 -0
  30. package/gateway/webhook-server.js +324 -0
  31. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  32. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  33. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  34. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  35. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  36. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  37. package/habilidades/eval-framework/SKILL.md +212 -212
  38. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  39. package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
  40. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
  41. package/habilidades/harness-claude-code/SKILL.md +299 -299
  42. package/habilidades/infra-github-actions/SKILL.md +166 -166
  43. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  44. package/habilidades/manejo-errores/.evolved.json +8 -8
  45. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  46. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  47. package/habilidades/nextjs-testing/SKILL.md +89 -5
  48. package/habilidades/node-experto/SKILL.md +37 -1
  49. package/habilidades/patrones-python/SKILL.md +229 -229
  50. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  51. package/habilidades/planear-fase/SKILL.md +319 -319
  52. package/habilidades/react-experto/SKILL.md +45 -4
  53. package/habilidades/release-semver/.evolved.json +8 -8
  54. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
  55. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
  56. package/habilidades/tdd-workflow/SKILL.md +36 -4
  57. package/habilidades/testing-python/SKILL.md +340 -340
  58. package/habilidades/web-fetcher-routing/SKILL.md +75 -0
  59. package/hooks/claudemd-bloat-detector.js +161 -161
  60. package/hooks/inyeccion-contexto.js +8 -3
  61. package/hooks/lib/agent-routing.js +107 -107
  62. package/hooks/lib/auto-consolidator.js +335 -335
  63. package/hooks/lib/error-classifier.js +308 -308
  64. package/hooks/lib/merkle-audit.js +96 -96
  65. package/hooks/lib/provenance-tracker.js +191 -191
  66. package/hooks/lib/rate-limit-ip.js +177 -0
  67. package/hooks/lib/rate-limit-tracker.js +253 -253
  68. package/hooks/lib/resource-quota.js +122 -122
  69. package/hooks/lib/retry-jitter.js +165 -165
  70. package/hooks/lib/security-net.js +201 -0
  71. package/hooks/lib/skill-auditor.js +588 -588
  72. package/hooks/lib/sync-status.js +228 -228
  73. package/hooks/lib/taint-tracker.js +107 -107
  74. package/hooks/lib/text-similarity.js +241 -241
  75. package/hooks/lib/toon-compressor.js +245 -245
  76. package/hooks/lib/webhook-dedup.js +184 -0
  77. package/hooks/lib/webhook-verify.js +123 -0
  78. package/hooks/proteccion-rutas.js +120 -15
  79. package/hooks/registro-turnos.js +209 -209
  80. package/hooks/sugerir-regenerar-inventario.js +170 -170
  81. package/hooks/validar-formato-post-subagente.js +140 -140
  82. package/hooks/validar-memoria-hook.js +218 -218
  83. package/instintos/prompt-appendices.yaml +57 -57
  84. package/manifiestos/agent-output-schemas.json +57 -57
  85. package/manifiestos/modulos.json +31 -0
  86. package/manifiestos/skills-lock.json +1114 -1093
  87. package/package.json +6 -4
  88. package/plantillas/auditor-veto-template.md +105 -105
  89. package/plantillas/github-workflows/README.md +47 -47
  90. package/plantillas/github-workflows/release-please.yml +44 -44
  91. package/plantillas/github-workflows/swl-ci.yml +107 -107
  92. package/plantillas/github-workflows/swl-security.yml +51 -51
  93. package/plugin.json +2 -2
  94. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  95. package/reglas/arreglar-al-detectar.md +147 -147
  96. package/reglas/fragmentos-compartidos.md +152 -152
  97. package/reglas/harness-claude-code.md +213 -213
  98. package/reglas/usar-context7.md +226 -226
  99. package/reglas/usar-sistema-swl.md +251 -0
  100. package/schemas/diary-entry.schema.json +80 -80
  101. package/scripts/audit-tools/audit-history.js +330 -0
  102. package/scripts/audit-tools/bundle-tracker.js +290 -0
  103. package/scripts/audit-tools/canary-monitor.js +352 -0
  104. package/scripts/audit-tools/code-profiler.js +605 -0
  105. package/scripts/audit-tools/dep-doctor.js +320 -0
  106. package/scripts/audit-tools/env-validator.js +206 -0
  107. package/scripts/audit-tools/lib/fs-walk.js +48 -0
  108. package/scripts/audit-tools/lib/output.js +23 -0
  109. package/scripts/audit-tools/migration-checker.js +392 -0
  110. package/scripts/audit-tools/pentest-scanner.js +1436 -0
  111. package/scripts/benchmark-memoria.js +167 -167
  112. package/scripts/comandos/skills.js +251 -2
  113. package/scripts/configurar-branch-protection.js +418 -418
  114. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  115. package/scripts/field-report.js +199 -199
  116. package/scripts/generar-checklists-consolidados.js +273 -273
  117. package/scripts/generar-inventario.js +420 -420
  118. package/scripts/generar-matriz-lenguajes.js +271 -271
  119. package/scripts/lib/artefactos-python.js +43 -43
  120. package/scripts/lib/benchmark-metrics.js +160 -160
  121. package/scripts/lib/budget-enforcer.js +252 -252
  122. package/scripts/lib/configurar-ci.js +380 -380
  123. package/scripts/lib/contadores-inventario.js +217 -217
  124. package/scripts/lib/detectar-stack-detallado.js +307 -307
  125. package/scripts/lib/diary-entry.js +234 -234
  126. package/scripts/lib/eval-metrics-store.js +218 -218
  127. package/scripts/lib/eval-quality.js +171 -171
  128. package/scripts/lib/eval-schemas.js +144 -144
  129. package/scripts/lib/eval-self-correct.js +106 -106
  130. package/scripts/lib/eval-validator.js +185 -185
  131. package/scripts/lib/jaccard-similarity.js +98 -98
  132. package/scripts/lib/longmemeval-runner.js +125 -125
  133. package/scripts/lib/npm-version.js +261 -261
  134. package/scripts/lib/paquetes-conocidos.js +50 -50
  135. package/scripts/lib/prompt-builder.js +264 -264
  136. package/scripts/lib/rrf-fusion.js +175 -175
  137. package/scripts/lib/scoring-instintos.js +277 -277
  138. package/scripts/lib/semantic-search.js +252 -252
  139. package/scripts/limpiar-artefactos-python.js +131 -131
  140. package/scripts/mcp-server/README.md +128 -128
  141. package/scripts/mcp-server/handlers.js +206 -206
  142. package/scripts/migrar-csv-a-array.js +168 -168
  143. package/scripts/migrar-fase-dominio.js +201 -201
  144. package/scripts/publicar.js +511 -511
  145. package/scripts/run-eval.js +141 -141
  146. package/scripts/validar-manifest.js +195 -195
  147. package/scripts/validar-userland-vacio.js +110 -110
  148. package/scripts/verificar-release.js +110 -0
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: web-fetcher-routing
3
+ description: >
4
+ Routing inteligente para fetching de URLs según dominio y formato:
5
+ GitHub raw, PDF, sitios JS-heavy y default. Reduce tokens consumidos
6
+ evitando WebFetch cuando hay alternativa más eficiente. Cargar antes
7
+ de hacer WebFetch a URLs externas, especialmente repositorios GitHub,
8
+ PDFs públicos, o sitios con paywall/Cloudflare/JS heavy.
9
+ ---
10
+
11
+ # web-fetcher-routing
12
+
13
+ Selecciona la herramienta correcta para cada URL antes de intentar el fetch.
14
+ Un WebFetch al HTML de GitHub UI consume 10-30× más tokens que leer el raw.
15
+ Un PDF vía Read devuelve basura binaria. La elección equivocada cuesta tokens
16
+ reales; este skill la automatiza.
17
+
18
+ ## Cuándo cargar
19
+
20
+ - Antes de hacer `WebFetch` a cualquier URL externa no trivial.
21
+ - Cuando la URL apunta a GitHub (repositorio, archivo o blob).
22
+ - Cuando la URL termina en `.pdf` o el contexto indica documento PDF.
23
+ - Cuando el fetch previo falló con 402, página de login o contenido vacío
24
+ (señal de JS-heavy o Cloudflare).
25
+
26
+ ## Cuándo NO cargar
27
+
28
+ - URLs internas del proyecto o localhost — usar `Read` directamente.
29
+ - Contenido ya cargado en el contexto de esta sesión — no re-fetchar.
30
+ - Cuando el usuario dictó explícitamente la herramienta a usar
31
+ ("usa WebFetch", "lee con curl").
32
+
33
+ ## Tabla de routing
34
+
35
+ | Patrón de URL | Herramienta | Razón |
36
+ |---|---|---|
37
+ | `github.com/[user]/[repo]/blob/[branch]/[path]` | Reescribir a `raw.githubusercontent.com/[user]/[repo]/[branch]/[path]` y hacer `WebFetch` con la URL reescrita | Evita el HTML de la UI de GitHub; 10-30× menos tokens |
38
+ | `raw.githubusercontent.com/...` | `WebFetch` directo | Ya es raw content |
39
+ | URL termina en `.pdf` | Invocar `Skill("swl-markitdown")` para conversión con `pdftotext` o `markitdown` | `Read` no soporta PDF; `WebFetch` devuelve binario o HTML de visor |
40
+ | `x.com`, `twitter.com`, cualquier host con CAPTCHA o Cloudflare detectado | Invocar `Skill("agent-browser")` con headless Chrome | `WebFetch` devuelve 402 o página de login; `agent-browser` usa accessibility tree (~82% menos tokens que screenshots) |
41
+ | `mp.weixin.qq.com`, `feishu.cn`, `larksuite.com` | Invocar `Skill("agent-browser")` | Plataformas chinas con autenticación o JS requerido |
42
+ | Todo lo demás | `WebFetch` directo | El caso común; probar primero antes de escalar |
43
+
44
+ ## Algoritmo de decisión
45
+
46
+ 1. Parsear la URL: extraer esquema, dominio y extensión del path.
47
+ 2. Comparar con los patrones de la tabla, en orden de arriba hacia abajo.
48
+ 3. Si el dominio es `github.com` con segmento `/blob/` en el path:
49
+ reescribir la URL antes de hacer el fetch.
50
+ 4. Invocar la herramienta asignada al patrón que coincide.
51
+ 5. Si el resultado tiene señales de paywall o error (primeras 10 líneas
52
+ contienen "Subscribe", "Sign in", "403", página vacía): escalar al
53
+ patrón JS-heavy con `Skill("agent-browser")`.
54
+ 6. Reportar qué método se usó y por qué, en una línea antes del contenido.
55
+
56
+ ## Ejemplo de reescritura GitHub
57
+
58
+ ```
59
+ # URL original
60
+ https://github.com/tw93/Waza/blob/main/skills/read/SKILL.md
61
+
62
+ # URL reescrita para raw
63
+ https://raw.githubusercontent.com/tw93/Waza/main/skills/read/SKILL.md
64
+ ```
65
+
66
+ ## Señales de fallo que activan escalado
67
+
68
+ | Señal en la respuesta | Acción |
69
+ |---|---|
70
+ | HTTP 402 | Escalar a `agent-browser` |
71
+ | Primeras líneas con "Sign in" o "Subscribe" | Detener, avisar al usuario |
72
+ | Contenido HTML con menos de 200 caracteres | Reintentar con método alternativo |
73
+ | Binario o caracteres ilegibles | Verificar si es PDF; si sí, usar `swl-markitdown` |
74
+
75
+ <!-- Adaptado de Waza/skills/read bajo MIT License (tw93/Waza) -->
@@ -1,161 +1,161 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * Hook: claudemd-bloat-detector.js
6
- * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
7
- *
8
- * Ejecuta `scripts/auditar-claudemd.js` contra archivos `CLAUDE.md`
9
- * recién modificados y emite un nudge a `.planning/evolucion/nudges.jsonl`
10
- * si el veredicto es WARN o ERROR.
11
- *
12
- * Aplica ADR-0016 (best practices Anthropic "The CLAUDE.md file"):
13
- * detecta inflación (líneas excesivas, bullets monolíticos, secciones
14
- * canónicas ausentes, ausencia de @references) y sugiere intervención
15
- * con `/swl:claudemd refactor`.
16
- *
17
- * Opt-out: SWL_CLAUDEMD_BLOAT=0 desactiva completamente el hook.
18
- *
19
- * Comportamiento:
20
- * - Nunca bloquea operaciones (exit code 0 siempre)
21
- * - Solo emite nudge cuando veredicto != OK — ruido mínimo
22
- * - Solo se dispara con archivos cuyo basename sea exactamente CLAUDE.md
23
- * - Respeta exclusiones: temp/, node_modules/, respositorios-git/
24
- *
25
- * Formato del nudge:
26
- * {
27
- * id: string,
28
- * kind: "claudemd-bloat",
29
- * target: "documentador-swl",
30
- * source: "hooks/claudemd-bloat-detector.js",
31
- * message: "...",
32
- * data: { archivo, veredicto, lineas, hallazgos_count },
33
- * ts: ISO,
34
- * accionado: false
35
- * }
36
- */
37
-
38
- const fs = require('fs');
39
- const path = require('path');
40
- const crypto = require('crypto');
41
-
42
- // ─── Opt-out global ───────────────────────────────────────────────────────
43
- if (process.env.SWL_CLAUDEMD_BLOAT === '0') {
44
- process.exit(0);
45
- }
46
-
47
- let hookInput = '';
48
- try {
49
- hookInput = fs.readFileSync(0, 'utf-8');
50
- } catch (_) {
51
- process.exit(0);
52
- }
53
-
54
- let evento;
55
- try {
56
- evento = JSON.parse(hookInput);
57
- } catch (_) {
58
- process.exit(0);
59
- }
60
-
61
- const toolName = evento?.tool_name;
62
- const toolInput = evento?.tool_input;
63
-
64
- if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
65
- process.exit(0);
66
- }
67
-
68
- const filePath = toolInput?.file_path;
69
- if (!filePath) {
70
- process.exit(0);
71
- }
72
-
73
- // Solo CLAUDE.md (basename exacto, case-sensitive)
74
- const basename = path.basename(filePath);
75
- if (basename !== 'CLAUDE.md') {
76
- process.exit(0);
77
- }
78
-
79
- const pathNormalized = filePath.replace(/\\/g, '/');
80
- const RUTAS_EXCLUIDAS = [
81
- '/temp/',
82
- '/node_modules/',
83
- '/respositorios-git/',
84
- '/.planning/',
85
- ];
86
- if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
87
- process.exit(0);
88
- }
89
-
90
- // El archivo debe existir
91
- if (!fs.existsSync(filePath)) {
92
- process.exit(0);
93
- }
94
-
95
- // ─── Ejecutar auditor (módulo, no subproceso) ─────────────────────────────
96
- const CWD = process.cwd();
97
- const auditorPath = path.join(CWD, 'scripts', 'auditar-claudemd.js');
98
- if (!fs.existsSync(auditorPath)) {
99
- // No hay auditor instalado en este destino; salir silenciosamente
100
- process.exit(0);
101
- }
102
-
103
- let resultado;
104
- try {
105
- const { auditar } = require(auditorPath);
106
- resultado = auditar(filePath);
107
- } catch (_) {
108
- // Cualquier error del auditor: salir silenciosamente, no romper el hook
109
- process.exit(0);
110
- }
111
-
112
- // Solo emitir nudge si veredicto != OK
113
- if (!resultado || resultado.veredicto === 'OK') {
114
- process.exit(0);
115
- }
116
-
117
- // ─── Construir nudge ──────────────────────────────────────────────────────
118
- const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
119
- const topHallazgos = (resultado.hallazgos || [])
120
- .slice(0, 3)
121
- .map((h) => ` - [${h.severidad}] ${h.mensaje}`)
122
- .join('\n');
123
-
124
- const nudge = {
125
- id: crypto.randomBytes(8).toString('hex'),
126
- kind: 'claudemd-bloat',
127
- target: 'documentador-swl',
128
- source: 'hooks/claudemd-bloat-detector.js',
129
- message:
130
- `[claudemd] ${rutaRelativa} veredicto: ${resultado.veredicto} ` +
131
- `(${resultado.hallazgos.length} hallazgos)\n` +
132
- topHallazgos + '\n' +
133
- ` Ejecutar \`/swl:claudemd audit\` para detalle, ` +
134
- `\`/swl:claudemd refactor\` para sugerencias de extracción.`,
135
- data: {
136
- archivo: rutaRelativa,
137
- veredicto: resultado.veredicto,
138
- lineas: resultado.metricas?.lineas,
139
- secciones_ausentes: resultado.metricas?.secciones_ausentes || [],
140
- tiene_at_references: resultado.metricas?.tiene_at_references,
141
- hallazgos_count: resultado.hallazgos.length,
142
- },
143
- ts: new Date().toISOString(),
144
- accionado: false,
145
- accionado_ts: null,
146
- accionado_por: null,
147
- };
148
-
149
- // ─── Persistir a nudges.jsonl ─────────────────────────────────────────────
150
- try {
151
- const nudgesPath = path.join(CWD, '.planning', 'evolucion', 'nudges.jsonl');
152
- const nudgesDir = path.dirname(nudgesPath);
153
- if (!fs.existsSync(nudgesDir)) {
154
- fs.mkdirSync(nudgesDir, { recursive: true });
155
- }
156
- fs.appendFileSync(nudgesPath, JSON.stringify(nudge) + '\n', 'utf-8');
157
- } catch (_) {
158
- // No fallar el hook por error de escritura
159
- }
160
-
161
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Hook: claudemd-bloat-detector.js
6
+ * Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
7
+ *
8
+ * Ejecuta `scripts/auditar-claudemd.js` contra archivos `CLAUDE.md`
9
+ * recién modificados y emite un nudge a `.planning/evolucion/nudges.jsonl`
10
+ * si el veredicto es WARN o ERROR.
11
+ *
12
+ * Aplica ADR-0016 (best practices Anthropic "The CLAUDE.md file"):
13
+ * detecta inflación (líneas excesivas, bullets monolíticos, secciones
14
+ * canónicas ausentes, ausencia de @references) y sugiere intervención
15
+ * con `/swl:claudemd refactor`.
16
+ *
17
+ * Opt-out: SWL_CLAUDEMD_BLOAT=0 desactiva completamente el hook.
18
+ *
19
+ * Comportamiento:
20
+ * - Nunca bloquea operaciones (exit code 0 siempre)
21
+ * - Solo emite nudge cuando veredicto != OK — ruido mínimo
22
+ * - Solo se dispara con archivos cuyo basename sea exactamente CLAUDE.md
23
+ * - Respeta exclusiones: temp/, node_modules/, respositorios-git/
24
+ *
25
+ * Formato del nudge:
26
+ * {
27
+ * id: string,
28
+ * kind: "claudemd-bloat",
29
+ * target: "documentador-swl",
30
+ * source: "hooks/claudemd-bloat-detector.js",
31
+ * message: "...",
32
+ * data: { archivo, veredicto, lineas, hallazgos_count },
33
+ * ts: ISO,
34
+ * accionado: false
35
+ * }
36
+ */
37
+
38
+ const fs = require('fs');
39
+ const path = require('path');
40
+ const crypto = require('crypto');
41
+
42
+ // ─── Opt-out global ───────────────────────────────────────────────────────
43
+ if (process.env.SWL_CLAUDEMD_BLOAT === '0') {
44
+ process.exit(0);
45
+ }
46
+
47
+ let hookInput = '';
48
+ try {
49
+ hookInput = fs.readFileSync(0, 'utf-8');
50
+ } catch (_) {
51
+ process.exit(0);
52
+ }
53
+
54
+ let evento;
55
+ try {
56
+ evento = JSON.parse(hookInput);
57
+ } catch (_) {
58
+ process.exit(0);
59
+ }
60
+
61
+ const toolName = evento?.tool_name;
62
+ const toolInput = evento?.tool_input;
63
+
64
+ if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
65
+ process.exit(0);
66
+ }
67
+
68
+ const filePath = toolInput?.file_path;
69
+ if (!filePath) {
70
+ process.exit(0);
71
+ }
72
+
73
+ // Solo CLAUDE.md (basename exacto, case-sensitive)
74
+ const basename = path.basename(filePath);
75
+ if (basename !== 'CLAUDE.md') {
76
+ process.exit(0);
77
+ }
78
+
79
+ const pathNormalized = filePath.replace(/\\/g, '/');
80
+ const RUTAS_EXCLUIDAS = [
81
+ '/temp/',
82
+ '/node_modules/',
83
+ '/respositorios-git/',
84
+ '/.planning/',
85
+ ];
86
+ if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
87
+ process.exit(0);
88
+ }
89
+
90
+ // El archivo debe existir
91
+ if (!fs.existsSync(filePath)) {
92
+ process.exit(0);
93
+ }
94
+
95
+ // ─── Ejecutar auditor (módulo, no subproceso) ─────────────────────────────
96
+ const CWD = process.cwd();
97
+ const auditorPath = path.join(CWD, 'scripts', 'auditar-claudemd.js');
98
+ if (!fs.existsSync(auditorPath)) {
99
+ // No hay auditor instalado en este destino; salir silenciosamente
100
+ process.exit(0);
101
+ }
102
+
103
+ let resultado;
104
+ try {
105
+ const { auditar } = require(auditorPath);
106
+ resultado = auditar(filePath);
107
+ } catch (_) {
108
+ // Cualquier error del auditor: salir silenciosamente, no romper el hook
109
+ process.exit(0);
110
+ }
111
+
112
+ // Solo emitir nudge si veredicto != OK
113
+ if (!resultado || resultado.veredicto === 'OK') {
114
+ process.exit(0);
115
+ }
116
+
117
+ // ─── Construir nudge ──────────────────────────────────────────────────────
118
+ const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
119
+ const topHallazgos = (resultado.hallazgos || [])
120
+ .slice(0, 3)
121
+ .map((h) => ` - [${h.severidad}] ${h.mensaje}`)
122
+ .join('\n');
123
+
124
+ const nudge = {
125
+ id: crypto.randomBytes(8).toString('hex'),
126
+ kind: 'claudemd-bloat',
127
+ target: 'documentador-swl',
128
+ source: 'hooks/claudemd-bloat-detector.js',
129
+ message:
130
+ `[claudemd] ${rutaRelativa} veredicto: ${resultado.veredicto} ` +
131
+ `(${resultado.hallazgos.length} hallazgos)\n` +
132
+ topHallazgos + '\n' +
133
+ ` Ejecutar \`/swl:claudemd audit\` para detalle, ` +
134
+ `\`/swl:claudemd refactor\` para sugerencias de extracción.`,
135
+ data: {
136
+ archivo: rutaRelativa,
137
+ veredicto: resultado.veredicto,
138
+ lineas: resultado.metricas?.lineas,
139
+ secciones_ausentes: resultado.metricas?.secciones_ausentes || [],
140
+ tiene_at_references: resultado.metricas?.tiene_at_references,
141
+ hallazgos_count: resultado.hallazgos.length,
142
+ },
143
+ ts: new Date().toISOString(),
144
+ accionado: false,
145
+ accionado_ts: null,
146
+ accionado_por: null,
147
+ };
148
+
149
+ // ─── Persistir a nudges.jsonl ─────────────────────────────────────────────
150
+ try {
151
+ const nudgesPath = path.join(CWD, '.planning', 'evolucion', 'nudges.jsonl');
152
+ const nudgesDir = path.dirname(nudgesPath);
153
+ if (!fs.existsSync(nudgesDir)) {
154
+ fs.mkdirSync(nudgesDir, { recursive: true });
155
+ }
156
+ fs.appendFileSync(nudgesPath, JSON.stringify(nudge) + '\n', 'utf-8');
157
+ } catch (_) {
158
+ // No fallar el hook por error de escritura
159
+ }
160
+
161
+ process.exit(0);
@@ -147,9 +147,14 @@ function generarSugerenciaSkillsComunidad(cwd) {
147
147
  const { skills, agentes } = _techDetector.sugerirSkills(cwd);
148
148
  if (skills.length === 0) return '';
149
149
 
150
- // Verificar si ya hay skills de comunidad instalados (.agents/skills/)
150
+ // Verificar si ya hay skills de comunidad instalados. Hay 2 señales:
151
+ // 1. Marker `.claude/skills/.autoskills-installed.json` (escrito por
152
+ // `swl-ses skills auto`). Es la señal canónica desde v1.4.x.
153
+ // 2. Existencia de `.agents/skills/` — compatibilidad hacia atrás con
154
+ // proyectos que usaron `npx autoskills` directamente sin el wrapper.
155
+ const markerAutoskills = path.join(cwd, '.claude', 'skills', '.autoskills-installed.json');
151
156
  const agentsSkillsDir = path.join(cwd, '.agents', 'skills');
152
- const yaTieneComunidad = fs.existsSync(agentsSkillsDir);
157
+ const yaTieneComunidad = fs.existsSync(markerAutoskills) || fs.existsSync(agentsSkillsDir);
153
158
 
154
159
  // Generar resumen de stack
155
160
  const resumen = _techDetector.generarResumenStack(cwd);
@@ -163,7 +168,7 @@ function generarSugerenciaSkillsComunidad(cwd) {
163
168
 
164
169
  if (!yaTieneComunidad) {
165
170
  // Solo sugerir si no tiene skills de comunidad aun
166
- lineas.push(' Skills de comunidad: ejecuta `npx autoskills --dry-run` para ver skills oficiales disponibles');
171
+ lineas.push(' Skills de comunidad: ejecuta `npx -y @saulwade/swl-ses@latest skills auto --dry-run` para ver skills disponibles');
167
172
  }
168
173
 
169
174
  lineas.push('--- FIN STACK ---');
@@ -1,107 +1,107 @@
1
- 'use strict';
2
-
3
- /**
4
- * agent-routing.js — Helper para inferir fase y dominio del routing.
5
- *
6
- * Lee el frontmatter del agente (agentes/<nombre>.md) y extrae los campos
7
- * `fase` y `dominio` introducidos en v1.1.0 (ADR 0012). El orquestador no
8
- * registra explícitamente "qué fase/dominio motivó la elección" — pero
9
- * cuando elige un agente, podemos asumir que lo eligió porque su fase y
10
- * dominio matcheaban. Por lo tanto, leer el frontmatter del agente
11
- * efectivamente invocado nos da la celda fase×dominio del routing.
12
- *
13
- * Esto es proxy de precisión, no precisión absoluta:
14
- * - Tasa de éxito por celda (fase, dominio) sirve como indicador de
15
- * que el routing está enviando trabajo correctamente al agente
16
- * adecuado.
17
- * - Una celda con tasa de éxito baja sugiere routing impreciso (el
18
- * agente recibe trabajo que no le compete) o agente sub-óptimo.
19
- *
20
- * Cache simple en memoria del proceso para evitar I/O repetida (cada
21
- * invocación de hook es proceso fresco, así que el cache no persiste,
22
- * pero protege dentro de la misma invocación).
23
- *
24
- * @module hooks/lib/agent-routing
25
- */
26
-
27
- const fs = require('fs');
28
- const path = require('path');
29
-
30
- const _cache = Object.create(null);
31
-
32
- /**
33
- * Resuelve la ruta del archivo de agente. Acepta tanto el repo madre como
34
- * proyectos consumidores (donde los agentes pueden estar en otras rutas).
35
- *
36
- * @param {string} agentName - p.ej. "backend-python-swl"
37
- * @param {string} [cwd] - Directorio de proyecto (default process.cwd())
38
- * @returns {string|null} - Ruta absoluta o null si no existe
39
- */
40
- function resolveAgentPath(agentName, cwd) {
41
- const baseDir = cwd || process.cwd();
42
- const candidates = [
43
- path.join(baseDir, 'agentes', `${agentName}.md`),
44
- path.join(baseDir, '.claude', 'agents', `${agentName}.md`),
45
- ];
46
- for (const c of candidates) {
47
- try { if (fs.statSync(c).isFile()) return c; } catch { /* siguiente */ }
48
- }
49
- return null;
50
- }
51
-
52
- /**
53
- * Extrae fase y dominio del frontmatter de un agente.
54
- *
55
- * @param {string} agentName - Nombre del agente sin extensión
56
- * @param {string} [cwd]
57
- * @returns {{ fase: string|null, dominio: string|null, source: string }}
58
- * - source: 'frontmatter' | 'cache' | 'unknown'
59
- */
60
- function getRouting(agentName, cwd) {
61
- if (!agentName) return { fase: null, dominio: null, source: 'unknown' };
62
- if (_cache[agentName]) {
63
- return { ..._cache[agentName], source: 'cache' };
64
- }
65
-
66
- const agentPath = resolveAgentPath(agentName, cwd);
67
- if (!agentPath) {
68
- const result = { fase: null, dominio: null, source: 'unknown' };
69
- _cache[agentName] = { fase: null, dominio: null };
70
- return result;
71
- }
72
-
73
- let raw;
74
- try {
75
- raw = fs.readFileSync(agentPath, 'utf8');
76
- } catch {
77
- return { fase: null, dominio: null, source: 'unknown' };
78
- }
79
-
80
- const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
81
- if (!fmMatch) {
82
- _cache[agentName] = { fase: null, dominio: null };
83
- return { fase: null, dominio: null, source: 'unknown' };
84
- }
85
- const fm = fmMatch[1];
86
-
87
- // Parser minimalista — solo busca las dos líneas que necesitamos.
88
- // Evitamos depender de un parser YAML completo (zero-deps).
89
- const faseMatch = fm.match(/^fase:\s*(\S+)/m);
90
- const dominioMatch = fm.match(/^dominio:\s*(\S+)/m);
91
-
92
- const result = {
93
- fase: faseMatch ? faseMatch[1].trim() : null,
94
- dominio: dominioMatch ? dominioMatch[1].trim() : null,
95
- };
96
- _cache[agentName] = result;
97
- return { ...result, source: 'frontmatter' };
98
- }
99
-
100
- /**
101
- * Limpia el cache (para tests o tras modificar agentes).
102
- */
103
- function clearCache() {
104
- for (const k of Object.keys(_cache)) delete _cache[k];
105
- }
106
-
107
- module.exports = { getRouting, resolveAgentPath, clearCache };
1
+ 'use strict';
2
+
3
+ /**
4
+ * agent-routing.js — Helper para inferir fase y dominio del routing.
5
+ *
6
+ * Lee el frontmatter del agente (agentes/<nombre>.md) y extrae los campos
7
+ * `fase` y `dominio` introducidos en v1.1.0 (ADR 0012). El orquestador no
8
+ * registra explícitamente "qué fase/dominio motivó la elección" — pero
9
+ * cuando elige un agente, podemos asumir que lo eligió porque su fase y
10
+ * dominio matcheaban. Por lo tanto, leer el frontmatter del agente
11
+ * efectivamente invocado nos da la celda fase×dominio del routing.
12
+ *
13
+ * Esto es proxy de precisión, no precisión absoluta:
14
+ * - Tasa de éxito por celda (fase, dominio) sirve como indicador de
15
+ * que el routing está enviando trabajo correctamente al agente
16
+ * adecuado.
17
+ * - Una celda con tasa de éxito baja sugiere routing impreciso (el
18
+ * agente recibe trabajo que no le compete) o agente sub-óptimo.
19
+ *
20
+ * Cache simple en memoria del proceso para evitar I/O repetida (cada
21
+ * invocación de hook es proceso fresco, así que el cache no persiste,
22
+ * pero protege dentro de la misma invocación).
23
+ *
24
+ * @module hooks/lib/agent-routing
25
+ */
26
+
27
+ const fs = require('fs');
28
+ const path = require('path');
29
+
30
+ const _cache = Object.create(null);
31
+
32
+ /**
33
+ * Resuelve la ruta del archivo de agente. Acepta tanto el repo madre como
34
+ * proyectos consumidores (donde los agentes pueden estar en otras rutas).
35
+ *
36
+ * @param {string} agentName - p.ej. "backend-python-swl"
37
+ * @param {string} [cwd] - Directorio de proyecto (default process.cwd())
38
+ * @returns {string|null} - Ruta absoluta o null si no existe
39
+ */
40
+ function resolveAgentPath(agentName, cwd) {
41
+ const baseDir = cwd || process.cwd();
42
+ const candidates = [
43
+ path.join(baseDir, 'agentes', `${agentName}.md`),
44
+ path.join(baseDir, '.claude', 'agents', `${agentName}.md`),
45
+ ];
46
+ for (const c of candidates) {
47
+ try { if (fs.statSync(c).isFile()) return c; } catch { /* siguiente */ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ /**
53
+ * Extrae fase y dominio del frontmatter de un agente.
54
+ *
55
+ * @param {string} agentName - Nombre del agente sin extensión
56
+ * @param {string} [cwd]
57
+ * @returns {{ fase: string|null, dominio: string|null, source: string }}
58
+ * - source: 'frontmatter' | 'cache' | 'unknown'
59
+ */
60
+ function getRouting(agentName, cwd) {
61
+ if (!agentName) return { fase: null, dominio: null, source: 'unknown' };
62
+ if (_cache[agentName]) {
63
+ return { ..._cache[agentName], source: 'cache' };
64
+ }
65
+
66
+ const agentPath = resolveAgentPath(agentName, cwd);
67
+ if (!agentPath) {
68
+ const result = { fase: null, dominio: null, source: 'unknown' };
69
+ _cache[agentName] = { fase: null, dominio: null };
70
+ return result;
71
+ }
72
+
73
+ let raw;
74
+ try {
75
+ raw = fs.readFileSync(agentPath, 'utf8');
76
+ } catch {
77
+ return { fase: null, dominio: null, source: 'unknown' };
78
+ }
79
+
80
+ const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
81
+ if (!fmMatch) {
82
+ _cache[agentName] = { fase: null, dominio: null };
83
+ return { fase: null, dominio: null, source: 'unknown' };
84
+ }
85
+ const fm = fmMatch[1];
86
+
87
+ // Parser minimalista — solo busca las dos líneas que necesitamos.
88
+ // Evitamos depender de un parser YAML completo (zero-deps).
89
+ const faseMatch = fm.match(/^fase:\s*(\S+)/m);
90
+ const dominioMatch = fm.match(/^dominio:\s*(\S+)/m);
91
+
92
+ const result = {
93
+ fase: faseMatch ? faseMatch[1].trim() : null,
94
+ dominio: dominioMatch ? dominioMatch[1].trim() : null,
95
+ };
96
+ _cache[agentName] = result;
97
+ return { ...result, source: 'frontmatter' };
98
+ }
99
+
100
+ /**
101
+ * Limpia el cache (para tests o tras modificar agentes).
102
+ */
103
+ function clearCache() {
104
+ for (const k of Object.keys(_cache)) delete _cache[k];
105
+ }
106
+
107
+ module.exports = { getRouting, resolveAgentPath, clearCache };