UNPKG

@opichi/smartcode

Version:

Universal code intelligence MCP server - analyze any codebase with TypeScript excellence and multi-language support

249 lines (242 loc) 10.3 kB
import { PatternDetector } from './patterns.js'; export class CodeAnalyzer { graph; patternDetector; patterns = []; constructor(graph) { this.graph = graph; this.patternDetector = new PatternDetector(graph); } async analyzeCodebase() { console.log('Analyzing codebase patterns...'); this.patterns = this.patternDetector.detectPatterns(); console.log(`Detected ${this.patterns.length} architectural patterns`); } analyzeImpact(nodeId) { const changedNode = this.graph.getNodesByType('function') .concat(this.graph.getNodesByType('class')) .find(n => n.id === nodeId); if (!changedNode) { throw new Error(`Node ${nodeId} not found`); } // Get impact radius const impactedIds = this.graph.getImpactRadius(nodeId, 3); const allNodes = this.graph.getNodesByType('function') .concat(this.graph.getNodesByType('class')) .concat(this.graph.getNodesByType('variable')); const directlyAffected = allNodes.filter(n => this.graph.getRelatedNodes(nodeId).some(related => related.id === n.id)); const indirectlyAffected = allNodes.filter(n => impactedIds.has(n.id) && !directlyAffected.some(direct => direct.id === n.id)); const riskLevel = this.calculateRiskLevel(changedNode, directlyAffected, indirectlyAffected); const suggestions = this.generateImpactSuggestions(changedNode, directlyAffected, indirectlyAffected); return { changedNode, directlyAffected, indirectlyAffected, riskLevel, suggestions }; } suggestImplementation(description, context) { const suggestions = []; // Analyze existing patterns to suggest similar implementations const relevantPatterns = this.findRelevantPatterns(description); relevantPatterns.forEach(pattern => { const suggestion = this.generatePatternBasedSuggestion(description, pattern); if (suggestion) { suggestions.push(suggestion); } }); // Look for similar existing implementations const similarNodes = this.findSimilarImplementations(description); similarNodes.forEach(node => { const suggestion = this.generateSimilarityBasedSuggestion(description, node); if (suggestion) { suggestions.push(suggestion); } }); return suggestions.sort((a, b) => b.confidence - a.confidence); } findSimilarCode(description) { // This would use semantic search in a real implementation // For now, we'll use keyword matching and pattern analysis const keywords = this.extractKeywords(description); const allNodes = this.graph.getNodesByType('function') .concat(this.graph.getNodesByType('class')); return allNodes.filter(node => this.calculateSimilarity(node, keywords) > 0.3).sort((a, b) => this.calculateSimilarity(b, keywords) - this.calculateSimilarity(a, keywords)); } getArchitecturalOverview() { const components = this.graph.getArchitecturalComponents(); const statistics = this.generateStatistics(); return { components, patterns: this.patterns, statistics }; } calculateRiskLevel(changedNode, directlyAffected, indirectlyAffected) { let riskScore = 0; // Base risk based on node type if (changedNode.type === 'class') riskScore += 2; else if (changedNode.type === 'function') riskScore += 1; // Risk based on number of dependents riskScore += Math.min(directlyAffected.length * 0.5, 3); riskScore += Math.min(indirectlyAffected.length * 0.2, 2); // Risk based on architectural importance const isArchitecturallyImportant = this.patterns.some(pattern => pattern.components.some(comp => comp.id === changedNode.id)); if (isArchitecturallyImportant) riskScore += 2; // Risk based on file type if (changedNode.filePath.includes('model') || changedNode.filePath.includes('controller') || changedNode.filePath.includes('service')) { riskScore += 1; } if (riskScore <= 2) return 'low'; if (riskScore <= 5) return 'medium'; return 'high'; } generateImpactSuggestions(changedNode, directlyAffected, indirectlyAffected) { const suggestions = []; if (directlyAffected.length > 0) { suggestions.push(`Review ${directlyAffected.length} directly affected components`); const testFiles = directlyAffected.filter(n => n.filePath.includes('test') || n.filePath.includes('spec')); if (testFiles.length > 0) { suggestions.push(`Update ${testFiles.length} test files`); } } if (indirectlyAffected.length > 5) { suggestions.push('Consider creating a migration plan due to wide impact'); } // Pattern-specific suggestions const affectedPatterns = this.patterns.filter(pattern => pattern.components.some(comp => directlyAffected.some(affected => affected.id === comp.id))); affectedPatterns.forEach(pattern => { suggestions.push(`Verify ${pattern.name} integrity after changes`); }); if (changedNode.type === 'class' && changedNode.metadata.methods) { suggestions.push('Update method signatures and documentation'); } return suggestions; } findRelevantPatterns(description) { const keywords = this.extractKeywords(description); return this.patterns.filter(pattern => { const patternText = `${pattern.name} ${pattern.description}`.toLowerCase(); return keywords.some(keyword => patternText.includes(keyword)); }); } generatePatternBasedSuggestion(description, pattern) { const suggestion = { type: 'pattern', description: `Follow ${pattern.name} for implementation`, confidence: pattern.confidence * 0.8, reasoning: `Existing ${pattern.name} found with ${pattern.components.length} components` }; // Add specific guidance based on pattern type switch (pattern.type) { case 'mvc': suggestion.description = `Create Model-View-Controller structure similar to existing ${pattern.name}`; suggestion.code = this.generateMVCTemplate(pattern); break; case 'api': suggestion.description = `Follow REST API pattern like ${pattern.name}`; suggestion.code = this.generateAPITemplate(pattern); break; case 'service': suggestion.description = `Implement as service layer following ${pattern.name}`; suggestion.code = this.generateServiceTemplate(pattern); break; } return suggestion; } generateSimilarityBasedSuggestion(description, similarNode) { return { type: 'implementation', description: `Base implementation on similar ${similarNode.type}: ${similarNode.name}`, filePath: similarNode.filePath, confidence: 0.6, reasoning: `Found similar implementation in ${similarNode.filePath}:${similarNode.startLine}` }; } findSimilarImplementations(description) { const keywords = this.extractKeywords(description); const allNodes = this.graph.getNodesByType('function') .concat(this.graph.getNodesByType('class')); return allNodes .filter(node => this.calculateSimilarity(node, keywords) > 0.4) .slice(0, 5); // Top 5 similar implementations } extractKeywords(text) { return text.toLowerCase() .split(/\s+/) .filter(word => word.length > 2) .filter(word => !['the', 'and', 'for', 'with', 'this', 'that'].includes(word)); } calculateSimilarity(node, keywords) { const nodeText = `${node.name} ${node.content}`.toLowerCase(); const matches = keywords.filter(keyword => nodeText.includes(keyword)); return matches.length / keywords.length; } generateMVCTemplate(pattern) { return `// Model class EntityModel { // Define properties and methods } // Controller class EntityController { // Handle HTTP requests } // Routes // Define API endpoints`; } generateAPITemplate(pattern) { return `// GET /resource app.get('/resource', controller.index); // POST /resource app.post('/resource', controller.create); // PUT /resource/:id app.put('/resource/:id', controller.update); // DELETE /resource/:id app.delete('/resource/:id', controller.destroy);`; } generateServiceTemplate(pattern) { return `class EntityService { async create(data) { // Business logic } async update(id, data) { // Business logic } async delete(id) { // Business logic } }`; } generateStatistics() { const allNodes = this.graph.getNodesByType('function') .concat(this.graph.getNodesByType('class')) .concat(this.graph.getNodesByType('variable')); return { totalNodes: allNodes.length, nodesByType: { functions: this.graph.getNodesByType('function').length, classes: this.graph.getNodesByType('class').length, variables: this.graph.getNodesByType('variable').length }, patternsDetected: this.patterns.length, averageComplexity: this.calculateAverageComplexity(allNodes) }; } calculateAverageComplexity(nodes) { const complexities = nodes.map(node => { const lines = node.content.split('\n').length; const branches = (node.content.match(/if|else|for|while|switch/g) || []).length; return lines + branches * 2; // Simple complexity metric }); return complexities.reduce((sum, c) => sum + c, 0) / complexities.length; } } //# sourceMappingURL=analyzer.js.map