swl-ses 3.3.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 +425 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/accesibilidad-wcag-swl.md +683 -0
- package/agentes/arquitecto-swl.md +210 -0
- package/agentes/auto-evolucion-swl.md +408 -0
- package/agentes/backend-api-swl.md +442 -0
- package/agentes/backend-node-swl.md +439 -0
- package/agentes/backend-python-swl.md +469 -0
- package/agentes/backend-workers-swl.md +444 -0
- package/agentes/cloud-infra-swl.md +466 -0
- package/agentes/consolidador-swl.md +487 -0
- package/agentes/datos-swl.md +568 -0
- package/agentes/depurador-swl.md +301 -0
- package/agentes/devops-ci-swl.md +352 -0
- package/agentes/disenador-ui-swl.md +546 -0
- package/agentes/documentador-swl.md +323 -0
- package/agentes/frontend-angular-swl.md +603 -0
- package/agentes/frontend-css-swl.md +700 -0
- package/agentes/frontend-react-swl.md +672 -0
- package/agentes/frontend-swl.md +483 -0
- package/agentes/frontend-tailwind-swl.md +808 -0
- package/agentes/implementador-swl.md +235 -0
- package/agentes/investigador-swl.md +274 -0
- package/agentes/investigador-ux-swl.md +482 -0
- package/agentes/migrador-swl.md +389 -0
- package/agentes/mobile-android-swl.md +473 -0
- package/agentes/mobile-cross-swl.md +501 -0
- package/agentes/mobile-ios-swl.md +464 -0
- package/agentes/notificador-swl.md +886 -0
- package/agentes/observabilidad-swl.md +408 -0
- package/agentes/orquestador-swl.md +490 -0
- package/agentes/planificador-swl.md +222 -0
- package/agentes/producto-prd-swl.md +565 -0
- package/agentes/release-manager-swl.md +545 -0
- package/agentes/rendimiento-swl.md +691 -0
- package/agentes/revisor-codigo-swl.md +254 -0
- package/agentes/revisor-seguridad-swl.md +316 -0
- package/agentes/tdd-qa-swl.md +323 -0
- package/agentes/ux-disenador-swl.md +498 -0
- package/bin/swl-ses.js +119 -0
- package/comandos/swl/actualizar.md +117 -0
- package/comandos/swl/aprender.md +348 -0
- package/comandos/swl/auditar-deps.md +390 -0
- package/comandos/swl/autoresearch.md +346 -0
- package/comandos/swl/checkpoint.md +296 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/crear-skill.md +609 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +302 -0
- package/comandos/swl/evolucionar.md +377 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/mapear-codebase.md +205 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/release.md +405 -0
- package/comandos/swl/salud.md +382 -0
- package/comandos/swl/verificar.md +292 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
- package/habilidades/angular-avanzado/SKILL.md +491 -0
- package/habilidades/angular-moderno/SKILL.md +326 -0
- package/habilidades/api-rest-diseno/SKILL.md +302 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
- package/habilidades/async-python/SKILL.md +474 -0
- package/habilidades/auth-patrones/SKILL.md +488 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
- package/habilidades/autoresearch/SKILL.md +248 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/checklist-calidad/SKILL.md +247 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +224 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/cloud-aws/SKILL.md +497 -0
- package/habilidades/compactacion-contexto/SKILL.md +201 -0
- package/habilidades/contenedores-docker/SKILL.md +453 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/css-moderno/SKILL.md +463 -0
- package/habilidades/datos-etl/SKILL.md +486 -0
- package/habilidades/dependencias-auditoria/SKILL.md +293 -0
- package/habilidades/deprecacion-migracion/SKILL.md +485 -0
- package/habilidades/design-tokens/SKILL.md +519 -0
- package/habilidades/discutir-fase/SKILL.md +167 -0
- package/habilidades/diseno-responsivo/SKILL.md +326 -0
- package/habilidades/django-experto/SKILL.md +395 -0
- package/habilidades/doc-sync/SKILL.md +259 -0
- package/habilidades/ejecutar-fase/SKILL.md +199 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/event-driven/SKILL.md +580 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
- package/habilidades/fastapi-experto/SKILL.md +368 -0
- package/habilidades/frontend-avanzado/SKILL.md +555 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
- package/habilidades/iam-secretos/SKILL.md +511 -0
- package/habilidades/instalar-sistema/SKILL.md +140 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
- package/habilidades/manejo-errores/SKILL.md +512 -0
- package/habilidades/mapear-codebase/SKILL.md +199 -0
- package/habilidades/microservicios/SKILL.md +473 -0
- package/habilidades/mobile-flutter/SKILL.md +566 -0
- package/habilidades/mobile-react-native/SKILL.md +493 -0
- package/habilidades/monitoring-alertas/SKILL.md +447 -0
- package/habilidades/node-experto/SKILL.md +521 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/nuevo-proyecto/SKILL.md +183 -0
- package/habilidades/patrones-python/SKILL.md +381 -0
- package/habilidades/performance-baseline/SKILL.md +243 -0
- package/habilidades/planear-fase/SKILL.md +184 -0
- package/habilidades/postgresql-experto/SKILL.md +379 -0
- package/habilidades/react-experto/SKILL.md +434 -0
- package/habilidades/react-optimizacion/SKILL.md +328 -0
- package/habilidades/release-semver/SKILL.md +226 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/sql-optimizacion/SKILL.md +314 -0
- package/habilidades/tailwind-experto/SKILL.md +412 -0
- package/habilidades/tdd-workflow/SKILL.md +267 -0
- package/habilidades/testing-python/SKILL.md +350 -0
- package/habilidades/threat-model-lite/SKILL.md +218 -0
- package/habilidades/typescript-avanzado/SKILL.md +454 -0
- package/habilidades/ux-diseno/SKILL.md +488 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificar-trabajo/SKILL.md +208 -0
- package/habilidades/wireframes-flujos/SKILL.md +396 -0
- package/habilidades/workflow-claude-code/SKILL.md +359 -0
- package/hooks/calidad-pre-commit.js +578 -0
- package/hooks/escaneo-secretos.js +302 -0
- package/hooks/extraccion-aprendizajes.js +550 -0
- package/hooks/linea-estado.js +249 -0
- package/hooks/monitor-contexto.js +230 -0
- package/hooks/proteccion-rutas.js +249 -0
- package/manifiestos/hooks-config.json +41 -0
- package/manifiestos/modulos.json +318 -0
- package/manifiestos/perfiles.json +189 -0
- package/package.json +45 -0
- package/plantillas/PROJECT.md +122 -0
- package/plantillas/REQUIREMENTS.md +132 -0
- package/plantillas/ROADMAP.md +143 -0
- package/plantillas/STATE.md +109 -0
- package/plantillas/research/ARCHITECTURE.md +220 -0
- package/plantillas/research/FEATURES.md +175 -0
- package/plantillas/research/PITFALLS.md +299 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/SUMMARY.md +165 -0
- package/plugin.json +144 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +183 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +179 -0
- package/reglas/git-workflow.md +186 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +473 -0
- package/scripts/actualizar.js +51 -0
- package/scripts/desinstalar.js +86 -0
- package/scripts/doctor.js +222 -0
- package/scripts/inicializar.js +89 -0
- package/scripts/instalador.js +333 -0
- package/scripts/lib/detectar-runtime.js +177 -0
- package/scripts/lib/estado.js +112 -0
- package/scripts/lib/hooks-settings.js +283 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/publicar.js +209 -0
- package/scripts/validar.js +120 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: validacion-ci-sistema
|
|
3
|
+
description: Validación de integridad del sistema SWL inspirado en ECC CI stack. Cubre validación de frontmatter de agentes, validación de skills (SKILL.md presente y no vacío), validación de hooks (sintaxis Node.js, exit codes), validación de comandos, validación de reglas, catálogo automático con métricas y script ejecutable de validación.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Validación e Integridad del Sistema SWL
|
|
7
|
+
|
|
8
|
+
## Propósito
|
|
9
|
+
|
|
10
|
+
El sistema SWL crece con el tiempo: nuevos agentes, skills, hooks, comandos
|
|
11
|
+
y reglas se agregan continuamente. Sin validación, el sistema acumula:
|
|
12
|
+
- Agentes con frontmatter incompleto que no se pueden invocar
|
|
13
|
+
- Skills sin SKILL.md que el loader ignora silenciosamente
|
|
14
|
+
- Hooks que fallan con códigos de salida incorrectos (bloqueando el flujo)
|
|
15
|
+
- Comandos con formato inválido que producen errores crípticos
|
|
16
|
+
|
|
17
|
+
Este skill define el sistema de validación de integridad del repositorio SWL.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 1. Campos Obligatorios en Frontmatter de Agentes
|
|
22
|
+
|
|
23
|
+
### Campos requeridos por familia
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
// validation/schemas/agente.schema.js
|
|
27
|
+
|
|
28
|
+
const CAMPOS_AGENTE = {
|
|
29
|
+
// Campos obligatorios para TODOS los agentes
|
|
30
|
+
obligatorios: ['name', 'description'],
|
|
31
|
+
|
|
32
|
+
// Campos opcionales con valores válidos
|
|
33
|
+
opcionales: {
|
|
34
|
+
model: ['claude-opus-4-5', 'claude-sonnet-4-6', 'claude-haiku-3-5'],
|
|
35
|
+
permissionMode: ['default', 'acceptEdits', 'bypassPermissions', 'plan'],
|
|
36
|
+
color: ['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'cyan', 'white'],
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Campos que deben existir si existen otros
|
|
40
|
+
condicionales: {
|
|
41
|
+
// Si tiene 'tools', debe ser array de strings conocidos
|
|
42
|
+
tools: {
|
|
43
|
+
valoresPermitidos: [
|
|
44
|
+
'Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob',
|
|
45
|
+
'WebSearch', 'WebFetch', 'TodoRead', 'TodoWrite',
|
|
46
|
+
'Task', 'Skill', 'mcp__context7__resolve-library-id',
|
|
47
|
+
'mcp__context7__get-library-docs',
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Función de validación de agente
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
// validation/validators/agente.validator.js
|
|
58
|
+
import { parse } from 'yaml';
|
|
59
|
+
import { readFileSync } from 'fs';
|
|
60
|
+
|
|
61
|
+
export function validarAgente(rutaArchivo) {
|
|
62
|
+
const contenido = readFileSync(rutaArchivo, 'utf8');
|
|
63
|
+
const errores = [];
|
|
64
|
+
|
|
65
|
+
// Extraer frontmatter
|
|
66
|
+
const match = contenido.match(/^---\n([\s\S]*?)\n---/);
|
|
67
|
+
if (!match) {
|
|
68
|
+
return [{ campo: 'frontmatter', error: 'No se encontró frontmatter YAML' }];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let frontmatter;
|
|
72
|
+
try {
|
|
73
|
+
frontmatter = parse(match[1]);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return [{ campo: 'frontmatter', error: `YAML inválido: ${e.message}` }];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Campos obligatorios
|
|
79
|
+
for (const campo of CAMPOS_AGENTE.obligatorios) {
|
|
80
|
+
if (!frontmatter[campo] || String(frontmatter[campo]).trim() === '') {
|
|
81
|
+
errores.push({
|
|
82
|
+
campo,
|
|
83
|
+
error: `Campo obligatorio ausente o vacío: '${campo}'`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Validar description — mínimo 20 caracteres para ser útil
|
|
89
|
+
if (frontmatter.description && frontmatter.description.length < 20) {
|
|
90
|
+
errores.push({
|
|
91
|
+
campo: 'description',
|
|
92
|
+
error: `Description demasiado corta (${frontmatter.description.length} chars, mínimo 20)`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Campos opcionales con valores permitidos
|
|
97
|
+
for (const [campo, valoresPermitidos] of Object.entries(CAMPOS_AGENTE.opcionales)) {
|
|
98
|
+
if (frontmatter[campo] !== undefined) {
|
|
99
|
+
if (!valoresPermitidos.includes(frontmatter[campo])) {
|
|
100
|
+
errores.push({
|
|
101
|
+
campo,
|
|
102
|
+
error: `Valor inválido '${frontmatter[campo]}'. Permitidos: ${valoresPermitidos.join(', ')}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Validar tools
|
|
109
|
+
if (frontmatter.tools) {
|
|
110
|
+
const toolsArray = String(frontmatter.tools).split(',').map(t => t.trim());
|
|
111
|
+
for (const tool of toolsArray) {
|
|
112
|
+
if (!CAMPOS_AGENTE.condicionales.tools.valoresPermitidos.includes(tool)) {
|
|
113
|
+
errores.push({
|
|
114
|
+
campo: 'tools',
|
|
115
|
+
error: `Tool desconocida: '${tool}'`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validar nombre = nombre del archivo (kebab-case)
|
|
122
|
+
const nombreEsperado = path.basename(rutaArchivo, '.md');
|
|
123
|
+
if (frontmatter.name && frontmatter.name !== nombreEsperado) {
|
|
124
|
+
errores.push({
|
|
125
|
+
campo: 'name',
|
|
126
|
+
error: `name '${frontmatter.name}' no coincide con nombre de archivo '${nombreEsperado}'`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return errores;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 2. Validación de Skills
|
|
137
|
+
|
|
138
|
+
### Reglas de validación de skill
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// validation/validators/skill.validator.js
|
|
142
|
+
import { statSync, readFileSync } from 'fs';
|
|
143
|
+
import path from 'path';
|
|
144
|
+
|
|
145
|
+
export function validarSkill(directorioSkill) {
|
|
146
|
+
const errores = [];
|
|
147
|
+
const rutaSkill = path.join(directorioSkill, 'SKILL.md');
|
|
148
|
+
|
|
149
|
+
// Regla 1: SKILL.md debe existir
|
|
150
|
+
try {
|
|
151
|
+
statSync(rutaSkill);
|
|
152
|
+
} catch {
|
|
153
|
+
errores.push({
|
|
154
|
+
skill: path.basename(directorioSkill),
|
|
155
|
+
error: 'SKILL.md no encontrado en el directorio',
|
|
156
|
+
});
|
|
157
|
+
return errores;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const contenido = readFileSync(rutaSkill, 'utf8');
|
|
161
|
+
|
|
162
|
+
// Regla 2: No debe estar vacío
|
|
163
|
+
if (contenido.trim().length === 0) {
|
|
164
|
+
errores.push({ skill: path.basename(directorioSkill), error: 'SKILL.md está vacío' });
|
|
165
|
+
return errores;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Regla 3: Frontmatter obligatorio
|
|
169
|
+
const match = contenido.match(/^---\n([\s\S]*?)\n---/);
|
|
170
|
+
if (!match) {
|
|
171
|
+
errores.push({ skill: path.basename(directorioSkill), error: 'Sin frontmatter YAML' });
|
|
172
|
+
return errores;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let frontmatter;
|
|
176
|
+
try {
|
|
177
|
+
frontmatter = parse(match[1]);
|
|
178
|
+
} catch (e) {
|
|
179
|
+
errores.push({
|
|
180
|
+
skill: path.basename(directorioSkill),
|
|
181
|
+
error: `Frontmatter YAML inválido: ${e.message}`,
|
|
182
|
+
});
|
|
183
|
+
return errores;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Regla 4: campos name y description obligatorios
|
|
187
|
+
if (!frontmatter.name) {
|
|
188
|
+
errores.push({ skill: path.basename(directorioSkill), error: "Falta campo 'name' en frontmatter" });
|
|
189
|
+
}
|
|
190
|
+
if (!frontmatter.description) {
|
|
191
|
+
errores.push({ skill: path.basename(directorioSkill), error: "Falta campo 'description' en frontmatter" });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Regla 5: mínimo de contenido (100 líneas)
|
|
195
|
+
const lineas = contenido.split('\n').length;
|
|
196
|
+
if (lineas < 50) {
|
|
197
|
+
errores.push({
|
|
198
|
+
skill: path.basename(directorioSkill),
|
|
199
|
+
error: `SKILL.md demasiado corto (${lineas} líneas, mínimo recomendado 50)`,
|
|
200
|
+
severidad: 'advertencia',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return errores;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 3. Validación de Hooks
|
|
211
|
+
|
|
212
|
+
### Reglas de hooks
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// validation/validators/hook.validator.js
|
|
216
|
+
import { execSync } from 'child_process';
|
|
217
|
+
|
|
218
|
+
export function validarHook(rutaHook) {
|
|
219
|
+
const errores = [];
|
|
220
|
+
const contenido = readFileSync(rutaHook, 'utf8');
|
|
221
|
+
|
|
222
|
+
// Regla 1: Sintaxis Node.js válida
|
|
223
|
+
try {
|
|
224
|
+
execSync(`node --check "${rutaHook}"`, { stdio: 'pipe' });
|
|
225
|
+
} catch (e) {
|
|
226
|
+
errores.push({
|
|
227
|
+
hook: path.basename(rutaHook),
|
|
228
|
+
error: `Sintaxis Node.js inválida: ${e.stderr?.toString().split('\n')[0]}`,
|
|
229
|
+
});
|
|
230
|
+
return errores;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Regla 2: Siempre debe terminar con process.exit()
|
|
234
|
+
// Un hook que no tiene exit explícito puede quedar colgado
|
|
235
|
+
if (!contenido.includes('process.exit(')) {
|
|
236
|
+
errores.push({
|
|
237
|
+
hook: path.basename(rutaHook),
|
|
238
|
+
error: 'No tiene process.exit() explícito — puede quedar colgado',
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Regla 3: Hooks de observación nunca deben usar exit(1) o exit(2)
|
|
243
|
+
// (solo permitido en hooks de blocking/validation)
|
|
244
|
+
const esObservacion = rutaHook.includes('observe');
|
|
245
|
+
if (esObservacion && contenido.includes('process.exit(1)')) {
|
|
246
|
+
errores.push({
|
|
247
|
+
hook: path.basename(rutaHook),
|
|
248
|
+
error: 'Hook de observación no debe usar exit(1) — bloquearía el flujo del agente',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Regla 4: Debe leer stdin si es hook Pre/PostToolUse
|
|
253
|
+
const esToolHook = rutaHook.match(/pre-|post-|observe/i);
|
|
254
|
+
if (esToolHook && !contenido.includes('stdin') && !contenido.includes('readFileSync')) {
|
|
255
|
+
errores.push({
|
|
256
|
+
hook: path.basename(rutaHook),
|
|
257
|
+
error: 'Hook de herramienta no lee stdin — no puede acceder al contexto del evento',
|
|
258
|
+
severidad: 'advertencia',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Regla 5: Timeout implícito — hooks no deben hacer operaciones que puedan colgar
|
|
263
|
+
if (contenido.includes('execSync') && !contenido.includes('timeout')) {
|
|
264
|
+
errores.push({
|
|
265
|
+
hook: path.basename(rutaHook),
|
|
266
|
+
error: 'execSync sin timeout — puede bloquear indefinidamente',
|
|
267
|
+
severidad: 'advertencia',
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return errores;
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Tabla de exit codes esperados por tipo de hook
|
|
276
|
+
|
|
277
|
+
| Tipo de Hook | Exit 0 | Exit 1 | Exit 2 |
|
|
278
|
+
|-------------|--------|--------|--------|
|
|
279
|
+
| Observación (PreToolUse/PostToolUse) | Continuar siempre | PROHIBIDO | PROHIBIDO |
|
|
280
|
+
| Validación (PreToolUse bloqueante) | Permitir herramienta | Bloquear con mensaje | — |
|
|
281
|
+
| Notificación (Stop/SubagentStop) | Notificado OK | Fallo notificación (ignorar) | — |
|
|
282
|
+
| Transformación (PostToolUse) | Output aceptado | Fallo (ignorar output) | — |
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 4. Validación de Comandos
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// validation/validators/comando.validator.js
|
|
290
|
+
|
|
291
|
+
export function validarComando(directorioComando) {
|
|
292
|
+
const errores = [];
|
|
293
|
+
const nombreComando = path.basename(directorioComando);
|
|
294
|
+
|
|
295
|
+
// Regla 1: Debe existir command.md o index.md
|
|
296
|
+
const archivosPermitidos = ['command.md', `${nombreComando}.md`, 'index.md'];
|
|
297
|
+
const archivosEncontrados = archivosPermitidos.filter(f =>
|
|
298
|
+
existsSync(path.join(directorioComando, f))
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
if (archivosEncontrados.length === 0) {
|
|
302
|
+
errores.push({
|
|
303
|
+
comando: nombreComando,
|
|
304
|
+
error: `Ningún archivo de comando encontrado. Esperado: ${archivosPermitidos.join(' o ')}`,
|
|
305
|
+
});
|
|
306
|
+
return errores;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const rutaArchivo = path.join(directorioComando, archivosEncontrados[0]);
|
|
310
|
+
const contenido = readFileSync(rutaArchivo, 'utf8');
|
|
311
|
+
|
|
312
|
+
// Regla 2: Nombre en kebab-case
|
|
313
|
+
if (!/^[a-z][a-z0-9-]*[a-z0-9]$/.test(nombreComando)) {
|
|
314
|
+
errores.push({
|
|
315
|
+
comando: nombreComando,
|
|
316
|
+
error: `Nombre no está en kebab-case: '${nombreComando}'`,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Regla 3: Si tiene frontmatter, debe ser válido
|
|
321
|
+
if (contenido.startsWith('---')) {
|
|
322
|
+
const match = contenido.match(/^---\n([\s\S]*?)\n---/);
|
|
323
|
+
if (!match) {
|
|
324
|
+
errores.push({ comando: nombreComando, error: 'Frontmatter YAML sin cerrar (falta ---)' });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Regla 4: No debe estar vacío
|
|
329
|
+
const sinFrontmatter = contenido.replace(/^---[\s\S]*?---\n/, '').trim();
|
|
330
|
+
if (sinFrontmatter.length < 10) {
|
|
331
|
+
errores.push({ comando: nombreComando, error: 'Comando sin contenido de instrucciones' });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return errores;
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 5. Validación de Reglas
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
// validation/validators/regla.validator.js
|
|
344
|
+
|
|
345
|
+
export function validarReglas(directorioReglas) {
|
|
346
|
+
const errores = [];
|
|
347
|
+
const archivos = globSync('**/*.md', { cwd: directorioReglas, absolute: true });
|
|
348
|
+
|
|
349
|
+
// Regla 1: No reglas vacías
|
|
350
|
+
for (const ruta of archivos) {
|
|
351
|
+
const contenido = readFileSync(ruta, 'utf8').trim();
|
|
352
|
+
if (contenido.length < 20) {
|
|
353
|
+
errores.push({ regla: path.relative(directorioReglas, ruta), error: 'Regla vacía o demasiado corta' });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Regla 2: Detectar posibles contradicciones (heurística)
|
|
358
|
+
// Una regla que diga "SIEMPRE X" y otra que diga "NUNCA X" en el mismo dominio
|
|
359
|
+
const contenidos = archivos.map(f => ({
|
|
360
|
+
nombre: path.basename(f, '.md'),
|
|
361
|
+
texto: readFileSync(f, 'utf8').toLowerCase(),
|
|
362
|
+
}));
|
|
363
|
+
|
|
364
|
+
for (let i = 0; i < contenidos.length; i++) {
|
|
365
|
+
for (let j = i + 1; j < contenidos.length; j++) {
|
|
366
|
+
const a = contenidos[i];
|
|
367
|
+
const b = contenidos[j];
|
|
368
|
+
// Extraer términos clave de reglas SIEMPRE/NUNCA
|
|
369
|
+
const siempresA = extraerTerminosSiempre(a.texto);
|
|
370
|
+
const nuncasB = extraerTerminosNunca(b.texto);
|
|
371
|
+
const conflictos = siempresA.filter(t => nuncasB.includes(t));
|
|
372
|
+
if (conflictos.length > 0) {
|
|
373
|
+
errores.push({
|
|
374
|
+
regla: `${a.nombre} ↔ ${b.nombre}`,
|
|
375
|
+
error: `Posible contradicción: SIEMPRE ${conflictos[0]} vs NUNCA ${conflictos[0]}`,
|
|
376
|
+
severidad: 'advertencia',
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return errores;
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 6. Catálogo Automático (Métricas)
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
// validation/catalogo.js
|
|
392
|
+
|
|
393
|
+
export async function generarCatalogo(raizSistema) {
|
|
394
|
+
const ahora = new Date().toISOString();
|
|
395
|
+
|
|
396
|
+
const agentes = globSync('agentes/**/*.md', { cwd: raizSistema });
|
|
397
|
+
const skills = globSync('habilidades/*/SKILL.md', { cwd: raizSistema });
|
|
398
|
+
const hooks = globSync('hooks/**/*.js', { cwd: raizSistema });
|
|
399
|
+
const comandos = globSync('comandos/*/', { cwd: raizSistema });
|
|
400
|
+
const reglas = globSync('reglas/**/*.md', { cwd: raizSistema });
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
generado_en: ahora,
|
|
404
|
+
totales: {
|
|
405
|
+
agentes: agentes.length,
|
|
406
|
+
skills: skills.length,
|
|
407
|
+
hooks: hooks.length,
|
|
408
|
+
comandos: comandos.length,
|
|
409
|
+
reglas: reglas.length,
|
|
410
|
+
},
|
|
411
|
+
agentes: agentes.map(a => ({
|
|
412
|
+
nombre: path.basename(a, '.md'),
|
|
413
|
+
ruta: a,
|
|
414
|
+
tamano_bytes: statSync(path.join(raizSistema, a)).size,
|
|
415
|
+
})),
|
|
416
|
+
skills: skills.map(s => ({
|
|
417
|
+
nombre: path.basename(path.dirname(s)),
|
|
418
|
+
ruta: s,
|
|
419
|
+
lineas: readFileSync(path.join(raizSistema, s), 'utf8').split('\n').length,
|
|
420
|
+
})),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## 7. Script de Validación Ejecutable
|
|
428
|
+
|
|
429
|
+
```javascript
|
|
430
|
+
#!/usr/bin/env node
|
|
431
|
+
// scripts/validar-sistema.js
|
|
432
|
+
// Uso: node scripts/validar-sistema.js [--fix] [--catalog]
|
|
433
|
+
|
|
434
|
+
import path from 'path';
|
|
435
|
+
import { fileURLToPath } from 'url';
|
|
436
|
+
import { globSync } from 'glob';
|
|
437
|
+
|
|
438
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
439
|
+
const RAIZ = path.resolve(__dirname, '..');
|
|
440
|
+
|
|
441
|
+
const args = process.argv.slice(2);
|
|
442
|
+
const MODO_FIX = args.includes('--fix');
|
|
443
|
+
const GENERAR_CAT = args.includes('--catalog');
|
|
444
|
+
|
|
445
|
+
let totalErrores = 0;
|
|
446
|
+
let totalAdvert = 0;
|
|
447
|
+
|
|
448
|
+
function reporte(tipo, items, validador) {
|
|
449
|
+
console.log(`\n${'═'.repeat(50)}`);
|
|
450
|
+
console.log(`Validando ${tipo} (${items.length})...`);
|
|
451
|
+
|
|
452
|
+
for (const item of items) {
|
|
453
|
+
const errores = validador(item);
|
|
454
|
+
const criticos = errores.filter(e => e.severidad !== 'advertencia');
|
|
455
|
+
const advert = errores.filter(e => e.severidad === 'advertencia');
|
|
456
|
+
|
|
457
|
+
if (criticos.length > 0) {
|
|
458
|
+
console.error(` FAIL ${path.basename(item)}`);
|
|
459
|
+
criticos.forEach(e => console.error(` ERROR: ${e.error}`));
|
|
460
|
+
totalErrores += criticos.length;
|
|
461
|
+
}
|
|
462
|
+
if (advert.length > 0) {
|
|
463
|
+
console.warn(` WARN ${path.basename(item)}`);
|
|
464
|
+
advert.forEach(e => console.warn(` WARN: ${e.error}`));
|
|
465
|
+
totalAdvert += advert.length;
|
|
466
|
+
}
|
|
467
|
+
if (criticos.length === 0 && advert.length === 0) {
|
|
468
|
+
console.log(` OK ${path.basename(item)}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Ejecutar validaciones
|
|
474
|
+
reporte('Agentes', globSync('agentes/**/*.md', { cwd: RAIZ, absolute: true }), validarAgente);
|
|
475
|
+
reporte('Skills', globSync('habilidades/*', { cwd: RAIZ, absolute: true }), validarSkill);
|
|
476
|
+
reporte('Hooks', globSync('hooks/**/*.js', { cwd: RAIZ, absolute: true }), validarHook);
|
|
477
|
+
reporte('Comandos', globSync('comandos/*', { cwd: RAIZ, absolute: true }), validarComando);
|
|
478
|
+
|
|
479
|
+
// Resumen final
|
|
480
|
+
console.log(`\n${'═'.repeat(50)}`);
|
|
481
|
+
console.log(`RESUMEN: ${totalErrores} errores, ${totalAdvert} advertencias`);
|
|
482
|
+
|
|
483
|
+
if (GENERAR_CAT) {
|
|
484
|
+
const catalogo = await generarCatalogo(RAIZ);
|
|
485
|
+
writeFileSync(
|
|
486
|
+
path.join(RAIZ, '.claude', 'catalogo.json'),
|
|
487
|
+
JSON.stringify(catalogo, null, 2),
|
|
488
|
+
);
|
|
489
|
+
console.log('Catálogo generado en .claude/catalogo.json');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Exit code para CI
|
|
493
|
+
process.exit(totalErrores > 0 ? 1 : 0);
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### package.json — scripts de validación
|
|
497
|
+
|
|
498
|
+
```json
|
|
499
|
+
{
|
|
500
|
+
"scripts": {
|
|
501
|
+
"validate": "node scripts/validar-sistema.js",
|
|
502
|
+
"validate:catalog": "node scripts/validar-sistema.js --catalog",
|
|
503
|
+
"validate:ci": "node scripts/validar-sistema.js && echo 'Sistema íntegro'"
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Uso en CI (GitHub Actions)
|
|
509
|
+
|
|
510
|
+
```yaml
|
|
511
|
+
# .github/workflows/validate-system.yml
|
|
512
|
+
name: Validar integridad del sistema SWL
|
|
513
|
+
|
|
514
|
+
on:
|
|
515
|
+
push:
|
|
516
|
+
branches: [main]
|
|
517
|
+
pull_request:
|
|
518
|
+
|
|
519
|
+
jobs:
|
|
520
|
+
validate:
|
|
521
|
+
runs-on: ubuntu-latest
|
|
522
|
+
steps:
|
|
523
|
+
- uses: actions/checkout@v4
|
|
524
|
+
- uses: actions/setup-node@v4
|
|
525
|
+
with: { node-version: '20' }
|
|
526
|
+
- run: npm ci
|
|
527
|
+
- run: npm run validate:catalog
|
|
528
|
+
- uses: actions/upload-artifact@v4
|
|
529
|
+
with:
|
|
530
|
+
name: catalogo-sistema
|
|
531
|
+
path: .claude/catalogo.json
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Anti-patrones
|
|
537
|
+
|
|
538
|
+
- Agregar agentes sin los campos `name` y `description` (el loader los ignora)
|
|
539
|
+
- Hooks con `process.exit(1)` en observaciones (bloquean el flujo del agente)
|
|
540
|
+
- Skills con SKILL.md vacío o sin frontmatter (el Skill tool no puede cargarlos)
|
|
541
|
+
- No ejecutar la validación antes de hacer commit de cambios al sistema
|
|
542
|
+
- Ignorar advertencias acumuladas (se vuelven errores con el tiempo)
|
|
543
|
+
- Comandos en snake_case o camelCase (deben ser kebab-case)
|