@qubiit/lmagent 2.5.0
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/.editorconfig +18 -0
- package/AGENTS.md +169 -0
- package/CLAUDE.md +122 -0
- package/CONTRIBUTING.md +90 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/config/commands.yaml +194 -0
- package/config/levels.yaml +135 -0
- package/config/models.yaml +192 -0
- package/config/settings.yaml +405 -0
- package/config/tools-extended.yaml +534 -0
- package/config/tools.yaml +437 -0
- package/docs/assets/logo.png +0 -0
- package/docs/commands.md +132 -0
- package/docs/customization-guide.md +445 -0
- package/docs/getting-started.md +154 -0
- package/docs/how-to-start.md +242 -0
- package/docs/navigation-index.md +227 -0
- package/docs/usage-guide.md +113 -0
- package/install.js +1044 -0
- package/package.json +35 -0
- package/pyproject.toml +182 -0
- package/rules/_bootstrap.md +138 -0
- package/rules/agents-ia.md +607 -0
- package/rules/api-design.md +337 -0
- package/rules/automations-n8n.md +646 -0
- package/rules/code-style.md +570 -0
- package/rules/documentation.md +98 -0
- package/rules/security.md +316 -0
- package/rules/stack.md +395 -0
- package/rules/testing.md +326 -0
- package/rules/workflow.md +353 -0
- package/scripts/create_skill.js +300 -0
- package/scripts/validate_skills.js +283 -0
- package/skills/ai-agent-engineer/SKILL.md +394 -0
- package/skills/ai-agent-engineer/references/agent-patterns.md +149 -0
- package/skills/api-designer/SKILL.md +429 -0
- package/skills/api-designer/references/api-standards.md +13 -0
- package/skills/architect/SKILL.md +285 -0
- package/skills/architect/references/c4-model.md +133 -0
- package/skills/automation-engineer/SKILL.md +352 -0
- package/skills/automation-engineer/references/n8n-patterns.md +127 -0
- package/skills/backend-engineer/SKILL.md +261 -0
- package/skills/backend-engineer/assets/fastapi-project-structure.yaml +74 -0
- package/skills/backend-engineer/references/debugging-guide.md +174 -0
- package/skills/backend-engineer/references/design-patterns.md +208 -0
- package/skills/backend-engineer/scripts/scaffold_backend.py +313 -0
- package/skills/bmad-methodology/SKILL.md +202 -0
- package/skills/bmad-methodology/references/scale-adaptive-levels.md +141 -0
- package/skills/browser-agent/SKILL.md +502 -0
- package/skills/browser-agent/scripts/playwright_setup.ts +16 -0
- package/skills/code-reviewer/SKILL.md +306 -0
- package/skills/code-reviewer/references/code-review-checklist.md +16 -0
- package/skills/data-engineer/SKILL.md +474 -0
- package/skills/data-engineer/assets/pg-monitoring-queries.sql +154 -0
- package/skills/data-engineer/references/index-strategy.md +128 -0
- package/skills/data-engineer/scripts/backup_postgres.py +221 -0
- package/skills/devops-engineer/SKILL.md +547 -0
- package/skills/devops-engineer/references/ci-cd-patterns.md +265 -0
- package/skills/devops-engineer/scripts/docker_healthcheck.py +125 -0
- package/skills/document-generator/SKILL.md +746 -0
- package/skills/document-generator/references/pdf-generation.md +22 -0
- package/skills/frontend-engineer/SKILL.md +532 -0
- package/skills/frontend-engineer/references/accessibility-guide.md +146 -0
- package/skills/frontend-engineer/scripts/audit_bundle.py +144 -0
- package/skills/git-workflow/SKILL.md +374 -0
- package/skills/git-workflow/references/git-flow.md +25 -0
- package/skills/mcp-builder/SKILL.md +471 -0
- package/skills/mcp-builder/references/mcp-server-guide.md +23 -0
- package/skills/mobile-engineer/SKILL.md +502 -0
- package/skills/mobile-engineer/references/platform-guidelines.md +160 -0
- package/skills/orchestrator/SKILL.md +246 -0
- package/skills/orchestrator/references/methodology-routing.md +117 -0
- package/skills/orchestrator/references/persona-mapping.md +85 -0
- package/skills/orchestrator/references/routing-logic.md +110 -0
- package/skills/performance-engineer/SKILL.md +549 -0
- package/skills/performance-engineer/references/caching-patterns.md +181 -0
- package/skills/performance-engineer/scripts/profile_endpoint.py +170 -0
- package/skills/product-manager/SKILL.md +488 -0
- package/skills/product-manager/references/prioritization-frameworks.md +126 -0
- package/skills/prompt-engineer/SKILL.md +433 -0
- package/skills/prompt-engineer/references/prompt-patterns.md +158 -0
- package/skills/qa-engineer/SKILL.md +441 -0
- package/skills/qa-engineer/references/testing-strategy.md +166 -0
- package/skills/qa-engineer/scripts/run_coverage.py +147 -0
- package/skills/scrum-master/SKILL.md +225 -0
- package/skills/scrum-master/references/sprint-ceremonies.md +159 -0
- package/skills/security-analyst/SKILL.md +390 -0
- package/skills/security-analyst/references/owasp-top10.md +188 -0
- package/skills/security-analyst/scripts/audit_security.py +242 -0
- package/skills/seo-auditor/SKILL.md +523 -0
- package/skills/seo-auditor/references/seo-checklist.md +17 -0
- package/skills/spec-driven-dev/SKILL.md +342 -0
- package/skills/spec-driven-dev/references/phase-gates.md +107 -0
- package/skills/supabase-expert/SKILL.md +602 -0
- package/skills/supabase-expert/references/supabase-patterns.md +19 -0
- package/skills/swe-agent/SKILL.md +311 -0
- package/skills/swe-agent/references/trajectory-format.md +134 -0
- package/skills/systematic-debugger/SKILL.md +512 -0
- package/skills/systematic-debugger/references/debugging-guide.md +12 -0
- package/skills/tech-lead/SKILL.md +409 -0
- package/skills/tech-lead/references/code-review-checklist.md +111 -0
- package/skills/technical-writer/SKILL.md +631 -0
- package/skills/technical-writer/references/doc-templates.md +218 -0
- package/skills/testing-strategist/SKILL.md +476 -0
- package/skills/testing-strategist/references/testing-pyramid.md +16 -0
- package/skills/ux-ui-designer/SKILL.md +419 -0
- package/skills/ux-ui-designer/references/design-system-foundation.md +168 -0
- package/skills_overview.txt +94 -0
- package/templates/PROJECT_KICKOFF.md +284 -0
- package/templates/SKILL_TEMPLATE.md +131 -0
- package/templates/USAGE.md +95 -0
- package/templates/agent-python/README.md +71 -0
- package/templates/agent-python/agent.py +272 -0
- package/templates/agent-python/config.yaml +76 -0
- package/templates/agent-python/prompts/system.md +109 -0
- package/templates/agent-python/requirements.txt +7 -0
- package/templates/automation-n8n/README.md +14 -0
- package/templates/automation-n8n/webhook-handler.json +57 -0
- package/templates/backend-node/Dockerfile +12 -0
- package/templates/backend-node/README.md +15 -0
- package/templates/backend-node/package.json +30 -0
- package/templates/backend-node/src/index.ts +19 -0
- package/templates/backend-node/src/routes.ts +7 -0
- package/templates/backend-node/tsconfig.json +22 -0
- package/templates/backend-python/Dockerfile +11 -0
- package/templates/backend-python/README.md +78 -0
- package/templates/backend-python/app/core/config.py +12 -0
- package/templates/backend-python/app/core/database.py +12 -0
- package/templates/backend-python/app/main.py +17 -0
- package/templates/backend-python/app/routers/__init__.py +1 -0
- package/templates/backend-python/app/routers/health.py +7 -0
- package/templates/backend-python/requirements-dev.txt +6 -0
- package/templates/backend-python/requirements.txt +4 -0
- package/templates/backend-python/tests/test_health.py +9 -0
- package/templates/checkpoint.yaml +117 -0
- package/templates/database/README.md +474 -0
- package/templates/frontend-react/README.md +446 -0
- package/templates/plan.yaml +320 -0
- package/templates/session.yaml +125 -0
- package/templates/spec.yaml +229 -0
- package/templates/tasks.yaml +330 -0
- package/workflows/bugfix-backend.md +380 -0
- package/workflows/documentation.md +232 -0
- package/workflows/generate-prd.md +320 -0
- package/workflows/ideation.md +396 -0
- package/workflows/new-agent-ia.md +497 -0
- package/workflows/new-automation.md +374 -0
- package/workflows/new-feature.md +290 -0
- package/workflows/optimize-performance.md +373 -0
- package/workflows/resolve-github-issue.md +524 -0
- package/workflows/security-review.md +291 -0
- package/workflows/spec-driven.md +476 -0
- package/workflows/testing-strategy.md +296 -0
- package/workflows/third-party-integration.md +277 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LMAgent Skill Generator — v2.3.0
|
|
5
|
+
*
|
|
6
|
+
* Genera la estructura completa de un nuevo skill interactivamente.
|
|
7
|
+
*
|
|
8
|
+
* Uso:
|
|
9
|
+
* node scripts/create_skill.js # Interactivo
|
|
10
|
+
* node scripts/create_skill.js --name "My Skill" # Semi-automático
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
import { join, resolve, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { createInterface } from 'readline';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
const ROOT = resolve(__dirname, '..');
|
|
21
|
+
const SKILLS_DIR = join(ROOT, 'skills');
|
|
22
|
+
|
|
23
|
+
// ─── Colores ──────────────────────────────────────────────────
|
|
24
|
+
const c = {
|
|
25
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
26
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
27
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
28
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
29
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
30
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ─── Prompt interactivo ───────────────────────────────────────
|
|
34
|
+
function createPrompt() {
|
|
35
|
+
const rl = createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
ask: (question, defaultValue = '') => {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
const suffix = defaultValue ? ` ${c.dim(`(${defaultValue})`)}` : '';
|
|
44
|
+
rl.question(` ${c.cyan('?')} ${question}${suffix}: `, (answer) => {
|
|
45
|
+
resolve(answer.trim() || defaultValue);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
askList: (question, hint = 'separar con comas') => {
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question(` ${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}: `, (answer) => {
|
|
52
|
+
const items = answer.split(',').map(s => s.trim()).filter(Boolean);
|
|
53
|
+
resolve(items);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
askYesNo: (question, defaultValue = true) => {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const hint = defaultValue ? 'S/n' : 's/N';
|
|
60
|
+
rl.question(` ${c.cyan('?')} ${question} ${c.dim(`(${hint})`)}: `, (answer) => {
|
|
61
|
+
if (!answer.trim()) return resolve(defaultValue);
|
|
62
|
+
resolve(['s', 'si', 'sí', 'y', 'yes'].includes(answer.trim().toLowerCase()));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
close: () => rl.close(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Generar slug del nombre ──────────────────────────────────
|
|
71
|
+
function slugify(name) {
|
|
72
|
+
return name
|
|
73
|
+
.toLowerCase()
|
|
74
|
+
.replace(/[áàäâ]/g, 'a')
|
|
75
|
+
.replace(/[éèëê]/g, 'e')
|
|
76
|
+
.replace(/[íìïî]/g, 'i')
|
|
77
|
+
.replace(/[óòöô]/g, 'o')
|
|
78
|
+
.replace(/[úùüû]/g, 'u')
|
|
79
|
+
.replace(/ñ/g, 'n')
|
|
80
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
81
|
+
.replace(/^-|-$/g, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Generar SKILL.md ─────────────────────────────────────────
|
|
85
|
+
function generateSkillMd(data) {
|
|
86
|
+
const expertise = data.expertise.map(e => ` - ${e}`).join('\n');
|
|
87
|
+
const activatesOn = data.activatesOn.map(a => ` - ${a}`).join('\n');
|
|
88
|
+
const triggers = data.triggers.map(t => ` - ${t}`).join('\n');
|
|
89
|
+
|
|
90
|
+
return `---
|
|
91
|
+
name: ${data.name}
|
|
92
|
+
description: ${data.description}
|
|
93
|
+
role: ${data.role}
|
|
94
|
+
type: ${data.type}
|
|
95
|
+
version: 2.3
|
|
96
|
+
icon: ${data.icon}
|
|
97
|
+
expertise:
|
|
98
|
+
${expertise}
|
|
99
|
+
activates_on:
|
|
100
|
+
${activatesOn}
|
|
101
|
+
triggers:
|
|
102
|
+
${triggers}
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
# ${data.name} Persona
|
|
106
|
+
|
|
107
|
+
## 🧠 System Prompt
|
|
108
|
+
> **Instrucciones para el LLM**: Copia este bloque en tu system prompt o contexto inicial.
|
|
109
|
+
|
|
110
|
+
\`\`\`markdown
|
|
111
|
+
Eres **${data.name}**, un experto en ${data.role.toLowerCase()}.
|
|
112
|
+
Tu objetivo es **[DEFINIR OBJETIVO PRINCIPAL EN MAYÚSCULAS]**.
|
|
113
|
+
Tu tono es **[Adjetivo 1, Adjetivo 2, Adjetivo 3]**.
|
|
114
|
+
|
|
115
|
+
**Principios Core:**
|
|
116
|
+
1. **[Principio 1]**: [Descripción]
|
|
117
|
+
2. **[Principio 2]**: [Descripción]
|
|
118
|
+
3. **[Principio 3]**: [Descripción]
|
|
119
|
+
4. **[Principio 4]**: [Descripción]
|
|
120
|
+
|
|
121
|
+
**Restricciones:**
|
|
122
|
+
- NUNCA [restricción 1].
|
|
123
|
+
- SIEMPRE [restricción 2].
|
|
124
|
+
- SIEMPRE [restricción 3].
|
|
125
|
+
- NUNCA [restricción 4].
|
|
126
|
+
\`\`\`
|
|
127
|
+
|
|
128
|
+
## 🔄 Arquitectura Cognitiva (Cómo Pensar)
|
|
129
|
+
|
|
130
|
+
### 1. Fase de Análisis
|
|
131
|
+
Antes de actuar, pregúntate:
|
|
132
|
+
- **Input**: ¿Qué se necesita?
|
|
133
|
+
- **Contexto**: ¿Qué restricciones existen?
|
|
134
|
+
- **Riesgo**: ¿Qué puede salir mal?
|
|
135
|
+
- **Salida**: ¿Cuál es el resultado esperado?
|
|
136
|
+
|
|
137
|
+
### 2. Fase de Diseño
|
|
138
|
+
- Definir **estructura** del entregable.
|
|
139
|
+
- Planear **enfoque** paso a paso.
|
|
140
|
+
- Identificar **dependencias** y **bloqueantes**.
|
|
141
|
+
|
|
142
|
+
### 3. Fase de Ejecución
|
|
143
|
+
- Implementar según el plan.
|
|
144
|
+
- Verificar en cada paso.
|
|
145
|
+
- Documentar decisiones.
|
|
146
|
+
|
|
147
|
+
### 4. Auto-Corrección
|
|
148
|
+
Antes de finalizar, verifica:
|
|
149
|
+
- "¿Cumple con los criterios de aceptación?"
|
|
150
|
+
- "¿Sigue los patrones del proyecto?"
|
|
151
|
+
- "¿Es mantenible y documentado?"
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Rol
|
|
156
|
+
|
|
157
|
+
${data.description}
|
|
158
|
+
|
|
159
|
+
## Responsabilidades
|
|
160
|
+
|
|
161
|
+
1. **[Responsabilidad 1]**: [Detalle]
|
|
162
|
+
2. **[Responsabilidad 2]**: [Detalle]
|
|
163
|
+
3. **[Responsabilidad 3]**: [Detalle]
|
|
164
|
+
4. **[Responsabilidad 4]**: [Detalle]
|
|
165
|
+
5. **[Responsabilidad 5]**: [Detalle]
|
|
166
|
+
|
|
167
|
+
## Stack Técnico
|
|
168
|
+
|
|
169
|
+
\`\`\`
|
|
170
|
+
[Tecnología 1] → [Propósito]
|
|
171
|
+
[Tecnología 2] → [Propósito]
|
|
172
|
+
[Tecnología 3] → [Propósito]
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
## Interacción con Otros Roles
|
|
176
|
+
|
|
177
|
+
| Rol | Colaboración |
|
|
178
|
+
|-----|-------------|
|
|
179
|
+
| [Rol 1] | [Cómo colaboran] |
|
|
180
|
+
| [Rol 2] | [Cómo colaboran] |
|
|
181
|
+
| [Rol 3] | [Cómo colaboran] |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 🛠️ Herramientas Preferidas
|
|
186
|
+
|
|
187
|
+
| Herramienta | Cuándo Usarla |
|
|
188
|
+
|-------------|---------------|
|
|
189
|
+
| \`view_file\` | [Cuándo] |
|
|
190
|
+
| \`run_command\` | [Cuándo] |
|
|
191
|
+
| \`grep_search\` | [Cuándo] |
|
|
192
|
+
| \`write_to_file\` | [Cuándo] |
|
|
193
|
+
|
|
194
|
+
## 📋 Definition of Done
|
|
195
|
+
|
|
196
|
+
Antes de considerar una tarea terminada, verifica TODO:
|
|
197
|
+
|
|
198
|
+
### Calidad
|
|
199
|
+
- [ ] [Criterio 1]
|
|
200
|
+
- [ ] [Criterio 2]
|
|
201
|
+
- [ ] [Criterio 3]
|
|
202
|
+
|
|
203
|
+
### Documentación
|
|
204
|
+
- [ ] [Criterio de documentación 1]
|
|
205
|
+
- [ ] [Criterio de documentación 2]
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
*Skill version: 2.3 | LMAgent Framework*
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Main ─────────────────────────────────────────────────────
|
|
214
|
+
async function main() {
|
|
215
|
+
console.log(c.bold('\n🛠️ LMAgent Skill Generator v2.3.0\n'));
|
|
216
|
+
|
|
217
|
+
const prompt = createPrompt();
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
// Obtener datos del skill
|
|
221
|
+
const name = await prompt.ask('Nombre del skill', '');
|
|
222
|
+
if (!name) {
|
|
223
|
+
console.log(c.red('\n❌ El nombre es obligatorio.\n'));
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const slug = slugify(name);
|
|
228
|
+
const skillDir = join(SKILLS_DIR, slug);
|
|
229
|
+
|
|
230
|
+
if (existsSync(skillDir)) {
|
|
231
|
+
console.log(c.red(`\n❌ Ya existe un skill en: ${skillDir}\n`));
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const description = await prompt.ask('Descripción breve', '');
|
|
236
|
+
const role = await prompt.ask('Rol del skill', `Experto en ${name}`);
|
|
237
|
+
const icon = await prompt.ask('Icono (emoji)', '🔧');
|
|
238
|
+
const type = await prompt.ask('Tipo (agent_persona / methodology)', 'agent_persona');
|
|
239
|
+
const expertise = await prompt.askList('Áreas de expertise');
|
|
240
|
+
const activatesOn = await prompt.askList('Cuándo se activa (contextos)');
|
|
241
|
+
|
|
242
|
+
const triggerSuggestion = `/${slug.split('-')[0]}`;
|
|
243
|
+
const triggersRaw = await prompt.askList('Triggers (con /)', `ej: ${triggerSuggestion}`);
|
|
244
|
+
const triggers = triggersRaw.length > 0 ? triggersRaw : [triggerSuggestion];
|
|
245
|
+
|
|
246
|
+
const createDirs = await prompt.askYesNo('¿Crear subdirectorios (scripts/, references/, assets/)?', false);
|
|
247
|
+
|
|
248
|
+
// Confirmar
|
|
249
|
+
console.log(c.bold('\n📋 Resumen:'));
|
|
250
|
+
console.log(` Nombre: ${c.cyan(name)}`);
|
|
251
|
+
console.log(` Slug: ${c.dim(slug)}`);
|
|
252
|
+
console.log(` Descripción: ${description}`);
|
|
253
|
+
console.log(` Rol: ${role}`);
|
|
254
|
+
console.log(` Icono: ${icon}`);
|
|
255
|
+
console.log(` Tipo: ${type}`);
|
|
256
|
+
console.log(` Expertise: ${expertise.join(', ')}`);
|
|
257
|
+
console.log(` Activa en: ${activatesOn.join(', ')}`);
|
|
258
|
+
console.log(` Triggers: ${triggers.join(', ')}`);
|
|
259
|
+
console.log(` Extra dirs: ${createDirs ? 'Sí' : 'No'}`);
|
|
260
|
+
|
|
261
|
+
const confirm = await prompt.askYesNo('\n¿Crear skill?', true);
|
|
262
|
+
if (!confirm) {
|
|
263
|
+
console.log(c.yellow('\n⚠️ Cancelado.\n'));
|
|
264
|
+
process.exit(0);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Crear estructura
|
|
268
|
+
mkdirSync(skillDir, { recursive: true });
|
|
269
|
+
|
|
270
|
+
if (createDirs) {
|
|
271
|
+
mkdirSync(join(skillDir, 'scripts'), { recursive: true });
|
|
272
|
+
mkdirSync(join(skillDir, 'references'), { recursive: true });
|
|
273
|
+
mkdirSync(join(skillDir, 'assets'), { recursive: true });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Generar SKILL.md
|
|
277
|
+
const skillMd = generateSkillMd({
|
|
278
|
+
name,
|
|
279
|
+
description,
|
|
280
|
+
role,
|
|
281
|
+
icon,
|
|
282
|
+
type,
|
|
283
|
+
expertise,
|
|
284
|
+
activatesOn,
|
|
285
|
+
triggers,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
writeFileSync(join(skillDir, 'SKILL.md'), skillMd, 'utf-8');
|
|
289
|
+
|
|
290
|
+
console.log(c.green(`\n✅ Skill creado exitosamente en: ${skillDir}`));
|
|
291
|
+
console.log(c.dim(' Editá SKILL.md para completar las secciones con placeholders [...]'));
|
|
292
|
+
console.log(c.dim(' Ejecutá: node scripts/validate_skills.js ' + slug + ' para validar'));
|
|
293
|
+
console.log('');
|
|
294
|
+
|
|
295
|
+
} finally {
|
|
296
|
+
prompt.close();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LMAgent Skills Validator — v2.3.0
|
|
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 = 2.5;
|
|
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
|
+
const ver = parseFloat(fm.version);
|
|
126
|
+
if (ver !== CURRENT_VERSION) {
|
|
127
|
+
warnings.push(`Versión ${fm.version} ≠ ${CURRENT_VERSION} (actual del framework)`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 6. Verificar que expertise sea una lista
|
|
132
|
+
if (fm.expertise && !Array.isArray(fm.expertise)) {
|
|
133
|
+
errors.push('Campo "expertise" debe ser una lista (array)');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 7. Verificar que activates_on sea una lista
|
|
137
|
+
if (fm.activates_on && !Array.isArray(fm.activates_on)) {
|
|
138
|
+
errors.push('Campo "activates_on" debe ser una lista (array)');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 8. Verificar que triggers sea una lista y empiece con /
|
|
142
|
+
if (fm.triggers) {
|
|
143
|
+
if (!Array.isArray(fm.triggers)) {
|
|
144
|
+
errors.push('Campo "triggers" debe ser una lista (array)');
|
|
145
|
+
} else {
|
|
146
|
+
for (const trigger of fm.triggers) {
|
|
147
|
+
if (!trigger.startsWith('/')) {
|
|
148
|
+
warnings.push(`Trigger "${trigger}" no empieza con / (convención)`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 9. Verificar nombre del directorio vs nombre del skill
|
|
155
|
+
const expectedDirName = fm.name
|
|
156
|
+
? fm.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, '')
|
|
157
|
+
: null;
|
|
158
|
+
// Solo warning, no error, porque los nombres pueden variar razonablemente
|
|
159
|
+
|
|
160
|
+
// 10. Verificar secciones del contenido
|
|
161
|
+
const expectedSections = [
|
|
162
|
+
{ name: 'System Prompt', alternatives: ['Persona', 'Role Definition'] },
|
|
163
|
+
{ name: 'Definition of Done', alternatives: ['Done', 'Criterios de Aceptación'] }
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const section of expectedSections) {
|
|
167
|
+
const found = content.includes(section.name) ||
|
|
168
|
+
section.alternatives.some(alt => content.includes(alt));
|
|
169
|
+
|
|
170
|
+
if (!found) {
|
|
171
|
+
warnings.push(`Sección recomendada faltante: "${section.name}" (o alternativas: ${section.alternatives.join(', ')})`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 11. Verificar subdirectorios opcionales
|
|
176
|
+
const extras = [];
|
|
177
|
+
for (const dir of OPTIONAL_DIRS) {
|
|
178
|
+
const dirPath = join(skillDir, dir);
|
|
179
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
180
|
+
const files = readdirSync(dirPath);
|
|
181
|
+
extras.push(`${dir}/ (${files.length} archivos)`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 12. Verificar que el SKILL.md tenga contenido sustancial
|
|
186
|
+
const lines = content.split(/\r?\n/).length;
|
|
187
|
+
if (lines < 50) {
|
|
188
|
+
warnings.push(`SKILL.md tiene solo ${lines} líneas (recomendado: 100+)`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { skillName, errors, warnings, frontmatter: fm, extras, lines };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function main() {
|
|
195
|
+
const filterSkill = process.argv[2];
|
|
196
|
+
|
|
197
|
+
console.log(gradient.pastel.multiline('\n🚀 LMAgent Skills Validator v2.5.0\n'));
|
|
198
|
+
console.log(chalk.dim(` Directorio: ${SKILLS_DIR}`));
|
|
199
|
+
console.log(chalk.dim(` Campos obligatorios: ${REQUIRED_FIELDS.length}`));
|
|
200
|
+
console.log('');
|
|
201
|
+
|
|
202
|
+
const c = chalk;
|
|
203
|
+
|
|
204
|
+
if (!existsSync(SKILLS_DIR)) {
|
|
205
|
+
console.error(c.red('❌ No se encontró el directorio skills/'));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Obtener lista de skills
|
|
210
|
+
let skillDirs = readdirSync(SKILLS_DIR)
|
|
211
|
+
.filter(item => {
|
|
212
|
+
const itemPath = join(SKILLS_DIR, item);
|
|
213
|
+
return statSync(itemPath).isDirectory();
|
|
214
|
+
})
|
|
215
|
+
.map(item => join(SKILLS_DIR, item));
|
|
216
|
+
|
|
217
|
+
if (filterSkill) {
|
|
218
|
+
skillDirs = skillDirs.filter(d => d.includes(filterSkill));
|
|
219
|
+
if (skillDirs.length === 0) {
|
|
220
|
+
console.error(c.red(`❌ No se encontró skill que contenga "${filterSkill}"`));
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let totalErrors = 0;
|
|
226
|
+
let totalWarnings = 0;
|
|
227
|
+
const results = [];
|
|
228
|
+
|
|
229
|
+
for (const skillDir of skillDirs) {
|
|
230
|
+
const result = validateSkill(skillDir);
|
|
231
|
+
results.push(result);
|
|
232
|
+
totalErrors += result.errors.length;
|
|
233
|
+
totalWarnings += result.warnings.length;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ─── Reporte ────────────────────────────────────────────────
|
|
237
|
+
console.log(c.bold(`📊 Validando ${results.length} skills...\n`));
|
|
238
|
+
|
|
239
|
+
for (const r of results) {
|
|
240
|
+
const hasErrors = r.errors.length > 0;
|
|
241
|
+
const hasWarnings = r.warnings.length > 0;
|
|
242
|
+
const icon = hasErrors ? '❌' : hasWarnings ? '⚠️' : '✅';
|
|
243
|
+
const nameColor = hasErrors ? c.red : hasWarnings ? c.yellow : c.green;
|
|
244
|
+
|
|
245
|
+
let info = '';
|
|
246
|
+
if (r.frontmatter) {
|
|
247
|
+
info = c.dim(` (${r.frontmatter.icon || '?'} ${r.frontmatter.type || '?'} | ${r.lines || '?'} líneas)`);
|
|
248
|
+
}
|
|
249
|
+
if (r.extras && r.extras.length > 0) {
|
|
250
|
+
info += c.dim(` [${r.extras.join(', ')}]`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(` ${icon} ${nameColor(r.skillName)}${info}`);
|
|
254
|
+
|
|
255
|
+
for (const err of r.errors) {
|
|
256
|
+
console.log(` ${c.red('ERROR')}: ${err}`);
|
|
257
|
+
}
|
|
258
|
+
for (const warn of r.warnings) {
|
|
259
|
+
console.log(` ${c.yellow('WARN')}: ${warn}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ─── Resumen ────────────────────────────────────────────────
|
|
264
|
+
console.log(c.bold('\n' + '─'.repeat(60)));
|
|
265
|
+
console.log(c.bold('📋 Resumen:'));
|
|
266
|
+
console.log(` Skills validados: ${c.cyan(results.length.toString())}`);
|
|
267
|
+
console.log(` Errores: ${totalErrors > 0 ? c.red(totalErrors.toString()) : c.green('0')}`);
|
|
268
|
+
console.log(` Warnings: ${totalWarnings > 0 ? c.yellow(totalWarnings.toString()) : c.green('0')}`);
|
|
269
|
+
console.log(c.bold('─'.repeat(60) + '\n'));
|
|
270
|
+
|
|
271
|
+
if (totalErrors > 0) {
|
|
272
|
+
console.log(c.red('❌ Validación FALLIDA — hay errores que corregir.\n'));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
} else if (totalWarnings > 0) {
|
|
275
|
+
console.log(c.yellow('⚠️ Validación OK con warnings.\n'));
|
|
276
|
+
process.exit(0);
|
|
277
|
+
} else {
|
|
278
|
+
console.log(c.green('✅ Todos los skills son válidos.\n'));
|
|
279
|
+
process.exit(0);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
main();
|