import { DiffProviderPort, LLMPort, OutputPort, Diff } from '../ports'; import { ReviewComment, ReviewCommentImpl } from '../entities/ReviewComment'; export interface MentorFeedbackOptions { 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 tone?: 'mentor' | 'strict' | 'friendly' | 'technical'; readonly level?: 'beginner' | 'intermediate' | 'advanced'; readonly focus?: ('code-quality' | 'best-practices' | 'architecture' | 'testing' | 'security' | 'performance')[]; readonly includeExamples?: boolean; readonly includeLearningResources?: boolean; } export interface MentorFeedback { readonly id: string; readonly filePath: string; readonly lineNumber?: number; readonly feedback: string; readonly category: 'code-quality' | 'best-practices' | 'architecture' | 'testing' | 'security' | 'performance'; readonly severity: 'info' | 'warning' | 'error' | 'suggestion'; readonly explanation: string; readonly examples?: string[]; readonly learningResources?: string[]; readonly encouragement?: string; } export interface MentorFeedbackUseCase { execute(options?: MentorFeedbackOptions): Promise; } export class MentorFeedbackUseCaseImpl implements MentorFeedbackUseCase { constructor( private readonly diffProvider: DiffProviderPort, private readonly llmPort: LLMPort, private readonly outputPort: OutputPort ) {} async execute(options: MentorFeedbackOptions = {}): 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 provide mentor feedback for'); } // Generate mentor feedback for each diff const allFeedback: MentorFeedback[] = []; for (const diff of diffs) { const feedback = await this.generateMentorFeedback(diff, options); allFeedback.push(...feedback); } // Output the feedback await this.outputMentorFeedback(allFeedback, options); return allFeedback; } catch (error) { await this.outputPort.displayError(`Mentor feedback generation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); throw error; } } private async generateMentorFeedback(diff: Diff, options: MentorFeedbackOptions): Promise { // Skip binary files if (diff.isBinary) { return []; } // Create prompt for LLM const prompt = this.createMentorPrompt(diff, options); const messages = [ { role: 'system' as const, content: 'You are an experienced software development mentor. Provide constructive, educational feedback that helps developers learn and improve their coding skills. Be encouraging while being thorough in your analysis.', }, { role: 'user' as const, content: prompt, }, ]; try { const response = await this.llmPort.generateResponse(messages, { temperature: 0.5, maxTokens: 2000, }); return this.parseMentorFeedback(response.content, diff); } catch (error) { return []; } } private createMentorPrompt(diff: Diff, options: MentorFeedbackOptions): string { const tone = options.tone || 'mentor'; const level = options.level || 'intermediate'; const focus = options.focus || ['code-quality', 'best-practices']; const includeExamples = options.includeExamples !== false; const includeLearningResources = options.includeLearningResources !== false; 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 ` As a ${tone} mentor, provide feedback on this code written by a ${level} level developer: File: ${diff.filePath} Changes: ${changes} Focus areas: ${focus.join(', ')} Please provide mentor feedback in JSON format: [ { "feedback": "Specific feedback about the code", "category": "code-quality|best-practices|architecture|testing|security|performance", "severity": "info|warning|error|suggestion", "explanation": "Educational explanation of why this matters", ${includeExamples ? '"examples": ["Code example 1", "Code example 2"],' : ''} ${includeLearningResources ? '"learningResources": ["Resource 1", "Resource 2"],' : ''} "encouragement": "Encouraging message to motivate the developer", "lineNumber": 42 } ] Guidelines: - Be constructive and encouraging - Explain the "why" behind your feedback - Provide actionable suggestions - Recognize good practices when you see them - Adapt your language to a ${level} level developer - Use a ${tone} tone - Focus on learning and growth `; } private parseMentorFeedback(response: string, diff: Diff): MentorFeedback[] { const feedback: MentorFeedback[] = []; try { // Try to parse JSON response const parsed = JSON.parse(response); if (Array.isArray(parsed)) { for (const item of parsed) { if (item.feedback && item.category && item.severity && item.explanation) { const mentorFeedback: MentorFeedback = { id: `mentor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, filePath: diff.filePath, lineNumber: item.lineNumber, feedback: item.feedback, category: item.category, severity: item.severity, explanation: item.explanation, examples: item.examples, learningResources: item.learningResources, encouragement: item.encouragement, }; feedback.push(mentorFeedback); } } } } catch (error) { // If JSON parsing fails, create a single feedback with the raw response const mentorFeedback: MentorFeedback = { id: `mentor-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, filePath: diff.filePath, feedback: response, category: 'code-quality', severity: 'info', explanation: 'General mentor feedback', encouragement: 'Keep up the good work!', }; feedback.push(mentorFeedback); } return feedback; } private async outputMentorFeedback(feedback: MentorFeedback[], options: MentorFeedbackOptions): Promise { if (feedback.length === 0) { await this.outputPort.displayMessage('No mentor feedback generated.', { format: options.outputFormat || 'console', outputPath: options.outputPath, }); return; } // Group feedback by category const groupedFeedback = feedback.reduce((groups, item) => { const category = item.category; if (!groups[category]) { groups[category] = []; } groups[category].push(item); return groups; }, {} as Record); let output = '# Mentor Feedback\n\n'; output += 'This feedback is designed to help you learn and improve your coding skills. Each point includes explanations and suggestions to guide your development journey.\n\n'; for (const [category, categoryFeedback] of Object.entries(groupedFeedback)) { output += `## ${category.charAt(0).toUpperCase() + category.slice(1).replace('-', ' ')} (${categoryFeedback.length})\n\n`; for (const item of categoryFeedback) { output += `### ${item.filePath}${item.lineNumber ? `:${item.lineNumber}` : ''}\n\n`; output += `**Feedback:** ${item.feedback}\n\n`; output += `**Explanation:** ${item.explanation}\n\n`; if (item.examples && item.examples.length > 0) { output += `**Examples:**\n`; for (const example of item.examples) { output += `\`\`\`\n${example}\n\`\`\`\n`; } output += '\n'; } if (item.learningResources && item.learningResources.length > 0) { output += `**Learning Resources:**\n`; for (const resource of item.learningResources) { output += `- ${resource}\n`; } output += '\n'; } if (item.encouragement) { output += `**Encouragement:** ${item.encouragement}\n\n`; } output += '---\n\n'; } } await this.outputPort.displayMessage(output, { format: options.outputFormat || 'console', outputPath: options.outputPath, }); } }