@saulwade/swl-ses 1.0.0 → 1.1.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 +8 -5
- package/README.md +2 -2
- package/agentes/accesibilidad-wcag-swl.md +5 -7
- package/agentes/arquitecto-swl.md +5 -3
- package/agentes/auto-evolucion-swl.md +42 -12
- package/agentes/backend-api-swl.md +5 -3
- package/agentes/backend-csharp-swl.md +5 -3
- package/agentes/backend-go-swl.md +5 -3
- package/agentes/backend-java-swl.md +5 -3
- package/agentes/backend-node-swl.md +5 -3
- package/agentes/backend-python-swl.md +5 -3
- package/agentes/backend-rust-swl.md +5 -3
- package/agentes/backend-workers-swl.md +5 -3
- package/agentes/cloud-infra-swl.md +5 -6
- package/agentes/consolidador-swl.md +5 -3
- package/agentes/datos-swl.md +5 -7
- package/agentes/depurador-swl.md +6 -3
- package/agentes/devops-ci-swl.md +5 -3
- package/agentes/disenador-ui-swl.md +5 -7
- package/agentes/documentador-swl.md +5 -3
- package/agentes/frontend-angular-swl.md +5 -11
- package/agentes/frontend-css-swl.md +5 -9
- package/agentes/frontend-react-swl.md +5 -9
- package/agentes/frontend-swl.md +5 -9
- package/agentes/frontend-tailwind-swl.md +5 -9
- package/agentes/implementador-swl.md +6 -3
- package/agentes/investigador-swl.md +5 -3
- package/agentes/investigador-ux-swl.md +5 -9
- package/agentes/llm-apps-swl.md +5 -3
- package/agentes/migrador-swl.md +6 -3
- package/agentes/mobile-android-swl.md +5 -3
- package/agentes/mobile-cross-swl.md +5 -3
- package/agentes/mobile-ios-swl.md +5 -3
- package/agentes/mobile-testing-swl.md +5 -3
- package/agentes/notificador-swl.md +5 -3
- package/agentes/observabilidad-swl.md +5 -3
- package/agentes/orquestador-swl.md +29 -8
- package/agentes/pagos-swl.md +5 -3
- package/agentes/perfilador-usuario-swl.md +4 -2
- package/agentes/planificador-swl.md +5 -3
- package/agentes/producto-prd-swl.md +5 -3
- package/agentes/red-team-swl.md +4 -2
- package/agentes/release-manager-swl.md +6 -8
- package/agentes/rendimiento-swl.md +5 -6
- package/agentes/resolutor-build-swl.md +5 -3
- package/agentes/revisor-angular-swl.md +5 -3
- package/agentes/revisor-codigo-swl.md +5 -3
- package/agentes/revisor-csharp-swl.md +5 -3
- package/agentes/revisor-go-swl.md +5 -3
- package/agentes/revisor-java-swl.md +5 -3
- package/agentes/revisor-kotlin-swl.md +5 -3
- package/agentes/revisor-nextjs-swl.md +5 -3
- package/agentes/revisor-php-swl.md +5 -3
- package/agentes/revisor-react-swl.md +5 -3
- package/agentes/revisor-rust-swl.md +5 -3
- package/agentes/revisor-seguridad-swl.md +5 -3
- package/agentes/revisor-swift-swl.md +5 -3
- package/agentes/revisor-typescript-swl.md +5 -3
- package/agentes/sre-swl.md +5 -3
- package/agentes/tdd-qa-swl.md +5 -3
- package/agentes/ux-disenador-swl.md +5 -9
- package/bin/swl-ses.js +27 -6
- package/comandos/swl/evaluar-skill.md +18 -0
- package/comandos/swl/evolucion-estado.md +49 -0
- package/comandos/swl/release.md +41 -0
- package/comandos/swl/salud.md +23 -0
- package/hooks/auto-evolucion.js +35 -1
- package/hooks/clasificador-mensajes.js +50 -3
- package/hooks/lib/agent-routing.js +107 -0
- package/hooks/lib/delegation-tracker.js +162 -44
- package/hooks/lib/evolution-tracker.js +12 -3
- package/hooks/lib/memory-search.js +59 -1
- package/hooks/lib/nudge-tracker.js +10 -1
- package/hooks/lib/provenance-tracker.js +11 -3
- package/hooks/lib/text-similarity.js +241 -0
- package/hooks/metricas-evolucion.js +168 -1
- package/hooks/monitor-contexto.js +54 -6
- package/hooks/preservar-estado-pre-compact.js +11 -1
- package/hooks/risk-scoring.js +10 -1
- package/hooks/tracking-costos.js +10 -1
- package/hooks/validar-formato-post-subagente.js +140 -0
- package/hooks/validar-memoria-hook.js +218 -0
- package/manifiestos/agent-output-schemas.json +57 -0
- package/manifiestos/hooks-config.json +18 -0
- package/manifiestos/modulos.json +5 -1
- package/manifiestos/skills-lock.json +1065 -0
- package/package.json +4 -6
- package/plugin.json +1 -1
- package/reglas/arquitectura.md +20 -0
- package/reglas/fragmentos-compartidos.md +152 -0
- package/reglas/gobernanza.md +10 -1
- package/reglas/seguridad-agentes.md +12 -0
- package/reglas/skills-estandar.md +19 -0
- package/reglas/usar-context7.md +226 -0
- package/schemas/agent-frontmatter.schema.json +18 -0
- package/scripts/auditar-agentes-gaps.js +9 -1
- package/scripts/auditar-cobertura-frameworks.js +9 -1
- package/scripts/auditar-skills-gaps.js +9 -1
- package/scripts/bootstrap-instintos.js +11 -1
- package/scripts/generar-inventario.js +112 -9
- package/scripts/generar-matriz-lenguajes.js +271 -0
- package/scripts/generar-skills-lock.js +190 -0
- package/scripts/lib/estado.js +12 -2
- package/scripts/lib/gitignore-manifest.js +32 -2
- package/scripts/lib/semantic-search.js +10 -0
- package/scripts/migrar-csv-a-array.js +168 -0
- package/scripts/migrar-fase-dominio.js +201 -0
- package/scripts/validar-userland-vacio.js +110 -0
|
@@ -9,7 +9,7 @@ description: >
|
|
|
9
9
|
disenador-ui-swl (capa visual y tokens). NO invocar para implementacion de
|
|
10
10
|
codigo — produce specs, no codigo. Para implementar usar frontend-*-swl con
|
|
11
11
|
la UI-SPEC.md producida.
|
|
12
|
-
tools: Read, Write, Grep, Glob, WebSearch
|
|
12
|
+
tools: [Read, Write, Grep, Glob, WebSearch]
|
|
13
13
|
model: claude-sonnet-4-6
|
|
14
14
|
modeloAlterno: claude-haiku-4-5-20251001
|
|
15
15
|
ventanaContexto: 200k
|
|
@@ -17,18 +17,14 @@ permissionMode: plan
|
|
|
17
17
|
color: pink
|
|
18
18
|
version: 1.0.0
|
|
19
19
|
nivelRiesgo: BAJO
|
|
20
|
-
skillsInvocables: ux-diseno, wireframes-flujos, design-tokens, accesibilidad-a11y, diseno-responsivo
|
|
21
|
-
skillsRestringidos:
|
|
22
|
-
- fastapi-python
|
|
23
|
-
- django-expert
|
|
24
|
-
- postgresql-table-design
|
|
25
|
-
- python-patterns
|
|
26
|
-
- angular-component
|
|
27
|
-
- angular-forms
|
|
20
|
+
skillsInvocables: [ux-diseno, wireframes-flujos, design-tokens, accesibilidad-a11y, diseno-responsivo]
|
|
21
|
+
skillsRestringidos: [fastapi-python, django-expert, postgresql-table-design, python-patterns, angular-component, angular-forms]
|
|
28
22
|
permisosRed: true
|
|
29
23
|
permisosEscritura: true
|
|
30
24
|
permisosComandos: false
|
|
31
25
|
evolvable: true # nivelRiesgo=BAJO
|
|
26
|
+
fase: plan
|
|
27
|
+
dominio: ux
|
|
32
28
|
exclusiones:
|
|
33
29
|
- "No invocar para implementar código de interfaz — este agente produce UI-SPEC.md, no código; usar frontend-react-swl, frontend-angular-swl o frontend-swl para la implementación."
|
|
34
30
|
- "No invocar cuando el pipeline tiene roles separados: preferir investigador-ux-swl para research y disenador-ui-swl para especificaciones visuales puras."
|
package/bin/swl-ses.js
CHANGED
|
@@ -64,13 +64,34 @@ function verificarCoherenciaVersion() {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if (versionMax && compararSemver(versionMax, VERSION) === 1) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
const localPS = parseSemver(versionMax);
|
|
68
|
+
const cliPS = parseSemver(VERSION);
|
|
69
|
+
// Tras el rebrand 2026-04-30, el CLI nuevo @saulwade/swl-ses
|
|
70
|
+
// arrancó en v1.0.0 mientras el paquete legacy (@saulwadeleon/...
|
|
71
|
+
// o swl-ses sin scope) estaba en 5.13.1. Si la diferencia de
|
|
72
|
+
// major es ≥3 hacia abajo del CLI nuevo, NO es "desactualizado":
|
|
73
|
+
// el state apunta a una instalación previa al rebrand. En ese
|
|
74
|
+
// caso emitimos un aviso DIFERENTE: "reinstala para sincronizar".
|
|
75
|
+
const esDriftDePaqueteLegacy = (
|
|
76
|
+
localPS && cliPS &&
|
|
77
|
+
localPS[0] >= 5 && cliPS[0] === 1 &&
|
|
78
|
+
(localPS[0] - cliPS[0]) >= 3
|
|
73
79
|
);
|
|
80
|
+
if (esDriftDePaqueteLegacy) {
|
|
81
|
+
process.stderr.write(
|
|
82
|
+
`\n[swl-ses] Detectada instalación previa del paquete legacy (v${versionMax}).\n` +
|
|
83
|
+
`[swl-ses] El sistema fue renombrado a @saulwade/swl-ses con SemVer reset (v${VERSION}).\n` +
|
|
84
|
+
`[swl-ses] Para sincronizar: npx @saulwade/swl-ses@latest install --target claude --profile <perfil> --force\n\n`
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
process.stderr.write(
|
|
88
|
+
`\n[swl-ses] AVISO: CLI desactualizado — ejecutando v${VERSION}, pero tu instalación SWL es v${versionMax}.\n` +
|
|
89
|
+
`[swl-ses] npm tiene cacheada una versión vieja. Para usar la versión correcta:\n` +
|
|
90
|
+
`[swl-ses] npx @saulwade/swl-ses@latest <comando>\n` +
|
|
91
|
+
`[swl-ses] O limpia la caché de npx:\n` +
|
|
92
|
+
`[swl-ses] npx clear-npx-cache\n\n`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
74
95
|
}
|
|
75
96
|
} catch { /* nunca bloquear el CLI por este check */ }
|
|
76
97
|
}
|
|
@@ -356,6 +356,24 @@ Asignar badge:
|
|
|
356
356
|
< 60 → Sin badge — no hacer merge hasta corregir
|
|
357
357
|
```
|
|
358
358
|
|
|
359
|
+
### Gate de promoción para auto-evolucion-swl (G8)
|
|
360
|
+
|
|
361
|
+
Este comando es el **gate obligatorio** del flujo de promoción de skills
|
|
362
|
+
auto-generados desde `_userland/plugins/` a `habilidades/`. El agente
|
|
363
|
+
`auto-evolucion-swl` invoca este comando antes de mover un skill al core
|
|
364
|
+
(ver `agentes/auto-evolucion-swl.md` sección "Gate G8").
|
|
365
|
+
|
|
366
|
+
Umbral de promoción:
|
|
367
|
+
|
|
368
|
+
| Badge | Acción de auto-evolucion |
|
|
369
|
+
|-------|-------------------------|
|
|
370
|
+
| Platino, Oro, Plata | Promover a `habilidades/`, registrar en manifiestos |
|
|
371
|
+
| Bronce | NO promover, devolver a `_userland/` con feedback |
|
|
372
|
+
| Sin badge | NO promover, registrar rechazo en `.planning/evolucion/promociones-rechazadas.jsonl` |
|
|
373
|
+
|
|
374
|
+
Si un skill es rechazado ≥3 veces consecutivas, el agente escala al usuario
|
|
375
|
+
con análisis de qué dimensiones bajan el score. Origen: ADR 0013 sección 3C.
|
|
376
|
+
|
|
359
377
|
## Paso 4 — Emitir reporte
|
|
360
378
|
|
|
361
379
|
Generar el siguiente reporte directamente en la conversación:
|
|
@@ -38,6 +38,30 @@ Si el archivo no existe: ejecutar el regenerado del Paso 0. Si sigue sin
|
|
|
38
38
|
existir: reportar "el ciclo de evolución no ha corrido aún; ejecuta cualquier
|
|
39
39
|
tarea para poblar métricas iniciales".
|
|
40
40
|
|
|
41
|
+
## Paso 1.5 — Cargar contexto del kernel (gobernanza)
|
|
42
|
+
|
|
43
|
+
Para el bloque **KERNEL** del dashboard, recolectar:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 1. Conteo de agentes evolvable=false vs total
|
|
47
|
+
TOTAL=$(ls agentes/*.md | grep -v "^agentes/_" | wc -l)
|
|
48
|
+
EVOL_FALSE=$(grep -l '^evolvable: false' agentes/*.md | wc -l)
|
|
49
|
+
|
|
50
|
+
# 2. Último ADR del repo madre — el más reciente por fecha en frontmatter
|
|
51
|
+
ULTIMO_ADR=$(ls .planning/adrs/[0-9]*.md | sort -r | head -1)
|
|
52
|
+
TITULO=$(head -1 "$ULTIMO_ADR" | sed 's/^# //')
|
|
53
|
+
FECHA=$(grep -m1 '^\*\*Fecha\*\*:' "$ULTIMO_ADR" | sed 's/.*\*\*Fecha\*\*:\s*//')
|
|
54
|
+
|
|
55
|
+
echo "ADR_KERNEL_LINEA=\"$TITULO — $FECHA\""
|
|
56
|
+
echo "RATIO=\"$EVOL_FALSE/$TOTAL\""
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Estos valores alimentan la sección KERNEL del template.
|
|
60
|
+
|
|
61
|
+
Si `.planning/evolucion/politica-evolvable.md` no existe, reportar como
|
|
62
|
+
hallazgo crítico: el kernel SIN política documentada es violación de
|
|
63
|
+
`reglas/gobernanza.md`.
|
|
64
|
+
|
|
41
65
|
## Paso 2 — Emitir el dashboard (formato humano)
|
|
42
66
|
|
|
43
67
|
Template de salida:
|
|
@@ -74,6 +98,26 @@ Template de salida:
|
|
|
74
98
|
Aplicadas ............. <n>
|
|
75
99
|
Revertidas ............ <n>
|
|
76
100
|
Neta .................. <n>
|
|
101
|
+
Rollback ratio ........ <%> (N/A si aplicadas == 0)
|
|
102
|
+
|
|
103
|
+
CALIDAD CONDUCTUAL (14d)
|
|
104
|
+
Fallos por tipo:
|
|
105
|
+
bad_output_format <n>
|
|
106
|
+
tool_error <n>
|
|
107
|
+
timeout <n>
|
|
108
|
+
schema_violation <n>
|
|
109
|
+
task_incomplete <n>
|
|
110
|
+
unknown <n>
|
|
111
|
+
Reintentos consecutivos
|
|
112
|
+
Total ............... <n> (mismo agente, misma task, ≤30 min)
|
|
113
|
+
Top agente .......... <agente>: <n>
|
|
114
|
+
Latencia top agente
|
|
115
|
+
Mediana ............. <ms>
|
|
116
|
+
p95 ................. <ms>
|
|
117
|
+
Sin instrumentación todavía:
|
|
118
|
+
• precision_routing_fase_dominio
|
|
119
|
+
• utilidad_memoria_recuperada
|
|
120
|
+
• violaciones_formato_post_ejecucion
|
|
77
121
|
|
|
78
122
|
ALERTAS PERSISTENTES
|
|
79
123
|
<si alertasActivas.length === 0>
|
|
@@ -82,6 +126,11 @@ Template de salida:
|
|
|
82
126
|
[!] kind=X target=Y count=Z (primera: fecha)
|
|
83
127
|
...
|
|
84
128
|
|
|
129
|
+
KERNEL (gobernanza)
|
|
130
|
+
Política evolvable .... .planning/evolucion/politica-evolvable.md
|
|
131
|
+
Último ADR kernel ..... <ADR-NNNN — título — fecha>
|
|
132
|
+
Agentes evolvable=false <n>/<total>
|
|
133
|
+
|
|
85
134
|
═══════════════════════════════════════════════════════════════
|
|
86
135
|
```
|
|
87
136
|
|
package/comandos/swl/release.md
CHANGED
|
@@ -122,6 +122,21 @@ Para proyectos no-SWL: actualiza los archivos detectados en Paso 1 (package.json
|
|
|
122
122
|
|
|
123
123
|
En ambos casos, verificar consistencia después de actualizar con `grep -r "versión-anterior" .` para detectar referencias olvidadas.
|
|
124
124
|
|
|
125
|
+
## Paso 6.5 — Regenerar skills-lock.json
|
|
126
|
+
|
|
127
|
+
Antes del CHANGELOG, regenerar el lock de skills para capturar el estado de
|
|
128
|
+
los 151 SKILL.md de la release:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
node scripts/generar-skills-lock.js
|
|
132
|
+
git add manifiestos/skills-lock.json
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
El lock contiene SHA256 de cada SKILL.md y permite que `/swl:salud` detecte
|
|
136
|
+
drift silencioso entre releases. Si el lock no cambió respecto al anterior,
|
|
137
|
+
el commit lo refleja como no-op (idempotente). El archivo es pequeño (~37KB)
|
|
138
|
+
y debe versionarse.
|
|
139
|
+
|
|
125
140
|
## Paso 7 — Generar CHANGELOG
|
|
126
141
|
|
|
127
142
|
Lee CHANGELOG.md existente (o créalo). Agrega entrada al inicio con formato Keep a Changelog:
|
|
@@ -178,6 +193,32 @@ Resultado: 13/15 OK, 2 FALLA(S) obligatoria(s)
|
|
|
178
193
|
|
|
179
194
|
Esta gate existe porque el agente `release-manager-swl` ha omitido archivos en 3 releases consecutivos (5.10.3, 5.10.4, 5.10.5 — ver APRENDIZAJES.md) pese a tener la checklist documentada en su frontmatter. Un checklist textual no basta: se necesita ejecución.
|
|
180
195
|
|
|
196
|
+
### 10.1.1 Gate de cobertura por lenguaje (regeneración obligatoria)
|
|
197
|
+
|
|
198
|
+
Tras el verificar-release.js, regenerar la matriz lenguaje × cobertura SWL:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node scripts/generar-matriz-lenguajes.js
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
El script reescribe `.planning/cobertura-lenguajes.md` con el estado actual.
|
|
205
|
+
Comparar contra el commit anterior:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
git diff .planning/cobertura-lenguajes.md
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Si algún lenguaje **bajó de status** (completo → parcial, parcial → faltante)
|
|
212
|
+
sin ADR documentado en `.planning/adrs/` justificando la regresión, **NO hacer
|
|
213
|
+
release**. La regresión silenciosa de cobertura erosiona la promesa "polyglot"
|
|
214
|
+
del repo.
|
|
215
|
+
|
|
216
|
+
Si el cambio es esperado (eliminación deliberada de un componente), el ADR
|
|
217
|
+
debe existir con número y fecha y referenciarse en el commit del release.
|
|
218
|
+
|
|
219
|
+
Lo mismo aplica para releases con incremento de cobertura: la nueva matriz se
|
|
220
|
+
commitea junto al release y se menciona en RELEASE-NOTES.
|
|
221
|
+
|
|
181
222
|
### 10.2 Verificación manual post-gate
|
|
182
223
|
|
|
183
224
|
```bash
|
package/comandos/swl/salud.md
CHANGED
|
@@ -286,6 +286,29 @@ Si `SWL_AUDIT_AGENTES` no está definida, este paso se omite — los reportes
|
|
|
286
286
|
son opt-in por diseño (CLAUDE.md: "Variables de entorno opt-in para
|
|
287
287
|
integraciones enterprise").
|
|
288
288
|
|
|
289
|
+
## Paso 5e — Verificación de drift de skills (skills-lock)
|
|
290
|
+
|
|
291
|
+
Si existe `manifiestos/skills-lock.json`, comparar el hash actual de cada
|
|
292
|
+
`habilidades/*/SKILL.md` contra el lock. Detecta ediciones silenciosas de
|
|
293
|
+
skills entre release y release.
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
node scripts/generar-skills-lock.js --check
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Estados posibles:
|
|
300
|
+
|
|
301
|
+
| Salida | Significado | Acción |
|
|
302
|
+
|--------|-------------|--------|
|
|
303
|
+
| `✓ skills-lock OK: N skills sin drift` | Todos los hashes coinciden | Continuar |
|
|
304
|
+
| `✗ skills-lock DRIFT detectado` | Skills modificados, nuevos o faltantes | Listar diferencias y proponer regenerar |
|
|
305
|
+
|
|
306
|
+
Si NO existe el lock (proyecto fresco o pre-1.1.0), este paso se omite y
|
|
307
|
+
emite advertencia sugerente: `node scripts/generar-skills-lock.js`.
|
|
308
|
+
|
|
309
|
+
El lock se regenera automáticamente como parte de `/swl:release`. Drift
|
|
310
|
+
fuera de release indica ediciones manuales no committed.
|
|
311
|
+
|
|
289
312
|
## Paso 6b — Formato de salida enriquecido (HealthRow)
|
|
290
313
|
|
|
291
314
|
El módulo `scripts/lib/health-row.js` genera filas de salud con formato visual enriquecido
|
package/hooks/auto-evolucion.js
CHANGED
|
@@ -42,6 +42,15 @@
|
|
|
42
42
|
const fs = require('fs');
|
|
43
43
|
const path = require('path');
|
|
44
44
|
|
|
45
|
+
// Escritura atómica obligatoria para estado mutable (regla CLAUDE.md).
|
|
46
|
+
// Fallback defensivo si el módulo no existe en el destino.
|
|
47
|
+
let atomicWriteJSON;
|
|
48
|
+
try {
|
|
49
|
+
({ atomicWriteJSON } = require('./lib/atomic-write'));
|
|
50
|
+
} catch {
|
|
51
|
+
atomicWriteJSON = (p, obj) => fs.writeFileSync(p, JSON.stringify(obj, null, 2), 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
45
54
|
let nudgeTracker;
|
|
46
55
|
try {
|
|
47
56
|
nudgeTracker = require('./lib/nudge-tracker');
|
|
@@ -70,6 +79,13 @@ try {
|
|
|
70
79
|
driftDetector = null;
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
let agentRouting;
|
|
83
|
+
try {
|
|
84
|
+
agentRouting = require('./lib/agent-routing');
|
|
85
|
+
} catch {
|
|
86
|
+
agentRouting = null;
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
let singletonGuard;
|
|
74
90
|
try {
|
|
75
91
|
singletonGuard = require('./lib/singleton-guard');
|
|
@@ -120,7 +136,7 @@ function leerNudges() {
|
|
|
120
136
|
function escribirNudges(obj) {
|
|
121
137
|
ensureDir(DIR_AUTOEVOL);
|
|
122
138
|
try {
|
|
123
|
-
|
|
139
|
+
atomicWriteJSON(NUDGES_PATH, obj);
|
|
124
140
|
} catch { /* ignore */ }
|
|
125
141
|
}
|
|
126
142
|
|
|
@@ -196,6 +212,21 @@ function analizarPayload(data) {
|
|
|
196
212
|
? clasificarTipoFallo(data, response)
|
|
197
213
|
: null;
|
|
198
214
|
|
|
215
|
+
// Inferir fase/dominio del frontmatter del agente para routing precision
|
|
216
|
+
// (ADR 0012). Si el agente no tiene frontmatter o no se encuentra el archivo,
|
|
217
|
+
// los campos quedan null y la métrica simplemente no cuenta esa entrada.
|
|
218
|
+
let routedPhase = null;
|
|
219
|
+
let routedDomain = null;
|
|
220
|
+
let routingSource = 'unknown';
|
|
221
|
+
if (agentRouting) {
|
|
222
|
+
try {
|
|
223
|
+
const r = agentRouting.getRouting(subagentType);
|
|
224
|
+
routedPhase = r.fase;
|
|
225
|
+
routedDomain = r.dominio;
|
|
226
|
+
routingSource = r.source;
|
|
227
|
+
} catch { /* no bloquear por fallo en routing inference */ }
|
|
228
|
+
}
|
|
229
|
+
|
|
199
230
|
return {
|
|
200
231
|
ts: new Date().toISOString(),
|
|
201
232
|
sessionId: String(data.session_id || 'default'),
|
|
@@ -205,6 +236,9 @@ function analizarPayload(data) {
|
|
|
205
236
|
trivial: toolCalls > 0 && toolCalls < MIN_TOOL_CALLS,
|
|
206
237
|
duracionMs,
|
|
207
238
|
tipo_fallo: tipoFallo,
|
|
239
|
+
routed_phase: routedPhase,
|
|
240
|
+
routed_domain: routedDomain,
|
|
241
|
+
routing_source: routingSource,
|
|
208
242
|
};
|
|
209
243
|
}
|
|
210
244
|
|
|
@@ -11,14 +11,31 @@
|
|
|
11
11
|
* arquitectura, seguridad, performance, refactor, documentación.
|
|
12
12
|
*
|
|
13
13
|
* Inspirado en obsidian-mind classify-message.py.
|
|
14
|
-
* Zero-dependencies: regex puro en JS.
|
|
14
|
+
* Zero-dependencies: regex puro en JS + lib propia text-similarity.js.
|
|
15
15
|
*
|
|
16
16
|
* Resultado:
|
|
17
17
|
* - Señales detectadas → hookSpecificOutput con hints de routing
|
|
18
18
|
* - Sin señales → exit 0 silencioso
|
|
19
19
|
* - Error interno → exit 0 silencioso (nunca bloquear)
|
|
20
|
+
*
|
|
21
|
+
* Opt-in opcional:
|
|
22
|
+
* - SWL_FUZZY_CLASIFICADOR=1 activa fuzzy matching (Levenshtein + stem ES)
|
|
23
|
+
* como SEGUNDA pasada cuando regex no detectó señales. NUNCA degrada
|
|
24
|
+
* señales del regex original. Útil para typos y variantes morfológicas
|
|
25
|
+
* ("documentar" → matches "documentación"). Costo: ~5-15ms por prompt.
|
|
20
26
|
*/
|
|
21
27
|
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const FUZZY_HABILITADO = process.env.SWL_FUZZY_CLASIFICADOR === '1';
|
|
30
|
+
let textSim = null;
|
|
31
|
+
if (FUZZY_HABILITADO) {
|
|
32
|
+
try {
|
|
33
|
+
textSim = require(path.join(__dirname, 'lib', 'text-similarity'));
|
|
34
|
+
} catch (_) {
|
|
35
|
+
// Si la lib no existe, fuzzy se desactiva silenciosamente
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
22
39
|
// ---------------------------------------------------------------------------
|
|
23
40
|
// Mapa de complejidad de tarea → modelo recomendado
|
|
24
41
|
//
|
|
@@ -212,22 +229,52 @@ function anyMatch(patterns, text) {
|
|
|
212
229
|
return false;
|
|
213
230
|
}
|
|
214
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Verifica si algún patrón aparece de forma aproximada (fuzzy) en el texto.
|
|
234
|
+
* Usa Levenshtein + stem español. Solo se invoca si SWL_FUZZY_CLASIFICADOR=1.
|
|
235
|
+
* NO se llama si `anyMatch` ya devolvió true para esa señal — fuzzy es
|
|
236
|
+
* fallback puro, no reemplazo.
|
|
237
|
+
*
|
|
238
|
+
* @param {string[]} patterns
|
|
239
|
+
* @param {string} text - texto original (no lowercase necesariamente)
|
|
240
|
+
* @returns {boolean}
|
|
241
|
+
*/
|
|
242
|
+
function anyMatchFuzzy(patterns, text) {
|
|
243
|
+
if (!textSim) return false;
|
|
244
|
+
for (const phrase of patterns) {
|
|
245
|
+
// Solo aplicar fuzzy a patterns de palabra única o frase corta.
|
|
246
|
+
// Patterns de >3 palabras tienden a generar falsos positivos.
|
|
247
|
+
const tokens = phrase.split(/\s+/).filter(Boolean);
|
|
248
|
+
if (tokens.length > 3) continue;
|
|
249
|
+
if (textSim.fuzzyContains(text, phrase)) return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
215
254
|
/**
|
|
216
255
|
* Clasifica el mensaje del usuario.
|
|
217
256
|
* @param {string} prompt
|
|
218
|
-
* @returns {{ hints: string[], signalNames: string[] }}
|
|
257
|
+
* @returns {{ hints: string[], signalNames: string[], fuzzyAdded: number }}
|
|
219
258
|
*/
|
|
220
259
|
function classify(prompt) {
|
|
221
260
|
const text = (prompt || '').toLowerCase();
|
|
222
261
|
const hints = [];
|
|
223
262
|
const signalNames = [];
|
|
263
|
+
let fuzzyAdded = 0;
|
|
264
|
+
|
|
224
265
|
for (const sig of SIGNALS) {
|
|
225
266
|
if (anyMatch(sig.patterns, text)) {
|
|
226
267
|
hints.push(sig.hint);
|
|
227
268
|
signalNames.push(sig.name);
|
|
269
|
+
} else if (FUZZY_HABILITADO && anyMatchFuzzy(sig.patterns, text)) {
|
|
270
|
+
// Fuzzy detectó algo que el regex perdió — marca con [fuzzy] para
|
|
271
|
+
// observabilidad
|
|
272
|
+
hints.push(sig.hint + ' [match aproximado]');
|
|
273
|
+
signalNames.push(sig.name);
|
|
274
|
+
fuzzyAdded++;
|
|
228
275
|
}
|
|
229
276
|
}
|
|
230
|
-
return { hints, signalNames };
|
|
277
|
+
return { hints, signalNames, fuzzyAdded };
|
|
231
278
|
}
|
|
232
279
|
|
|
233
280
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agent-routing.js — Helper para inferir fase y dominio del routing.
|
|
5
|
+
*
|
|
6
|
+
* Lee el frontmatter del agente (agentes/<nombre>.md) y extrae los campos
|
|
7
|
+
* `fase` y `dominio` introducidos en v1.1.0 (ADR 0012). El orquestador no
|
|
8
|
+
* registra explícitamente "qué fase/dominio motivó la elección" — pero
|
|
9
|
+
* cuando elige un agente, podemos asumir que lo eligió porque su fase y
|
|
10
|
+
* dominio matcheaban. Por lo tanto, leer el frontmatter del agente
|
|
11
|
+
* efectivamente invocado nos da la celda fase×dominio del routing.
|
|
12
|
+
*
|
|
13
|
+
* Esto es proxy de precisión, no precisión absoluta:
|
|
14
|
+
* - Tasa de éxito por celda (fase, dominio) sirve como indicador de
|
|
15
|
+
* que el routing está enviando trabajo correctamente al agente
|
|
16
|
+
* adecuado.
|
|
17
|
+
* - Una celda con tasa de éxito baja sugiere routing impreciso (el
|
|
18
|
+
* agente recibe trabajo que no le compete) o agente sub-óptimo.
|
|
19
|
+
*
|
|
20
|
+
* Cache simple en memoria del proceso para evitar I/O repetida (cada
|
|
21
|
+
* invocación de hook es proceso fresco, así que el cache no persiste,
|
|
22
|
+
* pero protege dentro de la misma invocación).
|
|
23
|
+
*
|
|
24
|
+
* @module hooks/lib/agent-routing
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
|
|
30
|
+
const _cache = Object.create(null);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resuelve la ruta del archivo de agente. Acepta tanto el repo madre como
|
|
34
|
+
* proyectos consumidores (donde los agentes pueden estar en otras rutas).
|
|
35
|
+
*
|
|
36
|
+
* @param {string} agentName - p.ej. "backend-python-swl"
|
|
37
|
+
* @param {string} [cwd] - Directorio de proyecto (default process.cwd())
|
|
38
|
+
* @returns {string|null} - Ruta absoluta o null si no existe
|
|
39
|
+
*/
|
|
40
|
+
function resolveAgentPath(agentName, cwd) {
|
|
41
|
+
const baseDir = cwd || process.cwd();
|
|
42
|
+
const candidates = [
|
|
43
|
+
path.join(baseDir, 'agentes', `${agentName}.md`),
|
|
44
|
+
path.join(baseDir, '.claude', 'agents', `${agentName}.md`),
|
|
45
|
+
];
|
|
46
|
+
for (const c of candidates) {
|
|
47
|
+
try { if (fs.statSync(c).isFile()) return c; } catch { /* siguiente */ }
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extrae fase y dominio del frontmatter de un agente.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} agentName - Nombre del agente sin extensión
|
|
56
|
+
* @param {string} [cwd]
|
|
57
|
+
* @returns {{ fase: string|null, dominio: string|null, source: string }}
|
|
58
|
+
* - source: 'frontmatter' | 'cache' | 'unknown'
|
|
59
|
+
*/
|
|
60
|
+
function getRouting(agentName, cwd) {
|
|
61
|
+
if (!agentName) return { fase: null, dominio: null, source: 'unknown' };
|
|
62
|
+
if (_cache[agentName]) {
|
|
63
|
+
return { ..._cache[agentName], source: 'cache' };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const agentPath = resolveAgentPath(agentName, cwd);
|
|
67
|
+
if (!agentPath) {
|
|
68
|
+
const result = { fase: null, dominio: null, source: 'unknown' };
|
|
69
|
+
_cache[agentName] = { fase: null, dominio: null };
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let raw;
|
|
74
|
+
try {
|
|
75
|
+
raw = fs.readFileSync(agentPath, 'utf8');
|
|
76
|
+
} catch {
|
|
77
|
+
return { fase: null, dominio: null, source: 'unknown' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
81
|
+
if (!fmMatch) {
|
|
82
|
+
_cache[agentName] = { fase: null, dominio: null };
|
|
83
|
+
return { fase: null, dominio: null, source: 'unknown' };
|
|
84
|
+
}
|
|
85
|
+
const fm = fmMatch[1];
|
|
86
|
+
|
|
87
|
+
// Parser minimalista — solo busca las dos líneas que necesitamos.
|
|
88
|
+
// Evitamos depender de un parser YAML completo (zero-deps).
|
|
89
|
+
const faseMatch = fm.match(/^fase:\s*(\S+)/m);
|
|
90
|
+
const dominioMatch = fm.match(/^dominio:\s*(\S+)/m);
|
|
91
|
+
|
|
92
|
+
const result = {
|
|
93
|
+
fase: faseMatch ? faseMatch[1].trim() : null,
|
|
94
|
+
dominio: dominioMatch ? dominioMatch[1].trim() : null,
|
|
95
|
+
};
|
|
96
|
+
_cache[agentName] = result;
|
|
97
|
+
return { ...result, source: 'frontmatter' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Limpia el cache (para tests o tras modificar agentes).
|
|
102
|
+
*/
|
|
103
|
+
function clearCache() {
|
|
104
|
+
for (const k of Object.keys(_cache)) delete _cache[k];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { getRouting, resolveAgentPath, clearCache };
|