UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

198 lines (197 loc) 8.28 kB
import { TokenEstimator } from './token-estimator.js'; import { readFileSecure } from '../../code-map-generator/fsUtils.js'; export class FileContentProcessor { static DEFAULT_LOC_THRESHOLD = 1000; static DEFAULT_MAX_CONTENT_LENGTH = 25; static async processFileContent(filePath, fileContent, options) { const startTime = Date.now(); const lines = fileContent.split('\n'); const totalLines = lines.length; const locThreshold = options.locThreshold || this.DEFAULT_LOC_THRESHOLD; const contentType = this.detectContentType(filePath); const originalTokenEstimate = TokenEstimator.estimateFileTokens(filePath, fileContent, this.mapToTokenEstimatorType(contentType)); const processingMetadata = { filePath, fileSize: fileContent.length, processingTime: 0, optimizationApplied: false, contentType, encoding: 'utf-8' }; if (totalLines <= locThreshold) { const contentSection = { type: 'full', startLine: 1, endLine: totalLines, content: fileContent, tokenCount: originalTokenEstimate.totalTokens, description: `Complete file content (${totalLines} lines)` }; processingMetadata.processingTime = Date.now() - startTime; return { content: fileContent, isOptimized: false, totalLines, fullContentLines: totalLines, tokenEstimate: originalTokenEstimate.totalTokens, contentSections: [contentSection], processingMetadata }; } return this.processLargeFile(filePath, fileContent, lines, options, processingMetadata, startTime); } static async processLargeFile(filePath, fileContent, lines, options, processingMetadata, startTime) { const totalLines = lines.length; const locThreshold = options.locThreshold || this.DEFAULT_LOC_THRESHOLD; const fullContentLines = lines.slice(0, locThreshold); const optimizationLines = lines.slice(locThreshold); const fullContent = fullContentLines.join('\n'); const optimizationContent = optimizationLines.join('\n'); const optimizedContent = await this.optimizeContent(optimizationContent, options); const fullContentTokens = TokenEstimator.estimateTokens(fullContent); const optimizedContentTokens = TokenEstimator.estimateTokens(optimizedContent); const totalTokens = fullContentTokens + optimizedContentTokens; const contentSections = [ { type: 'full', startLine: 1, endLine: locThreshold, content: fullContent, tokenCount: fullContentTokens, description: `Unoptimized content (lines 1-${locThreshold})` }, { type: 'optimized', startLine: locThreshold + 1, endLine: totalLines, content: optimizedContent, tokenCount: optimizedContentTokens, description: `Optimized content (lines ${locThreshold + 1}-${totalLines})` } ]; const combinedContent = this.combineContentWithMarkers(fullContent, optimizedContent, locThreshold); const originalOptimizationTokens = TokenEstimator.estimateTokens(optimizationContent); const optimizationRatio = optimizedContentTokens / originalOptimizationTokens; processingMetadata.processingTime = Date.now() - startTime; processingMetadata.optimizationApplied = true; processingMetadata.optimizationRatio = optimizationRatio; return { content: combinedContent, isOptimized: true, totalLines, fullContentLines: locThreshold, optimizedLines: totalLines - locThreshold, tokenEstimate: totalTokens, contentSections, processingMetadata }; } static async optimizeContent(content, options) { try { const maxLength = options.maxContentLength || this.DEFAULT_MAX_CONTENT_LENGTH; const preserveComments = options.preserveComments ?? true; let optimized = (content || '') .split('\n') .map(line => (line || '').trim()) .filter(line => { if (line.length === 0) return false; if (!preserveComments && (line.startsWith('//') || line.startsWith('#') || line.startsWith('/*'))) { return false; } return true; }) .join('\n'); if (optimized.length > maxLength * 100) { const lines = optimized.split('\n'); const targetLines = Math.floor(lines.length * 0.7); optimized = lines.slice(0, targetLines).join('\n') + '\n// ... (content optimized for token efficiency)'; } return optimized; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown optimization error'; return `// [OPTIMIZATION WARNING: ${errorMessage}]\n${content}`; } } static combineContentWithMarkers(fullContent, optimizedContent, threshold) { const marker = `\n// ===== OPTIMIZATION BOUNDARY (Line ${threshold}) =====\n// Content below this line has been optimized for token efficiency\n// Original structure and semantics are preserved\n\n`; return fullContent + marker + optimizedContent; } static detectContentType(filePath) { const extension = filePath.split('.').pop()?.toLowerCase(); const typeMap = { 'js': 'javascript', 'ts': 'typescript', 'jsx': 'javascript', 'tsx': 'typescript', 'py': 'python', 'java': 'java', 'cpp': 'cpp', 'c': 'c', 'cs': 'csharp', 'php': 'php', 'rb': 'ruby', 'go': 'go', 'rs': 'rust', 'swift': 'swift', 'kt': 'kotlin', 'json': 'json', 'xml': 'xml', 'html': 'html', 'css': 'css', 'md': 'markdown', 'yml': 'yaml', 'yaml': 'yaml' }; return typeMap[extension || ''] || 'text'; } static mapToTokenEstimatorType(contentType) { switch (contentType) { case 'xml': case 'html': return 'xml'; case 'json': return 'json'; case 'markdown': return 'markdown'; case 'javascript': case 'typescript': case 'python': case 'java': case 'cpp': case 'c': case 'csharp': case 'php': case 'ruby': case 'go': case 'rust': case 'swift': case 'kotlin': return 'code'; default: return 'plain'; } } static async readAndProcessFile(filePath, options) { try { const fileContent = await readFileSecure(filePath, options.allowedDirectory); return this.processFileContent(filePath, fileContent, options); } catch (error) { throw new Error(`Failed to read and process file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } static getProcessingStats(result) { const tokenEfficiency = result.isOptimized && result.processingMetadata.optimizationRatio ? (1 - result.processingMetadata.optimizationRatio) * 100 : 0; return { totalLines: result.totalLines, optimizationApplied: result.isOptimized, tokenEfficiency: Math.round(tokenEfficiency * 100) / 100, processingTime: result.processingMetadata.processingTime, contentSectionCount: result.contentSections.length }; } }