@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
JavaScript
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