@saulwade/swl-ses 2.3.1 → 2.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +242 -240
- 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 +690 -690
- package/agentes/arquitecto-swl.md +267 -267
- package/agentes/auto-evolucion-swl.md +908 -908
- package/agentes/backend-api-swl.md +472 -472
- package/agentes/backend-csharp-swl.md +420 -420
- package/agentes/backend-go-swl.md +390 -390
- package/agentes/backend-java-swl.md +281 -281
- package/agentes/backend-node-swl.md +479 -479
- package/agentes/backend-python-swl.md +605 -605
- package/agentes/backend-rust-swl.md +364 -364
- package/agentes/backend-workers-swl.md +482 -482
- package/agentes/cloud-infra-swl.md +509 -509
- package/agentes/consolidador-swl.md +541 -541
- package/agentes/datos-swl.md +605 -605
- package/agentes/depurador-swl.md +352 -352
- package/agentes/devops-ci-swl.md +400 -400
- package/agentes/disenador-ui-swl.md +569 -569
- package/agentes/documentador-swl.md +345 -345
- package/agentes/frontend-angular-swl.md +621 -621
- package/agentes/frontend-css-swl.md +716 -716
- package/agentes/frontend-react-swl.md +692 -692
- package/agentes/frontend-swl.md +496 -496
- package/agentes/frontend-tailwind-swl.md +826 -826
- package/agentes/gh-fix-ci-swl.md +275 -275
- package/agentes/implementador-swl.md +328 -328
- package/agentes/investigador-swl.md +432 -432
- package/agentes/investigador-ux-swl.md +505 -505
- package/agentes/llm-apps-swl.md +278 -278
- package/agentes/migrador-swl.md +442 -442
- package/agentes/mobile-android-swl.md +511 -511
- package/agentes/mobile-cross-swl.md +541 -541
- package/agentes/mobile-ios-swl.md +502 -502
- package/agentes/mobile-testing-swl.md +302 -302
- package/agentes/nemesis-auditor-swl.md +285 -285
- package/agentes/notificador-swl.md +918 -918
- package/agentes/observabilidad-swl.md +438 -438
- package/agentes/orquestador-swl.md +1053 -1026
- package/agentes/pagos-swl.md +310 -310
- package/agentes/perfilador-usuario-swl.md +321 -321
- package/agentes/planificador-swl.md +399 -399
- package/agentes/producto-prd-swl.md +589 -589
- package/agentes/red-team-swl.md +218 -218
- package/agentes/release-manager-swl.md +590 -590
- package/agentes/rendimiento-swl.md +713 -713
- package/agentes/resolutor-build-swl.md +245 -245
- package/agentes/revisor-angular-swl.md +278 -278
- package/agentes/revisor-codigo-swl.md +538 -505
- package/agentes/revisor-csharp-swl.md +264 -264
- package/agentes/revisor-go-swl.md +259 -259
- package/agentes/revisor-java-swl.md +257 -257
- package/agentes/revisor-kotlin-swl.md +273 -273
- package/agentes/revisor-nextjs-swl.md +281 -281
- package/agentes/revisor-php-swl.md +271 -271
- package/agentes/revisor-react-swl.md +278 -278
- package/agentes/revisor-rust-swl.md +346 -346
- package/agentes/revisor-seguridad-swl.md +399 -399
- package/agentes/revisor-swift-swl.md +268 -268
- package/agentes/revisor-typescript-swl.md +346 -346
- package/agentes/sre-swl.md +291 -291
- package/agentes/tdd-qa-swl.md +393 -393
- package/comandos/swl/contexto.md +0 -2
- package/comandos/swl/deuda-codigo.md +97 -0
- package/habilidades/angular-moderno/SKILL.md +5 -1
- package/habilidades/contenedores-docker/SKILL.md +3 -1
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -278
- package/habilidades/harness-claude-code/SKILL.md +5 -4
- package/habilidades/harness-claude-code/recursos/disciplina-harness-regla.md +6 -5
- package/habilidades/legacy-code-rescue/SKILL.md +2 -1
- package/habilidades/meta-skills-estandar/SKILL.md +1 -1
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +2 -2
- package/habilidades/postgresql-experto/SKILL.md +3 -1
- package/habilidades/prevencion-sobreingenieria/SKILL.md +70 -92
- package/habilidades/prevencion-sobreingenieria/recursos/soluciones-nativas.md +166 -0
- package/habilidades/prevencion-sobreingenieria/recursos/variables-residuales-post-refactor.md +85 -0
- package/habilidades/tdd-workflow/SKILL.md +3 -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/contexto-iteracion.js +2 -1
- package/hooks/contexto-subagente.js +68 -0
- package/hooks/guardrail-modelo.js +13 -3
- package/hooks/inyeccion-contexto.js +12 -12
- package/hooks/registro-turnos.js +5 -4
- package/hooks/spec-gate.js +2 -1
- package/hooks/sugerir-contribuir.js +2 -1
- package/hooks/sugerir-regenerar-inventario.js +3 -2
- package/hooks/tdd-gate.js +2 -1
- package/llms.txt +3 -3
- package/manifiestos/canonical-hashes.json +995 -10
- 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/skills-lock.json +24 -24
- package/package.json +93 -93
- package/plugin.json +369 -371
- package/reglas/arreglar-al-detectar.md +16 -0
- package/reglas/pruebas.md +7 -3
- package/schemas/agent-frontmatter.schema.json +3 -1
- package/scripts/actualizar.js +253 -254
- package/scripts/doctor.js +25 -17
- package/scripts/instalador.js +4 -1
- package/scripts/lib/detectar-runtime.js +58 -0
- package/scripts/lib/expandir-targets.js +71 -71
- package/scripts/lib/hooks-settings.js +40 -3
- package/scripts/lib/preservar-usuario.js +39 -5
- package/scripts/lib/toml-merge.js +204 -204
- package/scripts/mcp-server/auth.js +105 -105
- package/scripts/mcp-server/cache.js +106 -106
- package/scripts/tui/pantallas/inspect.js +175 -173
- package/scripts/tui/pantallas/uninstall-wizard.js +210 -208
- package/scripts/tui/pantallas/update-wizard.js +234 -232
- package/scripts/tui/pantallas/welcome.js +189 -187
- 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 -328
- package/hooks/monitor-contexto.js +0 -373
package/hooks/linea-estado.js
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook: linea-estado.js
|
|
6
|
-
* Tipo: PostToolUse
|
|
7
|
-
*
|
|
8
|
-
* Muestra una línea de estado compacta con:
|
|
9
|
-
* - Modelo activo
|
|
10
|
-
* - Tarea actual (derivada del tool_name y parámetros)
|
|
11
|
-
* - Directorio de trabajo abreviado
|
|
12
|
-
* - Barra de progreso de contexto estimado
|
|
13
|
-
*
|
|
14
|
-
* Además escribe un archivo bridge en /tmp/swl-ctx-{session}.json
|
|
15
|
-
* para que otros hooks (monitor-contexto, etc.) lean el estado.
|
|
16
|
-
*
|
|
17
|
-
* Integración con Claude Code hooks:
|
|
18
|
-
* El hook no bloquea (exit 0 siempre).
|
|
19
|
-
* Los mensajes informativos se escriben en stderr para que
|
|
20
|
-
* Claude Code los muestre en la barra de estado o los ignore;
|
|
21
|
-
* sin interferir con el flujo JSON en stdout.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
const fs = require('fs');
|
|
25
|
-
const path = require('path');
|
|
26
|
-
const os = require('os');
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Constantes de configuración
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
/** Ancho de la barra de progreso del contexto (en caracteres de bloque). */
|
|
33
|
-
const BARRA_ANCHO = 10;
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Techo por defecto cuando no se detecta el modelo.
|
|
37
|
-
* Mantiene la heurística histórica para compatibilidad.
|
|
38
|
-
*/
|
|
39
|
-
const TOKENS_MAXIMOS_DEFAULT = 200_000;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Techos de contexto por modelo (en tokens).
|
|
43
|
-
*
|
|
44
|
-
* IMPORTANTE: las claves son prefijos del `model_id` reportado por Claude Code
|
|
45
|
-
* en el evento del hook. La detección hace `modelId.includes(prefijo)` en orden;
|
|
46
|
-
* los prefijos más específicos van primero para que `claude-opus-4-8` no
|
|
47
|
-
* matchee accidentalmente con la entrada genérica `claude-opus`.
|
|
48
|
-
*
|
|
49
|
-
* Si el `model_id` contiene el sufijo `[1m]` (ej: `claude-opus-4-8[1m]`), el
|
|
50
|
-
* techo se fuerza a 1M independientemente del modelo base — Anthropic usa
|
|
51
|
-
* ese sufijo para indicar la variante de 1M context.
|
|
52
|
-
*/
|
|
53
|
-
const TECHOS_POR_MODELO = {
|
|
54
|
-
// Opus 4.8 — variante de 1M context (la actual por defecto)
|
|
55
|
-
'claude-opus-4-8': 1_000_000,
|
|
56
|
-
// Opus 4.7 — variante de 1M context (histórica)
|
|
57
|
-
'claude-opus-4-7': 1_000_000,
|
|
58
|
-
// Resto de modelos modernos: 200k context
|
|
59
|
-
'claude-opus-4-6': 200_000,
|
|
60
|
-
'claude-opus-4-5': 200_000,
|
|
61
|
-
// Sonnet 5 — 1M context nativo en la API (doc oficial)
|
|
62
|
-
'claude-sonnet-5': 1_000_000,
|
|
63
|
-
'claude-sonnet-4-6': 200_000,
|
|
64
|
-
'claude-sonnet-4-5': 200_000,
|
|
65
|
-
'claude-haiku-4-5': 200_000,
|
|
66
|
-
// Catch-all genéricos (orden: deben ir DESPUÉS de los específicos)
|
|
67
|
-
'claude-opus': 200_000,
|
|
68
|
-
'claude-sonnet': 200_000,
|
|
69
|
-
'claude-haiku': 200_000,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Modelos conocidos y su etiqueta corta para la barra.
|
|
74
|
-
* Clave: prefijo del model_id. Valor: etiqueta legible.
|
|
75
|
-
*/
|
|
76
|
-
const ETIQUETAS_MODELO = {
|
|
77
|
-
'claude-opus': 'opus',
|
|
78
|
-
'claude-sonnet': 'sonnet',
|
|
79
|
-
'claude-haiku': 'haiku',
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Determina el techo de tokens del contexto según el modelo activo.
|
|
84
|
-
*
|
|
85
|
-
* Reglas (en orden):
|
|
86
|
-
* 1. Si el `modelId` contiene `[1m]` → 1_000_000 (variante explícita 1M).
|
|
87
|
-
* 2. Si el `modelId` matchea un prefijo de TECHOS_POR_MODELO → ese techo.
|
|
88
|
-
* 3. Default → TOKENS_MAXIMOS_DEFAULT (200k).
|
|
89
|
-
*
|
|
90
|
-
* @param {string} modelId - Identificador completo del modelo, posiblemente
|
|
91
|
-
* con sufijo de variante (ej: `claude-opus-4-8[1m]`).
|
|
92
|
-
* @returns {number} Techo de tokens del contexto.
|
|
93
|
-
*/
|
|
94
|
-
function techoTokens(modelId) {
|
|
95
|
-
if (!modelId) return TOKENS_MAXIMOS_DEFAULT;
|
|
96
|
-
const id = String(modelId);
|
|
97
|
-
if (id.includes('[1m]') || id.includes('-1m')) return 1_000_000;
|
|
98
|
-
for (const [prefijo, techo] of Object.entries(TECHOS_POR_MODELO)) {
|
|
99
|
-
if (id.includes(prefijo)) return techo;
|
|
100
|
-
}
|
|
101
|
-
return TOKENS_MAXIMOS_DEFAULT;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Utilidades
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Construye la etiqueta corta del modelo activo.
|
|
110
|
-
* @param {string} modelId - Identificador completo del modelo.
|
|
111
|
-
* @returns {string}
|
|
112
|
-
*/
|
|
113
|
-
function etiquetaModelo(modelId) {
|
|
114
|
-
if (!modelId) return 'modelo?';
|
|
115
|
-
for (const [prefijo, etiqueta] of Object.entries(ETIQUETAS_MODELO)) {
|
|
116
|
-
if (modelId.includes(prefijo)) return etiqueta;
|
|
117
|
-
}
|
|
118
|
-
// Fallback: primeros 12 caracteres del modelo
|
|
119
|
-
return String(modelId).slice(0, 12);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Determina la tarea actual a partir del nombre de la herramienta
|
|
124
|
-
* y sus parámetros de entrada.
|
|
125
|
-
* @param {string} toolName - Nombre de la tool que acaba de ejecutarse.
|
|
126
|
-
* @param {object} toolInput - Parámetros de entrada de la tool.
|
|
127
|
-
* @returns {string}
|
|
128
|
-
*/
|
|
129
|
-
function tareaActual(toolName, toolInput) {
|
|
130
|
-
if (!toolName) return 'idle';
|
|
131
|
-
|
|
132
|
-
const nombre = String(toolName).toLowerCase();
|
|
133
|
-
|
|
134
|
-
switch (nombre) {
|
|
135
|
-
case 'write':
|
|
136
|
-
case 'edit': {
|
|
137
|
-
const filePath = toolInput?.file_path || toolInput?.path || '';
|
|
138
|
-
const base = path.basename(String(filePath));
|
|
139
|
-
return `editando ${base || 'archivo'}`;
|
|
140
|
-
}
|
|
141
|
-
case 'read': {
|
|
142
|
-
const filePath = toolInput?.file_path || toolInput?.path || '';
|
|
143
|
-
const base = path.basename(String(filePath));
|
|
144
|
-
return `leyendo ${base || 'archivo'}`;
|
|
145
|
-
}
|
|
146
|
-
case 'bash': {
|
|
147
|
-
// Mostrar primeros 28 chars del comando, sin saltos de línea
|
|
148
|
-
const cmd = String(toolInput?.command || '').replace(/\s+/g, ' ').slice(0, 28);
|
|
149
|
-
return `bash: ${cmd || '...'}`;
|
|
150
|
-
}
|
|
151
|
-
case 'grep': {
|
|
152
|
-
const patron = String(toolInput?.pattern || '').slice(0, 20);
|
|
153
|
-
return `grep: ${patron || '...'}`;
|
|
154
|
-
}
|
|
155
|
-
case 'glob': {
|
|
156
|
-
const patron = String(toolInput?.pattern || '').slice(0, 20);
|
|
157
|
-
return `glob: ${patron || '...'}`;
|
|
158
|
-
}
|
|
159
|
-
case 'task':
|
|
160
|
-
case 'agent':
|
|
161
|
-
return `agente: ${String(toolInput?.description || '').slice(0, 25) || '...'}`;
|
|
162
|
-
default:
|
|
163
|
-
return nombre.slice(0, 30);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Abrevia una ruta de directorio para mostrar en la barra de estado.
|
|
169
|
-
* Muestra sólo los últimos N segmentos.
|
|
170
|
-
* @param {string} dirPath - Ruta completa.
|
|
171
|
-
* @param {number} maxSegmentos - Número máximo de segmentos a mostrar.
|
|
172
|
-
* @returns {string}
|
|
173
|
-
*/
|
|
174
|
-
function abreviarDirectorio(dirPath, maxSegmentos = 2) {
|
|
175
|
-
if (!dirPath) return '.';
|
|
176
|
-
// Normalizar separadores en Windows
|
|
177
|
-
const normalizado = String(dirPath).replace(/\\/g, '/');
|
|
178
|
-
const partes = normalizado.split('/').filter(Boolean);
|
|
179
|
-
if (partes.length <= maxSegmentos) return normalizado;
|
|
180
|
-
return '…/' + partes.slice(-maxSegmentos).join('/');
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Construye la barra de progreso visual.
|
|
185
|
-
* @param {number} porcentaje - Valor entre 0 y 100.
|
|
186
|
-
* @returns {string} Ej: "████████░░ 80%"
|
|
187
|
-
*/
|
|
188
|
-
function barraProgreso(porcentaje) {
|
|
189
|
-
const pct = Math.max(0, Math.min(100, porcentaje));
|
|
190
|
-
const llenos = Math.round((pct / 100) * BARRA_ANCHO);
|
|
191
|
-
const vacios = BARRA_ANCHO - llenos;
|
|
192
|
-
const barra = '█'.repeat(llenos) + '░'.repeat(vacios);
|
|
193
|
-
return `${barra} ${pct}%`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Estima el porcentaje de contexto utilizado.
|
|
198
|
-
* Claude Code no expone directamente los tokens consumidos en el hook,
|
|
199
|
-
* por lo que usamos una heurística basada en el número de invocaciones
|
|
200
|
-
* de herramientas acumuladas en la sesión cuando el campo `usage` no está.
|
|
201
|
-
*
|
|
202
|
-
* La función lee el archivo bridge previo para mantener el contador,
|
|
203
|
-
* y lo incrementa en cada invocación.
|
|
204
|
-
*
|
|
205
|
-
* @param {string} bridgePath - Ruta del archivo bridge.
|
|
206
|
-
* @param {number} tokensReportados - Tokens reportados por Claude (si existen).
|
|
207
|
-
* @param {number} techo - Techo de tokens del modelo activo.
|
|
208
|
-
* @returns {{ porcentaje: number, invocaciones: number, techo: number }}
|
|
209
|
-
*/
|
|
210
|
-
function estimarContexto(bridgePath, tokensReportados, techo) {
|
|
211
|
-
const techoEfectivo = (typeof techo === 'number' && techo > 0)
|
|
212
|
-
? techo
|
|
213
|
-
: TOKENS_MAXIMOS_DEFAULT;
|
|
214
|
-
let invocaciones = 1;
|
|
215
|
-
|
|
216
|
-
// Leer estado previo del bridge
|
|
217
|
-
try {
|
|
218
|
-
const previo = JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
|
|
219
|
-
invocaciones = (previo.invocaciones || 0) + 1;
|
|
220
|
-
} catch (_) {
|
|
221
|
-
// Archivo no existe aún — primera invocación
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Si Claude reporta tokens usados (campo no-estándar, puede no existir)
|
|
225
|
-
if (typeof tokensReportados === 'number' && tokensReportados > 0) {
|
|
226
|
-
const pct = Math.min(100, Math.round((tokensReportados / techoEfectivo) * 100));
|
|
227
|
-
return { porcentaje: pct, invocaciones, techo: techoEfectivo };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Heurística: cada invocación consume ~500 tokens promedio (conversación + tool)
|
|
231
|
-
const tokenesEstimados = invocaciones * 500;
|
|
232
|
-
const pct = Math.min(99, Math.round((tokenesEstimados / techoEfectivo) * 100));
|
|
233
|
-
return { porcentaje: pct, invocaciones, techo: techoEfectivo };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
// Entrypoint principal
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
|
-
|
|
240
|
-
// Solo conectar stdin cuando se ejecuta como script (hook),
|
|
241
|
-
// no cuando se carga vía require() desde tests u otros módulos.
|
|
242
|
-
if (require.main === module) iniciarHook();
|
|
243
|
-
|
|
244
|
-
function iniciarHook() {
|
|
245
|
-
let inputRaw = '';
|
|
246
|
-
|
|
247
|
-
process.stdin.on('data', chunk => {
|
|
248
|
-
inputRaw += chunk;
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
process.stdin.on('end', () => {
|
|
252
|
-
try {
|
|
253
|
-
const data = JSON.parse(inputRaw);
|
|
254
|
-
|
|
255
|
-
// Extraer campos del evento de hook de Claude Code
|
|
256
|
-
// Estructura documentada de PostToolUse:
|
|
257
|
-
// data.session_id, data.model, data.tool_name, data.tool_input,
|
|
258
|
-
// data.tool_result, data.usage (si existe)
|
|
259
|
-
const sessionId = String(data.session_id || 'default');
|
|
260
|
-
const modelId = String(data.model || '');
|
|
261
|
-
const toolName = String(data.tool_name || '');
|
|
262
|
-
const toolInput = data.tool_input || {};
|
|
263
|
-
const tokensUsados = data.usage?.input_tokens ?? data.usage?.total_tokens ?? 0;
|
|
264
|
-
|
|
265
|
-
// Ruta del archivo bridge compartido entre hooks
|
|
266
|
-
const bridgePath = path.join(os.tmpdir(), `swl-ctx-${sessionId}.json`);
|
|
267
|
-
|
|
268
|
-
// Resolver techo según modelo (Opus 4.8 con sufijo [1m] = 1M; resto 200k)
|
|
269
|
-
const techo = techoTokens(modelId);
|
|
270
|
-
|
|
271
|
-
// Calcular métricas
|
|
272
|
-
const { porcentaje, invocaciones } = estimarContexto(bridgePath, tokensUsados, techo);
|
|
273
|
-
const modeloLabel = etiquetaModelo(modelId);
|
|
274
|
-
const tarea = tareaActual(toolName, toolInput);
|
|
275
|
-
const directorio = abreviarDirectorio(process.cwd());
|
|
276
|
-
const barra = barraProgreso(porcentaje);
|
|
277
|
-
|
|
278
|
-
// Construir payload del bridge para otros hooks
|
|
279
|
-
const bridgePayload = {
|
|
280
|
-
sessionId,
|
|
281
|
-
modelId,
|
|
282
|
-
modeloLabel,
|
|
283
|
-
tarea,
|
|
284
|
-
directorio,
|
|
285
|
-
porcentaje,
|
|
286
|
-
invocaciones,
|
|
287
|
-
tokensUsados,
|
|
288
|
-
techoTokens: techo,
|
|
289
|
-
timestamp: new Date().toISOString(),
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
// Escribir bridge (ignorar error si /tmp no existe en este entorno)
|
|
293
|
-
try {
|
|
294
|
-
fs.writeFileSync(bridgePath, JSON.stringify(bridgePayload, null, 2), 'utf8');
|
|
295
|
-
} catch (errBridge) {
|
|
296
|
-
// No bloquear el flujo si no se puede escribir el bridge
|
|
297
|
-
// No se pudo escribir bridge — ignorar
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Construir la línea de estado como JSON válido para Claude Code
|
|
301
|
-
const lineaEstado = `${modeloLabel} | ${tarea} | ${directorio} | ctx: ${barra}`;
|
|
302
|
-
|
|
303
|
-
// PostToolUse hooks: no escribir nada a stdout para permitir
|
|
304
|
-
// que Claude Code procese la respuesta sin interferencia.
|
|
305
|
-
|
|
306
|
-
} catch (err) {
|
|
307
|
-
// Si el hook falla por cualquier razón, nunca bloquear a Claude
|
|
308
|
-
// No escribir a stderr — Claude Code lo reporta como hook error
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (require.main !== module) {
|
|
314
|
-
module.exports = {
|
|
315
|
-
techoTokens,
|
|
316
|
-
estimarContexto,
|
|
317
|
-
etiquetaModelo,
|
|
318
|
-
tareaActual,
|
|
319
|
-
abreviarDirectorio,
|
|
320
|
-
barraProgreso,
|
|
321
|
-
_internals: {
|
|
322
|
-
TECHOS_POR_MODELO,
|
|
323
|
-
TOKENS_MAXIMOS_DEFAULT,
|
|
324
|
-
ETIQUETAS_MODELO,
|
|
325
|
-
BARRA_ANCHO,
|
|
326
|
-
},
|
|
327
|
-
};
|
|
328
|
-
}
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Hook: monitor-contexto.js
|
|
6
|
-
* Tipo: PostToolUse
|
|
7
|
-
*
|
|
8
|
-
* Lee el archivo bridge generado por linea-estado.js y emite advertencias
|
|
9
|
-
* cuando el contexto estimado supera umbrales críticos.
|
|
10
|
-
*
|
|
11
|
-
* Umbrales por defecto (calibrados con evidencia empírica de
|
|
12
|
-
* github.com/anthropics/claude-code issue #34685 — degradación detectable
|
|
13
|
-
* ~20%, auto-compactación de Claude Code dispara ~40%, sweet spot 40-50%):
|
|
14
|
-
*
|
|
15
|
-
* ≥ 40%: WARNING — degradación detectable, evitar trabajo complejo
|
|
16
|
-
* ≥ 55%: CRITICAL — detenerse, guardar estado, compactar
|
|
17
|
-
* ≥ 35% en punto natural: sugerencia de compactación proactiva
|
|
18
|
-
*
|
|
19
|
-
* Override por env var (útil para experimentos o sesiones cortas):
|
|
20
|
-
* SWL_CTX_WARNING (default 40)
|
|
21
|
-
* SWL_CTX_CRITICAL (default 55)
|
|
22
|
-
* SWL_CTX_PROACTIVA (default 35)
|
|
23
|
-
*
|
|
24
|
-
* Histórico: hasta v1.1.0 los umbrales eran 65/75/50. Se bajaron en v1.1.1
|
|
25
|
-
* tras analizar el issue #34685 que documenta degradación al 20-40%.
|
|
26
|
-
*
|
|
27
|
-
* Debounce: solo emite aviso cada N_DEBOUNCE invocaciones de herramienta.
|
|
28
|
-
* La escalación de severidad (pasar de WARNING a CRITICAL) bypasea el debounce.
|
|
29
|
-
*
|
|
30
|
-
* El hook nunca bloquea (siempre exit 0); sólo emite mensajes en stdout
|
|
31
|
-
* como texto enriquecido que Claude Code puede mostrar al usuario.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
const fs = require('fs');
|
|
35
|
-
const path = require('path');
|
|
36
|
-
const os = require('os');
|
|
37
|
-
|
|
38
|
-
const { phase1_PruneToolResults, shouldCompress } = require('./lib/context-compressor');
|
|
39
|
-
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
// Constantes de configuración
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Lee un umbral numérico desde env var con default. Inválido o fuera de rango
|
|
46
|
-
* (0-99) → usar default.
|
|
47
|
-
*/
|
|
48
|
-
function umbralEnv(nombre, def) {
|
|
49
|
-
const valor = parseInt(process.env[nombre] || '', 10);
|
|
50
|
-
if (Number.isFinite(valor) && valor >= 0 && valor < 100) return valor;
|
|
51
|
-
return def;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Resuelve los tres umbrales aplicando defaults y validando el invariante de
|
|
56
|
-
* ordenamiento: PROACTIVA < WARNING < CRITICAL.
|
|
57
|
-
*
|
|
58
|
-
* Si los env vars violan el orden (p.ej. WARNING=80 y CRITICAL=50, que haría
|
|
59
|
-
* CRITICAL inalcanzable lógicamente) o PROACTIVA >= WARNING (haciendo la
|
|
60
|
-
* sugerencia proactiva inalcanzable), se descarta la configuración inválida
|
|
61
|
-
* y se usan los defaults para los tres. La fallback es silenciosa porque el
|
|
62
|
-
* hook nunca debe romper el flujo principal.
|
|
63
|
-
*/
|
|
64
|
-
function resolverUmbrales() {
|
|
65
|
-
const defaults = { proactiva: 35, warning: 40, critical: 55 };
|
|
66
|
-
|
|
67
|
-
const proactiva = umbralEnv('SWL_CTX_PROACTIVA', defaults.proactiva);
|
|
68
|
-
const warning = umbralEnv('SWL_CTX_WARNING', defaults.warning);
|
|
69
|
-
const critical = umbralEnv('SWL_CTX_CRITICAL', defaults.critical);
|
|
70
|
-
|
|
71
|
-
if (proactiva < warning && warning < critical) {
|
|
72
|
-
return { proactiva, warning, critical };
|
|
73
|
-
}
|
|
74
|
-
return defaults;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const UMBRALES = resolverUmbrales();
|
|
78
|
-
|
|
79
|
-
/** Porcentaje de USO a partir del cual se emite WARNING. */
|
|
80
|
-
const UMBRAL_WARNING = UMBRALES.warning;
|
|
81
|
-
|
|
82
|
-
/** Porcentaje de USO a partir del cual se emite CRITICAL. */
|
|
83
|
-
const UMBRAL_CRITICAL = UMBRALES.critical;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Número de invocaciones de herramienta entre avisos consecutivos del mismo nivel.
|
|
87
|
-
* La escalación de severidad siempre bypasea este valor.
|
|
88
|
-
*/
|
|
89
|
-
const N_DEBOUNCE = 5;
|
|
90
|
-
|
|
91
|
-
/** Cada N invocaciones, sugerir compactacion si estamos en punto natural. */
|
|
92
|
-
const N_COMPACTACION_PROACTIVA = 40;
|
|
93
|
-
|
|
94
|
-
/** Umbral de uso para activar compactacion proactiva (antes del warning). */
|
|
95
|
-
const UMBRAL_COMPACTACION_PROACTIVA = UMBRALES.proactiva;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Ruta del archivo de estado interno de este monitor.
|
|
99
|
-
* Almacena cuándo se emitió el último aviso y con qué severidad.
|
|
100
|
-
*/
|
|
101
|
-
function rutaEstadoMonitor(sessionId) {
|
|
102
|
-
return path.join(os.tmpdir(), `swl-monitor-${sessionId}.json`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
// Tipos de severidad
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
const SEVERIDAD = {
|
|
109
|
-
NINGUNA: 0,
|
|
110
|
-
WARNING: 1,
|
|
111
|
-
CRITICAL: 2,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
// Utilidades
|
|
116
|
-
// ---------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Lee el archivo bridge de linea-estado.js.
|
|
120
|
-
* @param {string} sessionId
|
|
121
|
-
* @returns {object|null} Payload del bridge o null si no existe/falla.
|
|
122
|
-
*/
|
|
123
|
-
function leerBridge(sessionId) {
|
|
124
|
-
const bridgePath = path.join(os.tmpdir(), `swl-ctx-${sessionId}.json`);
|
|
125
|
-
try {
|
|
126
|
-
return JSON.parse(fs.readFileSync(bridgePath, 'utf8'));
|
|
127
|
-
} catch (_) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Lee el estado interno previo del monitor.
|
|
134
|
-
* @param {string} estadoPath
|
|
135
|
-
* @returns {{ ultimaInvocacion: number, ultimaSeveridad: number }}
|
|
136
|
-
*/
|
|
137
|
-
function leerEstadoMonitor(estadoPath) {
|
|
138
|
-
try {
|
|
139
|
-
const datos = JSON.parse(fs.readFileSync(estadoPath, 'utf8'));
|
|
140
|
-
return {
|
|
141
|
-
ultimaInvocacion: datos.ultimaInvocacion ?? 0,
|
|
142
|
-
ultimaSeveridad: datos.ultimaSeveridad ?? SEVERIDAD.NINGUNA,
|
|
143
|
-
compresionEmitida: datos.compresionEmitida ?? false,
|
|
144
|
-
};
|
|
145
|
-
} catch (_) {
|
|
146
|
-
return { ultimaInvocacion: 0, ultimaSeveridad: SEVERIDAD.NINGUNA, compresionEmitida: false };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Persiste el estado del monitor.
|
|
152
|
-
* @param {string} estadoPath
|
|
153
|
-
* @param {object} estado
|
|
154
|
-
*/
|
|
155
|
-
function guardarEstadoMonitor(estadoPath, estado) {
|
|
156
|
-
try {
|
|
157
|
-
fs.writeFileSync(estadoPath, JSON.stringify(estado, null, 2), 'utf8');
|
|
158
|
-
} catch (_) {
|
|
159
|
-
// Ignorar errores de escritura — no bloquear el flujo
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Determina la severidad actual en función del porcentaje de uso.
|
|
165
|
-
* @param {number} porcentajeUso - Porcentaje de contexto consumido (0-100).
|
|
166
|
-
* @returns {number} Valor de SEVERIDAD.*
|
|
167
|
-
*/
|
|
168
|
-
function calcularSeveridad(porcentajeUso) {
|
|
169
|
-
if (porcentajeUso >= UMBRAL_CRITICAL) return SEVERIDAD.CRITICAL;
|
|
170
|
-
if (porcentajeUso >= UMBRAL_WARNING) return SEVERIDAD.WARNING;
|
|
171
|
-
return SEVERIDAD.NINGUNA;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Detecta si la ultima operacion es un punto natural para compactar.
|
|
176
|
-
* Puntos naturales:
|
|
177
|
-
* - Despues de un git commit exitoso
|
|
178
|
-
* - Despues de correr tests exitosamente
|
|
179
|
-
* - Cuando tool_name es "Agent" (fin de subagente)
|
|
180
|
-
* @param {object} data - Payload del hook
|
|
181
|
-
* @returns {boolean}
|
|
182
|
-
*/
|
|
183
|
-
function esPuntoNatural(data) {
|
|
184
|
-
const toolName = String(data.tool_name || '');
|
|
185
|
-
const toolInput = data.tool_input || {};
|
|
186
|
-
const comando = String(toolInput.command || '');
|
|
187
|
-
|
|
188
|
-
// Despues de un commit exitoso
|
|
189
|
-
if (toolName === 'Bash' && /\bgit\s+commit\b/.test(comando)) return true;
|
|
190
|
-
|
|
191
|
-
// Despues de correr tests
|
|
192
|
-
if (toolName === 'Bash' && /\b(pytest|jest|vitest|go test|cargo test|dotnet test|phpunit|npm test)\b/.test(comando)) return true;
|
|
193
|
-
|
|
194
|
-
// Fin de un subagente
|
|
195
|
-
if (toolName === 'Agent') return true;
|
|
196
|
-
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Genera el mensaje de sugerencia de compactacion proactiva.
|
|
202
|
-
* @param {number} porcentajeUso
|
|
203
|
-
* @param {number} invocaciones
|
|
204
|
-
* @returns {string}
|
|
205
|
-
*/
|
|
206
|
-
function generarMensajeCompactacion(porcentajeUso, invocaciones) {
|
|
207
|
-
return [
|
|
208
|
-
'',
|
|
209
|
-
'--- SUGERENCIA DE COMPACTACION ---',
|
|
210
|
-
` Contexto: ${porcentajeUso}% usado | ${invocaciones} tool calls en sesion`,
|
|
211
|
-
' Punto natural de pausa detectado.',
|
|
212
|
-
' Considera ejecutar /compact o /swl:compactar para liberar contexto',
|
|
213
|
-
' antes de continuar con tareas complejas.',
|
|
214
|
-
'',
|
|
215
|
-
].join('\n');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Genera el mensaje de advertencia según severidad.
|
|
220
|
-
* El mensaje se emite en stdout para que Claude Code lo procese
|
|
221
|
-
* como feedback informativo dentro de la conversación.
|
|
222
|
-
*
|
|
223
|
-
* @param {number} severidad
|
|
224
|
-
* @param {number} porcentajeUso
|
|
225
|
-
* @param {number} porcentajeRestante
|
|
226
|
-
* @returns {string}
|
|
227
|
-
*/
|
|
228
|
-
function generarMensaje(severidad, porcentajeUso, porcentajeRestante) {
|
|
229
|
-
const barra = '█'.repeat(Math.round(porcentajeUso / 10)) +
|
|
230
|
-
'░'.repeat(10 - Math.round(porcentajeUso / 10));
|
|
231
|
-
|
|
232
|
-
if (severidad === SEVERIDAD.CRITICAL) {
|
|
233
|
-
return [
|
|
234
|
-
'',
|
|
235
|
-
'╔══════════════════════════════════════════════════════╗',
|
|
236
|
-
'║ CONTEXTO CRÍTICO — ACCIÓN REQUERIDA ║',
|
|
237
|
-
'╚══════════════════════════════════════════════════════╝',
|
|
238
|
-
` Contexto usado: ${barra} ${porcentajeUso}% (${porcentajeRestante}% restante)`,
|
|
239
|
-
'',
|
|
240
|
-
' Acciones recomendadas:',
|
|
241
|
-
' 1. Guarda el estado actual con un commit atómico.',
|
|
242
|
-
' 2. Ejecuta /compact o solicita compactación del contexto.',
|
|
243
|
-
' 3. Divide la tarea en partes más pequeñas.',
|
|
244
|
-
' 4. No inicies trabajo complejo hasta compactar.',
|
|
245
|
-
'',
|
|
246
|
-
].join('\n');
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (severidad === SEVERIDAD.WARNING) {
|
|
250
|
-
return [
|
|
251
|
-
'',
|
|
252
|
-
'⚠ ADVERTENCIA DE CONTEXTO',
|
|
253
|
-
` Contexto usado: ${barra} ${porcentajeUso}% (${porcentajeRestante}% restante)`,
|
|
254
|
-
' Evitar tareas complejas. Considera compactar pronto.',
|
|
255
|
-
'',
|
|
256
|
-
].join('\n');
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return '';
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
|
-
// Entrypoint principal
|
|
264
|
-
// ---------------------------------------------------------------------------
|
|
265
|
-
|
|
266
|
-
let inputRaw = '';
|
|
267
|
-
|
|
268
|
-
process.stdin.on('data', chunk => {
|
|
269
|
-
inputRaw += chunk;
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
process.stdin.on('end', () => {
|
|
273
|
-
try {
|
|
274
|
-
const data = JSON.parse(inputRaw);
|
|
275
|
-
|
|
276
|
-
const sessionId = String(data.session_id || 'default');
|
|
277
|
-
const estadoPath = rutaEstadoMonitor(sessionId);
|
|
278
|
-
|
|
279
|
-
// Leer el bridge de linea-estado
|
|
280
|
-
const bridge = leerBridge(sessionId);
|
|
281
|
-
if (!bridge) {
|
|
282
|
-
// Sin bridge no hay datos — salir silenciosamente
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const porcentajeUso = bridge.porcentaje ?? 0;
|
|
287
|
-
const invocacionesActual = bridge.invocaciones ?? 0;
|
|
288
|
-
const porcentajeRestante = 100 - porcentajeUso;
|
|
289
|
-
const severidadActual = calcularSeveridad(porcentajeUso);
|
|
290
|
-
|
|
291
|
-
// Leer estado previo del monitor (necesario tanto para WARNING/CRITICAL como para proactiva)
|
|
292
|
-
const estadoPrevio = leerEstadoMonitor(estadoPath);
|
|
293
|
-
const { ultimaInvocacion, ultimaSeveridad } = estadoPrevio;
|
|
294
|
-
|
|
295
|
-
// Resetear flag de compresión si el contexto bajó (post-compactación real)
|
|
296
|
-
if (severidadActual < SEVERIDAD.CRITICAL && estadoPrevio.compresionEmitida) {
|
|
297
|
-
guardarEstadoMonitor(estadoPath, {
|
|
298
|
-
...estadoPrevio,
|
|
299
|
-
compresionEmitida: false,
|
|
300
|
-
timestamp: new Date().toISOString(),
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (severidadActual !== SEVERIDAD.NINGUNA) {
|
|
305
|
-
// Determinar si debemos emitir aviso:
|
|
306
|
-
// a) Escalación de severidad → siempre emitir (bypasea debounce)
|
|
307
|
-
// b) Mismo nivel → solo si han pasado N_DEBOUNCE invocaciones
|
|
308
|
-
const esEscalacion = severidadActual > ultimaSeveridad;
|
|
309
|
-
const invocacionesDesdeUltimoAviso = invocacionesActual - ultimaInvocacion;
|
|
310
|
-
const debounceExpirado = invocacionesDesdeUltimoAviso >= N_DEBOUNCE;
|
|
311
|
-
|
|
312
|
-
const debeEmitir = esEscalacion || debounceExpirado;
|
|
313
|
-
|
|
314
|
-
if (debeEmitir) {
|
|
315
|
-
const mensaje = generarMensaje(severidadActual, porcentajeUso, porcentajeRestante);
|
|
316
|
-
if (mensaje) {
|
|
317
|
-
// Emitir advertencia como systemMessage (stderr causa hook error)
|
|
318
|
-
process.stdout.write(JSON.stringify({ systemMessage: mensaje }));
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Actualizar estado del monitor
|
|
322
|
-
guardarEstadoMonitor(estadoPath, {
|
|
323
|
-
ultimaInvocacion: invocacionesActual,
|
|
324
|
-
ultimaSeveridad: severidadActual,
|
|
325
|
-
compresionEmitida: estadoPrevio.compresionEmitida || false,
|
|
326
|
-
timestamp: new Date().toISOString(),
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// --- Compresión automática Fase 1 (sin LLM) al llegar a CRITICAL ---
|
|
332
|
-
// Solo emitir UNA VEZ por sesión para evitar spam de banners
|
|
333
|
-
if (severidadActual === SEVERIDAD.CRITICAL && shouldCompress(porcentajeUso)) {
|
|
334
|
-
const yaEmitioBanner = estadoPrevio.compresionEmitida === true;
|
|
335
|
-
if (!yaEmitioBanner) {
|
|
336
|
-
try {
|
|
337
|
-
const mensajeCompresion = [
|
|
338
|
-
'',
|
|
339
|
-
'--- COMPRESION AUTOMATICA (Fases 1+1b) ---',
|
|
340
|
-
` Contexto: ${porcentajeUso}% usado`,
|
|
341
|
-
' Fase 1: tool results antiguos podados por posición.',
|
|
342
|
-
' Fase 1b: resultados de Read/Bash/Grep/Glob > 15 turnos comprimidos por TTL.',
|
|
343
|
-
' Para compresión avanzada (Fases 2-4), ejecuta /swl:compactar',
|
|
344
|
-
' o pide: "comprime el contexto preservando las decisiones clave".',
|
|
345
|
-
'',
|
|
346
|
-
].join('\n');
|
|
347
|
-
process.stdout.write(JSON.stringify({ systemMessage: mensajeCompresion }));
|
|
348
|
-
// Marcar como emitido para no repetir
|
|
349
|
-
guardarEstadoMonitor(estadoPath, {
|
|
350
|
-
...estadoPrevio,
|
|
351
|
-
ultimaInvocacion: invocacionesActual,
|
|
352
|
-
ultimaSeveridad: severidadActual,
|
|
353
|
-
compresionEmitida: true,
|
|
354
|
-
timestamp: new Date().toISOString(),
|
|
355
|
-
});
|
|
356
|
-
} catch (_) {}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// --- Compactacion proactiva: sugerir en punto natural antes de alcanzar WARNING ---
|
|
361
|
-
if (severidadActual === SEVERIDAD.NINGUNA &&
|
|
362
|
-
porcentajeUso >= UMBRAL_COMPACTACION_PROACTIVA &&
|
|
363
|
-
invocacionesActual % N_COMPACTACION_PROACTIVA === 0 &&
|
|
364
|
-
esPuntoNatural(data)) {
|
|
365
|
-
const mensajeCompactacion = generarMensajeCompactacion(porcentajeUso, invocacionesActual);
|
|
366
|
-
process.stdout.write(JSON.stringify({ systemMessage: mensajeCompactacion }));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
} catch (err) {
|
|
370
|
-
// El hook nunca bloquea por errores internos
|
|
371
|
-
// No escribir a stderr — Claude Code lo reporta como hook error
|
|
372
|
-
}
|
|
373
|
-
});
|