import { DiffProviderPort, LLMPort, OutputPort, Diff } from '../ports'; import { ReviewComment, ReviewCommentImpl } from '../entities/ReviewComment'; export interface RefactorOptions { readonly baseRef?: string; readonly headRef?: string; readonly includeStaged?: boolean; readonly includeUnstaged?: boolean; readonly filePatterns?: string[]; readonly excludePatterns?: string[]; readonly outputFormat?: 'console' | 'markdown' | 'json' | 'html' | 'file'; readonly outputPath?: string; readonly focus?: ('performance' | 'readability' | 'maintainability' | 'architecture')[]; readonly severity?: 'minor' | 'moderate' | 'major'; } export interface RefactorSuggestion { readonly id: string; readonly filePath: string; readonly lineNumber?: number; readonly currentCode: string; readonly suggestedCode: string; readonly reason: string; readonly impact: 'low' | 'medium' | 'high'; readonly effort: 'low' | 'medium' | 'high'; readonly category: 'performance' | 'readability' | 'maintainability' | 'architecture' | 'style'; } export interface SuggestRefactorUseCase { execute(options?: RefactorOptions): Promise; } export class SuggestRefactorUseCaseImpl implements SuggestRefactorUseCase { constructor( private readonly diffProvider: DiffProviderPort, private readonly llmPort: LLMPort, private readonly outputPort: OutputPort ) {} async execute(options: RefactorOptions = {}): Promise { try { // Get diffs const diffs = await this.diffProvider.getDiffs({ baseRef: options.baseRef, headRef: options.headRef, includeStaged: options.includeStaged, includeUnstaged: options.includeUnstaged, filePatterns: options.filePatterns, excludePatterns: options.excludePatterns, }); if (diffs.length === 0) { throw new Error('No changes found to suggest refactoring for'); } // Analyze each diff for refactoring opportunities const allSuggestions: RefactorSuggestion[] = []; for (const diff of diffs) { const suggestions = await this.analyzeRefactoringOpportunities(diff, options); allSuggestions.push(...suggestions); } // Output the suggestions await this.outputRefactorSuggestions(allSuggestions, { format: options.outputFormat || 'console', outputPath: options.outputPath, }); return allSuggestions; } catch (error) { await this.outputPort.displayError(`Refactoring analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`); throw error; } } private async analyzeRefactoringOpportunities(diff: Diff, options: RefactorOptions): Promise { // Skip binary files if (diff.isBinary) { return []; } // Create prompt for LLM const prompt = this.createRefactorPrompt(diff, options); const messages = [ { role: 'system' as const, content: 'You are an expert software architect and refactoring specialist. Analyze code for refactoring opportunities and suggest improvements that enhance code quality, performance, and maintainability.', }, { role: 'user' as const, content: prompt, }, ]; try { const response = await this.llmPort.generateResponse(messages, { temperature: 0.4, maxTokens: 2500, }); return this.parseRefactorSuggestions(response.content, diff); } catch (error) { return []; } } private createRefactorPrompt(diff: Diff, options: RefactorOptions): string { const focus = options.focus || ['performance', 'readability', 'maintainability']; const severity = options.severity || 'moderate'; const changes = diff.hunks .map(hunk => hunk.lines .filter(line => line.type === 'added' || line.type === 'removed') .map(line => `${line.type === 'added' ? '+' : '-'}${line.content}`) .join('\n') ) .join('\n'); return ` Analyze the following code changes in ${diff.filePath} for refactoring opportunities: ${changes} Focus areas: ${focus.join(', ')} Severity level: ${severity} Please provide refactoring suggestions in JSON format: [ { "currentCode": "the code that could be improved", "suggestedCode": "the improved version", "reason": "why this refactoring is beneficial", "impact": "low|medium|high", "effort": "low|medium|high", "category": "performance|readability|maintainability|architecture|style", "lineNumber": 42 } ] Consider: 1. Code duplication and opportunities for extraction 2. Complex functions that could be broken down 3. Performance optimizations 4. Better naming and structure 5. Design pattern applications 6. Error handling improvements 7. Type safety enhancements `; } private parseRefactorSuggestions(response: string, diff: Diff): RefactorSuggestion[] { const suggestions: RefactorSuggestion[] = []; try { // Try to parse JSON response const parsed = JSON.parse(response); if (Array.isArray(parsed)) { for (const item of parsed) { if (item.currentCode && item.suggestedCode && item.reason) { const suggestion: RefactorSuggestion = { id: `refactor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, filePath: diff.filePath, lineNumber: item.lineNumber, currentCode: item.currentCode, suggestedCode: item.suggestedCode, reason: item.reason, impact: item.impact || 'medium', effort: item.effort || 'medium', category: item.category || 'maintainability', }; suggestions.push(suggestion); } } } } catch (error) { // If JSON parsing fails, create a single suggestion with the raw response const suggestion: RefactorSuggestion = { id: `refactor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, filePath: diff.filePath, currentCode: 'See analysis below', suggestedCode: 'See suggestions below', reason: response, impact: 'medium', effort: 'medium', category: 'maintainability', }; suggestions.push(suggestion); } return suggestions; } private async outputRefactorSuggestions(suggestions: RefactorSuggestion[], options: { format?: string; outputPath?: string }): Promise { if (suggestions.length === 0) { await this.outputPort.displayMessage('No refactoring suggestions found.', { format: (options.format || 'console') as any, outputPath: options.outputPath, }); return; } // Group suggestions by category const groupedSuggestions = suggestions.reduce((groups, suggestion) => { const category = suggestion.category; if (!groups[category]) { groups[category] = []; } groups[category].push(suggestion); return groups; }, {} as Record); let output = '# Refactoring Suggestions\n\n'; for (const [category, categorySuggestions] of Object.entries(groupedSuggestions)) { output += `## ${category.charAt(0).toUpperCase() + category.slice(1)} (${categorySuggestions.length})\n\n`; for (const suggestion of categorySuggestions) { output += `### ${suggestion.filePath}${suggestion.lineNumber ? `:${suggestion.lineNumber}` : ''}\n\n`; output += `**Impact:** ${suggestion.impact} | **Effort:** ${suggestion.effort}\n\n`; output += `**Reason:** ${suggestion.reason}\n\n`; output += `**Current Code:**\n\`\`\`\n${suggestion.currentCode}\n\`\`\`\n\n`; output += `**Suggested Code:**\n\`\`\`\n${suggestion.suggestedCode}\n\`\`\`\n\n`; output += '---\n\n'; } } await this.outputPort.displayMessage(output, { format: (options.format || 'console') as any, outputPath: options.outputPath, }); } }