@qubiit/lmagent 3.1.4 → 3.1.8

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 (38) hide show
  1. package/.agents/rules/00-master.md +2 -2
  2. package/.agents/skills/ai-agent-engineer/SKILL.md +1 -1
  3. package/.agents/skills/api-designer/SKILL.md +1 -1
  4. package/.agents/skills/architect/SKILL.md +1 -1
  5. package/.agents/skills/automation-engineer/SKILL.md +1 -1
  6. package/.agents/skills/backend-engineer/SKILL.md +1 -1
  7. package/.agents/skills/bmad-methodology/SKILL.md +1 -1
  8. package/.agents/skills/browser-agent/SKILL.md +1 -1
  9. package/.agents/skills/code-reviewer/SKILL.md +1 -1
  10. package/.agents/skills/data-engineer/SKILL.md +1 -1
  11. package/.agents/skills/devops-engineer/SKILL.md +1 -1
  12. package/.agents/skills/document-generator/SKILL.md +1 -1
  13. package/.agents/skills/frontend-engineer/SKILL.md +1 -1
  14. package/.agents/skills/git-workflow/SKILL.md +1 -1
  15. package/.agents/skills/mcp-builder/SKILL.md +1 -1
  16. package/.agents/skills/mobile-engineer/SKILL.md +1 -1
  17. package/.agents/skills/orchestrator/SKILL.md +1 -1
  18. package/.agents/skills/performance-engineer/SKILL.md +1 -1
  19. package/.agents/skills/product-manager/SKILL.md +1 -1
  20. package/.agents/skills/prompt-engineer/SKILL.md +1 -1
  21. package/.agents/skills/qa-engineer/SKILL.md +1 -1
  22. package/.agents/skills/scrum-master/SKILL.md +1 -1
  23. package/.agents/skills/security-analyst/SKILL.md +1 -1
  24. package/.agents/skills/seo-auditor/SKILL.md +1 -1
  25. package/.agents/skills/spec-driven-dev/SKILL.md +1 -1
  26. package/.agents/skills/supabase-expert/SKILL.md +1 -1
  27. package/.agents/skills/swe-agent/SKILL.md +1 -1
  28. package/.agents/skills/systematic-debugger/SKILL.md +1 -1
  29. package/.agents/skills/tech-lead/SKILL.md +1 -1
  30. package/.agents/skills/technical-writer/SKILL.md +1 -1
  31. package/.agents/skills/testing-strategist/SKILL.md +1 -1
  32. package/.agents/skills/ux-ui-designer/SKILL.md +1 -1
  33. package/install.js +43 -24
  34. package/package.json +3 -2
  35. package/scripts/create_skill.js +300 -0
  36. package/scripts/fix_descriptions.js +51 -0
  37. package/scripts/token-analyzer.js +275 -0
  38. package/scripts/validate_skills.js +285 -0
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * LMAgent Skills Validator — v3.0.13
5
+ *
6
+ * Valida la integridad de todos los skills del framework.
7
+ * Verifica: frontmatter YAML, campos obligatorios, estructura de directorio.
8
+ *
9
+ * Uso:
10
+ * node scripts/validate_skills.js # Validar todos
11
+ * node scripts/validate_skills.js backend # Validar uno específico
12
+ *
13
+ * Exit codes:
14
+ * 0 = Todo OK
15
+ * 1 = Errores encontrados
16
+ */
17
+
18
+ import { readFileSync, readdirSync, existsSync, statSync } from 'fs';
19
+ import { join, resolve, dirname } from 'path';
20
+ import { fileURLToPath } from 'url';
21
+ import gradient from 'gradient-string';
22
+ import chalk from 'chalk';
23
+
24
+ const __filename = fileURLToPath(import.meta.url);
25
+ const __dirname = dirname(__filename);
26
+ const ROOT = resolve(__dirname, '..');
27
+ const SKILLS_DIR = join(ROOT, 'skills');
28
+
29
+ // ─── Configuración ────────────────────────────────────────────
30
+ const REQUIRED_FIELDS = ['name', 'description', 'role', 'type', 'version', 'icon', 'expertise', 'activates_on', 'triggers'];
31
+ const VALID_TYPES = ['agent_persona', 'methodology'];
32
+ const CURRENT_VERSION = '3.0.13';
33
+ const OPTIONAL_DIRS = ['scripts', 'references', 'assets'];
34
+
35
+ // ─── Colores (sin dependencias) ───────────────────────────────
36
+ const c = {
37
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
38
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
39
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
40
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
41
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
42
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
43
+ };
44
+
45
+ // ─── Parser de Frontmatter YAML (simple, sin deps) ───────────
46
+ function parseFrontmatter(content) {
47
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
48
+ if (!match) return null;
49
+
50
+ const yaml = match[1];
51
+ const result = {};
52
+ let currentKey = null;
53
+ let currentList = null;
54
+
55
+ for (const line of yaml.split(/\r?\n/)) {
56
+ // Lista: " - item"
57
+ const listMatch = line.match(/^\s{2,}- (.+)/);
58
+ if (listMatch && currentKey) {
59
+ if (!currentList) {
60
+ currentList = [];
61
+ result[currentKey] = currentList;
62
+ }
63
+ currentList.push(listMatch[1].trim());
64
+ continue;
65
+ }
66
+
67
+ // Key-value: "key: value"
68
+ const kvMatch = line.match(/^(\w[\w_]*)\s*:\s*(.*)/);
69
+ if (kvMatch) {
70
+ currentKey = kvMatch[1];
71
+ const rawValue = kvMatch[2].trim();
72
+ currentList = null;
73
+
74
+ if (rawValue === '' || rawValue === '|' || rawValue === '>') {
75
+ // Será un bloque o lista
76
+ result[currentKey] = rawValue;
77
+ } else {
78
+ // Valor simple — limpiar comillas
79
+ result[currentKey] = rawValue.replace(/^["']|["']$/g, '');
80
+ }
81
+ }
82
+ }
83
+
84
+ return result;
85
+ }
86
+
87
+ // ─── Validar un Skill ─────────────────────────────────────────
88
+ function validateSkill(skillDir) {
89
+ const skillName = skillDir.split(/[/\\]/).pop();
90
+ const skillMdPath = join(skillDir, 'SKILL.md');
91
+ const errors = [];
92
+ const warnings = [];
93
+
94
+ // 1. Verificar SKILL.md existe
95
+ if (!existsSync(skillMdPath)) {
96
+ errors.push('Falta archivo SKILL.md');
97
+ return { skillName, errors, warnings, frontmatter: null };
98
+ }
99
+
100
+ // 2. Parsear frontmatter
101
+ const content = readFileSync(skillMdPath, 'utf-8');
102
+ const fm = parseFrontmatter(content);
103
+
104
+ if (!fm) {
105
+ errors.push('No se encontró frontmatter YAML (debe empezar con ---)');
106
+ return { skillName, errors, warnings, frontmatter: null };
107
+ }
108
+
109
+ // 3. Verificar campos obligatorios
110
+ for (const field of REQUIRED_FIELDS) {
111
+ if (!(field in fm)) {
112
+ errors.push(`Campo obligatorio faltante: ${field}`);
113
+ } else if (fm[field] === '' || fm[field] === undefined) {
114
+ errors.push(`Campo obligatorio vacío: ${field}`);
115
+ }
116
+ }
117
+
118
+ // 4. Verificar tipo válido
119
+ if (fm.type && !VALID_TYPES.includes(fm.type)) {
120
+ warnings.push(`Tipo desconocido: "${fm.type}" (esperado: ${VALID_TYPES.join(', ')})`);
121
+ }
122
+
123
+ // 5. Verificar versión
124
+ if (fm.version) {
125
+ // Force string comparison for semver
126
+ const ver = String(fm.version);
127
+ if (ver !== CURRENT_VERSION) {
128
+ warnings.push(`Versión ${ver} ≠ ${CURRENT_VERSION} (actual del framework)`);
129
+ }
130
+ }
131
+
132
+ // 6. Verificar que expertise sea una lista
133
+ if (fm.expertise && !Array.isArray(fm.expertise)) {
134
+ errors.push('Campo "expertise" debe ser una lista (array)');
135
+ }
136
+
137
+ // 7. Verificar que activates_on sea una lista
138
+ if (fm.activates_on && !Array.isArray(fm.activates_on)) {
139
+ errors.push('Campo "activates_on" debe ser una lista (array)');
140
+ }
141
+
142
+ // 8. Verificar que triggers sea una lista y empiece con /
143
+ if (fm.triggers) {
144
+ if (!Array.isArray(fm.triggers)) {
145
+ errors.push('Campo "triggers" debe ser una lista (array)');
146
+ } else {
147
+ for (const trigger of fm.triggers) {
148
+ if (!trigger.startsWith('/')) {
149
+ warnings.push(`Trigger "${trigger}" no empieza con / (convención)`);
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ // 9. Verificar nombre del directorio vs nombre del skill
156
+ const expectedDirName = fm.name
157
+ ? fm.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '')
158
+ : null;
159
+ // Solo warning, no error, porque los nombres pueden variar razonablemente
160
+
161
+ // 10. Verificar secciones del contenido
162
+ const expectedSections = [
163
+ { name: 'System Prompt', alternatives: ['Persona', 'Role Definition'] },
164
+ { name: 'Definition of Done', alternatives: ['Done', 'Criterios de Aceptación'] }
165
+ ];
166
+
167
+ for (const section of expectedSections) {
168
+ const found = content.includes(section.name) ||
169
+ section.alternatives.some(alt => content.includes(alt));
170
+
171
+ if (!found) {
172
+ warnings.push(`Sección recomendada faltante: "${section.name}" (o alternativas: ${section.alternatives.join(', ')})`);
173
+ }
174
+ }
175
+
176
+ // 11. Verificar subdirectorios opcionales
177
+ const extras = [];
178
+ for (const dir of OPTIONAL_DIRS) {
179
+ const dirPath = join(skillDir, dir);
180
+ if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
181
+ const files = readdirSync(dirPath);
182
+ extras.push(`${dir}/ (${files.length} archivos)`);
183
+ }
184
+ }
185
+
186
+ // 12. Verificar que el SKILL.md tenga contenido sustancial
187
+ const lines = content.split(/\r?\n/).length;
188
+ if (lines < 50) {
189
+ warnings.push(`SKILL.md tiene solo ${lines} líneas (recomendado: 100+)`);
190
+ }
191
+
192
+ return { skillName, errors, warnings, frontmatter: fm, extras, lines };
193
+ }
194
+
195
+ function main() {
196
+ const filterSkill = process.argv[2];
197
+
198
+ const c = chalk;
199
+
200
+ console.log(c.bold('\n🔍 LMAgent Skill Validator v3.0.0\n'));
201
+ console.log(chalk.dim(` Directorio: ${SKILLS_DIR}`));
202
+ console.log(chalk.dim(` Campos obligatorios: ${REQUIRED_FIELDS.length}`));
203
+ console.log('');
204
+
205
+
206
+ if (!existsSync(SKILLS_DIR)) {
207
+ console.error(c.red('❌ No se encontró el directorio skills/'));
208
+ process.exit(1);
209
+ }
210
+
211
+ // Obtener lista de skills
212
+ let skillDirs = readdirSync(SKILLS_DIR)
213
+ .filter(item => {
214
+ const itemPath = join(SKILLS_DIR, item);
215
+ return statSync(itemPath).isDirectory();
216
+ })
217
+ .map(item => join(SKILLS_DIR, item));
218
+
219
+ if (filterSkill) {
220
+ skillDirs = skillDirs.filter(d => d.includes(filterSkill));
221
+ if (skillDirs.length === 0) {
222
+ console.error(c.red(`❌ No se encontró skill que contenga "${filterSkill}"`));
223
+ process.exit(1);
224
+ }
225
+ }
226
+
227
+ let totalErrors = 0;
228
+ let totalWarnings = 0;
229
+ const results = [];
230
+
231
+ for (const skillDir of skillDirs) {
232
+ const result = validateSkill(skillDir);
233
+ results.push(result);
234
+ totalErrors += result.errors.length;
235
+ totalWarnings += result.warnings.length;
236
+ }
237
+
238
+ // ─── Reporte ────────────────────────────────────────────────
239
+ console.log(c.bold(`📊 Validando ${results.length} skills...\n`));
240
+
241
+ for (const r of results) {
242
+ const hasErrors = r.errors.length > 0;
243
+ const hasWarnings = r.warnings.length > 0;
244
+ const icon = hasErrors ? '❌' : hasWarnings ? '⚠️' : '✅';
245
+ const nameColor = hasErrors ? c.red : hasWarnings ? c.yellow : c.green;
246
+
247
+ let info = '';
248
+ if (r.frontmatter) {
249
+ info = c.dim(` (${r.frontmatter.icon || '?'} ${r.frontmatter.type || '?'} | ${r.lines || '?'} líneas)`);
250
+ }
251
+ if (r.extras && r.extras.length > 0) {
252
+ info += c.dim(` [${r.extras.join(', ')}]`);
253
+ }
254
+
255
+ console.log(` ${icon} ${nameColor(r.skillName)}${info}`);
256
+
257
+ for (const err of r.errors) {
258
+ console.log(` ${c.red('ERROR')}: ${err}`);
259
+ }
260
+ for (const warn of r.warnings) {
261
+ console.log(` ${c.yellow('WARN')}: ${warn}`);
262
+ }
263
+ }
264
+
265
+ // ─── Resumen ────────────────────────────────────────────────
266
+ console.log(c.bold('\n' + '─'.repeat(60)));
267
+ console.log(c.bold('📋 Resumen:'));
268
+ console.log(` Skills validados: ${c.cyan(results.length.toString())}`);
269
+ console.log(` Errores: ${totalErrors > 0 ? c.red(totalErrors.toString()) : c.green('0')}`);
270
+ console.log(` Warnings: ${totalWarnings > 0 ? c.yellow(totalWarnings.toString()) : c.green('0')}`);
271
+ console.log(c.bold('─'.repeat(60) + '\n'));
272
+
273
+ if (totalErrors > 0) {
274
+ console.log(c.red('❌ Validación FALLIDA — hay errores que corregir.\n'));
275
+ process.exit(1);
276
+ } else if (totalWarnings > 0) {
277
+ console.log(c.yellow('⚠️ Validación OK con warnings.\n'));
278
+ process.exit(0);
279
+ } else {
280
+ console.log(c.green('✅ Todos los skills son válidos.\n'));
281
+ process.exit(0);
282
+ }
283
+ }
284
+
285
+ main();