@saulwade/swl-ses 1.4.1 → 1.4.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.
- package/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/agentes/nemesis-auditor-swl.md +161 -161
- package/bin/swl-mcp-server.js +187 -187
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +122 -122
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +11 -6
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +1 -1
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +9 -1
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
|
@@ -1,75 +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
|
+
---
|
|
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);
|