@saulwade/swl-ses 2.3.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +241 -199
- package/README.md +597 -597
- package/agentes/_intent-spec.md +73 -73
- package/agentes/_propose-step.md +90 -90
- package/agentes/accesibilidad-wcag-swl.md +2 -2
- package/agentes/arquitecto-swl.md +2 -2
- package/agentes/auto-evolucion-swl.md +2 -2
- package/agentes/backend-api-swl.md +2 -2
- package/agentes/backend-csharp-swl.md +2 -2
- package/agentes/backend-go-swl.md +2 -2
- package/agentes/backend-java-swl.md +2 -2
- package/agentes/backend-node-swl.md +2 -2
- package/agentes/backend-python-swl.md +2 -2
- package/agentes/backend-rust-swl.md +2 -2
- package/agentes/backend-workers-swl.md +2 -2
- package/agentes/cloud-infra-swl.md +2 -2
- package/agentes/consolidador-swl.md +2 -2
- package/agentes/datos-swl.md +2 -2
- package/agentes/depurador-swl.md +2 -2
- package/agentes/devops-ci-swl.md +2 -2
- package/agentes/disenador-ui-swl.md +2 -2
- package/agentes/documentador-swl.md +2 -2
- package/agentes/frontend-angular-swl.md +2 -2
- package/agentes/frontend-css-swl.md +2 -2
- package/agentes/frontend-react-swl.md +2 -2
- package/agentes/frontend-swl.md +2 -2
- package/agentes/frontend-tailwind-swl.md +2 -2
- package/agentes/gh-fix-ci-swl.md +275 -275
- package/agentes/implementador-swl.md +2 -2
- package/agentes/investigador-swl.md +2 -2
- package/agentes/investigador-ux-swl.md +2 -2
- package/agentes/llm-apps-swl.md +2 -2
- package/agentes/migrador-swl.md +2 -2
- package/agentes/mobile-android-swl.md +2 -2
- package/agentes/mobile-cross-swl.md +2 -2
- package/agentes/mobile-ios-swl.md +2 -2
- package/agentes/mobile-testing-swl.md +2 -2
- package/agentes/nemesis-auditor-swl.md +1 -1
- package/agentes/notificador-swl.md +2 -2
- package/agentes/observabilidad-swl.md +2 -2
- package/agentes/orquestador-swl.md +41 -14
- package/agentes/pagos-swl.md +2 -2
- package/agentes/perfilador-usuario-swl.md +2 -2
- package/agentes/planificador-swl.md +2 -2
- package/agentes/producto-prd-swl.md +2 -2
- package/agentes/red-team-swl.md +218 -218
- package/agentes/release-manager-swl.md +2 -2
- package/agentes/rendimiento-swl.md +2 -2
- package/agentes/resolutor-build-swl.md +2 -2
- package/agentes/revisor-angular-swl.md +2 -2
- package/agentes/revisor-codigo-swl.md +36 -3
- package/agentes/revisor-csharp-swl.md +2 -2
- package/agentes/revisor-go-swl.md +2 -2
- package/agentes/revisor-java-swl.md +2 -2
- package/agentes/revisor-kotlin-swl.md +2 -2
- package/agentes/revisor-nextjs-swl.md +2 -2
- package/agentes/revisor-php-swl.md +2 -2
- package/agentes/revisor-react-swl.md +2 -2
- package/agentes/revisor-rust-swl.md +2 -2
- package/agentes/revisor-seguridad-swl.md +2 -2
- package/agentes/revisor-swift-swl.md +2 -2
- package/agentes/revisor-typescript-swl.md +2 -2
- package/agentes/sre-swl.md +2 -2
- package/agentes/tdd-qa-swl.md +2 -2
- package/comandos/swl/briefing.md +119 -119
- package/comandos/swl/contexto.md +0 -2
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/deuda-codigo.md +97 -0
- package/comandos/swl/predecir.md +139 -139
- package/comandos/swl/sesiones.md +1 -1
- package/gateway/agent-executor.js +1 -1
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/agent-deep-links/SKILL.md +148 -148
- package/habilidades/agentes-como-servicio/SKILL.md +2 -2
- package/habilidades/angular-moderno/SKILL.md +24 -1
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -276
- package/habilidades/backend-async-postgres-testing/SKILL.md +215 -215
- package/habilidades/backend-error-design/SKILL.md +221 -221
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/calidad-anti-patrones-universales/SKILL.md +368 -368
- package/habilidades/calidad-contract-testing/SKILL.md +165 -165
- package/habilidades/calidad-mutation-testing/SKILL.md +170 -170
- package/habilidades/changelog-generator/scripts/parse-commits.js +367 -367
- package/habilidades/checklist-seguridad/recursos/stride-cobertura.md +60 -60
- package/habilidades/contenedores-docker/SKILL.md +3 -1
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/drift-detection/SKILL.md +179 -179
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +1 -1
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +57 -57
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +4 -4
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +253 -252
- package/habilidades/legacy-code-rescue/SKILL.md +268 -267
- package/habilidades/meta-skills-estandar/SKILL.md +1 -1
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/perfil-usuario/SKILL.md +200 -200
- package/habilidades/postgresql-experto/SKILL.md +3 -1
- package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
- package/habilidades/prevencion-sobreingenieria/recursos/EXAMPLES.md +580 -580
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -255
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -231
- package/habilidades/proceso-debate-adversarial/recursos/personas.md +105 -105
- package/habilidades/proceso-discovery-machote/SKILL.md +157 -157
- package/habilidades/proceso-dynamic-workflows/SKILL.md +138 -138
- package/habilidades/proceso-dynamic-workflows/recursos/template-adversarial-verify.js +65 -65
- package/habilidades/proceso-dynamic-workflows/recursos/template-triage.js +65 -65
- package/habilidades/proceso-intent-engineering/SKILL.md +269 -269
- package/habilidades/proceso-modular-split/SKILL.md +256 -256
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +2 -2
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/structured-outputs/SKILL.md +2 -2
- package/habilidades/swl-claudemd/recursos/contrato-aprender.md +83 -83
- package/habilidades/swl-claudemd/recursos/duplicacion-reglas-globales.md +85 -85
- package/habilidades/swl-claudemd/recursos/plantillas-init.md +94 -94
- package/habilidades/swl-dashboard/SKILL.md +2 -2
- package/habilidades/tdd-workflow/recursos/gherkin-bdd.md +111 -111
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +1 -1
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +1 -1
- package/hooks/audit-trail.js +4 -4
- package/hooks/calidad-pre-commit.js +15 -11
- package/hooks/captura-acciones-post.js +3 -2
- package/hooks/captura-acciones-session.js +3 -2
- package/hooks/ciclo-evolucion-subagente.js +26 -26
- package/hooks/ciclo-evolucion.js +26 -26
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/claudemd-duplicacion-detector.js +170 -170
- package/hooks/contexto-iteracion.js +145 -144
- package/hooks/contexto-subagente.js +68 -0
- package/hooks/guardrail-modelo.js +14 -4
- package/hooks/inyeccion-contexto.js +12 -12
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/autonomia.js +208 -208
- package/hooks/lib/ciclo-evolucion.js +47 -47
- package/hooks/lib/deep-links.js +185 -185
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/etapa-auto-evolucion.js +701 -701
- package/hooks/lib/etapa-metricas.js +388 -388
- package/hooks/lib/etapa-perfil-usuario.js +376 -376
- package/hooks/lib/loop-telemetry.js +321 -321
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/model-router.js +2 -2
- package/hooks/lib/propose-step.js +358 -358
- 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/token-estimator.js +1 -0
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/lib/usage-model.js +1 -1
- package/hooks/registro-turnos.js +210 -209
- package/hooks/session-briefing.js +98 -98
- package/hooks/spec-gate.js +212 -211
- package/hooks/sugerir-contribuir.js +2 -1
- package/hooks/sugerir-regenerar-inventario.js +171 -170
- package/hooks/tdd-gate.js +242 -241
- package/hooks/telemetria-skill-routing.js +100 -100
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-intent-spec.js +242 -242
- package/hooks/validar-memoria-hook.js +218 -218
- package/hooks/validar-planning-paths.js +134 -134
- package/instintos/autonomia.yaml +27 -27
- package/instintos/prompt-appendices.yaml +57 -57
- package/llms.txt +29 -29
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/canonical-hashes.json +2948 -1964
- package/manifiestos/hook-profiles.json +0 -2
- package/manifiestos/hooks-config.json +469 -477
- package/manifiestos/invariantes-criticos.json +30 -0
- package/manifiestos/modulos.json +1390 -1390
- package/manifiestos/planning-paths.json +44 -44
- package/manifiestos/skills-lock.json +1282 -1282
- package/package.json +93 -93
- 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 +369 -371
- package/reglas/accesibilidad.md +10 -10
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/api-diseno.md +9 -9
- package/reglas/arreglar-al-detectar.md +240 -240
- package/reglas/auditorias-documentales-estructurales.md +7 -7
- package/reglas/cloud-infra.md +8 -8
- package/reglas/consultar-vault-primero.md +195 -195
- package/reglas/debatir-antes-de-aceptar.md +158 -158
- package/reglas/fragmentos-compartidos.md +183 -183
- package/reglas/hooks.md +6 -6
- package/reglas/intent-engineering.md +218 -218
- package/reglas/markitdown.md +8 -8
- package/reglas/monitor-ci.md +309 -309
- package/reglas/patrones.md +6 -6
- package/reglas/sesiones-paralelas.md +180 -180
- package/reglas/sin-duplicacion-reglas-globales.md +182 -182
- package/reglas/skills-estandar.md +6 -6
- package/reglas/testing.md +7 -7
- package/reglas/tests-cleanup.md +224 -224
- package/reglas/usar-code-review-graph.md +155 -155
- package/reglas/usar-context7.md +226 -226
- package/reglas/verificar-citas-normativas.md +548 -548
- package/schemas/agent-frontmatter.schema.json +5 -3
- package/schemas/agent-message.schema.json +73 -73
- package/schemas/agent-output-implementacion.schema.json +114 -114
- package/schemas/agent-output-planificacion.schema.json +150 -150
- package/schemas/agent-output-review.schema.json +98 -98
- package/schemas/diary-entry.schema.json +112 -112
- package/schemas/hook-profiles.schema.json +54 -54
- package/schemas/hooks-config.schema.json +89 -89
- package/schemas/modulos.schema.json +38 -38
- package/schemas/perfiles.schema.json +36 -36
- package/schemas/plugin.schema.json +77 -77
- package/schemas/skill-evals.schema.json +119 -119
- package/schemas/skill-frontmatter.schema.json +245 -245
- 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/cli/aprobar-plan.js +73 -73
- package/scripts/cli/briefing.js +23 -23
- package/scripts/cli/ciclo-evolucion.js +26 -26
- package/scripts/cli/configurar-ci.js +40 -40
- package/scripts/cli/derivar-feature-list.js +25 -25
- package/scripts/cli/detectar-host.js +27 -27
- package/scripts/cli/diary-entry.js +69 -69
- package/scripts/cli/execution-state.js +18 -18
- package/scripts/cli/gateway-notify.js +41 -41
- package/scripts/cli/liberar-fase.js +42 -42
- package/scripts/cli/loop-telemetry.js +125 -125
- package/scripts/cli/mark-evolved.js +56 -56
- package/scripts/cli/metricas-dora.js +26 -26
- package/scripts/cli/near-duplicate.js +55 -55
- package/scripts/cli/notificaciones.js +123 -123
- package/scripts/cli/propose-step.js +29 -29
- package/scripts/cli/schedule-parse.js +19 -19
- package/scripts/cli/sugerir-modelo.js +20 -20
- package/scripts/cli/verificar-plan.js +36 -36
- package/scripts/cli/verificar-trazabilidad.js +35 -35
- 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-matriz-lenguajes.js +271 -271
- package/scripts/instalador.js +4 -1
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/auditar-invocaciones-comandos.js +104 -104
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/ci-reader.js +193 -193
- package/scripts/lib/claude-sessions.js +1 -0
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-host-swl.js +175 -175
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/detector-autoduplicacion-intra-archivo.js +234 -234
- package/scripts/lib/detector-reglas-duplicadas.js +220 -220
- 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/evidencia-release.js +322 -322
- package/scripts/lib/gate-hooks-requires.js +249 -249
- package/scripts/lib/gate-licencias.js +212 -212
- package/scripts/lib/git-metricas.js +257 -257
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/metricas-dora.js +204 -204
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/plan-lock.js +275 -275
- package/scripts/lib/pr-analyzer.js +399 -399
- package/scripts/lib/preservar-usuario.js +39 -5
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/reglas-globales-conocidas.json +180 -180
- package/scripts/lib/resolver-plan-fase.js +37 -37
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/schema-version.js +164 -164
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/tool-cost-analyzer.js +1 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +200 -200
- package/scripts/publicar.js +497 -497
- package/scripts/run-eval.js +141 -141
- package/scripts/tui/componentes/selector-multi.js +189 -189
- package/scripts/tui/componentes/selector-unico.js +158 -158
- package/scripts/tui/ejecutores.js +375 -375
- package/scripts/tui/index.js +162 -162
- package/scripts/tui/lib/colores.js +129 -129
- package/scripts/tui/lib/render.js +264 -264
- package/scripts/tui/lib/teclas.js +113 -113
- package/scripts/tui/pantallas/inspect.js +173 -173
- package/scripts/tui/pantallas/install-wizard.js +334 -334
- package/scripts/tui/pantallas/menu-principal.js +52 -52
- package/scripts/tui/pantallas/progreso.js +274 -274
- package/scripts/tui/pantallas/resumen.js +132 -132
- package/scripts/tui/pantallas/uninstall-wizard.js +208 -208
- package/scripts/tui/pantallas/update-wizard.js +232 -232
- package/scripts/tui/pantallas/welcome.js +187 -187
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/validar.js +1 -1
- package/scripts/verificar-release.js +51 -0
- package/hooks/lib/context-compressor.js +0 -657
- package/hooks/linea-estado.js +0 -326
- package/hooks/monitor-contexto.js +0 -373
|
@@ -1,170 +1,170 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook: claudemd-duplicacion-detector.js
|
|
6
|
-
* Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
|
|
7
|
-
*
|
|
8
|
-
* Detecta duplicación de reglas globales (`~/.claude/rules/`) inline en
|
|
9
|
-
* `CLAUDE.md` de proyecto recién modificados. Consume el detector
|
|
10
|
-
* `scripts/lib/detector-reglas-duplicadas.js` que lee el catálogo
|
|
11
|
-
* declarativo `scripts/lib/reglas-globales-conocidas.json`.
|
|
12
|
-
*
|
|
13
|
-
* Aplica la regla `reglas/sin-duplicacion-reglas-globales.md`:
|
|
14
|
-
* el `CLAUDE.md` del proyecto NO debe duplicar reglas que ya viven en
|
|
15
|
-
* `~/.claude/rules/`. La regla global es fuente de verdad; el proyecto
|
|
16
|
-
* referencia, no duplica.
|
|
17
|
-
*
|
|
18
|
-
* Opt-out: SWL_CLAUDEMD_DUPLICACION=0 desactiva completamente el hook.
|
|
19
|
-
*
|
|
20
|
-
* Comportamiento:
|
|
21
|
-
* - Nunca bloquea operaciones (exit code 0 siempre)
|
|
22
|
-
* - Solo emite nudge cuando hay duplicaciones detectadas
|
|
23
|
-
* - Solo se dispara con archivos cuyo basename sea exactamente CLAUDE.md
|
|
24
|
-
* - Respeta exclusiones: temp/, node_modules/, respositorios-git/
|
|
25
|
-
* - NO evalúa user-level (~/.claude/CLAUDE.md) — ahí sí pueden
|
|
26
|
-
* declararse preferencias personales
|
|
27
|
-
*
|
|
28
|
-
* Formato del nudge:
|
|
29
|
-
* {
|
|
30
|
-
* id: string,
|
|
31
|
-
* kind: "claudemd-duplicacion-reglas",
|
|
32
|
-
* target: "documentador-swl",
|
|
33
|
-
* source: "hooks/claudemd-duplicacion-detector.js",
|
|
34
|
-
* message: "...",
|
|
35
|
-
* data: { archivo, duplicaciones: [...] },
|
|
36
|
-
* ts: ISO,
|
|
37
|
-
* accionado: false
|
|
38
|
-
* }
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
const fs = require('fs');
|
|
42
|
-
const path = require('path');
|
|
43
|
-
const os = require('os');
|
|
44
|
-
const crypto = require('crypto');
|
|
45
|
-
|
|
46
|
-
// ─── Opt-out global ───────────────────────────────────────────────────────
|
|
47
|
-
if (process.env.SWL_CLAUDEMD_DUPLICACION === '0') {
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
let hookInput = '';
|
|
52
|
-
try {
|
|
53
|
-
hookInput = fs.readFileSync(0, 'utf-8');
|
|
54
|
-
} catch (_) {
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let evento;
|
|
59
|
-
try {
|
|
60
|
-
evento = JSON.parse(hookInput);
|
|
61
|
-
} catch (_) {
|
|
62
|
-
process.exit(0);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const toolName = evento?.tool_name;
|
|
66
|
-
const toolInput = evento?.tool_input;
|
|
67
|
-
|
|
68
|
-
if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
69
|
-
process.exit(0);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const filePath = toolInput?.file_path;
|
|
73
|
-
if (!filePath) {
|
|
74
|
-
process.exit(0);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Solo CLAUDE.md (basename exacto, case-sensitive)
|
|
78
|
-
const basename = path.basename(filePath);
|
|
79
|
-
if (basename !== 'CLAUDE.md') {
|
|
80
|
-
process.exit(0);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const pathNormalized = filePath.replace(/\\/g, '/');
|
|
84
|
-
const RUTAS_EXCLUIDAS = [
|
|
85
|
-
'/temp/',
|
|
86
|
-
'/node_modules/',
|
|
87
|
-
'/respositorios-git/',
|
|
88
|
-
'/.planning/',
|
|
89
|
-
];
|
|
90
|
-
if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
|
|
91
|
-
process.exit(0);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// User-level: no evaluar (puede declarar preferencias personales)
|
|
95
|
-
const homeClaudeDir = path.resolve(path.join(os.homedir(), '.claude'));
|
|
96
|
-
const rutaAbs = path.resolve(filePath);
|
|
97
|
-
const esUserLevel = rutaAbs.startsWith(homeClaudeDir + path.sep) || rutaAbs === path.join(homeClaudeDir, 'CLAUDE.md');
|
|
98
|
-
|
|
99
|
-
// El archivo debe existir
|
|
100
|
-
if (!fs.existsSync(filePath)) {
|
|
101
|
-
process.exit(0);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Cargar detector ──────────────────────────────────────────────────────
|
|
105
|
-
const CWD = process.cwd();
|
|
106
|
-
const detectorPath = path.join(CWD, 'scripts', 'lib', 'detector-reglas-duplicadas.js');
|
|
107
|
-
if (!fs.existsSync(detectorPath)) {
|
|
108
|
-
// No hay detector instalado en este destino; salir silenciosamente
|
|
109
|
-
process.exit(0);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let resultado;
|
|
113
|
-
try {
|
|
114
|
-
const { detectarDuplicaciones } = require(detectorPath);
|
|
115
|
-
const contenido = fs.readFileSync(filePath, 'utf8');
|
|
116
|
-
resultado = detectarDuplicaciones(contenido, null, { esUserLevel });
|
|
117
|
-
} catch (_) {
|
|
118
|
-
// Cualquier error del detector: salir silenciosamente
|
|
119
|
-
process.exit(0);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Solo emitir nudge si hay duplicaciones detectadas
|
|
123
|
-
if (!resultado || !resultado.evaluado || resultado.duplicaciones.length === 0) {
|
|
124
|
-
process.exit(0);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ─── Construir nudge ──────────────────────────────────────────────────────
|
|
128
|
-
const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
|
|
129
|
-
const topDuplicaciones = resultado.duplicaciones
|
|
130
|
-
.slice(0, 5)
|
|
131
|
-
.map((d) => ` - [${d.severidad}] ${d.id} (regla global: ${d.regla_global}, línea ~${d.linea_aproximada})`)
|
|
132
|
-
.join('\n');
|
|
133
|
-
|
|
134
|
-
const nudge = {
|
|
135
|
-
id: crypto.randomBytes(8).toString('hex'),
|
|
136
|
-
kind: 'claudemd-duplicacion-reglas',
|
|
137
|
-
target: 'documentador-swl',
|
|
138
|
-
source: 'hooks/claudemd-duplicacion-detector.js',
|
|
139
|
-
message:
|
|
140
|
-
`[claudemd-dup] ${rutaRelativa} duplica ${resultado.duplicaciones.length} regla(s) global(es):\n` +
|
|
141
|
-
topDuplicaciones + '\n' +
|
|
142
|
-
` Ejecutar \`/swl:claudemd audit\` para detalle, ` +
|
|
143
|
-
`\`/swl:claudemd refactor\` para sugerencias de reemplazo.\n` +
|
|
144
|
-
` Ver regla: \`reglas/sin-duplicacion-reglas-globales.md\`.`,
|
|
145
|
-
data: {
|
|
146
|
-
archivo: rutaRelativa,
|
|
147
|
-
total_evaluadas: resultado.total_reglas_evaluadas,
|
|
148
|
-
detectadas: resultado.duplicaciones.length,
|
|
149
|
-
ids: resultado.duplicaciones.map((d) => d.id),
|
|
150
|
-
reglas_globales: [...new Set(resultado.duplicaciones.map((d) => d.regla_global))],
|
|
151
|
-
},
|
|
152
|
-
ts: new Date().toISOString(),
|
|
153
|
-
accionado: false,
|
|
154
|
-
accionado_ts: null,
|
|
155
|
-
accionado_por: null,
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// ─── Persistir a nudges.jsonl ─────────────────────────────────────────────
|
|
159
|
-
try {
|
|
160
|
-
const nudgesPath = path.join(CWD, '.planning', 'evolution', 'nudges.jsonl');
|
|
161
|
-
const nudgesDir = path.dirname(nudgesPath);
|
|
162
|
-
if (!fs.existsSync(nudgesDir)) {
|
|
163
|
-
fs.mkdirSync(nudgesDir, { recursive: true });
|
|
164
|
-
}
|
|
165
|
-
fs.appendFileSync(nudgesPath, JSON.stringify(nudge) + '\n', 'utf-8');
|
|
166
|
-
} catch (_) {
|
|
167
|
-
// No fallar el hook por error de escritura
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
process.exit(0);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: claudemd-duplicacion-detector.js
|
|
6
|
+
* Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit)
|
|
7
|
+
*
|
|
8
|
+
* Detecta duplicación de reglas globales (`~/.claude/rules/`) inline en
|
|
9
|
+
* `CLAUDE.md` de proyecto recién modificados. Consume el detector
|
|
10
|
+
* `scripts/lib/detector-reglas-duplicadas.js` que lee el catálogo
|
|
11
|
+
* declarativo `scripts/lib/reglas-globales-conocidas.json`.
|
|
12
|
+
*
|
|
13
|
+
* Aplica la regla `reglas/sin-duplicacion-reglas-globales.md`:
|
|
14
|
+
* el `CLAUDE.md` del proyecto NO debe duplicar reglas que ya viven en
|
|
15
|
+
* `~/.claude/rules/`. La regla global es fuente de verdad; el proyecto
|
|
16
|
+
* referencia, no duplica.
|
|
17
|
+
*
|
|
18
|
+
* Opt-out: SWL_CLAUDEMD_DUPLICACION=0 desactiva completamente el hook.
|
|
19
|
+
*
|
|
20
|
+
* Comportamiento:
|
|
21
|
+
* - Nunca bloquea operaciones (exit code 0 siempre)
|
|
22
|
+
* - Solo emite nudge cuando hay duplicaciones detectadas
|
|
23
|
+
* - Solo se dispara con archivos cuyo basename sea exactamente CLAUDE.md
|
|
24
|
+
* - Respeta exclusiones: temp/, node_modules/, respositorios-git/
|
|
25
|
+
* - NO evalúa user-level (~/.claude/CLAUDE.md) — ahí sí pueden
|
|
26
|
+
* declararse preferencias personales
|
|
27
|
+
*
|
|
28
|
+
* Formato del nudge:
|
|
29
|
+
* {
|
|
30
|
+
* id: string,
|
|
31
|
+
* kind: "claudemd-duplicacion-reglas",
|
|
32
|
+
* target: "documentador-swl",
|
|
33
|
+
* source: "hooks/claudemd-duplicacion-detector.js",
|
|
34
|
+
* message: "...",
|
|
35
|
+
* data: { archivo, duplicaciones: [...] },
|
|
36
|
+
* ts: ISO,
|
|
37
|
+
* accionado: false
|
|
38
|
+
* }
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const fs = require('fs');
|
|
42
|
+
const path = require('path');
|
|
43
|
+
const os = require('os');
|
|
44
|
+
const crypto = require('crypto');
|
|
45
|
+
|
|
46
|
+
// ─── Opt-out global ───────────────────────────────────────────────────────
|
|
47
|
+
if (process.env.SWL_CLAUDEMD_DUPLICACION === '0') {
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let hookInput = '';
|
|
52
|
+
try {
|
|
53
|
+
hookInput = fs.readFileSync(0, 'utf-8');
|
|
54
|
+
} catch (_) {
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let evento;
|
|
59
|
+
try {
|
|
60
|
+
evento = JSON.parse(hookInput);
|
|
61
|
+
} catch (_) {
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const toolName = evento?.tool_name;
|
|
66
|
+
const toolInput = evento?.tool_input;
|
|
67
|
+
|
|
68
|
+
if (!toolName || !['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const filePath = toolInput?.file_path;
|
|
73
|
+
if (!filePath) {
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Solo CLAUDE.md (basename exacto, case-sensitive)
|
|
78
|
+
const basename = path.basename(filePath);
|
|
79
|
+
if (basename !== 'CLAUDE.md') {
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pathNormalized = filePath.replace(/\\/g, '/');
|
|
84
|
+
const RUTAS_EXCLUIDAS = [
|
|
85
|
+
'/temp/',
|
|
86
|
+
'/node_modules/',
|
|
87
|
+
'/respositorios-git/',
|
|
88
|
+
'/.planning/',
|
|
89
|
+
];
|
|
90
|
+
if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// User-level: no evaluar (puede declarar preferencias personales)
|
|
95
|
+
const homeClaudeDir = path.resolve(path.join(os.homedir(), '.claude'));
|
|
96
|
+
const rutaAbs = path.resolve(filePath);
|
|
97
|
+
const esUserLevel = rutaAbs.startsWith(homeClaudeDir + path.sep) || rutaAbs === path.join(homeClaudeDir, 'CLAUDE.md');
|
|
98
|
+
|
|
99
|
+
// El archivo debe existir
|
|
100
|
+
if (!fs.existsSync(filePath)) {
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Cargar detector ──────────────────────────────────────────────────────
|
|
105
|
+
const CWD = process.cwd();
|
|
106
|
+
const detectorPath = path.join(CWD, 'scripts', 'lib', 'detector-reglas-duplicadas.js');
|
|
107
|
+
if (!fs.existsSync(detectorPath)) {
|
|
108
|
+
// No hay detector instalado en este destino; salir silenciosamente
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let resultado;
|
|
113
|
+
try {
|
|
114
|
+
const { detectarDuplicaciones } = require(detectorPath);
|
|
115
|
+
const contenido = fs.readFileSync(filePath, 'utf8');
|
|
116
|
+
resultado = detectarDuplicaciones(contenido, null, { esUserLevel });
|
|
117
|
+
} catch (_) {
|
|
118
|
+
// Cualquier error del detector: salir silenciosamente
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Solo emitir nudge si hay duplicaciones detectadas
|
|
123
|
+
if (!resultado || !resultado.evaluado || resultado.duplicaciones.length === 0) {
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ─── Construir nudge ──────────────────────────────────────────────────────
|
|
128
|
+
const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
|
|
129
|
+
const topDuplicaciones = resultado.duplicaciones
|
|
130
|
+
.slice(0, 5)
|
|
131
|
+
.map((d) => ` - [${d.severidad}] ${d.id} (regla global: ${d.regla_global}, línea ~${d.linea_aproximada})`)
|
|
132
|
+
.join('\n');
|
|
133
|
+
|
|
134
|
+
const nudge = {
|
|
135
|
+
id: crypto.randomBytes(8).toString('hex'),
|
|
136
|
+
kind: 'claudemd-duplicacion-reglas',
|
|
137
|
+
target: 'documentador-swl',
|
|
138
|
+
source: 'hooks/claudemd-duplicacion-detector.js',
|
|
139
|
+
message:
|
|
140
|
+
`[claudemd-dup] ${rutaRelativa} duplica ${resultado.duplicaciones.length} regla(s) global(es):\n` +
|
|
141
|
+
topDuplicaciones + '\n' +
|
|
142
|
+
` Ejecutar \`/swl:claudemd audit\` para detalle, ` +
|
|
143
|
+
`\`/swl:claudemd refactor\` para sugerencias de reemplazo.\n` +
|
|
144
|
+
` Ver regla: \`reglas/sin-duplicacion-reglas-globales.md\`.`,
|
|
145
|
+
data: {
|
|
146
|
+
archivo: rutaRelativa,
|
|
147
|
+
total_evaluadas: resultado.total_reglas_evaluadas,
|
|
148
|
+
detectadas: resultado.duplicaciones.length,
|
|
149
|
+
ids: resultado.duplicaciones.map((d) => d.id),
|
|
150
|
+
reglas_globales: [...new Set(resultado.duplicaciones.map((d) => d.regla_global))],
|
|
151
|
+
},
|
|
152
|
+
ts: new Date().toISOString(),
|
|
153
|
+
accionado: false,
|
|
154
|
+
accionado_ts: null,
|
|
155
|
+
accionado_por: null,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ─── Persistir a nudges.jsonl ─────────────────────────────────────────────
|
|
159
|
+
try {
|
|
160
|
+
const nudgesPath = path.join(CWD, '.planning', 'evolution', 'nudges.jsonl');
|
|
161
|
+
const nudgesDir = path.dirname(nudgesPath);
|
|
162
|
+
if (!fs.existsSync(nudgesDir)) {
|
|
163
|
+
fs.mkdirSync(nudgesDir, { recursive: true });
|
|
164
|
+
}
|
|
165
|
+
fs.appendFileSync(nudgesPath, JSON.stringify(nudge) + '\n', 'utf-8');
|
|
166
|
+
} catch (_) {
|
|
167
|
+
// No fallar el hook por error de escritura
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
process.exit(0);
|
|
@@ -1,144 +1,145 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook: contexto-iteracion.js
|
|
6
|
-
* Tipo: UserPromptSubmit (matcher vacío)
|
|
7
|
-
*
|
|
8
|
-
* Anti-context-rot para loops iterativos: cuando hay una corrida activa en
|
|
9
|
-
* .planning/loops/ (autoresearch, nemesis --remediar, verificar
|
|
10
|
-
* --until-converge, debate adversarial), inyecta al contexto un bloque
|
|
11
|
-
* compacto con el estado del loop — iteración actual, últimas 3 filas del
|
|
12
|
-
* TSV y la recomendación de trayectoria (continuar / detener por plateau /
|
|
13
|
-
* cambiar estrategia).
|
|
14
|
-
*
|
|
15
|
-
* En sesiones largas el modelo pierde de vista en qué iteración va y si la
|
|
16
|
-
* métrica sigue mejorando; este hook re-ancla ese estado sin que el agente
|
|
17
|
-
* tenga que releer archivos. Patrón adoptado del análisis de autoresearch
|
|
18
|
-
* v2.1 (hook iteration-context), adaptado a SWL: estado en el directorio de
|
|
19
|
-
* la corrida (no /tmp) y throttle por tiempo (no por conteo de prompts).
|
|
20
|
-
*
|
|
21
|
-
* Throttle: máximo 1 inyección cada 4 minutos por corrida (estado en
|
|
22
|
-
* .inject-state.json dentro del directorio de la corrida — se limpia solo
|
|
23
|
-
* con la corrida). 4 min < TTL del prompt cache (5 min): la inyección no
|
|
24
|
-
* coincide dos veces dentro de la misma ventana de cache.
|
|
25
|
-
*
|
|
26
|
-
* Zero-deps (solo hooks/lib/loop-telemetry). Nunca bloquea.
|
|
27
|
-
* Opt-out: SWL_CONTEXTO_ITERACION=0
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
|
|
33
|
-
const { atomicWriteJSON } = require('./lib/atomic-write');
|
|
34
|
-
const { corridaActiva, analizarTrayectoria } = require('./lib/loop-telemetry');
|
|
35
|
-
|
|
36
|
-
/** Intervalo mínimo entre inyecciones para la misma corrida (4 minutos). */
|
|
37
|
-
const THROTTLE_MS = 4 * 60 * 1000;
|
|
38
|
-
|
|
39
|
-
const ARCHIVO_ESTADO = '.inject-state.json';
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Decide si toca inyectar según el estado de throttle de la corrida.
|
|
43
|
-
* @param {string} dirCorrida - Directorio de la corrida activa.
|
|
44
|
-
* @param {number} [ahora=Date.now()]
|
|
45
|
-
* @returns {boolean}
|
|
46
|
-
*/
|
|
47
|
-
function debeInyectar(dirCorrida, ahora = Date.now()) {
|
|
48
|
-
try {
|
|
49
|
-
const estado = JSON.parse(
|
|
50
|
-
fs.readFileSync(path.join(dirCorrida, ARCHIVO_ESTADO), 'utf8')
|
|
51
|
-
);
|
|
52
|
-
return ahora - (estado.ultimaInyeccion || 0) > THROTTLE_MS;
|
|
53
|
-
} catch {
|
|
54
|
-
return true; // sin estado = primera inyección
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Registra la inyección para el throttle. Best-effort, escritura atómica. */
|
|
59
|
-
function registrarInyeccion(dirCorrida, ahora = Date.now()) {
|
|
60
|
-
try {
|
|
61
|
-
atomicWriteJSON(
|
|
62
|
-
path.join(dirCorrida, ARCHIVO_ESTADO),
|
|
63
|
-
{ ultimaInyeccion: ahora }
|
|
64
|
-
);
|
|
65
|
-
} catch { /* silencioso */ }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Construye el bloque de contexto a inyectar.
|
|
70
|
-
* @param {{dir: string, tipo: string, ultimasFilas: object[], totalFilas: number}} activa
|
|
71
|
-
* @param {object} trayectoria - Resultado de analizarTrayectoria.
|
|
72
|
-
* @returns {string}
|
|
73
|
-
*/
|
|
74
|
-
function construirBloque(activa, trayectoria) {
|
|
75
|
-
const lineas = [];
|
|
76
|
-
lineas.push(`🔁 Loop activo: \`${activa.tipo}\` (${path.basename(activa.dir)})`);
|
|
77
|
-
lineas.push(
|
|
78
|
-
`Iteraciones: ${trayectoria.totalIteraciones} | keep: ${trayectoria.keeps} | ` +
|
|
79
|
-
`revert: ${trayectoria.reverts} | métrica: ${trayectoria.metricaInicial} → ${trayectoria.metricaFinal}`
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
if (activa.ultimasFilas.length > 0) {
|
|
83
|
-
lineas.push('Últimas iteraciones:');
|
|
84
|
-
for (const fila of activa.ultimasFilas) {
|
|
85
|
-
const desc = (fila.descripcion || '').slice(0, 80);
|
|
86
|
-
lineas.push(` ${fila.iteracion ?? '?'}. [${fila.estado || '?'}] métrica=${fila.metrica ?? '?'} — ${desc}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (trayectoria.plateau) {
|
|
91
|
-
lineas.push(
|
|
92
|
-
'⚠️ PLATEAU detectado: las últimas iteraciones no mejoran la métrica. ' +
|
|
93
|
-
'Considera cerrar el loop y reportar el mejor estado alcanzado en lugar de seguir iterando.'
|
|
94
|
-
);
|
|
95
|
-
} else {
|
|
96
|
-
lineas.push(`Recomendación de trayectoria: ${trayectoria.recomendacion}.`);
|
|
97
|
-
}
|
|
98
|
-
lineas.push(`Registro completo: ${path.join(activa.dir, 'iteraciones.tsv')}`);
|
|
99
|
-
|
|
100
|
-
return lineas.join('\n');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ---------------------------------------------------------------------------
|
|
104
|
-
// Entrypoint
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
function main() {
|
|
108
|
-
let inputRaw = '';
|
|
109
|
-
process.stdin.on('data', (c) => { inputRaw += c; });
|
|
110
|
-
|
|
111
|
-
process.stdin.on('end', () => {
|
|
112
|
-
try {
|
|
113
|
-
if (process.env.SWL_CONTEXTO_ITERACION === '0') return;
|
|
114
|
-
|
|
115
|
-
const activa = corridaActiva(process.cwd(), { maxEdadMin: 30 });
|
|
116
|
-
if (!activa) return;
|
|
117
|
-
if (!debeInyectar(activa.dir)) return;
|
|
118
|
-
|
|
119
|
-
const trayectoria = analizarTrayectoria(activa.dir);
|
|
120
|
-
// Sin iteraciones más allá del baseline no hay nada útil que anclar
|
|
121
|
-
if (trayectoria.totalIteraciones === 0) return;
|
|
122
|
-
|
|
123
|
-
const output = {
|
|
124
|
-
hookSpecificOutput: {
|
|
125
|
-
hookEventName: 'UserPromptSubmit',
|
|
126
|
-
additionalContext: construirBloque(activa, trayectoria),
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
// Marcar el throttle SOLO tras un write exitoso: si stdout falla
|
|
130
|
-
// (broken pipe, harness cerró el canal), no registrar la inyección
|
|
131
|
-
// para que el próximo prompt reintente en vez de saltarse 4 min con
|
|
132
|
-
// un mensaje que nunca llegó. Best-effort: el hook nunca bloquea.
|
|
133
|
-
process.stdout.write(JSON.stringify(output), (err) => {
|
|
134
|
-
if (!err) {
|
|
135
|
-
try { registrarInyeccion(activa.dir); } catch { /* throttle best-effort */ }
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
} catch { /* nunca bloquea */ }
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: contexto-iteracion.js
|
|
6
|
+
* Tipo: UserPromptSubmit (matcher vacío)
|
|
7
|
+
*
|
|
8
|
+
* Anti-context-rot para loops iterativos: cuando hay una corrida activa en
|
|
9
|
+
* .planning/loops/ (autoresearch, nemesis --remediar, verificar
|
|
10
|
+
* --until-converge, debate adversarial), inyecta al contexto un bloque
|
|
11
|
+
* compacto con el estado del loop — iteración actual, últimas 3 filas del
|
|
12
|
+
* TSV y la recomendación de trayectoria (continuar / detener por plateau /
|
|
13
|
+
* cambiar estrategia).
|
|
14
|
+
*
|
|
15
|
+
* En sesiones largas el modelo pierde de vista en qué iteración va y si la
|
|
16
|
+
* métrica sigue mejorando; este hook re-ancla ese estado sin que el agente
|
|
17
|
+
* tenga que releer archivos. Patrón adoptado del análisis de autoresearch
|
|
18
|
+
* v2.1 (hook iteration-context), adaptado a SWL: estado en el directorio de
|
|
19
|
+
* la corrida (no /tmp) y throttle por tiempo (no por conteo de prompts).
|
|
20
|
+
*
|
|
21
|
+
* Throttle: máximo 1 inyección cada 4 minutos por corrida (estado en
|
|
22
|
+
* .inject-state.json dentro del directorio de la corrida — se limpia solo
|
|
23
|
+
* con la corrida). 4 min < TTL del prompt cache (5 min): la inyección no
|
|
24
|
+
* coincide dos veces dentro de la misma ventana de cache.
|
|
25
|
+
*
|
|
26
|
+
* Zero-deps (solo hooks/lib/loop-telemetry). Nunca bloquea.
|
|
27
|
+
* Opt-out: SWL_CONTEXTO_ITERACION=0
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const fs = require('fs');
|
|
31
|
+
const path = require('path');
|
|
32
|
+
|
|
33
|
+
const { atomicWriteJSON } = require('./lib/atomic-write');
|
|
34
|
+
const { corridaActiva, analizarTrayectoria } = require('./lib/loop-telemetry');
|
|
35
|
+
|
|
36
|
+
/** Intervalo mínimo entre inyecciones para la misma corrida (4 minutos). */
|
|
37
|
+
const THROTTLE_MS = 4 * 60 * 1000;
|
|
38
|
+
|
|
39
|
+
const ARCHIVO_ESTADO = '.inject-state.json';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Decide si toca inyectar según el estado de throttle de la corrida.
|
|
43
|
+
* @param {string} dirCorrida - Directorio de la corrida activa.
|
|
44
|
+
* @param {number} [ahora=Date.now()]
|
|
45
|
+
* @returns {boolean}
|
|
46
|
+
*/
|
|
47
|
+
function debeInyectar(dirCorrida, ahora = Date.now()) {
|
|
48
|
+
try {
|
|
49
|
+
const estado = JSON.parse(
|
|
50
|
+
fs.readFileSync(path.join(dirCorrida, ARCHIVO_ESTADO), 'utf8')
|
|
51
|
+
);
|
|
52
|
+
return ahora - (estado.ultimaInyeccion || 0) > THROTTLE_MS;
|
|
53
|
+
} catch {
|
|
54
|
+
return true; // sin estado = primera inyección
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Registra la inyección para el throttle. Best-effort, escritura atómica. */
|
|
59
|
+
function registrarInyeccion(dirCorrida, ahora = Date.now()) {
|
|
60
|
+
try {
|
|
61
|
+
atomicWriteJSON(
|
|
62
|
+
path.join(dirCorrida, ARCHIVO_ESTADO),
|
|
63
|
+
{ ultimaInyeccion: ahora }
|
|
64
|
+
);
|
|
65
|
+
} catch { /* silencioso */ }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Construye el bloque de contexto a inyectar.
|
|
70
|
+
* @param {{dir: string, tipo: string, ultimasFilas: object[], totalFilas: number}} activa
|
|
71
|
+
* @param {object} trayectoria - Resultado de analizarTrayectoria.
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
function construirBloque(activa, trayectoria) {
|
|
75
|
+
const lineas = [];
|
|
76
|
+
lineas.push(`🔁 Loop activo: \`${activa.tipo}\` (${path.basename(activa.dir)})`);
|
|
77
|
+
lineas.push(
|
|
78
|
+
`Iteraciones: ${trayectoria.totalIteraciones} | keep: ${trayectoria.keeps} | ` +
|
|
79
|
+
`revert: ${trayectoria.reverts} | métrica: ${trayectoria.metricaInicial} → ${trayectoria.metricaFinal}`
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (activa.ultimasFilas.length > 0) {
|
|
83
|
+
lineas.push('Últimas iteraciones:');
|
|
84
|
+
for (const fila of activa.ultimasFilas) {
|
|
85
|
+
const desc = (fila.descripcion || '').slice(0, 80);
|
|
86
|
+
lineas.push(` ${fila.iteracion ?? '?'}. [${fila.estado || '?'}] métrica=${fila.metrica ?? '?'} — ${desc}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (trayectoria.plateau) {
|
|
91
|
+
lineas.push(
|
|
92
|
+
'⚠️ PLATEAU detectado: las últimas iteraciones no mejoran la métrica. ' +
|
|
93
|
+
'Considera cerrar el loop y reportar el mejor estado alcanzado en lugar de seguir iterando.'
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
lineas.push(`Recomendación de trayectoria: ${trayectoria.recomendacion}.`);
|
|
97
|
+
}
|
|
98
|
+
lineas.push(`Registro completo: ${path.join(activa.dir, 'iteraciones.tsv')}`);
|
|
99
|
+
|
|
100
|
+
return lineas.join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Entrypoint
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function main() {
|
|
108
|
+
let inputRaw = '';
|
|
109
|
+
process.stdin.on('data', (c) => { inputRaw += c; });
|
|
110
|
+
|
|
111
|
+
process.stdin.on('end', () => {
|
|
112
|
+
try {
|
|
113
|
+
if (process.env.SWL_CONTEXTO_ITERACION === '0') return;
|
|
114
|
+
|
|
115
|
+
const activa = corridaActiva(process.cwd(), { maxEdadMin: 30 });
|
|
116
|
+
if (!activa) return;
|
|
117
|
+
if (!debeInyectar(activa.dir)) return;
|
|
118
|
+
|
|
119
|
+
const trayectoria = analizarTrayectoria(activa.dir);
|
|
120
|
+
// Sin iteraciones más allá del baseline no hay nada útil que anclar
|
|
121
|
+
if (trayectoria.totalIteraciones === 0) return;
|
|
122
|
+
|
|
123
|
+
const output = {
|
|
124
|
+
hookSpecificOutput: {
|
|
125
|
+
hookEventName: 'UserPromptSubmit',
|
|
126
|
+
additionalContext: construirBloque(activa, trayectoria),
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
// Marcar el throttle SOLO tras un write exitoso: si stdout falla
|
|
130
|
+
// (broken pipe, harness cerró el canal), no registrar la inyección
|
|
131
|
+
// para que el próximo prompt reintente en vez de saltarse 4 min con
|
|
132
|
+
// un mensaje que nunca llegó. Best-effort: el hook nunca bloquea.
|
|
133
|
+
process.stdout.write(JSON.stringify(output), (err) => {
|
|
134
|
+
if (!err) {
|
|
135
|
+
try { registrarInyeccion(activa.dir); } catch { /* throttle best-effort */ }
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
} catch { /* nunca bloquea */ }
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Entrypoint directo o wrapper `node -e "require(...)"` (require.main undefined).
|
|
143
|
+
if (!require.main || require.main === module) main();
|
|
144
|
+
|
|
145
|
+
module.exports = { debeInyectar, registrarInyeccion, construirBloque, THROTTLE_MS, ARCHIVO_ESTADO };
|