@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,12 +1,12 @@
1
1
  ---
2
2
  name: verificar-trabajo
3
3
  description: Verificación goal-backward del trabajo ejecutado en 4 niveles progresivos — EXISTE, SUSTANTIVO, CONECTADO, DATOS_FLUYEN. Clasifica claims en 4 tipos (TASK, FIX, TEST_OR_BUILD, FEATURE_GO) con evidencia proporcional. Detecta stubs, componentes huérfanos, integraciones rotas y flujos incompletos. Produce veredictos estructurados JSON con clasificación de riesgo (Low/Medium/High) y evidencia verificable. Soporta loop de reparación cuando el veredicto es Fail.
4
- version: "1.2.0"
4
+ version: "1.2.1"
5
5
  evolved: true
6
- evolved-from: "1.1.1"
6
+ evolved-from: "1.2.0"
7
7
  evolved-at: "2026-05-15"
8
8
  evolved-by: "aprender"
9
- evolved-note: "Taxonomía de claim types adoptada de cc-sdd kiro-verify-completion: cada tipo de claim (TASK/FIX/TEST_OR_BUILD/FEATURE_GO) requiere evidencia distinta. Previene declarar éxito broader que la evidencia disponible."
9
+ evolved-note: "Gotchas v1.5.1 acumulados en este ciclo: (1) health-check post-hoc que recalcula contexto del install desde cwd actual → falso positivo (caso doctor 2026-05-15); (2) smoke E2E obligatorio para features cross-cutting (Sub-fase 11.5: transformarAgente nunca invocado pese a suite 100% verde); (3) auto-reparación debe verificar que el chequeo subsecuente ya no detecta el problema, si no → loop infinito (Sub-fase 12: doctor)."
10
10
  herramientasPermitidas: [Read, Write, Edit, Bash, Glob, Grep]
11
11
  exclusiones:
12
12
  - "No cargar durante la implementación activa de una tarea; la verificación es posterior a la implementación, no concurrente."
@@ -340,6 +340,11 @@ estructurado JSON, ver [recursos/plantilla-verificacion.md](recursos/plantilla-v
340
340
  - **Loop de reparación sin re-verificación completa**: tras el fix, el agente re-verifica solo el item que falló en lugar del veredicto completo. Causa: optimización incorrecta para ahorrar tiempo. Solución: el paso 3 del loop de reparación dice explícitamente "re-ejecutar la verificación COMPLETA" — un fix puede resolver un fallo pero introducir otro.
341
341
  - **Hollow Component no detectado en Nivel 2**: el componente Angular tiene template pero no usa ninguna señal del componente. El agente verifica que el archivo existe (Nivel 1) y que tiene contenido (Nivel 2), pero no detecta que el contenido es decorativo. Causa: la definición de "stub" en Nivel 2 no se aplicó al caso de templates sin binding. Solución: verificar explícitamente que el template usa al menos una variable/señal del componente.
342
342
  - **Verificación con tests + linter pasa pero al levantar BD fresca todo se rompe**: tests Python con mocks y `ruff check` retornan verde, pero `docker compose down -v && up` falla porque hay drift entre `database/schemas/`, seeds, funciones SQL y vistas. Caso real (SIGM 2026-05-04): primera pasada de `/swl:verificar` reportó "APROBADO 21/22" sin detectar que ~10 seeds tenían columnas obsoletas, UUIDs inválidos y un script `02-crear-buckets-minio.sh` que abortaba initdb del contenedor postgres. Causa: la verificación corre solo contra el código (que mockea BD); nunca aplica el SQL real. Solución: cuando el alcance toca cualquier archivo en `database/schemas/`, `database/seeds/`, `database/functions/` o `database/views/`, ejecutar como Nivel 4 (DATOS_FLUYEN) obligatorio: `docker compose down -v && docker compose up -d db && wait_for_health && docker logs <db_container> | grep -E "ERROR|skipped"`. Cualquier ERROR en logs de init invalida el veredicto Pass.
343
+ - **Health-check post-hoc que recalcula contexto del install desde cwd actual produce falso positivo**: un verificador (doctor, validador, monitor) que recalcula filtros — aplicados al momento del install — desde el `process.cwd()` actual genera discrepancia falsa cuando se ejecuta desde un dir distinto. Caso real (swl-ses doctor v1.4.3, 2026-05-15): reportaba "reglas: 65 (perfil esperaba 25)" porque recalculaba `filtrarReglasPorStack(detectarStack(cwd))` con `cwd` vacío de indicadores, mientras las 40 reglas-lenguajes se habían instalado correctamente con stack detectado en el dir del proyecto. Causa: el filtro depende del contexto del momento del install, no del momento del check. Solución obligatoria: **persistir el contexto del install en el state file** (en este caso `allLangs` + `stackInstalado` en `.swl-install-state.json`). Si el state es legacy (sin estos campos), **inferir el contexto desde los archivos realmente instalados** (ej: subdirs presentes bajo `reglas/lenguajes/<lang>/`), NO recalcular desde cwd. Aplica como regla general a cualquier check post-hoc cuya validez depende del contexto del install.
344
+
345
+ - **Suite 100% verde con bug latente cross-cutting que solo aparece en smoke E2E real**: las unit tests del transformador pasaban, los tests del instalador pasaban, los tests del manifiesto pasaban — pero el smoke `install --target codex --local --force` reveló que `instalarArchivo()` hacía `fs.copyFileSync(archivo.origen, destino)` literal sin invocar `transformarAgente()`. Los `.toml` de Codex y los `.md` normalizados de Cursor NUNCA se generaban en disco aunque las unit tests del transformador retornaran contenido correcto. Caso real (Sub-fase 11.5 v1.5.1, 2026-05-15): la integración de las dos capas (transformador + instalador) jamás se testeaba E2E porque cada una se probaba en aislamiento con mocks. **Regla obligatoria para features cross-cutting** (multi-capa: transformador + instalador + filesystem; o lib + bin + cliente; o backend + frontend + cache): el Nivel 4 DATOS_FLUYEN exige al menos un smoke E2E real que ejercite las tres capas con archivos reales en disco temporal. NO sustituible por unit tests aunque la suite sea 100% verde. Comando mínimo: `mkdir tmp && cd tmp && bin/CLI <comando-happy-path> && ls -R` + inspección del primer archivo generado.
346
+
347
+ - **Auto-reparación ejecuta acción sin verificar que el chequeo subsecuente ya no detecta el problema**: el doctor reporta "faltan reglas: 0/25" en Codex, el usuario elige "reparar automáticamente", el doctor invoca `instalador.install({force:true})`, reinstala 230 archivos, vuelve a chequear y reporta lo mismo. Loop infinito. Caso real (Sub-fase 12 v1.5.1, 2026-05-15): los 4 falsos positivos del doctor sobre Codex/Cursor persistían tras cada "reparación" porque la causa raíz era un filtro hardcoded (`tiposPrincipales = ['agentes', 'habilidades', 'comandos', 'reglas']` sin respetar `runtime.tiposSoportados` ni `dir<Tipo>`). Cada reinstalación dejaba el filtro intacto, el chequeo seguía detectando el "problema". **Regla**: toda función de auto-reparación debe ejecutar el chequeo subsecuente ANTES de reportar "Reparado" — si el chequeo sigue detectando el problema, NO marcar como reparado y reportar "no se pudo reparar, requiere diagnóstico manual". Implementación: wrap la acción en `try { accion(); const res = chequeo(); if (!res.ok) throw new Error('reparación sin efecto'); }`.
343
348
 
344
349
  ## Regla de oro del verificador
345
350
 
@@ -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) -->
@@ -37,8 +37,16 @@ const RETRY_INTERVAL_MS = 60 * 60 * 1000;
37
37
  /** Timeout para npm view (5 segundos). */
38
38
  const NPM_TIMEOUT_MS = 5000;
39
39
 
40
- /** Archivo flag para throttling entre sesiones. */
40
+ /**
41
+ * Archivo flag para throttling entre sesiones.
42
+ *
43
+ * En producción usa `os.tmpdir()/swl-ses-update-check.json`. Los tests
44
+ * pueden override la ruta exportando `SWL_UPDATE_FLAG_PATH` con un path
45
+ * único por test (evita race entre archivos `.test.js` que corren en
46
+ * paralelo y comparten el mismo flag).
47
+ */
41
48
  function flagPath() {
49
+ if (process.env.SWL_UPDATE_FLAG_PATH) return process.env.SWL_UPDATE_FLAG_PATH;
42
50
  return path.join(os.tmpdir(), 'swl-ses-update-check.json');
43
51
  }
44
52
 
@@ -137,6 +145,10 @@ function debeVerificar() {
137
145
 
138
146
  /**
139
147
  * Registra que se hizo un check.
148
+ *
149
+ * `notificado` se inicializa en 0 explícitamente. Sin este campo, la
150
+ * comparación `data.notificado < 2` en el branch de repetición evaluaba
151
+ * `undefined < 2 === false` y el aviso nunca se repetía dentro del throttle.
140
152
  */
141
153
  function registrarCheck(local, remota, hayNueva) {
142
154
  try {
@@ -145,6 +157,7 @@ function registrarCheck(local, remota, hayNueva) {
145
157
  local,
146
158
  remota,
147
159
  hayNueva,
160
+ notificado: 0,
148
161
  }), 'utf8');
149
162
  } catch { /* silencioso */ }
150
163
  }
@@ -164,12 +177,15 @@ process.stdin.on('end', async () => {
164
177
  // Si el último check detectó actualización, repetir notificación (1 vez)
165
178
  try {
166
179
  const data = JSON.parse(fs.readFileSync(flagPath(), 'utf8'));
167
- if (data.hayNueva && data.notificado < 2) {
180
+ // Defense-in-depth: aceptar flags antiguos sin campo `notificado`.
181
+ // `undefined < 2` evalúa `false` por NaN comparison — usar fallback a 0.
182
+ const notificadoActual = data.notificado || 0;
183
+ if (data.hayNueva && notificadoActual < 2) {
168
184
  process.stderr.write(
169
185
  `[swl-ses] Versión ${data.remota} disponible (actual: ${data.local}). ` +
170
186
  `Ejecuta: npx @saulwade/swl-ses@latest update\n`
171
187
  );
172
- data.notificado = (data.notificado || 0) + 1;
188
+ data.notificado = notificadoActual + 1;
173
189
  fs.writeFileSync(flagPath(), JSON.stringify(data), 'utf8');
174
190
  }
175
191
  } catch (err) {
@@ -201,6 +217,18 @@ process.stdin.on('end', async () => {
201
217
  '',
202
218
  ].join('\n');
203
219
  process.stderr.write(msg);
220
+ } else if (process.env.SWL_FORCE_UPDATE_NOTIFICATION === '1') {
221
+ // Modo debug: mostrar siempre el estado aunque local == remota.
222
+ // Útil para el autor (cuya local nunca queda atrás) y para verificar
223
+ // que el mecanismo funciona end-to-end sin esperar a un escenario real.
224
+ let estado;
225
+ const cmp = compararSemver(local, remota);
226
+ if (cmp === 0) estado = 'al día';
227
+ else if (cmp > 0) estado = 'local adelantada (publicación pendiente)';
228
+ else estado = 'comparación inconclusa';
229
+ process.stderr.write(
230
+ `[swl-ses/check-update] FORCE: local=${local}, remota=${remota}, ${estado}\n`
231
+ );
204
232
  }
205
233
  } catch (err) {
206
234
  // El hook NUNCA bloquea Claude Code, pero deja una traza diagnóstica.
@@ -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);