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
JavaScript
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;