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.
Files changed (177) hide show
  1. package/CLAUDE.md +425 -0
  2. package/_userland/agentes/.gitkeep +0 -0
  3. package/_userland/habilidades/.gitkeep +0 -0
  4. package/agentes/accesibilidad-wcag-swl.md +683 -0
  5. package/agentes/arquitecto-swl.md +210 -0
  6. package/agentes/auto-evolucion-swl.md +408 -0
  7. package/agentes/backend-api-swl.md +442 -0
  8. package/agentes/backend-node-swl.md +439 -0
  9. package/agentes/backend-python-swl.md +469 -0
  10. package/agentes/backend-workers-swl.md +444 -0
  11. package/agentes/cloud-infra-swl.md +466 -0
  12. package/agentes/consolidador-swl.md +487 -0
  13. package/agentes/datos-swl.md +568 -0
  14. package/agentes/depurador-swl.md +301 -0
  15. package/agentes/devops-ci-swl.md +352 -0
  16. package/agentes/disenador-ui-swl.md +546 -0
  17. package/agentes/documentador-swl.md +323 -0
  18. package/agentes/frontend-angular-swl.md +603 -0
  19. package/agentes/frontend-css-swl.md +700 -0
  20. package/agentes/frontend-react-swl.md +672 -0
  21. package/agentes/frontend-swl.md +483 -0
  22. package/agentes/frontend-tailwind-swl.md +808 -0
  23. package/agentes/implementador-swl.md +235 -0
  24. package/agentes/investigador-swl.md +274 -0
  25. package/agentes/investigador-ux-swl.md +482 -0
  26. package/agentes/migrador-swl.md +389 -0
  27. package/agentes/mobile-android-swl.md +473 -0
  28. package/agentes/mobile-cross-swl.md +501 -0
  29. package/agentes/mobile-ios-swl.md +464 -0
  30. package/agentes/notificador-swl.md +886 -0
  31. package/agentes/observabilidad-swl.md +408 -0
  32. package/agentes/orquestador-swl.md +490 -0
  33. package/agentes/planificador-swl.md +222 -0
  34. package/agentes/producto-prd-swl.md +565 -0
  35. package/agentes/release-manager-swl.md +545 -0
  36. package/agentes/rendimiento-swl.md +691 -0
  37. package/agentes/revisor-codigo-swl.md +254 -0
  38. package/agentes/revisor-seguridad-swl.md +316 -0
  39. package/agentes/tdd-qa-swl.md +323 -0
  40. package/agentes/ux-disenador-swl.md +498 -0
  41. package/bin/swl-ses.js +119 -0
  42. package/comandos/swl/actualizar.md +117 -0
  43. package/comandos/swl/aprender.md +348 -0
  44. package/comandos/swl/auditar-deps.md +390 -0
  45. package/comandos/swl/autoresearch.md +346 -0
  46. package/comandos/swl/checkpoint.md +296 -0
  47. package/comandos/swl/compactar.md +283 -0
  48. package/comandos/swl/crear-skill.md +609 -0
  49. package/comandos/swl/discutir-fase.md +230 -0
  50. package/comandos/swl/ejecutar-fase.md +302 -0
  51. package/comandos/swl/evolucionar.md +377 -0
  52. package/comandos/swl/instalar.md +220 -0
  53. package/comandos/swl/mapear-codebase.md +205 -0
  54. package/comandos/swl/nuevo-proyecto.md +154 -0
  55. package/comandos/swl/planear-fase.md +221 -0
  56. package/comandos/swl/release.md +405 -0
  57. package/comandos/swl/salud.md +382 -0
  58. package/comandos/swl/verificar.md +292 -0
  59. package/habilidades/accesibilidad-a11y/SKILL.md +584 -0
  60. package/habilidades/angular-avanzado/SKILL.md +491 -0
  61. package/habilidades/angular-moderno/SKILL.md +326 -0
  62. package/habilidades/api-rest-diseno/SKILL.md +302 -0
  63. package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
  64. package/habilidades/aprendizaje-continuo/SKILL.md +369 -0
  65. package/habilidades/async-python/SKILL.md +474 -0
  66. package/habilidades/auth-patrones/SKILL.md +488 -0
  67. package/habilidades/auto-evolucion-protocolo/SKILL.md +376 -0
  68. package/habilidades/autoresearch/SKILL.md +248 -0
  69. package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
  70. package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
  71. package/habilidades/checklist-calidad/SKILL.md +247 -0
  72. package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
  73. package/habilidades/checklist-seguridad/SKILL.md +224 -0
  74. package/habilidades/checkpoints-verificacion/SKILL.md +309 -0
  75. package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
  76. package/habilidades/ci-cd-pipelines/SKILL.md +583 -0
  77. package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
  78. package/habilidades/cloud-aws/SKILL.md +497 -0
  79. package/habilidades/compactacion-contexto/SKILL.md +201 -0
  80. package/habilidades/contenedores-docker/SKILL.md +453 -0
  81. package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
  82. package/habilidades/css-moderno/SKILL.md +463 -0
  83. package/habilidades/datos-etl/SKILL.md +486 -0
  84. package/habilidades/dependencias-auditoria/SKILL.md +293 -0
  85. package/habilidades/deprecacion-migracion/SKILL.md +485 -0
  86. package/habilidades/design-tokens/SKILL.md +519 -0
  87. package/habilidades/discutir-fase/SKILL.md +167 -0
  88. package/habilidades/diseno-responsivo/SKILL.md +326 -0
  89. package/habilidades/django-experto/SKILL.md +395 -0
  90. package/habilidades/doc-sync/SKILL.md +259 -0
  91. package/habilidades/ejecutar-fase/SKILL.md +199 -0
  92. package/habilidades/estructura-proyecto-claude/SKILL.md +459 -0
  93. package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
  94. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +213 -0
  95. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
  96. package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
  97. package/habilidades/event-driven/SKILL.md +580 -0
  98. package/habilidades/extractor-de-aprendizajes/SKILL.md +234 -0
  99. package/habilidades/fastapi-experto/SKILL.md +368 -0
  100. package/habilidades/frontend-avanzado/SKILL.md +555 -0
  101. package/habilidades/git-worktrees-paralelo/SKILL.md +246 -0
  102. package/habilidades/iam-secretos/SKILL.md +511 -0
  103. package/habilidades/instalar-sistema/SKILL.md +140 -0
  104. package/habilidades/kubernetes-orquestacion/SKILL.md +549 -0
  105. package/habilidades/manejo-errores/SKILL.md +512 -0
  106. package/habilidades/mapear-codebase/SKILL.md +199 -0
  107. package/habilidades/microservicios/SKILL.md +473 -0
  108. package/habilidades/mobile-flutter/SKILL.md +566 -0
  109. package/habilidades/mobile-react-native/SKILL.md +493 -0
  110. package/habilidades/monitoring-alertas/SKILL.md +447 -0
  111. package/habilidades/node-experto/SKILL.md +521 -0
  112. package/habilidades/notificaciones-multicanal/SKILL.md +448 -0
  113. package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
  114. package/habilidades/nuevo-proyecto/SKILL.md +183 -0
  115. package/habilidades/patrones-python/SKILL.md +381 -0
  116. package/habilidades/performance-baseline/SKILL.md +243 -0
  117. package/habilidades/planear-fase/SKILL.md +184 -0
  118. package/habilidades/postgresql-experto/SKILL.md +379 -0
  119. package/habilidades/react-experto/SKILL.md +434 -0
  120. package/habilidades/react-optimizacion/SKILL.md +328 -0
  121. package/habilidades/release-semver/SKILL.md +226 -0
  122. package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
  123. package/habilidades/sql-optimizacion/SKILL.md +314 -0
  124. package/habilidades/tailwind-experto/SKILL.md +412 -0
  125. package/habilidades/tdd-workflow/SKILL.md +267 -0
  126. package/habilidades/testing-python/SKILL.md +350 -0
  127. package/habilidades/threat-model-lite/SKILL.md +218 -0
  128. package/habilidades/typescript-avanzado/SKILL.md +454 -0
  129. package/habilidades/ux-diseno/SKILL.md +488 -0
  130. package/habilidades/validacion-ci-sistema/SKILL.md +543 -0
  131. package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
  132. package/habilidades/verificar-trabajo/SKILL.md +208 -0
  133. package/habilidades/wireframes-flujos/SKILL.md +396 -0
  134. package/habilidades/workflow-claude-code/SKILL.md +359 -0
  135. package/hooks/calidad-pre-commit.js +578 -0
  136. package/hooks/escaneo-secretos.js +302 -0
  137. package/hooks/extraccion-aprendizajes.js +550 -0
  138. package/hooks/linea-estado.js +249 -0
  139. package/hooks/monitor-contexto.js +230 -0
  140. package/hooks/proteccion-rutas.js +249 -0
  141. package/manifiestos/hooks-config.json +41 -0
  142. package/manifiestos/modulos.json +318 -0
  143. package/manifiestos/perfiles.json +189 -0
  144. package/package.json +45 -0
  145. package/plantillas/PROJECT.md +122 -0
  146. package/plantillas/REQUIREMENTS.md +132 -0
  147. package/plantillas/ROADMAP.md +143 -0
  148. package/plantillas/STATE.md +109 -0
  149. package/plantillas/research/ARCHITECTURE.md +220 -0
  150. package/plantillas/research/FEATURES.md +175 -0
  151. package/plantillas/research/PITFALLS.md +299 -0
  152. package/plantillas/research/STACK.md +233 -0
  153. package/plantillas/research/SUMMARY.md +165 -0
  154. package/plugin.json +144 -0
  155. package/reglas/accesibilidad.md +269 -0
  156. package/reglas/api-diseno.md +400 -0
  157. package/reglas/arquitectura.md +183 -0
  158. package/reglas/cloud-infra.md +247 -0
  159. package/reglas/docs.md +245 -0
  160. package/reglas/estilo-codigo.md +179 -0
  161. package/reglas/git-workflow.md +186 -0
  162. package/reglas/performance.md +195 -0
  163. package/reglas/pruebas.md +159 -0
  164. package/reglas/seguridad.md +151 -0
  165. package/reglas/skills-estandar.md +473 -0
  166. package/scripts/actualizar.js +51 -0
  167. package/scripts/desinstalar.js +86 -0
  168. package/scripts/doctor.js +222 -0
  169. package/scripts/inicializar.js +89 -0
  170. package/scripts/instalador.js +333 -0
  171. package/scripts/lib/detectar-runtime.js +177 -0
  172. package/scripts/lib/estado.js +112 -0
  173. package/scripts/lib/hooks-settings.js +283 -0
  174. package/scripts/lib/manifiestos.js +138 -0
  175. package/scripts/lib/seguridad.js +160 -0
  176. package/scripts/publicar.js +209 -0
  177. 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)