UNPKG

acs-framework-cli

Version:

🚀 CLI inteligente para configurar automáticamente el Augmented Context Standard (ACS) Framework. Context-as-Code que convierte tu conocimiento en un activo versionado.

480 lines (377 loc) 18.1 kB
#!/usr/bin/env node const fs = require('fs-extra'); const path = require('path'); const inquirer = require('inquirer'); // Import chalk de manera que funcione con diferentes versiones let chalk; try { chalk = require('chalk'); } catch (error) { chalk = { blue: { bold: (text) => text }, gray: (text) => text, yellow: (text) => text, cyan: (text) => text, green: (text) => text, red: (text) => text }; } // Detectar si se llama directamente o desde acs-hub const isDirectCall = require.main === module; if (isDirectCall) { console.log(chalk.blue.bold('\n🔄 ACS Framework CLI - Upgrade Tool')); console.log(chalk.gray('Migración segura para usuarios existentes\n')); // Mostrar mensaje de migración hacia acs-hub console.log(chalk.yellow.bold('⚡ ¡Experiencia mejorada disponible!')); console.log(chalk.white('Este comando ahora está integrado en acs-hub para una mejor UX')); console.log(chalk.cyan('💡 Próximamente usa: acs-hub → Upgrade ACS\n')); console.log(chalk.gray('Continuando con upgrade legacy...\n')); } class ACSUpgradeTool { constructor() { this.targetDir = process.cwd(); this.contextDir = path.join(this.targetDir, '.context'); this.backupDir = path.join(this.targetDir, '.context_backup_' + Date.now()); } async run() { try { // Verificar si existe .context if (!await fs.pathExists(this.contextDir)) { console.log(chalk.yellow('❌ No se encontró carpeta .context en este directorio')); console.log(chalk.gray(' Usa "acs-init" para configurar ACS Framework por primera vez\n')); process.exit(1); } console.log(chalk.cyan('🔍 Analizando configuración actual...\n')); // Analizar archivos existentes const analysis = await this.analyzeExistingSetup(); // Mostrar reporte this.showAnalysisReport(analysis); // Preguntar qué quiere hacer el usuario const action = await this.askUserAction(analysis); switch (action) { case 'backup_and_upgrade': await this.backupAndUpgrade(); break; case 'upgrade_template_only': await this.upgradeTemplateOnly(); break; case 'show_diff': await this.showTemplateDiff(); break; case 'cancel': console.log(chalk.gray('🔄 Operación cancelada')); break; } } catch (error) { console.error(chalk.red('❌ Error durante la migración:', error.message)); process.exit(1); } } async analyzeExistingSetup() { const files = await fs.readdir(this.contextDir); const analysis = { hasSystemPrompt: files.includes('SYSTEM_PROMPT.md'), hasCustomizations: false, customFiles: [], systemPromptVersion: null, lastModified: null }; // Verificar customizaciones const standardFiles = ['SYSTEM_PROMPT.md', 'ARCHITECTURE.md', 'FEATURES.md', 'RULES.md', 'AGENT_PROMPT.md', 'README.md']; analysis.customFiles = files.filter(file => !standardFiles.includes(file)); analysis.hasCustomizations = analysis.customFiles.length > 0; // Analizar SYSTEM_PROMPT si existe if (analysis.hasSystemPrompt) { const systemPromptPath = path.join(this.contextDir, 'SYSTEM_PROMPT.md'); const content = await fs.readFile(systemPromptPath, 'utf8'); // Detectar versión const versionMatch = content.match(/v(\d+\.\d+)/); analysis.systemPromptVersion = versionMatch ? versionMatch[1] : 'unknown'; // Detectar modificaciones manuales const stats = await fs.stat(systemPromptPath); analysis.lastModified = stats.mtime; // Verificar si tiene customizaciones manuales if (content.includes('PERSONALIZADO') || content.includes('CUSTOM') || content.includes('MODIFICADO')) { analysis.hasCustomizations = true; } } return analysis; } showAnalysisReport(analysis) { console.log(chalk.blue.bold('📊 Reporte de Análisis:')); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); if (analysis.hasSystemPrompt) { console.log(chalk.green(`✅ SYSTEM_PROMPT.md encontrado (versión ${analysis.systemPromptVersion})`)); } else { console.log(chalk.yellow('⚠️ SYSTEM_PROMPT.md NO encontrado')); } if (analysis.hasCustomizations) { console.log(chalk.yellow(`⚠️ Customizaciones detectadas:`)); analysis.customFiles.forEach(file => { console.log(chalk.gray(` - ${file}`)); }); } else { console.log(chalk.green('✅ No se detectaron customizaciones')); } if (analysis.lastModified) { console.log(chalk.gray(`📅 Última modificación: ${analysis.lastModified.toLocaleDateString()}`)); } console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); } async askUserAction(analysis) { const choices = []; if (analysis.hasCustomizations) { choices.push({ name: '🛡️ Crear backup completo y actualizar todo (RECOMENDADO)', value: 'backup_and_upgrade', short: 'Backup + Upgrade' }); choices.push({ name: '🎯 Solo actualizar SYSTEM_PROMPT.md (preservar customizaciones)', value: 'upgrade_template_only', short: 'Solo template' }); } else { choices.push({ name: '🚀 Actualizar a plantillas v2.0.3 (seguro)', value: 'backup_and_upgrade', short: 'Upgrade' }); } choices.push({ name: '👀 Ver diferencias antes de decidir', value: 'show_diff', short: 'Ver diff' }); choices.push({ name: '❌ Cancelar', value: 'cancel', short: 'Cancelar' }); const { action } = await inquirer.prompt([{ type: 'list', name: 'action', message: '¿Qué acción deseas realizar?', choices: choices }]); return action; } async backupAndUpgrade() { console.log(chalk.cyan('💾 Creando backup completo...\n')); // Crear backup await fs.copy(this.contextDir, this.backupDir); console.log(chalk.green(`✅ Backup creado en: ${this.backupDir}`)); // Regenerar todo con el CLI actual console.log(chalk.cyan('🔄 Regenerando configuración con plantillas v2.0.3...\n')); // Leer datos del proyecto existente const projectData = await this.extractProjectData(); // Llamar al CLI principal con los datos preservados const ACSFrameworkCLI = require('./index'); const cli = new ACSFrameworkCLI(); cli.projectData = projectData; cli.targetDir = this.targetDir; // Regenerar archivos con nuevas plantillas await cli.createContextStructure(); await cli.generateSystemPrompt(); await cli.generateArchitecture(); await cli.generateFeatures(); await cli.generateRules(); await cli.generateAgentPrompt(); console.log(chalk.green.bold('\n🎉 ¡Migración completada exitosamente!')); console.log(chalk.gray(`📁 Backup disponible en: ${path.basename(this.backupDir)}`)); console.log(chalk.yellow('\n💡 Revisa los cambios y restaura customizaciones específicas si es necesario')); } async upgradeTemplateOnly() { console.log(chalk.cyan('🎯 Actualizando solo SYSTEM_PROMPT.md...\n')); // Backup del archivo específico const systemPromptPath = path.join(this.contextDir, 'SYSTEM_PROMPT.md'); const backupPath = path.join(this.contextDir, 'SYSTEM_PROMPT.md.backup.' + Date.now()); if (await fs.pathExists(systemPromptPath)) { await fs.copy(systemPromptPath, backupPath); console.log(chalk.green(`✅ Backup del archivo: ${path.basename(backupPath)}`)); } // Leer datos del proyecto existente const projectData = await this.extractProjectData(); // SOLO regenerar SYSTEM_PROMPT - SIN llamar al CLI completo await this.generateSystemPromptDirect(projectData); console.log(chalk.green.bold('\n✅ SYSTEM_PROMPT.md actualizado con plantilla v2.0.3')); console.log(chalk.gray(`📁 Backup anterior: ${path.basename(backupPath)}`)); } async showTemplateDiff() { console.log(chalk.cyan('👀 Generando vista previa de cambios...\n')); // Generar template nuevo en memoria const projectData = await this.extractProjectData(); const ACSFrameworkCLI = require('./index'); const cli = new ACSFrameworkCLI(); cli.projectData = projectData; // Simular generación del nuevo template const newTemplate = await this.generateNewSystemPromptPreview(cli); // Leer template actual si existe const currentPath = path.join(this.contextDir, 'SYSTEM_PROMPT.md'); let currentContent = ''; if (await fs.pathExists(currentPath)) { currentContent = await fs.readFile(currentPath, 'utf8'); } console.log(chalk.blue.bold('📋 Vista previa de cambios:')); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log(chalk.yellow('NUEVO TEMPLATE incluye:')); console.log('• ⚠️ Reglas anti-alucinación más estrictas'); console.log('• 🔍 Validaciones obligatorias antes de generar código'); console.log('• 📋 Referencias específicas a archivos .context/'); console.log('• 🛡️ Mejores patterns de seguridad y consistencia'); console.log('• 🎯 Instrucciones más específicas para el stack tecnológico'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); // Recursivo para que tome una decisión const action = await this.askUserAction(await this.analyzeExistingSetup()); if (action !== 'show_diff' && action !== 'cancel') { await this.run(); } } async extractProjectData() { // Intentar extraer datos del SYSTEM_PROMPT existente o usar defaults const systemPromptPath = path.join(this.contextDir, 'SYSTEM_PROMPT.md'); let projectData = { projectName: path.basename(this.targetDir), projectDescription: 'Proyecto de software', projectType: 'Aplicación', projectStatus: 'En desarrollo activo', responsible: 'Equipo de desarrollo', technologies: [], criticalFlow: 'Flujo principal por definir', dateCreated: new Date().toLocaleDateString('es-ES'), namingConvention: 'Convenciones del equipo', commitStyle: 'Conventional Commits', includeTests: true }; if (await fs.pathExists(systemPromptPath)) { try { const content = await fs.readFile(systemPromptPath, 'utf8'); // Extraer datos existentes con regex básico const nameMatch = content.match(/proyecto "([^"]+)"/); if (nameMatch) projectData.projectName = nameMatch[1]; const descMatch = content.match(/\*\*Descripción:\*\* (.+)/); if (descMatch) projectData.projectDescription = descMatch[1]; const statusMatch = content.match(/\*\*Estado:\*\* (.+)/); if (statusMatch) projectData.projectStatus = statusMatch[1]; const responsibleMatch = content.match(/\*\*Responsable:\*\* (.+)/); if (responsibleMatch) projectData.responsible = responsibleMatch[1]; } catch (error) { console.log(chalk.yellow('⚠️ No se pudieron extraer todos los datos del SYSTEM_PROMPT actual')); } } return projectData; } async generateSystemPromptDirect(projectData) { // Generar SYSTEM_PROMPT directamente sin inicializar el CLI completo const technologies = projectData.technologies || []; const techList = technologies.length > 0 ? technologies : ['[Tecnologías por definir]']; const priorityTech = technologies.length > 0 ? technologies.slice(0, 3).join(', ') : '[Stack por definir]'; const content = `# Instrucciones para IA - ${projectData.projectName} Eres un asistente especializado para el proyecto "${projectData.projectName}". ## ⚠️ REGLAS CRÍTICAS PARA IA **ANTES de generar cualquier código o documentación:** 1. **🔍 VERIFICAR CONTEXTO**: Lee todos los archivos .context/ antes de asumir algo 2. **⏱️ TÓMATE TU TIEMPO**: Calidad sobre velocidad. Usa todas tus herramientas disponibles 3. **🔬 SÉ PROLIJO**: Revisa package.json, verifica dependencias, confirma versiones 4. **🚫 CERO ALUCINACIONES**: No documentes funciones que no existen, no inventes APIs 5. **📋 AUDITORÍA FINAL**: Antes de entregar código, revisa que sea consistente con el proyecto real **FILOSOFÍA**: Este proyecto usa ACS Framework para eliminar inconsistencias. **NO generes código que no respete la arquitectura documentada.** ## Contexto del Proyecto **Descripción:** ${projectData.projectDescription} **Tipo:** ${projectData.projectType} **Estado:** ${projectData.projectStatus} **Responsable:** ${projectData.responsible} **Stack tecnológico:** ${techList.map(tech => `- ${tech}`).join('\n')} **Arquitectura básica:** - Componentes: ${this.resolveMainComponents(projectData)} - Flujos críticos: ${projectData.criticalFlow} ## Al generar código: **Tecnologías a usar:** - Priorizar: ${priorityTech} - Mantener compatibilidad con stack existente - Usar versiones modernas y mejores prácticas **Patrones requeridos:** - ${projectData.namingConvention || 'Seguir convenciones de naming del equipo'} - Incluir manejo de errores apropiado - Logging adecuado para debugging - Documentar decisiones técnicas importantes **Estilo de commits:** ${projectData.commitStyle} ## Reglas específicas del proyecto: **SIEMPRE hacer:** - Verificar que las dependencias existan en package.json antes de usarlas - Validar inputs del usuario con el patrón establecido - Incluir manejo de errores apropiado según la arquitectura - Agregar comments en español para lógica compleja - Seguir los principios de seguridad del proyecto - Mantener consistencia con los patterns existentes ${projectData.includeTests ? '- Incluir tests siguiendo los patterns del proyecto' : '- Considerar agregar tests cuando sea apropiado'} **NUNCA hacer:** - Hardcodear credenciales, URLs o configuraciones sensibles - Ignorar las validaciones de entrada establecidas - Escribir código sin considerar el performance del stack actual - Usar librerías que no estén aprobadas o sean deprecated - Cambiar arquitectura sin consultar .context/ARCHITECTURE.md - Generar código que rompa las convenciones establecidas en .context/RULES.md ## Validaciones OBLIGATORIAS antes de generar código: 1. **📁 Verificar estructura**: Revisar .context/ARCHITECTURE.md para entender la organización 2. **📋 Leer package.json**: Confirmar dependencias, scripts y versiones disponibles 3. **🔍 Revisar .context/RULES.md**: Confirmar convenciones de naming, patterns y restricciones 4. **📖 Entender el negocio**: Leer .context/FEATURES.md para entender el contexto de negocio 5. **🧪 Considerar tests**: Si existen, seguir los patterns de testing establecidos ## Contexto de negocio: **Funcionalidades principales:** - ${projectData.criticalFlow} - [Revisar .context/FEATURES.md para detalles completos] **Componentes del sistema:** - ${this.resolveMainComponents(projectData)} Para más detalles sobre historias de usuario específicas, consultar \`.context/FEATURES.md\` ## Estilo de comunicación: - Responde en español técnico, claro y directo - NUNCA asumas funcionalidades: pregunta o verifica en .context/ - Si no estás seguro de algo, revisa la documentación del proyecto - Explica decisiones técnicas cuando sean relevantes - Sugiere mejoras alineadas con la arquitectura existente - Prioriza mantener consistencia sobre agregar features nuevas ## Referencias: - Documentación del proyecto: \`.context/\` folder - Arquitectura detallada: \`.context/ARCHITECTURE.md\` - Reglas del equipo: \`.context/RULES.md\` - Funcionalidades: \`.context/FEATURES.md\` --- *Generado por ACS Framework CLI - ${projectData.dateCreated}* *Copia este contenido al chatear con IA para mejor contexto* `; await fs.writeFile(path.join(this.contextDir, 'SYSTEM_PROMPT.md'), content); } resolveMainComponents(projectData) { // Simplificado para evitar dependencias del CLI principal return projectData.components || 'Componentes por definir'; } async generateNewSystemPromptPreview(cli) { // Reutilizar la lógica de generación pero solo retornar el contenido const technologies = cli.projectData.technologies || []; const techList = technologies.length > 0 ? technologies : ['[Tecnologías por definir]']; const priorityTech = technologies.length > 0 ? technologies.slice(0, 3).join(', ') : '[Stack por definir]'; return `# Instrucciones para IA - ${cli.projectData.projectName} Eres un asistente especializado para el proyecto "${cli.projectData.projectName}". ## ⚠️ REGLAS CRÍTICAS PARA IA **ANTES de generar cualquier código o documentación:** 1. **🔍 VERIFICAR CONTEXTO**: Lee todos los archivos .context/ antes de asumir algo 2. **⏱️ TÓMATE TU TIEMPO**: Calidad sobre velocidad. Usa todas tus herramientas disponibles 3. **🔬 SÉ PROLIJO**: Revisa package.json, verifica dependencias, confirma versiones 4. **🚫 CERO ALUCINACIONES**: No documentes funciones que no existen, no inventes APIs 5. **📋 AUDITORÍA FINAL**: Antes de entregar código, revisa que sea consistente con el proyecto real **FILOSOFÍA**: Este proyecto usa ACS Framework para eliminar inconsistencias. **NO generes código que no respete la arquitectura documentada.** [... resto del template mejorado ...]`; } } // Ejecutar si se llama directamente if (require.main === module) { const tool = new ACSUpgradeTool(); tool.run().catch(error => { console.error(chalk.red('❌ Error:', error.message)); process.exit(1); }); } module.exports = ACSUpgradeTool;