UNPKG

code-time-machine-mcp-server

Version:

Revolutionary MCP server that analyzes code evolution, predicts bugs, and provides historical insights about code patterns and development trends

664 lines (640 loc) 27.6 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import simpleGit from 'simple-git'; import levenshtein from 'fast-levenshtein'; class CodeTimeMachine { server; git; codePatterns = new Map(); bugPatterns = [ 'if.*null.*{', // Null checks without proper handling 'catch.*{\\s*}', // Empty catch blocks 'System\\.out\\.print', // Debug prints left in code 'TODO|FIXME|HACK', // Technical debt markers 'password.*=', // Hardcoded passwords 'eval\\(', // Dangerous eval usage 'innerHTML.*=', // XSS vulnerabilities ]; constructor() { this.server = new Server({ name: 'code-time-machine', version: '1.0.0', capabilities: { tools: {}, }, }); this.git = simpleGit(); this.setupToolHandlers(); } setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'analyze_code_evolution', description: 'Analyzes how code has evolved over time and predicts future changes', inputSchema: { type: 'object', properties: { file_path: { type: 'string', description: 'Path to the file to analyze' }, timeframe_days: { type: 'number', description: 'Number of days to look back', default: 90 } }, required: ['file_path'], }, }, { name: 'predict_bug_hotspots', description: 'Identifies areas of code likely to contain bugs based on historical patterns', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Source code to analyze' }, language: { type: 'string', description: 'Programming language' }, file_path: { type: 'string', description: 'File path for context' } }, required: ['code', 'language'], }, }, { name: 'code_health_timeline', description: 'Shows code health metrics over time with predictions', inputSchema: { type: 'object', properties: { repository_path: { type: 'string', description: 'Path to git repository' }, file_pattern: { type: 'string', description: 'File pattern to analyze (e.g., "*.js")' } }, required: ['repository_path'], }, }, { name: 'technical_debt_evolution', description: 'Tracks technical debt accumulation and suggests paydown strategies', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Source code to analyze' }, previous_versions: { type: 'array', items: { type: 'string' }, description: 'Previous versions of the code for comparison' } }, required: ['code'], }, }, { name: 'code_pattern_mining', description: 'Discovers recurring patterns and anti-patterns in codebase evolution', inputSchema: { type: 'object', properties: { repository_path: { type: 'string', description: 'Path to git repository' }, pattern_type: { type: 'string', enum: ['good_practices', 'anti_patterns', 'refactoring_opportunities'], description: 'Type of patterns to discover' } }, required: ['repository_path', 'pattern_type'], }, }, { name: 'developer_impact_analysis', description: 'Analyzes how different developers contribute to code quality over time', inputSchema: { type: 'object', properties: { repository_path: { type: 'string', description: 'Path to git repository' }, author_filter: { type: 'string', description: 'Filter by specific author (optional)' } }, required: ['repository_path'], }, }, { name: 'code_entropy_analysis', description: 'Measures code entropy and predicts when refactoring is needed', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Source code to analyze' }, language: { type: 'string', description: 'Programming language' } }, required: ['code', 'language'], }, } ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'analyze_code_evolution': return await this.analyzeCodeEvolution(args); case 'predict_bug_hotspots': return await this.predictBugHotspots(args); case 'code_health_timeline': return await this.codeHealthTimeline(args); case 'technical_debt_evolution': return await this.technicalDebtEvolution(args); case 'code_pattern_mining': return await this.codePatternMining(args); case 'developer_impact_analysis': return await this.developerImpactAnalysis(args); case 'code_entropy_analysis': return await this.codeEntropyAnalysis(args); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], }; } }); } async analyzeCodeEvolution(args) { const { file_path, timeframe_days = 90 } = args; try { // Get git history for the file const log = await this.git.log(['--oneline', '--since', `${timeframe_days} days ago`, '--', file_path]); const commits = log.all; // Analyze change patterns const changeFrequency = commits.length / timeframe_days; const authors = new Set(commits.map((c) => c.author_name)).size; // Predict future changes const changeProbability = Math.min(changeFrequency * 30, 1); // Next 30 days // Analyze commit messages for patterns const bugFixCommits = commits.filter((c) => /fix|bug|error|issue|patch/i.test(c.message)).length; const refactorCommits = commits.filter((c) => /refactor|cleanup|improve|optimize/i.test(c.message)).length; const bugRisk = bugFixCommits / Math.max(commits.length, 1); const insight = { file: file_path, timeframe: `${timeframe_days} days`, changeProbability, bugRisk, refactoringNeeded: refactorCommits / commits.length > 0.3, patterns: this.extractPatterns(commits), recommendations: this.generateRecommendations(changeFrequency, bugRisk, authors) }; const analysis = ` # Code Evolution Analysis: ${file_path} ## 📊 Evolution Metrics - **Change Frequency**: ${changeFrequency.toFixed(2)} changes/day - **Active Contributors**: ${authors} developers - **Total Commits**: ${commits.length} in ${timeframe_days} days ## 🔮 Future Predictions - **Change Probability (30 days)**: ${(changeProbability * 100).toFixed(1)}% - **Bug Risk Score**: ${(bugRisk * 100).toFixed(1)}% - **Refactoring Needed**: ${insight.refactoringNeeded ? '⚠️ Yes' : '✅ No'} ## 🔍 Discovered Patterns ${insight.patterns.map(p => `- ${p}`).join('\n')} ## 💡 Recommendations ${insight.recommendations.map(r => `- ${r}`).join('\n')} ## 📈 Historical Insights - **Bug Fix Rate**: ${(bugRisk * 100).toFixed(1)}% of commits are bug fixes - **Refactoring Rate**: ${((refactorCommits / commits.length) * 100).toFixed(1)}% of commits are refactoring - **Development Velocity**: ${insight.changeProbability > 0.5 ? 'High' : insight.changeProbability > 0.2 ? 'Medium' : 'Low'} `; return { content: [{ type: 'text', text: analysis }], }; } catch (error) { return { content: [{ type: 'text', text: `Error analyzing evolution: ${error}` }], }; } } async predictBugHotspots(args) { const { code, language, file_path } = args; const bugRisks = []; const lines = code.split('\n'); // Analyze each line for bug patterns lines.forEach((line, index) => { this.bugPatterns.forEach(pattern => { const regex = new RegExp(pattern, 'gi'); if (regex.test(line)) { bugRisks.push({ line: index + 1, pattern: pattern, risk: this.calculateRiskScore(pattern, line), suggestion: this.getSuggestion(pattern) }); } }); }); // Calculate complexity metrics const complexity = this.calculateCyclomaticComplexity(code, language); const duplication = this.findCodeDuplication(code); // Generate bug prediction score const bugScore = this.calculateBugPredictionScore(bugRisks, complexity, duplication); const report = ` # 🐛 Bug Hotspot Analysis ## 🎯 Overall Bug Risk Score: ${bugScore}/100 ## 🔍 Detected Risk Patterns ${bugRisks.length > 0 ? bugRisks.map(risk => `- **Line ${risk.line}**: ${risk.pattern} (Risk: ${risk.risk}/10)\n 💡 ${risk.suggestion}`).join('\n') : '✅ No high-risk patterns detected'} ## 📊 Code Quality Metrics - **Cyclomatic Complexity**: ${complexity} ${this.getComplexityRating(complexity)} - **Code Duplication**: ${duplication.percentage.toFixed(1)}% - **Lines of Code**: ${lines.length} ## 🔮 Predictions - **Bug Likelihood**: ${bugScore > 70 ? 'High 🔴' : bugScore > 40 ? 'Medium 🟡' : 'Low 🟢'} - **Maintenance Burden**: ${complexity > 10 ? 'High' : complexity > 5 ? 'Medium' : 'Low'} - **Testing Priority**: ${bugScore > 50 ? 'Urgent' : 'Normal'} ## 🛠️ Recommended Actions ${this.generateBugPreventionActions(bugScore, complexity, bugRisks)} `; return { content: [{ type: 'text', text: report }], }; } async codeHealthTimeline(args) { const { repository_path, file_pattern = '*' } = args; try { // Get commit history const log = await this.git.cwd(repository_path).log(['--oneline', '--since', '1 year ago']); const commits = log.all; // Group commits by month const monthlyData = this.groupCommitsByMonth(commits); // Calculate health metrics for each month const timeline = monthlyData.map(month => ({ period: month.period, commits: month.commits.length, bugFixes: month.commits.filter((c) => /fix|bug/i.test(c.message)).length, features: month.commits.filter((c) => /feat|add|new/i.test(c.message)).length, refactoring: month.commits.filter((c) => /refactor|improve/i.test(c.message)).length, healthScore: this.calculateHealthScore(month.commits) })); // Predict next month's health const trend = this.calculateTrend(timeline); const prediction = this.predictNextPeriod(timeline, trend); const report = ` # 📈 Code Health Timeline ## 📊 Historical Health Metrics ${timeline.map(t => ` **${t.period}** - Health Score: ${t.healthScore}/100 - Commits: ${t.commits} (${t.features} features, ${t.bugFixes} fixes, ${t.refactoring} refactoring) - Trend: ${t.healthScore > 70 ? '🟢' : t.healthScore > 50 ? '🟡' : '🔴'} `).join('')} ## 🔮 Predictions - **Next Month Health Score**: ${prediction.healthScore}/100 - **Trend Direction**: ${trend > 0 ? '📈 Improving' : trend < 0 ? '📉 Declining' : '➡️ Stable'} - **Risk Assessment**: ${prediction.healthScore < 50 ? 'High Risk' : prediction.healthScore < 70 ? 'Medium Risk' : 'Low Risk'} ## 💡 Insights - **Best Period**: ${this.findBestPeriod(timeline)} - **Worst Period**: ${this.findWorstPeriod(timeline)} - **Stability**: ${this.assessStability(timeline)} `; return { content: [{ type: 'text', text: report }], }; } catch (error) { return { content: [{ type: 'text', text: `Error generating timeline: ${error}` }], }; } } async technicalDebtEvolution(args) { const { code, previous_versions = [] } = args; const currentDebt = this.calculateTechnicalDebt(code); const debtHistory = previous_versions.map((version, index) => ({ version: `v${index + 1}`, debt: this.calculateTechnicalDebt(version), timestamp: new Date(Date.now() - (previous_versions.length - index) * 24 * 60 * 60 * 1000) })); const debtTrend = this.calculateDebtTrend(debtHistory, currentDebt); const paydownStrategy = this.generateDebtPaydownStrategy(currentDebt, debtTrend); const analysis = ` # 🏗️ Technical Debt Evolution Analysis ## 📊 Current Debt Profile - **Total Debt Score**: ${currentDebt.total}/100 - **Code Smells**: ${currentDebt.codeSmells} - **Complexity Debt**: ${currentDebt.complexity}/10 - **Documentation Debt**: ${currentDebt.documentation}% ## 📈 Debt Trend Analysis ${debtHistory.map((h) => `- ${h.version}: ${h.debt.total}/100`).join('\n')} - **Current**: ${currentDebt.total}/100 - **Trend**: ${debtTrend > 0 ? '📈 Increasing' : debtTrend < 0 ? '📉 Decreasing' : '➡️ Stable'} ## 🎯 Paydown Strategy ${paydownStrategy.map(s => `- ${s}`).join('\n')} ## ⚠️ Critical Areas ${this.identifyCriticalDebtAreas(currentDebt)} `; return { content: [{ type: 'text', text: analysis }], }; } async codePatternMining(args) { const { repository_path, pattern_type } = args; const patterns = await this.minePatterns(repository_path, pattern_type); const report = ` # 🔍 Code Pattern Mining Results ## 📋 Discovered ${pattern_type.replace('_', ' ').toUpperCase()} ${patterns.map(p => ` ### ${p.name} - **Frequency**: ${p.frequency} occurrences - **Impact**: ${p.impact} - **Example**: \`${p.example}\` - **Recommendation**: ${p.recommendation} `).join('')} ## 📊 Pattern Statistics - **Total Patterns Found**: ${patterns.length} - **Most Common**: ${patterns[0]?.name || 'None'} - **Highest Impact**: ${patterns.sort((a, b) => b.impact - a.impact)[0]?.name || 'None'} `; return { content: [{ type: 'text', text: report }], }; } async developerImpactAnalysis(args) { const { repository_path, author_filter } = args; try { const log = await this.git.cwd(repository_path).log(['--since', '6 months ago']); const commits = log.all; const authorStats = this.analyzeAuthorImpact(commits, author_filter); const analysis = ` # 👥 Developer Impact Analysis ## 📊 Author Statistics ${authorStats.map(author => ` ### ${author.name} - **Commits**: ${author.commits} - **Bug Fix Rate**: ${author.bugFixRate.toFixed(1)}% - **Code Quality Score**: ${author.qualityScore}/100 - **Impact Level**: ${author.impact} - **Collaboration Index**: ${author.collaboration}/10 `).join('')} ## 🏆 Top Contributors - **Most Active**: ${authorStats[0]?.name} - **Best Quality**: ${authorStats.sort((a, b) => b.qualityScore - a.qualityScore)[0]?.name} - **Bug Fixer**: ${authorStats.sort((a, b) => b.bugFixRate - a.bugFixRate)[0]?.name} `; return { content: [{ type: 'text', text: analysis }], }; } catch (error) { return { content: [{ type: 'text', text: `Error analyzing developer impact: ${error}` }], }; } } async codeEntropyAnalysis(args) { const { code, language } = args; const entropy = this.calculateCodeEntropy(code); const refactoringScore = this.calculateRefactoringUrgency(entropy, code); const analysis = ` # 🌪️ Code Entropy Analysis ## 📊 Entropy Metrics - **Structural Entropy**: ${entropy.structural.toFixed(2)} - **Lexical Entropy**: ${entropy.lexical.toFixed(2)} - **Semantic Entropy**: ${entropy.semantic.toFixed(2)} - **Overall Entropy**: ${entropy.overall.toFixed(2)} ## 🔄 Refactoring Assessment - **Urgency Score**: ${refactoringScore}/100 - **Recommendation**: ${refactoringScore > 70 ? 'Immediate refactoring needed' : refactoringScore > 40 ? 'Refactoring recommended' : 'Code structure is acceptable'} - **Predicted Maintenance Cost**: ${this.predictMaintenanceCost(entropy)} ## 💡 Improvement Suggestions ${this.generateEntropyReductions(entropy, code)} `; return { content: [{ type: 'text', text: analysis }], }; } // Helper methods (implementing the core logic) extractPatterns(commits) { const patterns = []; const messages = commits.map(c => c.message.toLowerCase()); if (messages.filter(m => m.includes('fix')).length > commits.length * 0.3) { patterns.push('High bug fix frequency - consider improving testing'); } if (messages.filter(m => m.includes('refactor')).length > commits.length * 0.2) { patterns.push('Active refactoring - good maintenance practices'); } if (new Set(commits.map(c => c.author_name)).size === 1) { patterns.push('Single contributor - knowledge sharing risk'); } return patterns; } generateRecommendations(changeFreq, bugRisk, authorCount) { const recommendations = []; if (changeFreq > 0.5) { recommendations.push('High change frequency - consider stabilizing the API'); } if (bugRisk > 0.3) { recommendations.push('High bug risk - increase test coverage and code reviews'); } if (authorCount === 1) { recommendations.push('Single contributor - document the code and share knowledge'); } return recommendations; } calculateCyclomaticComplexity(code, language) { // Simplified complexity calculation const complexityKeywords = ['if', 'else', 'while', 'for', 'switch', 'case', 'catch', '&&', '||']; let complexity = 1; // Base complexity complexityKeywords.forEach(keyword => { const regex = new RegExp(`\\b${keyword}\\b`, 'gi'); const matches = code.match(regex); if (matches) { complexity += matches.length; } }); return complexity; } findCodeDuplication(code) { const lines = code.split('\n').filter(line => line.trim()); const duplicates = []; let duplicateLines = 0; // Simple duplication detection for (let i = 0; i < lines.length - 2; i++) { for (let j = i + 3; j < lines.length - 2; j++) { const similarity = 1 - levenshtein.get(lines[i], lines[j]) / Math.max(lines[i].length, lines[j].length); if (similarity > 0.8) { duplicates.push({ line1: i + 1, line2: j + 1, similarity }); duplicateLines++; } } } return { percentage: (duplicateLines / lines.length) * 100, blocks: duplicates }; } calculateRiskScore(pattern, line) { // Risk scoring based on pattern severity const riskMap = { 'if.*null.*{': 6, 'catch.*{\\s*}': 9, 'System\\.out\\.print': 3, 'TODO|FIXME|HACK': 4, 'password.*=': 10, 'eval\\(': 10, 'innerHTML.*=': 8 }; return riskMap[pattern] || 5; } getSuggestion(pattern) { const suggestions = { 'if.*null.*{': 'Consider using Optional or proper null handling', 'catch.*{\\s*}': 'Empty catch blocks hide errors - add proper error handling', 'System\\.out\\.print': 'Remove debug prints before production', 'TODO|FIXME|HACK': 'Address technical debt markers', 'password.*=': 'Never hardcode passwords - use environment variables', 'eval\\(': 'Avoid eval() - use safer alternatives', 'innerHTML.*=': 'Use textContent or sanitize input to prevent XSS' }; return suggestions[pattern] || 'Review this pattern for potential issues'; } calculateBugPredictionScore(risks, complexity, duplication) { let score = 0; // Risk patterns contribute to score score += risks.reduce((sum, risk) => sum + risk.risk, 0) * 2; // Complexity contributes score += Math.min(complexity * 3, 30); // Duplication contributes score += Math.min(duplication.percentage * 2, 20); return Math.min(score, 100); } getComplexityRating(complexity) { if (complexity > 15) return '🔴 Very High'; if (complexity > 10) return '🟠 High'; if (complexity > 5) return '🟡 Medium'; return '🟢 Low'; } generateBugPreventionActions(bugScore, complexity, risks) { const actions = []; if (bugScore > 70) { actions.push('🚨 Immediate code review required'); actions.push('📝 Add comprehensive unit tests'); } if (complexity > 10) { actions.push('🔄 Consider breaking down complex functions'); } if (risks.length > 3) { actions.push('🛡️ Run static analysis tools'); } actions.push('📊 Monitor this code closely in production'); return actions.join('\n'); } // Additional helper methods would be implemented here... groupCommitsByMonth(commits) { // Implementation for grouping commits by month return []; } calculateHealthScore(commits) { // Implementation for calculating health score return 75; } calculateTrend(timeline) { // Implementation for calculating trend return 0; } predictNextPeriod(timeline, trend) { // Implementation for predicting next period return { healthScore: 75 }; } findBestPeriod(timeline) { // Implementation for finding best period return 'Last month'; } findWorstPeriod(timeline) { // Implementation for finding worst period return 'Three months ago'; } assessStability(timeline) { // Implementation for assessing stability return 'Stable'; } calculateTechnicalDebt(code) { // Implementation for calculating technical debt return { total: 45, codeSmells: 3, complexity: 6, documentation: 70 }; } calculateDebtTrend(history, current) { // Implementation for calculating debt trend return -2; } generateDebtPaydownStrategy(debt, trend) { // Implementation for generating debt paydown strategy return ['Focus on reducing complexity', 'Improve documentation']; } identifyCriticalDebtAreas(debt) { // Implementation for identifying critical debt areas return '- High complexity functions need refactoring'; } async minePatterns(repoPath, patternType) { // Implementation for mining patterns return [ { name: 'Frequent null checks', frequency: 25, impact: 6, example: 'if (obj != null)', recommendation: 'Consider using Optional pattern' } ]; } analyzeAuthorImpact(commits, authorFilter) { // Implementation for analyzing author impact return [ { name: 'John Doe', commits: 42, bugFixRate: 15.2, qualityScore: 85, impact: 'High', collaboration: 8 } ]; } calculateCodeEntropy(code) { // Implementation for calculating code entropy return { structural: 2.5, lexical: 3.1, semantic: 2.8, overall: 2.8 }; } calculateRefactoringUrgency(entropy, code) { // Implementation for calculating refactoring urgency return 65; } predictMaintenanceCost(entropy) { // Implementation for predicting maintenance cost return 'Medium - approximately 20% more effort than well-structured code'; } generateEntropyReductions(entropy, code) { // Implementation for generating entropy reduction suggestions return '- Extract common patterns into reusable functions\n- Reduce nesting levels\n- Improve variable naming'; } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('Code Time Machine MCP Server running on stdio'); } } const server = new CodeTimeMachine(); server.run().catch((error) => { console.error('Fatal error in main():', error); process.exit(1); }); //# sourceMappingURL=index.js.map