elsabro 2.0.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.
Files changed (58) hide show
  1. package/README.md +268 -0
  2. package/agents/elsabro-analyst.md +176 -0
  3. package/agents/elsabro-debugger.md +293 -0
  4. package/agents/elsabro-executor.md +477 -0
  5. package/agents/elsabro-orchestrator.md +426 -0
  6. package/agents/elsabro-planner.md +278 -0
  7. package/agents/elsabro-qa.md +273 -0
  8. package/agents/elsabro-quick-dev.md +309 -0
  9. package/agents/elsabro-scrum-master.md +217 -0
  10. package/agents/elsabro-tech-writer.md +347 -0
  11. package/agents/elsabro-ux-designer.md +278 -0
  12. package/agents/elsabro-verifier.md +295 -0
  13. package/agents/elsabro-yolo-dev.md +322 -0
  14. package/bin/install.js +497 -0
  15. package/commands/elsabro/add-phase.md +114 -0
  16. package/commands/elsabro/add-todo.md +158 -0
  17. package/commands/elsabro/audit-milestone.md +147 -0
  18. package/commands/elsabro/check-todos.md +192 -0
  19. package/commands/elsabro/complete-milestone.md +138 -0
  20. package/commands/elsabro/debug.md +153 -0
  21. package/commands/elsabro/discuss-phase.md +160 -0
  22. package/commands/elsabro/execute.md +299 -0
  23. package/commands/elsabro/help.md +102 -0
  24. package/commands/elsabro/insert-phase.md +117 -0
  25. package/commands/elsabro/list-phase-assumptions.md +129 -0
  26. package/commands/elsabro/map-codebase.md +108 -0
  27. package/commands/elsabro/new-milestone.md +128 -0
  28. package/commands/elsabro/new.md +230 -0
  29. package/commands/elsabro/pause-work.md +261 -0
  30. package/commands/elsabro/plan-milestone-gaps.md +129 -0
  31. package/commands/elsabro/plan.md +272 -0
  32. package/commands/elsabro/progress.md +187 -0
  33. package/commands/elsabro/quick.md +99 -0
  34. package/commands/elsabro/remove-phase.md +136 -0
  35. package/commands/elsabro/research-phase.md +174 -0
  36. package/commands/elsabro/resume-work.md +288 -0
  37. package/commands/elsabro/set-profile.md +216 -0
  38. package/commands/elsabro/settings.md +185 -0
  39. package/commands/elsabro/start.md +204 -0
  40. package/commands/elsabro/update.md +71 -0
  41. package/commands/elsabro/verify-work.md +269 -0
  42. package/commands/elsabro/verify.md +207 -0
  43. package/hooks/dist/.gitkeep +2 -0
  44. package/package.json +45 -0
  45. package/references/error-handling-instructions.md +312 -0
  46. package/references/source-hierarchy.md +150 -0
  47. package/references/token-optimization.md +225 -0
  48. package/skills/api-setup.md +315 -0
  49. package/skills/auth-setup.md +180 -0
  50. package/skills/database-setup.md +238 -0
  51. package/skills/expo-app.md +261 -0
  52. package/skills/nextjs-app.md +206 -0
  53. package/skills/payments-setup.md +421 -0
  54. package/skills/sentry-setup.md +295 -0
  55. package/templates/error-handling-config.json +138 -0
  56. package/templates/session-state.json +69 -0
  57. package/templates/starters/.gitkeep +2 -0
  58. package/workflows/.gitkeep +2 -0
package/bin/install.js ADDED
@@ -0,0 +1,497 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const readline = require('readline');
7
+ const { execFileSync } = require('child_process');
8
+
9
+ // Colors
10
+ const cyan = '\x1b[36m';
11
+ const green = '\x1b[32m';
12
+ const yellow = '\x1b[33m';
13
+ const red = '\x1b[31m';
14
+ const dim = '\x1b[2m';
15
+ const bold = '\x1b[1m';
16
+ const reset = '\x1b[0m';
17
+
18
+ // Get version from package.json
19
+ const pkg = require('../package.json');
20
+
21
+ // Parse args
22
+ const args = process.argv.slice(2);
23
+ const hasGlobal = args.includes('--global') || args.includes('-g');
24
+ const hasLocal = args.includes('--local') || args.includes('-l');
25
+ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
26
+ const hasUpdate = args.includes('--update');
27
+ const hasHelp = args.includes('--help') || args.includes('-h');
28
+
29
+ const banner = `
30
+ ${cyan}███████╗██╗ ███████╗ █████╗ ██████╗ ██████╗ ██████╗
31
+ ██╔════╝██║ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔═══██╗
32
+ █████╗ ██║ ███████╗███████║██████╔╝██████╔╝██║ ██║
33
+ ██╔══╝ ██║ ╚════██║██╔══██║██╔══██╗██╔══██╗██║ ██║
34
+ ███████╗███████╗███████║██║ ██║██████╔╝██║ ██║╚██████╔╝
35
+ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝ ╚═════╝${reset}
36
+
37
+ ${bold}El Sabro${reset} ${dim}v${pkg.version}${reset}
38
+ Tu asistente AI para crear apps increíbles
39
+ Sin necesidad de saber programar.
40
+ `;
41
+
42
+ // Detect package manager used
43
+ function detectPackageManager() {
44
+ const userAgent = process.env.npm_config_user_agent || '';
45
+ if (userAgent.includes('pnpm')) return 'pnpm';
46
+ if (userAgent.includes('bun')) return 'bun';
47
+ if (userAgent.includes('yarn')) return 'yarn';
48
+ return 'npm';
49
+ }
50
+
51
+ // Get global directory (validates all sources)
52
+ function getGlobalDir(explicitDir = null) {
53
+ let targetDir = null;
54
+
55
+ if (explicitDir) {
56
+ targetDir = expandTilde(explicitDir);
57
+ } else if (process.env.CLAUDE_CONFIG_DIR) {
58
+ targetDir = expandTilde(process.env.CLAUDE_CONFIG_DIR);
59
+ // Validate env var path too (security: env vars can be manipulated)
60
+ validatePath(targetDir);
61
+ } else {
62
+ return path.join(os.homedir(), '.claude');
63
+ }
64
+
65
+ return targetDir;
66
+ }
67
+
68
+ // Expand ~ to home directory
69
+ function expandTilde(filePath) {
70
+ if (filePath && filePath.startsWith('~/')) {
71
+ return path.join(os.homedir(), filePath.slice(2));
72
+ }
73
+ return filePath;
74
+ }
75
+
76
+ // Validate path is safe (no path traversal)
77
+ function validatePath(inputPath) {
78
+ if (!inputPath) return true;
79
+
80
+ // Check for path traversal BEFORE normalization (catches encoded attempts)
81
+ if (inputPath.includes('..')) {
82
+ console.error(` ${red}✗${reset} Ruta inválida: no se permite '..'`);
83
+ process.exit(1);
84
+ }
85
+
86
+ // Resolve to absolute path and normalize
87
+ const resolved = path.resolve(inputPath);
88
+ const homeDir = os.homedir();
89
+ const cwd = process.cwd();
90
+
91
+ // Expanded blocklist for system directories
92
+ const suspicious = ['/etc', '/usr', '/bin', '/sbin', '/var', '/tmp', '/root', '/proc', '/sys', '/dev', '/boot'];
93
+
94
+ // If not under home or cwd, check against blocklist
95
+ if (!resolved.startsWith(homeDir) && !resolved.startsWith(cwd)) {
96
+ for (const pattern of suspicious) {
97
+ if (resolved === pattern || resolved.startsWith(pattern + '/')) {
98
+ console.error(` ${red}✗${reset} Ruta inválida: no se permite instalar en ${pattern}`);
99
+ process.exit(1);
100
+ }
101
+ }
102
+ }
103
+
104
+ // Windows: check for system directories and UNC paths
105
+ if (process.platform === 'win32') {
106
+ // Block UNC paths
107
+ if (resolved.startsWith('\\\\') || resolved.startsWith('//')) {
108
+ console.error(` ${red}✗${reset} Ruta inválida: UNC paths no permitidos`);
109
+ process.exit(1);
110
+ }
111
+
112
+ const winSuspicious = ['C:\\Windows', 'C:\\Program Files', 'C:\\System32'];
113
+ for (const pattern of winSuspicious) {
114
+ if (resolved.toLowerCase().startsWith(pattern.toLowerCase())) {
115
+ console.error(` ${red}✗${reset} Ruta inválida: no se permite instalar en ${pattern}`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ }
120
+
121
+ return true;
122
+ }
123
+
124
+ // Get executable name for current platform
125
+ function getExecutable(name) {
126
+ return process.platform === 'win32' ? `${name}.cmd` : name;
127
+ }
128
+
129
+ // Parse --config-dir argument
130
+ function parseConfigDirArg() {
131
+ const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
132
+ if (configDirIndex !== -1) {
133
+ const nextArg = args[configDirIndex + 1];
134
+ if (!nextArg || nextArg.startsWith('-')) {
135
+ console.error(` ${yellow}--config-dir requires a path argument${reset}`);
136
+ process.exit(1);
137
+ }
138
+ return nextArg;
139
+ }
140
+ const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
141
+ if (configDirArg) {
142
+ const value = configDirArg.split('=')[1];
143
+ if (!value) {
144
+ console.error(` ${yellow}--config-dir requires a non-empty path${reset}`);
145
+ process.exit(1);
146
+ }
147
+ return value;
148
+ }
149
+ return null;
150
+ }
151
+
152
+ const explicitConfigDir = parseConfigDirArg();
153
+ if (explicitConfigDir) {
154
+ validatePath(explicitConfigDir);
155
+ }
156
+ const packageManager = detectPackageManager();
157
+
158
+ console.log(banner);
159
+ console.log(` ${dim}Package manager detectado: ${packageManager}${reset}\n`);
160
+
161
+ // Show help
162
+ if (hasHelp) {
163
+ console.log(` ${yellow}Uso:${reset}
164
+
165
+ ${cyan}npm:${reset} npx elsabro [opciones]
166
+ ${cyan}pnpm:${reset} pnpm dlx elsabro [opciones]
167
+ ${cyan}bun:${reset} bunx elsabro [opciones]
168
+
169
+ ${yellow}Opciones:${reset}
170
+ ${cyan}-g, --global${reset} Instalar globalmente (recomendado)
171
+ ${cyan}-l, --local${reset} Instalar solo en este proyecto
172
+ ${cyan}-u, --uninstall${reset} Desinstalar ELSABRO
173
+ ${cyan}--update${reset} Actualizar a la última versión
174
+ ${cyan}-c, --config-dir <ruta>${reset} Directorio de configuración personalizado
175
+ ${cyan}-h, --help${reset} Mostrar esta ayuda
176
+
177
+ ${yellow}Ejemplos:${reset}
178
+ ${dim}# Instalar globalmente (funciona en todos los proyectos)${reset}
179
+ npx elsabro --global
180
+ pnpm dlx elsabro --global
181
+ bunx elsabro --global
182
+
183
+ ${dim}# Instalar solo en este proyecto${reset}
184
+ npx elsabro --local
185
+
186
+ ${dim}# Actualizar${reset}
187
+ npx elsabro --update
188
+
189
+ ${dim}# Desinstalar${reset}
190
+ npx elsabro --global --uninstall
191
+
192
+ ${yellow}Después de instalar:${reset}
193
+ Abre Claude Code y escribe: ${cyan}/elsabro:start${reset}
194
+ `);
195
+ process.exit(0);
196
+ }
197
+
198
+ // Check for newer version on npm
199
+ const MAX_RESPONSE_SIZE = 100 * 1024; // 100KB limit for npm response
200
+
201
+ async function checkLatestVersion() {
202
+ try {
203
+ const https = require('https');
204
+ return new Promise((resolve, reject) => {
205
+ const req = https.get('https://registry.npmjs.org/elsabro/latest', { timeout: 5000 }, (res) => {
206
+ let data = '';
207
+ res.on('data', chunk => {
208
+ data += chunk;
209
+ // Security: prevent memory exhaustion from large response
210
+ if (data.length > MAX_RESPONSE_SIZE) {
211
+ req.destroy();
212
+ resolve(null);
213
+ }
214
+ });
215
+ res.on('end', () => {
216
+ try {
217
+ const json = JSON.parse(data);
218
+ resolve(json.version || null);
219
+ } catch {
220
+ resolve(null);
221
+ }
222
+ });
223
+ });
224
+ req.on('error', () => resolve(null));
225
+ req.on('timeout', () => { req.destroy(); resolve(null); });
226
+ });
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+
232
+ // Handle update using execFileSync (safer than exec)
233
+ if (hasUpdate) {
234
+ console.log(` ${cyan}Verificando actualizaciones...${reset}\n`);
235
+
236
+ // Check if there's a newer version first to prevent infinite loops
237
+ checkLatestVersion().then((latestVersion) => {
238
+ if (!latestVersion) {
239
+ console.log(` ${yellow}⚠${reset} No se pudo verificar la última versión`);
240
+ console.log(` ${dim}Intentando actualizar de todas formas...${reset}\n`);
241
+ } else if (latestVersion === pkg.version) {
242
+ console.log(` ${green}✓${reset} Ya tienes la última versión (${pkg.version})\n`);
243
+ process.exit(0);
244
+ } else {
245
+ console.log(` ${cyan}Nueva versión disponible: ${latestVersion}${reset}`);
246
+ console.log(` ${dim}Versión actual: ${pkg.version}${reset}\n`);
247
+ }
248
+
249
+ try {
250
+ // Use execFileSync with the package manager directly
251
+ // Use getExecutable for Windows compatibility
252
+ if (packageManager === 'pnpm') {
253
+ execFileSync(getExecutable('pnpm'), ['dlx', 'elsabro@latest', '--global'], { stdio: 'inherit' });
254
+ } else if (packageManager === 'bun') {
255
+ execFileSync(getExecutable('bunx'), ['elsabro@latest', '--global'], { stdio: 'inherit' });
256
+ } else {
257
+ execFileSync(getExecutable('npx'), ['elsabro@latest', '--global'], { stdio: 'inherit' });
258
+ }
259
+ console.log(`\n ${green}✓${reset} ELSABRO actualizado correctamente\n`);
260
+ } catch (error) {
261
+ console.error(`\n ${red}✗${reset} Error al actualizar: ${error.message}\n`);
262
+ process.exit(1);
263
+ }
264
+ process.exit(0);
265
+ });
266
+ } else {
267
+
268
+ // Interactive prompt for location if not specified
269
+ async function promptLocation() {
270
+ if (hasGlobal || hasLocal) return hasGlobal ? 'global' : 'local';
271
+
272
+ const rl = readline.createInterface({
273
+ input: process.stdin,
274
+ output: process.stdout
275
+ });
276
+
277
+ return new Promise((resolve) => {
278
+ console.log(` ${yellow}¿Dónde quieres instalar ELSABRO?${reset}\n`);
279
+ console.log(` ${cyan}1)${reset} Global ${dim}(recomendado - funciona en todos los proyectos)${reset}`);
280
+ console.log(` ${cyan}2)${reset} Local ${dim}(solo en este proyecto)${reset}\n`);
281
+
282
+ rl.question(` Tu elección [1]: `, (answer) => {
283
+ rl.close();
284
+ resolve(answer === '2' ? 'local' : 'global');
285
+ });
286
+ });
287
+ }
288
+
289
+ // Copy directory recursively with security limits
290
+ const MAX_DEPTH = 20;
291
+ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
292
+
293
+ function copyDir(src, dest, depth = 0) {
294
+ // Prevent stack overflow from deeply nested directories
295
+ if (depth > MAX_DEPTH) {
296
+ throw new Error(`Profundidad máxima excedida en: ${src}`);
297
+ }
298
+
299
+ // Check source is not a symlink (security: prevent symlink attacks)
300
+ const srcStats = fs.lstatSync(src);
301
+ if (srcStats.isSymbolicLink()) {
302
+ console.log(` ${yellow}⚠${reset} Saltando symlink: ${src}`);
303
+ return;
304
+ }
305
+
306
+ fs.mkdirSync(dest, { recursive: true });
307
+ const entries = fs.readdirSync(src, { withFileTypes: true });
308
+
309
+ for (const entry of entries) {
310
+ const srcPath = path.join(src, entry.name);
311
+ const destPath = path.join(dest, entry.name);
312
+
313
+ // Check each entry for symlinks
314
+ const entryStats = fs.lstatSync(srcPath);
315
+ if (entryStats.isSymbolicLink()) {
316
+ console.log(` ${yellow}⚠${reset} Saltando symlink: ${entry.name}`);
317
+ continue;
318
+ }
319
+
320
+ if (entry.isDirectory()) {
321
+ copyDir(srcPath, destPath, depth + 1);
322
+ } else {
323
+ // Check file size before copying
324
+ if (entryStats.size > MAX_FILE_SIZE) {
325
+ throw new Error(`Archivo demasiado grande: ${srcPath} (${Math.round(entryStats.size / 1024 / 1024)}MB)`);
326
+ }
327
+ fs.copyFileSync(srcPath, destPath);
328
+ }
329
+ }
330
+ }
331
+
332
+ // Main install function
333
+ async function install() {
334
+ const location = await promptLocation();
335
+ const isGlobal = location === 'global';
336
+
337
+ const baseDir = isGlobal
338
+ ? getGlobalDir(explicitConfigDir)
339
+ : path.join(process.cwd(), '.claude');
340
+
341
+ console.log(`\n ${cyan}Instalando ELSABRO...${reset}`);
342
+ console.log(` ${dim}Destino: ${baseDir}${reset}\n`);
343
+
344
+ // Create base directory
345
+ fs.mkdirSync(baseDir, { recursive: true });
346
+
347
+ // Directories to copy
348
+ const srcDir = path.join(__dirname, '..');
349
+ const copies = [
350
+ { src: 'commands/elsabro', dest: 'commands/elsabro', name: 'Comandos' },
351
+ { src: 'agents', dest: 'agents', name: 'Agentes' },
352
+ { src: 'skills', dest: 'skills', name: 'Skills' },
353
+ { src: 'templates', dest: 'templates', name: 'Templates' },
354
+ { src: 'workflows', dest: 'workflows', name: 'Workflows' },
355
+ { src: 'references', dest: 'references', name: 'Referencias' },
356
+ { src: 'hooks/dist', dest: 'hooks', name: 'Hooks' }
357
+ ];
358
+
359
+ const failures = [];
360
+
361
+ for (const item of copies) {
362
+ const srcPath = path.join(srcDir, item.src);
363
+ const destPath = path.join(baseDir, item.dest);
364
+
365
+ try {
366
+ // Safe removal: check for symlinks before removing
367
+ if (fs.existsSync(destPath)) {
368
+ const destStats = fs.lstatSync(destPath);
369
+ if (destStats.isSymbolicLink()) {
370
+ console.log(` ${yellow}⚠${reset} Saltando symlink existente: ${destPath}`);
371
+ continue;
372
+ }
373
+ fs.rmSync(destPath, { recursive: true });
374
+ }
375
+
376
+ if (fs.existsSync(srcPath)) {
377
+ copyDir(srcPath, destPath);
378
+ console.log(` ${green}✓${reset} ${item.name} instalados`);
379
+ } else {
380
+ console.log(` ${yellow}⚠${reset} ${item.name} no encontrados (saltando)`);
381
+ }
382
+ } catch (error) {
383
+ console.log(` ${red}✗${reset} Error instalando ${item.name}: ${error.message}`);
384
+ failures.push(item.name);
385
+ }
386
+ }
387
+
388
+ // Update settings.json for hooks
389
+ const settingsPath = path.join(baseDir, 'settings.json');
390
+ let settings = {};
391
+
392
+ if (fs.existsSync(settingsPath)) {
393
+ try {
394
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
395
+ } catch (e) {
396
+ settings = {};
397
+ }
398
+ }
399
+
400
+ // Add hook configurations
401
+ settings.hooks = settings.hooks || {};
402
+ settings.hooks.postToolUse = settings.hooks.postToolUse || [];
403
+
404
+ // Add version tracking
405
+ settings.elsabro = {
406
+ version: pkg.version,
407
+ installedAt: new Date().toISOString(),
408
+ packageManager: packageManager
409
+ };
410
+
411
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
412
+ console.log(` ${green}✓${reset} Configuración actualizada`);
413
+
414
+ // Summary
415
+ console.log(`\n ${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}`);
416
+
417
+ if (failures.length === 0) {
418
+ console.log(` ${green}✓${reset} ${bold}ELSABRO instalado correctamente${reset}`);
419
+ console.log(`\n ${yellow}Próximos pasos:${reset}`);
420
+ console.log(` 1. Abre Claude Code`);
421
+ console.log(` 2. Escribe: ${cyan}/elsabro:start${reset}`);
422
+ console.log(` 3. ¡Empieza a crear tu app!\n`);
423
+
424
+ console.log(` ${dim}Comandos útiles:${reset}`);
425
+ console.log(` ${cyan}/elsabro:start${reset} - Wizard para empezar`);
426
+ console.log(` ${cyan}/elsabro:help${reset} - Ver todos los comandos`);
427
+ console.log(` ${cyan}/elsabro:update${reset} - Actualizar ELSABRO\n`);
428
+ } else {
429
+ console.log(` ${yellow}⚠${reset} ELSABRO instalado con algunos errores`);
430
+ console.log(` Componentes fallidos: ${failures.join(', ')}\n`);
431
+ }
432
+
433
+ console.log(` ${green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${reset}\n`);
434
+ }
435
+
436
+ // Uninstall function
437
+ async function uninstall() {
438
+ const location = await promptLocation();
439
+ const isGlobal = location === 'global';
440
+
441
+ const baseDir = isGlobal
442
+ ? getGlobalDir(explicitConfigDir)
443
+ : path.join(process.cwd(), '.claude');
444
+
445
+ console.log(`\n ${cyan}Desinstalando ELSABRO...${reset}\n`);
446
+
447
+ const toRemove = [
448
+ 'commands/elsabro',
449
+ 'agents',
450
+ 'skills',
451
+ 'templates',
452
+ 'workflows',
453
+ 'references'
454
+ ];
455
+
456
+ for (const item of toRemove) {
457
+ const itemPath = path.join(baseDir, item);
458
+ if (fs.existsSync(itemPath)) {
459
+ // Safe removal: check for symlinks before removing
460
+ const itemStats = fs.lstatSync(itemPath);
461
+ if (itemStats.isSymbolicLink()) {
462
+ console.log(` ${yellow}⚠${reset} Saltando symlink: ${item}`);
463
+ continue;
464
+ }
465
+ fs.rmSync(itemPath, { recursive: true });
466
+ console.log(` ${green}✓${reset} Eliminado: ${item}`);
467
+ }
468
+ }
469
+
470
+ // Remove from settings
471
+ const settingsPath = path.join(baseDir, 'settings.json');
472
+ if (fs.existsSync(settingsPath)) {
473
+ try {
474
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
475
+ delete settings.elsabro;
476
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
477
+ } catch (e) {
478
+ // Ignore
479
+ }
480
+ }
481
+
482
+ console.log(`\n ${green}✓${reset} ELSABRO desinstalado correctamente\n`);
483
+ }
484
+
485
+ // Main (note: update handling is above with its own process.exit)
486
+ if (hasUninstall) {
487
+ uninstall().catch((error) => {
488
+ console.error(` ${red}✗${reset} Error: ${error.message}`);
489
+ process.exit(1);
490
+ });
491
+ } else {
492
+ install().catch((error) => {
493
+ console.error(` ${red}✗${reset} Error: ${error.message}`);
494
+ process.exit(1);
495
+ });
496
+ }
497
+ }
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: add-phase
3
+ description: Agregar una nueva fase al final del milestone actual
4
+ ---
5
+
6
+ # /elsabro:add-phase
7
+
8
+ <command-name>add-phase</command-name>
9
+
10
+ ## Propósito
11
+
12
+ Agregar una nueva fase al milestone activo. Útil cuando se descubre trabajo adicional necesario.
13
+
14
+ ## Uso
15
+
16
+ ```bash
17
+ # Agregar fase básica
18
+ /elsabro:add-phase "Documentation"
19
+
20
+ # Agregar con descripción
21
+ /elsabro:add-phase "Documentation" --desc="Write user guides and API docs"
22
+
23
+ # Agregar a milestone específico
24
+ /elsabro:add-phase "Documentation" --milestone=M001
25
+
26
+ # Agregar con estimación
27
+ /elsabro:add-phase "Documentation" --estimate=3d
28
+ ```
29
+
30
+ ## Proceso
31
+
32
+ ### 1. Crear Fase
33
+
34
+ ```
35
+ User: /elsabro:add-phase "Integration Testing"
36
+
37
+ ELSABRO: Creating new phase...
38
+
39
+ Phase added to M001:
40
+ ┌──────────────────────────────────────┐
41
+ │ P5: Integration Testing │
42
+ │ Status: ⬜ Pending │
43
+ │ Estimate: TBD │
44
+ │ Dependencies: P4 │
45
+ └──────────────────────────────────────┘
46
+
47
+ Define tasks for this phase? (y/n)
48
+ ```
49
+
50
+ ### 2. Definir Tasks (Opcional)
51
+
52
+ Si responde 'y':
53
+
54
+ ```
55
+ Enter tasks (one per line, empty to finish):
56
+ > Set up integration test environment
57
+ > Write API integration tests
58
+ > Write E2E tests for critical flows
59
+ >
60
+
61
+ Tasks added:
62
+ - [ ] Set up integration test environment
63
+ - [ ] Write API integration tests
64
+ - [ ] Write E2E tests for critical flows
65
+ ```
66
+
67
+ ## Actualización Automática
68
+
69
+ Actualiza `PHASES.md` y `MILESTONE.md`:
70
+
71
+ ```markdown
72
+ ## Phases
73
+
74
+ | # | Phase | Status | Tasks | ETA |
75
+ |---|-------|--------|-------|-----|
76
+ | P1 | Design | ✅ Done | 5/5 | W1 |
77
+ | P2 | Backend | ✅ Done | 8/8 | W2 |
78
+ | P3 | Frontend | 🟡 WIP | 4/6 | W3 |
79
+ | P4 | Testing | ⬜ Pending | 0/3 | W4 |
80
+ | P5 | Integration Testing | ⬜ NEW | 0/3 | W5 | ← Added
81
+ ```
82
+
83
+ ## Opciones
84
+
85
+ ```bash
86
+ --milestone, -m # Milestone target (default: active)
87
+ --desc, -d # Phase description
88
+ --estimate, -e # Time estimate (1d, 2w, etc.)
89
+ --after, -a # Insert after specific phase
90
+ --deps # Dependencies (comma-separated)
91
+ --tasks # Initial tasks (comma-separated)
92
+ ```
93
+
94
+ ## Validaciones
95
+
96
+ - Verifica milestone existe
97
+ - Verifica nombre único de fase
98
+ - Sugiere dependencias basado en orden
99
+
100
+ ## Output
101
+
102
+ ```
103
+ ✓ Phase "Integration Testing" added to M001
104
+
105
+ Position: P5 (after P4: Testing)
106
+ Status: Pending
107
+ Tasks: 3 defined
108
+
109
+ Impact on milestone:
110
+ - Original ETA: 2024-02-15
111
+ - New ETA: 2024-02-22 (+1 week)
112
+
113
+ View phases: /elsabro:progress --phases
114
+ ```