@saulwade/swl-ses 1.6.0 → 1.6.3
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 +32 -61
- package/README.md +4 -4
- package/agentes/_intent-spec.md +73 -0
- package/agentes/auto-evolucion-swl.md +24 -0
- package/agentes/cloud-infra-swl.md +25 -0
- package/agentes/datos-swl.md +24 -1
- package/agentes/devops-ci-swl.md +24 -0
- package/agentes/frontend-angular-swl.md +7 -7
- package/agentes/frontend-css-swl.md +4 -4
- package/agentes/frontend-react-swl.md +7 -7
- package/agentes/frontend-swl.md +9 -9
- package/agentes/frontend-tailwind-swl.md +4 -4
- package/agentes/migrador-swl.md +22 -0
- package/agentes/pagos-swl.md +25 -0
- package/agentes/release-manager-swl.md +24 -0
- package/agentes/rendimiento-swl.md +2 -2
- package/agentes/sre-swl.md +24 -0
- package/comandos/swl/brainstorm.md +1 -0
- package/comandos/swl/compactar.md +1 -1
- package/comandos/swl/discutir-fase.md +15 -1
- package/comandos/swl/mapear-codebase.md +1 -1
- package/comandos/swl/nemesis.md +29 -0
- package/comandos/swl/planear-fase.md +18 -2
- package/comandos/swl/verificar.md +4 -4
- package/habilidades/aprender-de-git-diff/SKILL.md +288 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +7 -1
- package/habilidades/diseno-herramientas-agente/SKILL.md +17 -0
- package/habilidades/doc-sync/SKILL.md +441 -1
- package/habilidades/doubt-driven-review/SKILL.md +177 -171
- package/habilidades/feynman-auditor-swl/SKILL.md +129 -123
- package/habilidades/infra-github-actions/SKILL.md +172 -166
- package/habilidades/meta-skills-estandar/SKILL.md +6 -0
- package/habilidades/meta-skills-estandar/recursos/skill-judge-rubrica.md +281 -0
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/nemesis-evaluacion-json/SKILL.md +5 -0
- package/habilidades/nemesis-redistribuir/SKILL.md +5 -0
- package/habilidades/node-experto/SKILL.md +197 -3
- package/habilidades/prevencion-racionalizacion/SKILL.md +1 -0
- package/habilidades/privacy-memoria/SKILL.md +1 -0
- package/habilidades/proceso-autoverificacion-evidencias/SKILL.md +258 -0
- package/habilidades/proceso-confianza-pre-implementacion/SKILL.md +246 -0
- package/habilidades/proceso-ddia-fundamentos/SKILL.md +255 -0
- package/habilidades/proceso-ddia-streaming/SKILL.md +231 -0
- package/habilidades/proceso-intent-engineering/SKILL.md +269 -0
- package/habilidades/reducir-entropia/SKILL.md +219 -0
- package/habilidades/sre-patrones/SKILL.md +1 -1
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +172 -166
- package/habilidades/tdd-workflow/SKILL.md +178 -3
- package/habilidades/verificacion-evidencia/SKILL.md +1 -0
- package/habilidades/web-fetcher-routing/SKILL.md +81 -75
- package/habilidades/workflow-claude-code/SKILL.md +2 -2
- package/hooks/lib/task-budget.js +218 -0
- package/hooks/validar-intent-spec.js +222 -0
- package/manifiestos/hooks-config.json +9 -0
- package/manifiestos/modulos.json +12 -2
- package/manifiestos/skills-lock.json +1191 -1142
- package/package.json +5 -3
- package/plugin.json +9 -2
- package/reglas/auditorias-documentales-estructurales.md +205 -0
- package/reglas/fragmentos-compartidos.md +26 -0
- package/reglas/intent-engineering.md +214 -0
- package/reglas/registro-componentes-nuevos.md +38 -0
- package/schemas/agent-frontmatter.schema.json +294 -167
- package/schemas/agent-message.schema.json +73 -53
- package/schemas/agent-output-implementacion.schema.json +114 -85
- package/schemas/agent-output-planificacion.schema.json +150 -113
- package/schemas/agent-output-review.schema.json +98 -78
- package/schemas/diary-entry.schema.json +42 -10
- package/schemas/hook-profiles.schema.json +54 -39
- package/schemas/hooks-config.schema.json +89 -74
- package/schemas/instinct.schema.json +152 -115
- package/schemas/modulos.schema.json +38 -29
- package/schemas/perfiles.schema.json +36 -28
- package/schemas/plugin.schema.json +77 -64
- package/schemas/skill-evals.schema.json +119 -95
- package/schemas/skill-frontmatter.schema.json +245 -170
- package/scripts/generar-inventario.js +452 -420
- package/scripts/lib/schema-version.js +164 -0
- package/scripts/validar-manifest.js +1 -1
- package/scripts/validar.js +3 -2
- package/scripts/verificar-docs-vs-codigo.js +654 -425
- package/scripts/verificar-evolucion.js +19 -3
|
@@ -1,75 +1,81 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: web-fetcher-routing
|
|
3
|
-
description: >
|
|
4
|
-
Routing inteligente para fetching de URLs según dominio y formato:
|
|
5
|
-
GitHub raw, PDF, sitios JS-heavy y default. Reduce tokens consumidos
|
|
6
|
-
evitando WebFetch cuando hay alternativa más eficiente. Cargar antes
|
|
7
|
-
de hacer WebFetch a URLs externas, especialmente repositorios GitHub,
|
|
8
|
-
PDFs públicos, o sitios con paywall/Cloudflare/JS heavy.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
1
|
+
---
|
|
2
|
+
name: web-fetcher-routing
|
|
3
|
+
description: >
|
|
4
|
+
Routing inteligente para fetching de URLs según dominio y formato:
|
|
5
|
+
GitHub raw, PDF, sitios JS-heavy y default. Reduce tokens consumidos
|
|
6
|
+
evitando WebFetch cuando hay alternativa más eficiente. Cargar antes
|
|
7
|
+
de hacer WebFetch a URLs externas, especialmente repositorios GitHub,
|
|
8
|
+
PDFs públicos, o sitios con paywall/Cloudflare/JS heavy.
|
|
9
|
+
version: "1.0.0"
|
|
10
|
+
exclusiones:
|
|
11
|
+
- "No cargar para descarga de archivos binarios grandes (>10 MB) — el routing está optimizado para texto y HTML."
|
|
12
|
+
- "No cargar cuando la URL ya es directa al recurso (raw GitHub, PDF, JSON API) y no hay duda de cómo procesarla."
|
|
13
|
+
- "No cargar para web scraping con interacción (login, formularios) — eso es agent-browser."
|
|
14
|
+
- "No cargar para fetching local de filesystem (file://) — usar Read tool."
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# web-fetcher-routing
|
|
18
|
+
|
|
19
|
+
Selecciona la herramienta correcta para cada URL antes de intentar el fetch.
|
|
20
|
+
Un WebFetch al HTML de GitHub UI consume 10-30× más tokens que leer el raw.
|
|
21
|
+
Un PDF vía Read devuelve basura binaria. La elección equivocada cuesta tokens
|
|
22
|
+
reales; este skill la automatiza.
|
|
23
|
+
|
|
24
|
+
## Cuándo cargar
|
|
25
|
+
|
|
26
|
+
- Antes de hacer `WebFetch` a cualquier URL externa no trivial.
|
|
27
|
+
- Cuando la URL apunta a GitHub (repositorio, archivo o blob).
|
|
28
|
+
- Cuando la URL termina en `.pdf` o el contexto indica documento PDF.
|
|
29
|
+
- Cuando el fetch previo falló con 402, página de login o contenido vacío
|
|
30
|
+
(señal de JS-heavy o Cloudflare).
|
|
31
|
+
|
|
32
|
+
## Cuándo NO cargar
|
|
33
|
+
|
|
34
|
+
- URLs internas del proyecto o localhost — usar `Read` directamente.
|
|
35
|
+
- Contenido ya cargado en el contexto de esta sesión — no re-fetchar.
|
|
36
|
+
- Cuando el usuario dictó explícitamente la herramienta a usar
|
|
37
|
+
("usa WebFetch", "lee con curl").
|
|
38
|
+
|
|
39
|
+
## Tabla de routing
|
|
40
|
+
|
|
41
|
+
| Patrón de URL | Herramienta | Razón |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `github.com/[user]/[repo]/blob/[branch]/[path]` | Reescribir a `raw.githubusercontent.com/[user]/[repo]/[branch]/[path]` y hacer `WebFetch` con la URL reescrita | Evita el HTML de la UI de GitHub; 10-30× menos tokens |
|
|
44
|
+
| `raw.githubusercontent.com/...` | `WebFetch` directo | Ya es raw content |
|
|
45
|
+
| URL termina en `.pdf` | Invocar `Skill("swl-markitdown")` para conversión con `pdftotext` o `markitdown` | `Read` no soporta PDF; `WebFetch` devuelve binario o HTML de visor |
|
|
46
|
+
| `x.com`, `twitter.com`, cualquier host con CAPTCHA o Cloudflare detectado | Invocar `Skill("agent-browser")` con headless Chrome | `WebFetch` devuelve 402 o página de login; `agent-browser` usa accessibility tree (~82% menos tokens que screenshots) |
|
|
47
|
+
| `mp.weixin.qq.com`, `feishu.cn`, `larksuite.com` | Invocar `Skill("agent-browser")` | Plataformas chinas con autenticación o JS requerido |
|
|
48
|
+
| Todo lo demás | `WebFetch` directo | El caso común; probar primero antes de escalar |
|
|
49
|
+
|
|
50
|
+
## Algoritmo de decisión
|
|
51
|
+
|
|
52
|
+
1. Parsear la URL: extraer esquema, dominio y extensión del path.
|
|
53
|
+
2. Comparar con los patrones de la tabla, en orden de arriba hacia abajo.
|
|
54
|
+
3. Si el dominio es `github.com` con segmento `/blob/` en el path:
|
|
55
|
+
reescribir la URL antes de hacer el fetch.
|
|
56
|
+
4. Invocar la herramienta asignada al patrón que coincide.
|
|
57
|
+
5. Si el resultado tiene señales de paywall o error (primeras 10 líneas
|
|
58
|
+
contienen "Subscribe", "Sign in", "403", página vacía): escalar al
|
|
59
|
+
patrón JS-heavy con `Skill("agent-browser")`.
|
|
60
|
+
6. Reportar qué método se usó y por qué, en una línea antes del contenido.
|
|
61
|
+
|
|
62
|
+
## Ejemplo de reescritura GitHub
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
# URL original
|
|
66
|
+
https://github.com/tw93/Waza/blob/main/skills/read/SKILL.md
|
|
67
|
+
|
|
68
|
+
# URL reescrita para raw
|
|
69
|
+
https://raw.githubusercontent.com/tw93/Waza/main/skills/read/SKILL.md
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Señales de fallo que activan escalado
|
|
73
|
+
|
|
74
|
+
| Señal en la respuesta | Acción |
|
|
75
|
+
|---|---|
|
|
76
|
+
| HTTP 402 | Escalar a `agent-browser` |
|
|
77
|
+
| Primeras líneas con "Sign in" o "Subscribe" | Detener, avisar al usuario |
|
|
78
|
+
| Contenido HTML con menos de 200 caracteres | Reintentar con método alternativo |
|
|
79
|
+
| Binario o caracteres ilegibles | Verificar si es PDF; si sí, usar `swl-markitdown` |
|
|
80
|
+
|
|
81
|
+
<!-- Adaptado de Waza/skills/read bajo MIT License (tw93/Waza) -->
|
|
@@ -14,7 +14,7 @@ exclusiones:
|
|
|
14
14
|
- "No cargar para configurar la estructura de directorios de un proyecto Claude-ready; usar `estructura-proyecto-claude`."
|
|
15
15
|
- "No cargar para resolver problemas de degradación de contexto en mitad de una sesión larga; en ese caso ejecutar `/swl:compactar` directamente."
|
|
16
16
|
- "No cargar para consultar documentación de librerías o frameworks externos; usar Context7 MCP."
|
|
17
|
-
- "No cargar para ajustar permisos de herramientas en settings.json; usar `
|
|
17
|
+
- "No cargar para ajustar permisos de herramientas en settings.json; usar editar manualmente `.claude/settings.json` o `~/.claude/settings.json`."
|
|
18
18
|
evolvable: true # default para skill estandar
|
|
19
19
|
---
|
|
20
20
|
# Workflow con Claude Code
|
|
@@ -37,7 +37,7 @@ Para ESTRUCTURA de proyecto, usar `estructura-proyecto-claude`.
|
|
|
37
37
|
- Se necesita estructura de archivos para un proyecto Claude-ready (CLAUDE.md, .claude/agents/, etc.); usar `estructura-proyecto-claude`.
|
|
38
38
|
- El contexto ya está al 70%+ y el usuario quiere compactar ahora mismo; ejecutar `/swl:compactar` sin cargar skills adicionales.
|
|
39
39
|
- El usuario pregunta por una librería específica (ej: "¿cómo uso FastAPI?"); la respuesta está en Context7 MCP, no en este skill de workflow.
|
|
40
|
-
- Se busca configurar hooks, permisos o settings.json;
|
|
40
|
+
- Se busca configurar hooks, permisos o settings.json; editar manualmente `.claude/settings.json` o `~/.claude/settings.json`.
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* task-budget.js — presupuesto SEMÁNTICO de tokens por tarea.
|
|
5
|
+
*
|
|
6
|
+
* Adaptación de SuperClaude_Framework `pm_agent/token_budget.py` (MIT) a Node
|
|
7
|
+
* zero-deps. Provee un contrato de presupuesto por COMPLEJIDAD de tarea que
|
|
8
|
+
* agentes y orquestador pueden consultar para gating, NO un contador exacto
|
|
9
|
+
* de tokens consumidos (el modelo no expone tokens en runtime).
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANTE: no confundir con `hooks/lib/token-budget.js`, que distribuye
|
|
12
|
+
* presupuesto entre BLOQUES DE CONTEXTO (memoria/observaciones/reglas) con
|
|
13
|
+
* scoring de recencia+importancia. Aquel es para inyección de contexto;
|
|
14
|
+
* éste es para clasificación de TAREAS.
|
|
15
|
+
*
|
|
16
|
+
* Niveles de complejidad y presupuesto recomendado:
|
|
17
|
+
* - simple: 200 tokens — typo fix, rename, comentario, formato.
|
|
18
|
+
* - medium: 1000 tokens — bugfix acotado, feature pequeña.
|
|
19
|
+
* - complex: 2500 tokens — feature grande, refactor cross-módulo.
|
|
20
|
+
*
|
|
21
|
+
* Uso típico:
|
|
22
|
+
* const { TaskBudget } = require('./task-budget');
|
|
23
|
+
* const budget = new TaskBudget('medium');
|
|
24
|
+
* if (budget.tryUse(150)) {
|
|
25
|
+
* // proceder con la sub-tarea
|
|
26
|
+
* } else {
|
|
27
|
+
* // presupuesto agotado — pausar, reportar, escalar
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* Semántica de "rebasado": si un agente declara tarea "simple" y consume
|
|
31
|
+
* 800 tokens, hay señal de que la clasificación fue incorrecta o el scope
|
|
32
|
+
* creció. El orquestador puede actuar (re-clasificar, pausar, alertar).
|
|
33
|
+
*
|
|
34
|
+
* @module hooks/lib/task-budget
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/** @typedef {'simple' | 'medium' | 'complex'} ComplexityLevel */
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Límites por defecto. Defaults recomendados por el material fuente
|
|
41
|
+
* (SuperClaude pm_agent). Modificables vía constructor para escenarios
|
|
42
|
+
* especiales.
|
|
43
|
+
* @type {Readonly<Record<ComplexityLevel, number>>}
|
|
44
|
+
*/
|
|
45
|
+
const DEFAULT_LIMITS = Object.freeze({
|
|
46
|
+
simple: 200,
|
|
47
|
+
medium: 1000,
|
|
48
|
+
complex: 2500,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gestor de presupuesto semántico por tarea.
|
|
53
|
+
*
|
|
54
|
+
* Inmutable en `limit` y `complexity` tras la construcción; mutable solo
|
|
55
|
+
* en `used` vía `tryUse()` / `forceUse()` / `reset()`.
|
|
56
|
+
*/
|
|
57
|
+
class TaskBudget {
|
|
58
|
+
/**
|
|
59
|
+
* @param {ComplexityLevel} [complexity='medium'] - Complejidad declarada.
|
|
60
|
+
* @param {Partial<Record<ComplexityLevel, number>>} [overrides] - Límites custom.
|
|
61
|
+
*/
|
|
62
|
+
constructor(complexity = 'medium', overrides = undefined) {
|
|
63
|
+
const limits = overrides
|
|
64
|
+
? Object.freeze({ ...DEFAULT_LIMITS, ...overrides })
|
|
65
|
+
: DEFAULT_LIMITS;
|
|
66
|
+
|
|
67
|
+
// Default defensivo a 'medium' si llega un nivel desconocido — el
|
|
68
|
+
// material fuente hace lo mismo. Evita excepciones por typo.
|
|
69
|
+
const normalized = Object.prototype.hasOwnProperty.call(limits, complexity)
|
|
70
|
+
? complexity
|
|
71
|
+
: 'medium';
|
|
72
|
+
|
|
73
|
+
/** @type {ComplexityLevel} */
|
|
74
|
+
this.complexity = /** @type {ComplexityLevel} */ (normalized);
|
|
75
|
+
/** @type {number} */
|
|
76
|
+
this.limit = limits[normalized];
|
|
77
|
+
/** @type {number} */
|
|
78
|
+
this.used = 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Intenta consumir `amount` tokens. Si excede el presupuesto, NO consume
|
|
83
|
+
* y retorna false. Útil cuando el caller debe decidir entre proceder o
|
|
84
|
+
* escalar.
|
|
85
|
+
*
|
|
86
|
+
* @param {number} amount - Cantidad a consumir. Debe ser número finito >= 0.
|
|
87
|
+
* @returns {boolean} true si se consumió, false si excedería el presupuesto.
|
|
88
|
+
* @throws {TypeError} si amount no es número finito >= 0.
|
|
89
|
+
*/
|
|
90
|
+
tryUse(amount) {
|
|
91
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
92
|
+
throw new TypeError(`TaskBudget.tryUse: amount debe ser número finito >= 0, recibido: ${amount}`);
|
|
93
|
+
}
|
|
94
|
+
if (this.used + amount > this.limit) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
this.used += amount;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Alias semántico de `tryUse` para callers que prefieren `allocate`.
|
|
103
|
+
* Mantiene paridad con el API original (pm_agent.token_budget.allocate).
|
|
104
|
+
*
|
|
105
|
+
* @param {number} amount
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
allocate(amount) {
|
|
109
|
+
return this.tryUse(amount);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Consume `amount` tokens FORZOSAMENTE, incluso si excede el presupuesto.
|
|
114
|
+
* Útil cuando el consumo ya ocurrió y se quiere registrar para auditoría.
|
|
115
|
+
* Retorna true si quedó dentro del presupuesto, false si lo rebasó.
|
|
116
|
+
*
|
|
117
|
+
* @param {number} amount
|
|
118
|
+
* @returns {boolean} true si dentro del presupuesto, false si lo rebasó.
|
|
119
|
+
* @throws {TypeError} si amount no es número finito >= 0.
|
|
120
|
+
*/
|
|
121
|
+
forceUse(amount) {
|
|
122
|
+
if (!Number.isFinite(amount) || amount < 0) {
|
|
123
|
+
throw new TypeError(`TaskBudget.forceUse: amount debe ser número finito >= 0, recibido: ${amount}`);
|
|
124
|
+
}
|
|
125
|
+
this.used += amount;
|
|
126
|
+
return this.used <= this.limit;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Tokens disponibles restantes. Puede ser negativo si se usó `forceUse`
|
|
131
|
+
* con consumo que rebasó el límite.
|
|
132
|
+
* @returns {number}
|
|
133
|
+
*/
|
|
134
|
+
get remaining() {
|
|
135
|
+
return this.limit - this.used;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fracción del presupuesto consumida en [0, ∞). 1.0 = exactamente lleno;
|
|
140
|
+
* >1.0 = rebasado. Útil para alertas "65% consumido".
|
|
141
|
+
* @returns {number}
|
|
142
|
+
*/
|
|
143
|
+
get usedFraction() {
|
|
144
|
+
return this.limit === 0 ? 0 : this.used / this.limit;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* true si el presupuesto fue rebasado (used > limit). Señal operacional
|
|
149
|
+
* de mala clasificación de complejidad o scope creep.
|
|
150
|
+
* @returns {boolean}
|
|
151
|
+
*/
|
|
152
|
+
get isOver() {
|
|
153
|
+
return this.used > this.limit;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resetea el contador de usados. NO cambia el límite ni la complejidad.
|
|
158
|
+
*/
|
|
159
|
+
reset() {
|
|
160
|
+
this.used = 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Representación para logs / debugging.
|
|
165
|
+
* @returns {string}
|
|
166
|
+
*/
|
|
167
|
+
toString() {
|
|
168
|
+
return `TaskBudget(complexity=${this.complexity}, limit=${this.limit}, used=${this.used})`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Sugiere nivel de complejidad a partir de heurística simple sobre el
|
|
174
|
+
* descriptor de la tarea. NO es clasificación inteligente — es atajo para
|
|
175
|
+
* callers que no quieren razonar el nivel manualmente.
|
|
176
|
+
*
|
|
177
|
+
* Heurística:
|
|
178
|
+
* - menciona "typo", "rename", "format", "comment" → simple
|
|
179
|
+
* - menciona "feature", "refactor", "migration", "arquitectura" → complex
|
|
180
|
+
* - resto → medium
|
|
181
|
+
*
|
|
182
|
+
* @param {string} description - Descripción libre de la tarea.
|
|
183
|
+
* @returns {ComplexityLevel}
|
|
184
|
+
*/
|
|
185
|
+
function suggestComplexity(description) {
|
|
186
|
+
if (typeof description !== 'string' || description.length === 0) {
|
|
187
|
+
return 'medium';
|
|
188
|
+
}
|
|
189
|
+
const lower = description.toLowerCase();
|
|
190
|
+
|
|
191
|
+
const simpleHints = ['typo', 'rename', 'format', 'comment', 'whitespace', 'comentario', 'formato'];
|
|
192
|
+
if (simpleHints.some((hint) => lower.includes(hint))) {
|
|
193
|
+
return 'simple';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const complexHints = [
|
|
197
|
+
'refactor',
|
|
198
|
+
'migration',
|
|
199
|
+
'migración',
|
|
200
|
+
'feature nueva',
|
|
201
|
+
'cross-module',
|
|
202
|
+
'cross-modulo',
|
|
203
|
+
'arquitectura',
|
|
204
|
+
'rewrite',
|
|
205
|
+
'reescritura',
|
|
206
|
+
];
|
|
207
|
+
if (complexHints.some((hint) => lower.includes(hint))) {
|
|
208
|
+
return 'complex';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return 'medium';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = {
|
|
215
|
+
TaskBudget,
|
|
216
|
+
DEFAULT_LIMITS,
|
|
217
|
+
suggestComplexity,
|
|
218
|
+
};
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook: validar-intent-spec.js
|
|
6
|
+
* Tipo: PostToolUse (aplica a: Write, Edit, MultiEdit sobre agentes/*.md)
|
|
7
|
+
*
|
|
8
|
+
* Verifica que los agentes con `nivelRiesgo: ALTO` declaren las 6 partes
|
|
9
|
+
* del framework Intent Engineering exigidas por `reglas/intent-engineering.md`:
|
|
10
|
+
* - strategy (Parte 1)
|
|
11
|
+
* - healthMetrics (Parte 4)
|
|
12
|
+
* - steering (Parte 6 — prompt-level)
|
|
13
|
+
* - hardGuardrails (Parte 6 — orchestration-level)
|
|
14
|
+
* - fragmentos: [_intent-spec]
|
|
15
|
+
* - maxTurnos (Parte 8 — Stop Rules)
|
|
16
|
+
*
|
|
17
|
+
* Comportamiento:
|
|
18
|
+
* - NUNCA bloquea (exit code 0 siempre — blocking:false en hooks-config)
|
|
19
|
+
* - Solo emite nudge cuando faltan campos en agente ALTO — ruido mínimo
|
|
20
|
+
* - Solo se dispara con archivos `agentes/*.md` (no fragmentos `_*.md`)
|
|
21
|
+
* - Respeta exclusiones: temp/, node_modules/, respositorios-git/, _userland/
|
|
22
|
+
*
|
|
23
|
+
* Opt-out: SWL_INTENT_SPEC=0 desactiva completamente el hook.
|
|
24
|
+
*
|
|
25
|
+
* Formato del nudge:
|
|
26
|
+
* {
|
|
27
|
+
* id: string,
|
|
28
|
+
* kind: "intent-spec-incomplete",
|
|
29
|
+
* target: "auto-evolucion-swl",
|
|
30
|
+
* source: "hooks/validar-intent-spec.js",
|
|
31
|
+
* message: "...",
|
|
32
|
+
* data: { archivo, agente, faltan: [...] },
|
|
33
|
+
* ts: ISO,
|
|
34
|
+
* accionado: false
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* Origen: ADR-0027 (F3 Opción B integración Lead Agents + DDIA).
|
|
38
|
+
* Plan de promoción a `blocking:true`: tras 2 semanas sin falsos positivos
|
|
39
|
+
* documentados, con nudges accionados, considerar bloqueo activo.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const fs = require('fs');
|
|
43
|
+
const path = require('path');
|
|
44
|
+
const crypto = require('crypto');
|
|
45
|
+
|
|
46
|
+
// ─── Opt-out global ───────────────────────────────────────────────────────
|
|
47
|
+
if (process.env.SWL_INTENT_SPEC === '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
|
+
const pathNormalized = filePath.replace(/\\/g, '/');
|
|
78
|
+
|
|
79
|
+
// Solo agentes/*.md (NO fragmentos _*.md)
|
|
80
|
+
if (!/\/agentes\/[^_][^/]*\.md$/.test(pathNormalized)) {
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const RUTAS_EXCLUIDAS = [
|
|
85
|
+
'/temp/',
|
|
86
|
+
'/node_modules/',
|
|
87
|
+
'/respositorios-git/',
|
|
88
|
+
'/_userland/',
|
|
89
|
+
'/.planning/',
|
|
90
|
+
];
|
|
91
|
+
if (RUTAS_EXCLUIDAS.some((excluida) => pathNormalized.includes(excluida))) {
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(filePath)) {
|
|
96
|
+
process.exit(0);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── Parsear frontmatter YAML ─────────────────────────────────────────────
|
|
100
|
+
let contenido;
|
|
101
|
+
try {
|
|
102
|
+
contenido = fs.readFileSync(filePath, 'utf-8');
|
|
103
|
+
} catch (_) {
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Normalizar line endings (CRLF Windows → LF) antes del regex.
|
|
108
|
+
// Sin esto el hook nunca detectaba frontmatter en archivos guardados desde
|
|
109
|
+
// Git for Windows con autocrlf=true (bug reportado 2026-05-18).
|
|
110
|
+
contenido = contenido.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
111
|
+
|
|
112
|
+
const matchFM = contenido.match(/^---\n([\s\S]+?)\n---/);
|
|
113
|
+
if (!matchFM) {
|
|
114
|
+
// Sin frontmatter — no es un agente conforme. Otros validadores lo detectan.
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const yamlBlob = matchFM[1];
|
|
119
|
+
|
|
120
|
+
// Parser mínimo de frontmatter (sin dependencias). Solo lee:
|
|
121
|
+
// - nivelRiesgo: VALOR
|
|
122
|
+
// - presencia de claves de primer nivel
|
|
123
|
+
const lineas = yamlBlob.split('\n');
|
|
124
|
+
const claves = new Set();
|
|
125
|
+
let nivelRiesgo = null;
|
|
126
|
+
let fragmentosTieneIntentSpec = false;
|
|
127
|
+
let dentroDeFragmentos = false;
|
|
128
|
+
|
|
129
|
+
// Strip inline YAML comments (' # ...' o '\t# ...') y comillas wrapping.
|
|
130
|
+
// Sin esto, `nivelRiesgo: ALTO # ejemplo` se parsearía como "ALTO # ejemplo"
|
|
131
|
+
// y la comparación con 'ALTO' fallaría silenciosamente (bug latente 2026-05-18).
|
|
132
|
+
function limpiarValorYaml(crudo) {
|
|
133
|
+
let v = crudo;
|
|
134
|
+
// Comentario inline: '#' precedido de whitespace (no '#' dentro de strings comillados,
|
|
135
|
+
// que en este parser mínimo no soportamos pero sí intentamos respetar).
|
|
136
|
+
const m = v.match(/^(.*?)(?:\s+#.*)?$/);
|
|
137
|
+
if (m) v = m[1];
|
|
138
|
+
v = v.trim();
|
|
139
|
+
// Strip comillas wrapping si las hay
|
|
140
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
141
|
+
v = v.slice(1, -1);
|
|
142
|
+
}
|
|
143
|
+
return v;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const linea of lineas) {
|
|
147
|
+
// Detectar clave de primer nivel (sin indent)
|
|
148
|
+
const m = linea.match(/^([a-zA-Z_][a-zA-Z0-9_]*):/);
|
|
149
|
+
if (m) {
|
|
150
|
+
claves.add(m[1]);
|
|
151
|
+
if (m[1] === 'nivelRiesgo') {
|
|
152
|
+
const crudo = linea.split(':').slice(1).join(':').trim();
|
|
153
|
+
nivelRiesgo = limpiarValorYaml(crudo);
|
|
154
|
+
dentroDeFragmentos = false;
|
|
155
|
+
} else if (m[1] === 'fragmentos') {
|
|
156
|
+
dentroDeFragmentos = true;
|
|
157
|
+
// Caso fragmentos: [_intent-spec] inline
|
|
158
|
+
if (linea.includes('_intent-spec')) {
|
|
159
|
+
fragmentosTieneIntentSpec = true;
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
dentroDeFragmentos = false;
|
|
163
|
+
}
|
|
164
|
+
} else if (dentroDeFragmentos && linea.includes('_intent-spec')) {
|
|
165
|
+
fragmentosTieneIntentSpec = true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Solo evaluar agentes ALTO
|
|
170
|
+
if (nivelRiesgo !== 'ALTO') {
|
|
171
|
+
process.exit(0);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Verificar las 6 partes obligatorias
|
|
175
|
+
const CAMPOS_OBLIGATORIOS = ['strategy', 'healthMetrics', 'steering', 'hardGuardrails', 'maxTurnos'];
|
|
176
|
+
const faltan = CAMPOS_OBLIGATORIOS.filter((c) => !claves.has(c));
|
|
177
|
+
|
|
178
|
+
if (!fragmentosTieneIntentSpec) {
|
|
179
|
+
faltan.push('fragmentos:[_intent-spec]');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (faltan.length === 0) {
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── Emitir nudge ─────────────────────────────────────────────────────────
|
|
187
|
+
const CWD = process.cwd();
|
|
188
|
+
const rutaRelativa = path.relative(CWD, filePath).replace(/\\/g, '/');
|
|
189
|
+
const nombreAgente = path.basename(filePath, '.md');
|
|
190
|
+
|
|
191
|
+
const nudge = {
|
|
192
|
+
id: crypto.randomBytes(8).toString('hex'),
|
|
193
|
+
kind: 'intent-spec-incomplete',
|
|
194
|
+
target: 'auto-evolucion-swl',
|
|
195
|
+
source: 'hooks/validar-intent-spec.js',
|
|
196
|
+
message:
|
|
197
|
+
`Agente ALTO riesgo ${nombreAgente} no declara intent spec completo. ` +
|
|
198
|
+
`Faltan: ${faltan.join(', ')}. Cargar Skill("proceso-intent-engineering") + ` +
|
|
199
|
+
`regla intent-engineering.md y completar frontmatter.`,
|
|
200
|
+
data: {
|
|
201
|
+
archivo: rutaRelativa,
|
|
202
|
+
agente: nombreAgente,
|
|
203
|
+
nivelRiesgo: 'ALTO',
|
|
204
|
+
faltan,
|
|
205
|
+
},
|
|
206
|
+
ts: new Date().toISOString(),
|
|
207
|
+
accionado: false,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const nudgesDir = path.join(CWD, '.planning', 'evolucion');
|
|
211
|
+
const nudgesFile = path.join(nudgesDir, 'nudges.jsonl');
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
if (!fs.existsSync(nudgesDir)) {
|
|
215
|
+
fs.mkdirSync(nudgesDir, { recursive: true });
|
|
216
|
+
}
|
|
217
|
+
fs.appendFileSync(nudgesFile, JSON.stringify(nudge) + '\n');
|
|
218
|
+
} catch (_) {
|
|
219
|
+
// Si falla persistir, no romper el flujo del usuario
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
process.exit(0);
|
|
@@ -356,6 +356,15 @@
|
|
|
356
356
|
"maxConsecutiveFailures": 5,
|
|
357
357
|
"degradeOnFailure": "skip"
|
|
358
358
|
},
|
|
359
|
+
"validar-intent-spec.js": {
|
|
360
|
+
"event": "PostToolUse",
|
|
361
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
362
|
+
"description": "Verifica que agentes nivelRiesgo:ALTO declaren las 6 partes del framework Intent Engineering (strategy, healthMetrics, steering, hardGuardrails, maxTurnos, fragmentos:[_intent-spec]) segun reglas/intent-engineering.md. Solo aplica a agentes/*.md no fragmentos. Emite nudge a .planning/evolucion/nudges.jsonl si faltan campos. No bloquea (modo warn-only inicial; promocion a blocking:true tras 2 semanas estables). Excluye temp/, node_modules/, respositorios-git/, _userland/, .planning/. Opt-out: SWL_INTENT_SPEC=0. ADR-0027.",
|
|
363
|
+
"blocking": false,
|
|
364
|
+
"async": true,
|
|
365
|
+
"maxConsecutiveFailures": 5,
|
|
366
|
+
"degradeOnFailure": "skip"
|
|
367
|
+
},
|
|
359
368
|
"notificacion-telegram.js": {
|
|
360
369
|
"event": "Stop",
|
|
361
370
|
"matcher": "",
|
package/manifiestos/modulos.json
CHANGED
|
@@ -234,7 +234,14 @@
|
|
|
234
234
|
"habilidades/tracing-processor",
|
|
235
235
|
"habilidades/guardrail-semantico",
|
|
236
236
|
"habilidades/legacy-code-rescue",
|
|
237
|
-
"habilidades/harness-claude-code"
|
|
237
|
+
"habilidades/harness-claude-code",
|
|
238
|
+
"habilidades/proceso-confianza-pre-implementacion",
|
|
239
|
+
"habilidades/proceso-autoverificacion-evidencias",
|
|
240
|
+
"habilidades/aprender-de-git-diff",
|
|
241
|
+
"habilidades/reducir-entropia",
|
|
242
|
+
"habilidades/proceso-intent-engineering",
|
|
243
|
+
"habilidades/proceso-ddia-fundamentos",
|
|
244
|
+
"habilidades/proceso-ddia-streaming"
|
|
238
245
|
],
|
|
239
246
|
"targets": [
|
|
240
247
|
"claude",
|
|
@@ -886,7 +893,9 @@
|
|
|
886
893
|
"reglas/usar-sistema-swl.md",
|
|
887
894
|
"reglas/arreglar-al-detectar.md",
|
|
888
895
|
"reglas/analisis-previo-tareas-grandes.md",
|
|
889
|
-
"reglas/registro-componentes-nuevos.md"
|
|
896
|
+
"reglas/registro-componentes-nuevos.md",
|
|
897
|
+
"reglas/auditorias-documentales-estructurales.md",
|
|
898
|
+
"reglas/intent-engineering.md"
|
|
890
899
|
],
|
|
891
900
|
"targets": [
|
|
892
901
|
"claude",
|
|
@@ -1005,6 +1014,7 @@
|
|
|
1005
1014
|
"hooks/inbox-aviso.js",
|
|
1006
1015
|
"hooks/aiisms-detector.js",
|
|
1007
1016
|
"hooks/claudemd-bloat-detector.js",
|
|
1017
|
+
"hooks/validar-intent-spec.js",
|
|
1008
1018
|
"hooks/sugerir-regenerar-inventario.js",
|
|
1009
1019
|
"hooks/sugerir-contribuir.js",
|
|
1010
1020
|
"hooks/_run-hook.sh",
|