|
| 1 | +import { Command } from 'commander'; |
| 2 | +import chalk from 'chalk'; |
| 3 | +import ora from 'ora'; |
| 4 | +import path from 'path'; |
| 5 | +import fs from 'fs-extra'; |
| 6 | +import { ProjectConfig, SupportedIDE } from '../../types'; |
| 7 | +import { getSupportedIDEs } from '../../adapters'; |
| 8 | +import { ProjectAnalyzer } from '../../services/projectAnalyzer'; |
| 9 | +import { ApiKeyManager } from '../../services/apiKeyManager'; |
| 10 | +import { runRetrofitPrompts } from '../prompts/retrofit'; |
| 11 | +import { generateDocumentation } from '../../generators'; |
| 12 | +import { version } from '../../../package.json'; |
| 13 | + |
| 14 | +export const analyzeCommand = new Command('analyze') |
| 15 | + .description('Analyze existing project and generate AI-optimized documentation') |
| 16 | + .option('-o, --output <path>', 'output directory for generated files', '.') |
| 17 | + .option( |
| 18 | + '-i, --ide <ide>', |
| 19 | + 'target IDE (claude, cursor, windsurf, cline, roo, gemini, copilot)', |
| 20 | + (value) => { |
| 21 | + const validIDEs = getSupportedIDEs(); |
| 22 | + const ides = value.split(',').map((ide) => ide.trim()); |
| 23 | + for (const ide of ides) { |
| 24 | + if (!validIDEs.includes(ide as SupportedIDE)) { |
| 25 | + throw new Error(`Invalid IDE: ${ide}. Valid options: ${validIDEs.join(', ')}`); |
| 26 | + } |
| 27 | + } |
| 28 | + return ides as SupportedIDE[]; |
| 29 | + } |
| 30 | + ) |
| 31 | + .option('--no-ai', 'skip AI analysis and use pattern-based analysis only') |
| 32 | + .option('--config-only', 'generate configuration file only without documentation') |
| 33 | + .action(async (options) => { |
| 34 | + console.log(chalk.blue.bold('\n🔍 Context Forge Analyzer\n')); |
| 35 | + console.log( |
| 36 | + chalk.gray("Let's analyze your existing project and create AI-optimized documentation.\n") |
| 37 | + ); |
| 38 | + |
| 39 | + const spinner = ora(); |
| 40 | + const projectPath = process.cwd(); |
| 41 | + const outputPath = path.resolve(options.output); |
| 42 | + |
| 43 | + try { |
| 44 | + // Initialize services |
| 45 | + const analyzer = new ProjectAnalyzer(projectPath); |
| 46 | + const apiKeyManager = new ApiKeyManager(projectPath); |
| 47 | + |
| 48 | + // Step 1: Basic Project Analysis |
| 49 | + spinner.start('Analyzing project structure...'); |
| 50 | + const basicAnalysis = await analyzer.analyzeBasic(); |
| 51 | + spinner.succeed(`Detected: ${basicAnalysis.summary}`); |
| 52 | + |
| 53 | + console.log(chalk.cyan('\n📊 Project Overview:')); |
| 54 | + console.log(` • Type: ${basicAnalysis.projectType}`); |
| 55 | + console.log(` • Tech Stack: ${basicAnalysis.techStack.join(', ')}`); |
| 56 | + console.log(` • Components: ${basicAnalysis.fileStats.components} files`); |
| 57 | + console.log(` • API Routes: ${basicAnalysis.fileStats.routes} files`); |
| 58 | + if (basicAnalysis.existingDocs.length > 0) { |
| 59 | + console.log(` • Documentation: ${basicAnalysis.existingDocs.join(', ')}`); |
| 60 | + } |
| 61 | + |
| 62 | + let detailedAnalysis = null; |
| 63 | + let apiConfig = null; |
| 64 | + |
| 65 | + // Step 2: AI Analysis (if requested) |
| 66 | + if (options.ai !== false) { |
| 67 | + const useAI = await analyzer.shouldUseAI(); |
| 68 | + |
| 69 | + if (useAI) { |
| 70 | + apiConfig = await apiKeyManager.setupApiKey(); |
| 71 | + if (apiConfig) { |
| 72 | + spinner.start('Analyzing with AI...'); |
| 73 | + detailedAnalysis = await analyzer.analyzeDeep(apiConfig); |
| 74 | + spinner.succeed('AI analysis complete'); |
| 75 | + |
| 76 | + if (detailedAnalysis.insights.length > 0) { |
| 77 | + console.log(chalk.cyan('\n🤖 AI Insights:')); |
| 78 | + detailedAnalysis.insights.forEach((insight) => { |
| 79 | + console.log(` • ${insight}`); |
| 80 | + }); |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + // Step 3: Retrofit Questions |
| 87 | + spinner.stop(); // Stop spinner before interactive prompts |
| 88 | + const config: ProjectConfig = await runRetrofitPrompts( |
| 89 | + basicAnalysis, |
| 90 | + detailedAnalysis, |
| 91 | + options.ide |
| 92 | + ); |
| 93 | + console.log(chalk.green('✔ Configuration complete')); |
| 94 | + |
| 95 | + // Step 4: Generate Documentation |
| 96 | + if (!options.configOnly) { |
| 97 | + spinner.start('Generating AI-optimized documentation...'); |
| 98 | + await generateDocumentation(config, outputPath); |
| 99 | + spinner.succeed('Documentation generated successfully!'); |
| 100 | + |
| 101 | + console.log(chalk.green('\n✅ Analysis and generation complete!')); |
| 102 | + |
| 103 | + // Show comprehensive summary |
| 104 | + console.log(chalk.blue.bold('\n📋 Summary of Changes:\n')); |
| 105 | + |
| 106 | + // API Configuration |
| 107 | + if (apiConfig) { |
| 108 | + console.log(chalk.cyan('🔑 API Configuration:')); |
| 109 | + console.log(` • API key stored in: ${chalk.green('.context-forge-api')}`); |
| 110 | + console.log(` • Provider: ${chalk.green(apiConfig.provider)}`); |
| 111 | + console.log(` • Added to .gitignore: ${chalk.green('✓')}\n`); |
| 112 | + } |
| 113 | + |
| 114 | + // Generated Files |
| 115 | + console.log(chalk.cyan('📁 Generated Files:')); |
| 116 | + console.log(chalk.gray(' ' + outputPath + '/')); |
| 117 | + |
| 118 | + // CLAUDE.md status |
| 119 | + const claudeMdPath = path.join(outputPath, 'CLAUDE.md'); |
| 120 | + if (await fs.pathExists(claudeMdPath)) { |
| 121 | + console.log(chalk.yellow(' ├── CLAUDE.md (UPDATED - appended retrofit section)')); |
| 122 | + } |
| 123 | + |
| 124 | + // Docs folder |
| 125 | + const docsPath = path.join(outputPath, 'Docs'); |
| 126 | + if (await fs.pathExists(docsPath)) { |
| 127 | + console.log(' ├── Docs/'); |
| 128 | + const docFiles = await fs.readdir(docsPath); |
| 129 | + docFiles.forEach((file, index) => { |
| 130 | + const isLast = index === docFiles.length - 1; |
| 131 | + console.log(` │ ${isLast ? '└──' : '├──'} ${chalk.green(file)}`); |
| 132 | + }); |
| 133 | + } |
| 134 | + |
| 135 | + // PRPs folder |
| 136 | + const prpsPath = path.join(outputPath, 'PRPs'); |
| 137 | + if (await fs.pathExists(prpsPath)) { |
| 138 | + console.log(' └── PRPs/'); |
| 139 | + const prpFiles = await fs.readdir(prpsPath); |
| 140 | + prpFiles.forEach((file, index) => { |
| 141 | + const isLast = index === prpFiles.length - 1; |
| 142 | + console.log(` ${isLast ? '└──' : '├──'} ${chalk.green(file)}`); |
| 143 | + }); |
| 144 | + } |
| 145 | + |
| 146 | + console.log(chalk.gray('\n💡 Next steps:')); |
| 147 | + console.log(chalk.gray(' 1. Review the updated CLAUDE.md file')); |
| 148 | + console.log(chalk.gray(' 2. Check the PRPs folder for feature-specific implementations')); |
| 149 | + console.log(chalk.gray(' 3. Use these files with Claude Code for development')); |
| 150 | + |
| 151 | + // Save summary to file |
| 152 | + let summaryContent = `# Context Forge Retrofit Summary |
| 153 | +Generated on: ${new Date().toLocaleString()} |
| 154 | +
|
| 155 | +## Project Analysis |
| 156 | +- **Type**: ${config.projectType} |
| 157 | +- **Tech Stack**: ${Object.values(config.techStack).filter(Boolean).join(', ')} |
| 158 | +- **Components**: ${basicAnalysis.fileStats.components} files |
| 159 | +- **API Routes**: ${basicAnalysis.fileStats.routes} files |
| 160 | +- **Test Coverage**: ${basicAnalysis.fileStats.tests} test files |
| 161 | +
|
| 162 | +## API Configuration |
| 163 | +${apiConfig ? `- **Provider**: ${apiConfig.provider} |
| 164 | +- **Key Location**: .context-forge-api |
| 165 | +- **Added to .gitignore**: ✓` : 'No API configuration used'} |
| 166 | +
|
| 167 | +## Generated Files |
| 168 | +
|
| 169 | +### Updated Files |
| 170 | +- **CLAUDE.md**: Appended retrofit section with date marker |
| 171 | +
|
| 172 | +### New Files Created |
| 173 | +`; |
| 174 | + |
| 175 | + // Add Docs files |
| 176 | + if (await fs.pathExists(docsPath)) { |
| 177 | + summaryContent += '\n#### Docs/\n'; |
| 178 | + const docFiles = await fs.readdir(docsPath); |
| 179 | + docFiles.forEach(file => { |
| 180 | + summaryContent += `- ${file}\n`; |
| 181 | + }); |
| 182 | + } |
| 183 | + |
| 184 | + // Add PRP files |
| 185 | + if (await fs.pathExists(prpsPath)) { |
| 186 | + summaryContent += '\n#### PRPs/\n'; |
| 187 | + const prpFiles = await fs.readdir(prpsPath); |
| 188 | + prpFiles.forEach(file => { |
| 189 | + summaryContent += `- ${file}\n`; |
| 190 | + }); |
| 191 | + } |
| 192 | + |
| 193 | + summaryContent += `\n## Planned Features |
| 194 | +${config.plannedFeatures && config.plannedFeatures.length > 0 |
| 195 | + ? config.plannedFeatures.map(f => `- ${f}`).join('\n') |
| 196 | + : 'No specific features documented'} |
| 197 | +
|
| 198 | +## Next Steps |
| 199 | +1. Review the updated CLAUDE.md file |
| 200 | +2. Check the PRPs folder for feature-specific implementations |
| 201 | +3. Use these files with Claude Code for development |
| 202 | +
|
| 203 | +--- |
| 204 | +*Generated by Context Forge v${version}* |
| 205 | +`; |
| 206 | + |
| 207 | + const summaryPath = path.join(outputPath, 'retrofit-summary.md'); |
| 208 | + await fs.writeFile(summaryPath, summaryContent); |
| 209 | + console.log(chalk.gray(`\n📄 Summary saved to: ${chalk.green('retrofit-summary.md')}`)); |
| 210 | + } |
| 211 | + } catch (error) { |
| 212 | + spinner.fail('Analysis failed'); |
| 213 | + throw error; |
| 214 | + } |
| 215 | + }); |
0 commit comments