@saulwade/swl-ses 1.6.1 → 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 +2 -2
- 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 +23 -0
- package/agentes/devops-ci-swl.md +24 -0
- package/agentes/migrador-swl.md +22 -0
- package/agentes/pagos-swl.md +25 -0
- package/agentes/release-manager-swl.md +24 -0
- package/agentes/sre-swl.md +24 -0
- package/comandos/swl/planear-fase.md +16 -0
- package/habilidades/aprender-de-git-diff/SKILL.md +288 -0
- package/habilidades/diseno-herramientas-agente/SKILL.md +17 -1
- package/habilidades/meta-skills-estandar/SKILL.md +6 -0
- package/habilidades/meta-skills-estandar/recursos/skill-judge-rubrica.md +281 -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/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 +11 -2
- package/manifiestos/skills-lock.json +90 -41
- package/package.json +2 -2
- package/plugin.json +9 -2
- 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 +3 -1
- package/scripts/lib/schema-version.js +164 -0
- package/scripts/validar-manifest.js +1 -1
- package/scripts/validar.js +3 -2
|
@@ -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",
|
|
@@ -887,7 +894,8 @@
|
|
|
887
894
|
"reglas/arreglar-al-detectar.md",
|
|
888
895
|
"reglas/analisis-previo-tareas-grandes.md",
|
|
889
896
|
"reglas/registro-componentes-nuevos.md",
|
|
890
|
-
"reglas/auditorias-documentales-estructurales.md"
|
|
897
|
+
"reglas/auditorias-documentales-estructurales.md",
|
|
898
|
+
"reglas/intent-engineering.md"
|
|
891
899
|
],
|
|
892
900
|
"targets": [
|
|
893
901
|
"claude",
|
|
@@ -1006,6 +1014,7 @@
|
|
|
1006
1014
|
"hooks/inbox-aviso.js",
|
|
1007
1015
|
"hooks/aiisms-detector.js",
|
|
1008
1016
|
"hooks/claudemd-bloat-detector.js",
|
|
1017
|
+
"hooks/validar-intent-spec.js",
|
|
1009
1018
|
"hooks/sugerir-regenerar-inventario.js",
|
|
1010
1019
|
"hooks/sugerir-contribuir.js",
|
|
1011
1020
|
"hooks/_run-hook.sh",
|