Skip to main content
Glama
FosterG4

Code Reference Optimizer MCP Server

by FosterG4
RelationshipAnalyzer.ts18.5 kB
import type { ASTNode, SymbolInfo } from '../types/index.js'; import type { SymbolRelationship, SemanticContext } from './SemanticAnalyzer.js'; export interface InheritanceChain { symbol: string; ancestors: string[]; descendants: string[]; depth: number; } export interface CompositionRelation { container: string; contained: string; relationship: 'has-a' | 'uses' | 'aggregates' | 'composes'; strength: number; // 0-1, how tightly coupled } export interface DependencyCluster { id: string; symbols: string[]; cohesion: number; // How related symbols are within cluster coupling: number; // How dependent cluster is on other clusters relationships: SymbolRelationship[]; } export interface ArchitecturalPattern { type: 'singleton' | 'factory' | 'observer' | 'strategy' | 'decorator' | 'adapter' | 'mvc' | 'repository'; symbols: string[]; confidence: number; description: string; } export class RelationshipAnalyzer { /** * Analyze inheritance hierarchies in the codebase */ analyzeInheritanceHierarchy(context: SemanticContext): Map<string, InheritanceChain> { const inheritanceChains = new Map<string, InheritanceChain>(); const inheritanceMap = new Map<string, string[]>(); // child -> parents const childrenMap = new Map<string, string[]>(); // parent -> children // Build inheritance maps from relationships for (const [symbol, relationships] of context.relationships) { const parents: string[] = []; for (const rel of relationships) { if (rel.type === 'inherits' || rel.type === 'implements') { parents.push(rel.target); // Update children map const children = childrenMap.get(rel.target) || []; children.push(symbol); childrenMap.set(rel.target, children); } } if (parents.length > 0) { inheritanceMap.set(symbol, parents); } } // Build inheritance chains for each symbol for (const [symbol] of context.symbols) { const ancestors = this.getAncestors(symbol, inheritanceMap); const descendants = this.getDescendants(symbol, childrenMap); const depth = this.calculateInheritanceDepth(symbol, inheritanceMap); inheritanceChains.set(symbol, { symbol, ancestors, descendants, depth }); } return inheritanceChains; } /** * Analyze composition relationships */ analyzeComposition(context: SemanticContext): CompositionRelation[] { const compositions: CompositionRelation[] = []; for (const [symbol, relationships] of context.relationships) { for (const rel of relationships) { let compositionType: CompositionRelation['relationship']; let strength: number; switch (rel.type) { case 'contains': compositionType = 'composes'; strength = 0.9; break; case 'uses': compositionType = 'uses'; strength = 0.6; break; case 'calls': compositionType = 'uses'; strength = 0.4; break; default: continue; } // Analyze the strength based on frequency and context const adjustedStrength = this.calculateCompositionStrength( symbol, rel.target, context, strength ); compositions.push({ container: symbol, contained: rel.target, relationship: compositionType, strength: adjustedStrength }); } } return compositions; } /** * Identify dependency clusters using graph analysis */ identifyDependencyClusters(context: SemanticContext): DependencyCluster[] { const clusters: DependencyCluster[] = []; const visited = new Set<string>(); const adjacencyList = this.buildAdjacencyList(context); let clusterId = 0; for (const [symbol] of context.symbols) { if (!visited.has(symbol)) { const clusterSymbols = this.findConnectedComponent(symbol, adjacencyList, visited); if (clusterSymbols.length > 1) { const clusterRelationships = this.getClusterRelationships(clusterSymbols, context); const cohesion = this.calculateCohesion(clusterSymbols, clusterRelationships); const coupling = this.calculateCoupling(clusterSymbols, context); clusters.push({ id: `cluster_${clusterId++}`, symbols: clusterSymbols, cohesion, coupling, relationships: clusterRelationships }); } } } return clusters; } /** * Detect architectural patterns in the codebase */ detectArchitecturalPatterns(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; // Detect Singleton pattern patterns.push(...this.detectSingletonPattern(context)); // Detect Factory pattern patterns.push(...this.detectFactoryPattern(context)); // Detect Observer pattern patterns.push(...this.detectObserverPattern(context)); // Detect Strategy pattern patterns.push(...this.detectStrategyPattern(context)); // Detect MVC pattern patterns.push(...this.detectMVCPattern(context)); return patterns.filter(p => p.confidence > 0.6); } /** * Calculate coupling between two symbols */ calculateCoupling(symbol1: string, symbol2: string, context: SemanticContext): number { const relationships1 = context.relationships.get(symbol1) || []; const relationships2 = context.relationships.get(symbol2) || []; let couplingScore = 0; // Direct relationships for (const rel of relationships1) { if (rel.target === symbol2) { switch (rel.type) { case 'inherits': case 'implements': couplingScore += 0.9; break; case 'contains': couplingScore += 0.8; break; case 'uses': case 'calls': couplingScore += 0.6; break; case 'imports': couplingScore += 0.4; break; } } } // Bidirectional relationships for (const rel of relationships2) { if (rel.target === symbol1) { couplingScore += 0.3; // Lower weight for reverse relationships } } return Math.min(couplingScore, 1.0); } /** * Analyze symbol stability (how often it changes vs how much it's depended upon) */ analyzeSymbolStability(symbol: string, context: SemanticContext): { afferentCoupling: number; // How many symbols depend on this one efferentCoupling: number; // How many symbols this one depends on instability: number; // 0 = stable, 1 = unstable abstractness: number; // 0 = concrete, 1 = abstract } { let afferentCoupling = 0; let efferentCoupling = 0; // Count incoming dependencies (afferent) for (const [, relationships] of context.relationships) { for (const rel of relationships) { if (rel.target === symbol) { afferentCoupling++; } } } // Count outgoing dependencies (efferent) const symbolRelationships = context.relationships.get(symbol) || []; efferentCoupling = symbolRelationships.length; // Calculate instability: I = Ce / (Ca + Ce) const totalCoupling = afferentCoupling + efferentCoupling; const instability = totalCoupling > 0 ? efferentCoupling / totalCoupling : 0; // Calculate abstractness based on symbol type const symbolInfo = context.symbols.get(symbol); let abstractness = 0; if (symbolInfo) { switch (symbolInfo.type) { case 'interface': case 'trait': abstractness = 1.0; break; case 'class': // Check if it has abstract methods or is a base class abstractness = this.calculateClassAbstractness(symbol, context); break; case 'function': abstractness = 0.2; // Functions are mostly concrete break; default: abstractness = 0.1; } } return { afferentCoupling, efferentCoupling, instability, abstractness }; } private getAncestors(symbol: string, inheritanceMap: Map<string, string[]>): string[] { const ancestors: string[] = []; const visited = new Set<string>(); const traverse = (current: string) => { if (visited.has(current)) return; visited.add(current); const parents = inheritanceMap.get(current) || []; for (const parent of parents) { ancestors.push(parent); traverse(parent); } }; traverse(symbol); return [...new Set(ancestors)]; } private getDescendants(symbol: string, childrenMap: Map<string, string[]>): string[] { const descendants: string[] = []; const visited = new Set<string>(); const traverse = (current: string) => { if (visited.has(current)) return; visited.add(current); const children = childrenMap.get(current) || []; for (const child of children) { descendants.push(child); traverse(child); } }; traverse(symbol); return [...new Set(descendants)]; } private calculateInheritanceDepth(symbol: string, inheritanceMap: Map<string, string[]>): number { const visited = new Set<string>(); const traverse = (current: string): number => { if (visited.has(current)) return 0; visited.add(current); const parents = inheritanceMap.get(current) || []; if (parents.length === 0) return 0; return 1 + Math.max(...parents.map(parent => traverse(parent))); }; return traverse(symbol); } private calculateCompositionStrength( container: string, contained: string, context: SemanticContext, baseStrength: number ): number { // Count how many times the contained symbol is referenced const references = context.crossReferences.get(contained) || []; const containerReferences = references.filter(ref => ref.referencedBy === container); // Adjust strength based on reference frequency const frequencyMultiplier = Math.min(containerReferences.length / 5, 1.5); return Math.min(baseStrength * frequencyMultiplier, 1.0); } private buildAdjacencyList(context: SemanticContext): Map<string, Set<string>> { const adjacencyList = new Map<string, Set<string>>(); // Initialize with all symbols for (const [symbol] of context.symbols) { adjacencyList.set(symbol, new Set()); } // Add edges based on relationships for (const [symbol, relationships] of context.relationships) { const neighbors = adjacencyList.get(symbol) || new Set(); for (const rel of relationships) { if (rel.type === 'uses' || rel.type === 'calls' || rel.type === 'contains') { neighbors.add(rel.target); // Add bidirectional edge for clustering const targetNeighbors = adjacencyList.get(rel.target) || new Set(); targetNeighbors.add(symbol); adjacencyList.set(rel.target, targetNeighbors); } } adjacencyList.set(symbol, neighbors); } return adjacencyList; } private findConnectedComponent( startSymbol: string, adjacencyList: Map<string, Set<string>>, visited: Set<string> ): string[] { const component: string[] = []; const queue: string[] = [startSymbol]; while (queue.length > 0) { const current = queue.shift()!; if (visited.has(current)) continue; visited.add(current); component.push(current); const neighbors = adjacencyList.get(current) || new Set(); for (const neighbor of neighbors) { if (!visited.has(neighbor)) { queue.push(neighbor); } } } return component; } private getClusterRelationships(symbols: string[], context: SemanticContext): SymbolRelationship[] { const clusterRelationships: SymbolRelationship[] = []; const symbolSet = new Set(symbols); for (const symbol of symbols) { const relationships = context.relationships.get(symbol) || []; for (const rel of relationships) { if (symbolSet.has(rel.target)) { clusterRelationships.push(rel); } } } return clusterRelationships; } private calculateCohesion(symbols: string[], relationships: SymbolRelationship[]): number { if (symbols.length <= 1) return 1.0; const maxPossibleRelationships = symbols.length * (symbols.length - 1); const actualRelationships = relationships.length; return actualRelationships / maxPossibleRelationships; } private calculateCoupling(clusterSymbols: string[], context: SemanticContext): number { const symbolSet = new Set(clusterSymbols); let externalRelationships = 0; let totalRelationships = 0; for (const symbol of clusterSymbols) { const relationships = context.relationships.get(symbol) || []; totalRelationships += relationships.length; for (const rel of relationships) { if (!symbolSet.has(rel.target)) { externalRelationships++; } } } return totalRelationships > 0 ? externalRelationships / totalRelationships : 0; } private detectSingletonPattern(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; for (const [symbol, symbolInfo] of context.symbols) { if (symbolInfo.type === 'class') { // Look for singleton indicators const relationships = context.relationships.get(symbol) || []; let hasPrivateConstructor = false; let hasStaticInstance = false; // This is a simplified detection - in practice, you'd analyze the AST more deeply if (symbol.toLowerCase().includes('singleton') || relationships.some(r => r.type === 'contains' && r.target.includes('instance'))) { patterns.push({ type: 'singleton', symbols: [symbol], confidence: 0.7, description: `${symbol} appears to implement the Singleton pattern` }); } } } return patterns; } private detectFactoryPattern(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; for (const [symbol, symbolInfo] of context.symbols) { if (symbolInfo.type === 'class' || symbolInfo.type === 'function') { if (symbol.toLowerCase().includes('factory') || symbol.toLowerCase().includes('create') || symbol.toLowerCase().includes('builder')) { const relationships = context.relationships.get(symbol) || []; const createsObjects = relationships.some(r => r.type === 'calls' && r.target.includes('new')); if (createsObjects || relationships.length > 2) { patterns.push({ type: 'factory', symbols: [symbol], confidence: 0.6, description: `${symbol} appears to implement the Factory pattern` }); } } } } return patterns; } private detectObserverPattern(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; // Look for observer-like relationships const observerSymbols: string[] = []; for (const [symbol, relationships] of context.relationships) { const hasNotifyPattern = relationships.some(r => r.target.toLowerCase().includes('notify') || r.target.toLowerCase().includes('update') || r.target.toLowerCase().includes('observer') ); if (hasNotifyPattern) { observerSymbols.push(symbol); } } if (observerSymbols.length > 0) { patterns.push({ type: 'observer', symbols: observerSymbols, confidence: 0.7, description: 'Observer pattern detected based on notify/update relationships' }); } return patterns; } private detectStrategyPattern(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; // Look for strategy-like inheritance hierarchies const inheritanceChains = this.analyzeInheritanceHierarchy(context); for (const [symbol, chain] of inheritanceChains) { if (chain.descendants.length > 2 && (symbol.toLowerCase().includes('strategy') || symbol.toLowerCase().includes('algorithm') || symbol.toLowerCase().includes('policy'))) { patterns.push({ type: 'strategy', symbols: [symbol, ...chain.descendants], confidence: 0.8, description: `Strategy pattern detected with ${symbol} as base strategy` }); } } return patterns; } private detectMVCPattern(context: SemanticContext): ArchitecturalPattern[] { const patterns: ArchitecturalPattern[] = []; const models: string[] = []; const views: string[] = []; const controllers: string[] = []; for (const [symbol] of context.symbols) { const lowerSymbol = symbol.toLowerCase(); if (lowerSymbol.includes('model')) { models.push(symbol); } else if (lowerSymbol.includes('view') || lowerSymbol.includes('ui')) { views.push(symbol); } else if (lowerSymbol.includes('controller') || lowerSymbol.includes('handler')) { controllers.push(symbol); } } if (models.length > 0 && views.length > 0 && controllers.length > 0) { patterns.push({ type: 'mvc', symbols: [...models, ...views, ...controllers], confidence: 0.8, description: 'MVC pattern detected based on naming conventions' }); } return patterns; } private calculateClassAbstractness(symbol: string, context: SemanticContext): number { const relationships = context.relationships.get(symbol) || []; const descendants = relationships.filter(r => r.type === 'inherits').length; // Classes with many descendants are likely more abstract return Math.min(descendants / 5, 1.0); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/FosterG4/mcpsaver'

If you have feedback or need assistance with the MCP directory API, please join our Discord server