Code Review MCP Server

by crazyrabbitLTC
Verified
/** * @file Code Processor * @version 0.1.0 * * Processes code for review */ import * as fs from 'fs'; /** * Processes code for LLM review */ export class CodeProcessor { /** * Maximum characters per chunk to send to LLM */ MAX_CHARS_PER_CHUNK = 100000; /** * Processes Repomix output for review * @param repomixOutput Repomix output or path to output file * @returns Processed code */ async processRepomixOutput(repomixOutput) { try { let content = repomixOutput; // If the output is a file path, read it if (repomixOutput.trim().endsWith('.txt') && fs.existsSync(repomixOutput.trim())) { console.log(`Reading Repomix output from file: ${repomixOutput}`); content = fs.readFileSync(repomixOutput.trim(), 'utf-8'); } else { console.log('Processing Repomix output from string'); } // Process the output const processedOutput = this.formatRepomixOutput(content); // Check if we need to chunk the content due to size if (processedOutput.length > this.MAX_CHARS_PER_CHUNK) { console.warn(`Repomix output exceeds maximum size (${this.MAX_CHARS_PER_CHUNK} chars). Chunking content...`); const chunks = this.chunkLargeCodebase(processedOutput); console.log(`Split content into ${chunks.length} chunks. Using first chunk.`); return chunks[0]; } return processedOutput; } catch (error) { console.error('Error processing Repomix output:', error); throw new Error(`Failed to process Repomix output: ${error.message}`); } } /** * Splits large codebases into manageable chunks * @param code Code to chunk * @returns Array of code chunks */ chunkLargeCodebase(code) { if (!code || code.length <= this.MAX_CHARS_PER_CHUNK) { return [code]; } const chunks = []; let currentIndex = 0; while (currentIndex < code.length) { // Find a good break point (end of a file or section) let endIndex = currentIndex + this.MAX_CHARS_PER_CHUNK; if (endIndex >= code.length) { endIndex = code.length; } else { // Try to find a file boundary to split at const nextFileBoundary = code.indexOf('================', endIndex); if (nextFileBoundary !== -1 && nextFileBoundary - endIndex < this.MAX_CHARS_PER_CHUNK * 0.2) { // If the next file boundary is within 20% of the max chunk size, use it endIndex = nextFileBoundary; } else { // Otherwise, find the last newline before the max size const lastNewline = code.lastIndexOf('\n', endIndex); if (lastNewline !== -1 && lastNewline > currentIndex) { endIndex = lastNewline; } } } // Add the chunk chunks.push(code.substring(currentIndex, endIndex)); currentIndex = endIndex; } return chunks; } /** * Formats Repomix output for LLM consumption * @param repomixOutput Repomix output to format * @returns Formatted output */ formatRepomixOutput(repomixOutput) { // Extract the most relevant parts of the Repomix output let formatted = repomixOutput; // Remove any ASCII art or unnecessarily long headers formatted = formatted.replace(/^\s*[-=*]{10,}\s*$/gm, '================'); // Ensure file headers are prominent formatted = formatted.replace(/^File: (.+)$/gm, '================\nFile: $1\n================'); // Add line numbers to help with references const lines = formatted.split('\n'); let currentFile = ''; let lineCounter = 0; let result = []; for (const line of lines) { // Check if this is a file header if (line.startsWith('File: ')) { currentFile = line.replace('File: ', '').trim(); lineCounter = 0; result.push(line); } // Check if this is a file boundary else if (line === '================') { lineCounter = 0; result.push(line); } // Normal code line else { if (currentFile && !line.startsWith('================')) { lineCounter++; } result.push(line); } } return result.join('\n'); } }