@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.
- package/CLAUDE.md +19 -2
- package/README.md +561 -561
- package/agentes/arquitecto-swl.md +33 -1
- package/agentes/nemesis-auditor-swl.md +59 -19
- package/bin/swl-mcp-server.js +214 -214
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/nemesis.md +230 -56
- 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/ejecutar-task-iterativo/SKILL.md +278 -278
- 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/SKILL.md +225 -1
- 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/nemesis-evaluacion-json/SKILL.md +266 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +341 -0
- package/habilidades/node-experto/SKILL.md +105 -4
- 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/protocolo-revision-swl/SKILL.md +350 -276
- 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/tdd-workflow/SKILL.md +150 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +8 -3
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/check-update.js +31 -3
- 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 +1324 -1321
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +2 -2
- 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 +353 -351
- 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/registro-componentes-nuevos.md +192 -0
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/actualizar.js +110 -1
- 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/derivar-feature-list.js +489 -489
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +58 -4
- 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/expandir-targets.js +71 -71
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/mcp_config.py +127 -0
- 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/lib/toml-merge.js +204 -204
- package/scripts/lib/transformadores/codex.js +375 -375
- package/scripts/lib/transformadores/cursor.js +359 -359
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-orchestrator.py +8 -18
- package/scripts/mcp-pool-manager.py +12 -23
- package/scripts/mcp-server/README.md +170 -170
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/mcp-server/telemetry.js +78 -78
- 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-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.
|
|
4
|
+
version: "1.2.1"
|
|
5
5
|
evolved: true
|
|
6
|
-
evolved-from: "1.
|
|
6
|
+
evolved-from: "1.2.0"
|
|
7
7
|
evolved-at: "2026-05-15"
|
|
8
8
|
evolved-by: "aprender"
|
|
9
|
-
evolved-note: "
|
|
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) -->
|
package/hooks/check-update.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
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 =
|
|
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);
|