UNPKG

@opichi/smartcode

Version:

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

227 lines 9.46 kB
export class CodeKnowledgeGraph { nodes = new Map(); relationships = []; projectStructure = null; constructor() { } addNode(node) { this.nodes.set(node.id, node); } addNodes(nodes) { nodes.forEach(node => this.addNode(node)); } addRelationship(relationship) { this.relationships.push(relationship); } setProjectStructure(structure) { this.projectStructure = structure; } buildRelationships() { console.log('Building code relationships...'); // Clear existing relationships this.relationships = []; // Build different types of relationships this.buildImportRelationships(); this.buildClassMethodRelationships(); this.buildCallRelationships(); this.buildArchitecturalRelationships(); console.log(`Built ${this.relationships.length} relationships`); } buildImportRelationships() { const importNodes = Array.from(this.nodes.values()).filter(n => n.type === 'import'); importNodes.forEach(importNode => { const source = importNode.metadata.source; if (!source) return; // Find what this import is importing from const targetNodes = this.findNodesByFile(source); targetNodes.forEach(targetNode => { if (targetNode.type === 'export' || targetNode.type === 'function' || targetNode.type === 'class') { this.addRelationship({ from: importNode.id, to: targetNode.id, type: 'imports', metadata: { source } }); } }); }); } buildClassMethodRelationships() { const classNodes = Array.from(this.nodes.values()).filter(n => n.type === 'class'); classNodes.forEach(classNode => { // Find methods in the same file that belong to this class const fileMethods = this.findNodesByFile(classNode.filePath) .filter(n => n.type === 'function' && n.startLine > classNode.startLine && n.endLine < classNode.endLine); fileMethods.forEach(method => { this.addRelationship({ from: classNode.id, to: method.id, type: 'defines', metadata: { memberType: 'method' } }); }); }); } buildCallRelationships() { // This is a simplified version - in practice, you'd need more sophisticated AST analysis const functionNodes = Array.from(this.nodes.values()).filter(n => n.type === 'function'); functionNodes.forEach(funcNode => { // Look for function calls in the content const content = funcNode.content; // Find other functions that might be called functionNodes.forEach(otherFunc => { if (funcNode.id !== otherFunc.id && content.includes(otherFunc.name + '(')) { this.addRelationship({ from: funcNode.id, to: otherFunc.id, type: 'calls', metadata: { callType: 'function' } }); } }); }); } buildArchitecturalRelationships() { if (!this.projectStructure) return; // Build MVC relationships for web frameworks if (this.projectStructure.framework === 'rails' || this.projectStructure.framework === 'express' || this.projectStructure.framework === 'django') { this.buildMVCRelationships(); } // Build API route relationships this.buildRouteRelationships(); } buildMVCRelationships() { const models = this.findNodesByPattern('model'); const controllers = this.findNodesByPattern('controller'); const views = this.findNodesByPattern('view'); // Connect controllers to models controllers.forEach(controller => { models.forEach(model => { if (this.isRelated(controller, model)) { this.addRelationship({ from: controller.id, to: model.id, type: 'uses', metadata: { pattern: 'mvc', relationship: 'controller-model' } }); } }); }); // Connect controllers to views controllers.forEach(controller => { views.forEach(view => { if (this.isRelated(controller, view)) { this.addRelationship({ from: controller.id, to: view.id, type: 'uses', metadata: { pattern: 'mvc', relationship: 'controller-view' } }); } }); }); } buildRouteRelationships() { // Find route definitions and connect them to controllers const routes = Array.from(this.nodes.values()).filter(n => n.content.includes('router.') || n.content.includes('app.get') || n.content.includes('app.post') || n.content.includes('Route::')); const controllers = this.findNodesByPattern('controller'); routes.forEach(route => { controllers.forEach(controller => { if (route.content.includes(controller.name) || this.extractControllerFromRoute(route.content) === controller.name) { this.addRelationship({ from: route.id, to: controller.id, type: 'uses', metadata: { pattern: 'routing' } }); } }); }); } findNodesByFile(filePath) { return Array.from(this.nodes.values()).filter(n => n.filePath === filePath || n.filePath.endsWith(filePath)); } findNodesByPattern(pattern) { return Array.from(this.nodes.values()).filter(n => n.filePath.toLowerCase().includes(pattern) || n.name.toLowerCase().includes(pattern)); } isRelated(node1, node2) { // Simple heuristic: check if names are related const name1 = node1.name.toLowerCase().replace(/controller|model|view/, ''); const name2 = node2.name.toLowerCase().replace(/controller|model|view/, ''); return name1.includes(name2) || name2.includes(name1) || this.extractEntityName(name1) === this.extractEntityName(name2); } extractEntityName(name) { // Extract the core entity name (e.g., 'user' from 'UserController') return name.replace(/(controller|model|view|service)s?$/, '').toLowerCase(); } extractControllerFromRoute(routeContent) { // Extract controller name from route definition const matches = routeContent.match(/(\w+)Controller/); return matches ? matches[1] + 'Controller' : ''; } // Query methods getRelatedNodes(nodeId, relationshipType) { const relationships = this.relationships.filter(rel => (rel.from === nodeId || rel.to === nodeId) && (!relationshipType || rel.type === relationshipType)); const relatedNodeIds = relationships.map(rel => rel.from === nodeId ? rel.to : rel.from); return relatedNodeIds .map(id => this.nodes.get(id)) .filter((node) => node !== undefined); } getNodesByType(type) { return Array.from(this.nodes.values()).filter(n => n.type === type); } findNodesByName(name) { return Array.from(this.nodes.values()).filter(n => n.name.toLowerCase().includes(name.toLowerCase())); } getArchitecturalComponents() { const components = new Map(); components.set('models', this.findNodesByPattern('model')); components.set('controllers', this.findNodesByPattern('controller')); components.set('services', this.findNodesByPattern('service')); components.set('routes', this.findNodesByPattern('route')); components.set('views', this.findNodesByPattern('view')); components.set('components', this.findNodesByPattern('component')); return components; } getImpactRadius(nodeId, maxDepth = 3) { const impacted = new Set(); const queue = [{ id: nodeId, depth: 0 }]; const visited = new Set(); while (queue.length > 0) { const { id, depth } = queue.shift(); if (visited.has(id) || depth > maxDepth) continue; visited.add(id); impacted.add(id); // Find all nodes that depend on this one const dependents = this.relationships .filter(rel => rel.to === id) .map(rel => rel.from); dependents.forEach(depId => { if (!visited.has(depId)) { queue.push({ id: depId, depth: depth + 1 }); } }); } return impacted; } exportGraph() { return { nodes: Array.from(this.nodes.values()), relationships: this.relationships }; } } //# sourceMappingURL=graph.js.map