UNPKG

mcp-codesentry

Version:

CodeSentry MCP - AI-powered code review assistant with 5 specialized review tools for security, best practices, and comprehensive code analysis

209 lines (178 loc) 7.21 kB
/** * Review Engine * Orchestrates the review process */ import { logger } from '../utils/logger.js'; import { CodeFlattener } from '../repomix/flattener.js'; import { StorageManager } from '../storage/manager.js'; import { GeminiClient } from '../gemini/client.js'; import type { ReviewResponse, ReviewPlanParams, ReviewImplementationParams, TaskContext } from '../types/index.js'; export class ReviewEngine { constructor( private flattener: CodeFlattener, private storage: StorageManager, private geminiClient: GeminiClient ) { logger.info('Review engine initialized'); } /** * Orchestrates the plan review process */ async reviewPlan(params: ReviewPlanParams): Promise<ReviewResponse> { try { logger.info(`Starting plan review for task: ${params.taskId}`); // 1. Flatten codebase using repomix const codebaseContext = await this.flattener.flattenCodebase(params.codebasePath); if (!codebaseContext) { throw new Error('Failed to flatten codebase for review'); } // 2. Store pre-review snapshot const snapshotId = await this.storage.storeSnapshot(params.taskId, codebaseContext, 'pre'); logger.info(`Stored pre-review snapshot: ${snapshotId}`); // 3. Send to Gemini for review const reviewParams = { ...params, codebaseContext }; const geminiResponse = await this.geminiClient.reviewPlan(reviewParams); // 4. Store task context with plan and review const taskContext: TaskContext = { taskId: params.taskId, taskDescription: params.taskDescription, plan: params.implementationPlan, reviews: [geminiResponse], preReviewSnapshotId: snapshotId, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; await this.storage.storeTaskContext(params.taskId, taskContext); // 5. Format and return response return this.formatReviewResponse(geminiResponse, 'plan', params.taskId); } catch (error) { logger.error(`Error in plan review for task ${params.taskId}:`, error); throw new Error(`Plan review failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Orchestrates the implementation review process */ async reviewImplementation(params: ReviewImplementationParams): Promise<ReviewResponse> { try { logger.info(`Starting implementation review for task: ${params.taskId}`); // 1. Get or create task context let taskContext = await this.storage.retrieveTaskContext(params.taskId); if (!taskContext) { // Create minimal context if not found taskContext = { taskId: params.taskId, taskDescription: params.taskDescription, plan: params.originalPlan, reviews: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; } // 2. Generate diff between before and after states const codebaseDiff = await this.flattener.getDiff(params.beforePath, params.afterPath); if (!codebaseDiff) { throw new Error('Failed to generate codebase diff'); } // 3. Store post-implementation snapshot const afterSnapshot = await this.flattener.flattenCodebase(params.afterPath); if (!afterSnapshot) { throw new Error('Failed to capture post-implementation snapshot'); } const snapshotId = await this.storage.storeSnapshot(params.taskId, afterSnapshot, 'post'); logger.info(`Stored post-implementation snapshot: ${snapshotId}`); // 4. Send to Gemini for implementation review const reviewParams = { ...params, codebaseSnapshot: codebaseDiff }; const geminiResponse = await this.geminiClient.reviewImplementation(reviewParams); // 5. Update task context with implementation details taskContext.implementation = params.implementationSummary; taskContext.postReviewSnapshotId = snapshotId; taskContext.reviews.push(geminiResponse); taskContext.updatedAt = new Date().toISOString(); await this.storage.storeTaskContext(params.taskId, taskContext); // 6. Format and return response return this.formatReviewResponse(geminiResponse, 'implementation', params.taskId); } catch (error) { logger.error(`Error in implementation review for task ${params.taskId}:`, error); throw new Error(`Implementation review failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Compares two task contexts or snapshots */ async compareTaskContexts(taskId1: string, taskId2: string): Promise<string> { try { const context1 = await this.storage.retrieveTaskContext(taskId1); const context2 = await this.storage.retrieveTaskContext(taskId2); if (!context1 || !context2) { throw new Error('One or both task contexts not found'); } // Simple comparison - could be enhanced with more sophisticated diffing return JSON.stringify({ taskId1: context1.taskId, taskId2: context2.taskId, planDifferences: this.comparePlans(context1.plan, context2.plan), reviewCount1: context1.reviews.length, reviewCount2: context2.reviews.length, timeDifference: new Date(context2.createdAt).getTime() - new Date(context1.createdAt).getTime() }, null, 2); } catch (error) { logger.error(`Error comparing task contexts ${taskId1} and ${taskId2}:`, error); throw error; } } /** * Formats Gemini response into standardized ReviewResponse */ private formatReviewResponse(geminiResponse: any, reviewType: 'plan' | 'implementation', taskId: string): ReviewResponse { // Gemini should return structured JSON, but handle potential variations const response = typeof geminiResponse === 'string' ? JSON.parse(geminiResponse) : geminiResponse; return { approved: response.approved || false, reviewType, feedback: { summary: response.feedback?.summary || 'No summary provided', issues: response.feedback?.issues || [], suggestions: response.feedback?.suggestions || [], strengths: response.feedback?.strengths || [] }, metadata: { taskId, reviewedAt: new Date().toISOString(), modelUsed: response.metadata?.modelUsed || 'gemini-1.5-pro', confidence: response.metadata?.confidence || 0.8 } }; } /** * Simple text comparison utility */ private comparePlans(plan1?: string, plan2?: string): string { if (!plan1 || !plan2) { return 'One or both plans are missing'; } if (plan1 === plan2) { return 'Plans are identical'; } // Simple length-based comparison - could be enhanced const lengthDiff = Math.abs(plan1.length - plan2.length); if (lengthDiff > plan1.length * 0.5) { return 'Plans are significantly different'; } else if (lengthDiff > plan1.length * 0.2) { return 'Plans have moderate differences'; } else { return 'Plans have minor differences'; } } }