UNPKG

code-auditor-mcp

Version:

Multi-language code quality auditor with MCP server - Analyze TypeScript, JavaScript, and Go code for SOLID principles, DRY violations, security patterns, and more

365 lines 15.8 kB
/** * Language Orchestrator * Coordinates multiple language analyzers and merges results */ import { discoverFiles } from '../utils/fileDiscovery.js'; import * as path from 'path'; export class LanguageOrchestrator { runtimeManager; codeIndex; constructor(runtimeManager, codeIndex) { this.runtimeManager = runtimeManager; this.codeIndex = codeIndex; } /** * Analyze a polyglot project */ async analyzePolyglotProject(projectPath, options = {}) { console.error(`[LanguageOrchestrator] Starting polyglot analysis of: ${projectPath}`); const startTime = Date.now(); // Initialize runtime manager if needed if (!this.runtimeManager.getAvailableRuntimes().length) { console.error(`[LanguageOrchestrator] Runtime manager not initialized, initializing...`); await this.runtimeManager.initialize(); } // 1. Discover and group files by language console.error(`[LanguageOrchestrator] Discovering files in: ${projectPath}`); const filesByLanguage = await this.discoverAndGroupFiles(projectPath, options); console.error(`[LanguageOrchestrator] Discovered files:`, Object.fromEntries(Object.entries(filesByLanguage).map(([lang, files]) => [lang, files.length]))); // 2. Determine which languages to analyze const languagesToAnalyze = this.selectLanguages(filesByLanguage, options); console.log(`[LanguageOrchestrator] Languages to analyze:`, languagesToAnalyze); // 2.5. Validate runtime compatibility for selected languages for (const language of languagesToAnalyze) { if (!this.runtimeManager.hasRuntime(language)) { console.warn(`[LanguageOrchestrator] No runtime available for ${language}, skipping...`); } } // 3. Run language-specific analyses in parallel const analysisPromises = languagesToAnalyze.map(language => this.analyzeLanguage(language, filesByLanguage[language] || [], options)); const analysisResults = await Promise.all(analysisPromises); console.log(`[LanguageOrchestrator] Completed ${analysisResults.length} language analyses`); // 4. Merge results const mergedResult = this.mergeLanguageResults(analysisResults, languagesToAnalyze); // 5. Build cross-references if enabled if (options.buildCrossReferences) { mergedResult.crossReferences = await this.buildCrossReferences(analysisResults); console.log(`[LanguageOrchestrator] Built ${mergedResult.crossReferences.length} cross-references`); } // 6. Detect cross-language violations if (options.enableCrossLanguageAnalysis) { mergedResult.crossLanguageViolations = await this.detectCrossLanguageViolations(analysisResults, mergedResult.crossReferences || []); console.log(`[LanguageOrchestrator] Found ${mergedResult.crossLanguageViolations.length} cross-language violations`); } // 7. Validate API contracts if (options.validateAPIContracts) { mergedResult.apiContracts = await this.validateAPIContracts(analysisResults); console.log(`[LanguageOrchestrator] Validated ${mergedResult.apiContracts?.length || 0} API contracts`); } // 8. Generate dependency graph if (options.generateDependencyGraph) { mergedResult.dependencyGraph = await this.generateDependencyGraph(analysisResults, mergedResult.crossReferences || []); console.log(`[LanguageOrchestrator] Generated dependency graph with ${mergedResult.dependencyGraph.nodes.length} nodes`); } // 9. Update index if requested if (options.updateIndex && mergedResult.indexEntries) { await this.updateCodeIndex(mergedResult.indexEntries, mergedResult.crossReferences || []); console.log(`[LanguageOrchestrator] Updated index with ${mergedResult.indexEntries.length} entries`); } // 10. Final metrics mergedResult.metrics.executionTime = Date.now() - startTime; console.log(`[LanguageOrchestrator] Analysis complete in ${mergedResult.metrics.executionTime}ms`); return mergedResult; } /** * Discover files and group by language */ async discoverAndGroupFiles(projectPath, options) { const allFiles = await discoverFiles(projectPath); const filesByLanguage = {}; for (const file of allFiles) { const language = this.detectLanguage(file); if (language) { if (!filesByLanguage[language]) { filesByLanguage[language] = []; } filesByLanguage[language].push(file); } } return filesByLanguage; } /** * Detect programming language from file extension */ detectLanguage(filePath) { const ext = path.extname(filePath).toLowerCase(); const languageMap = { '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript', '.go': 'go', '.py': 'python', '.rs': 'rust', '.java': 'java', '.kt': 'kotlin', '.cs': 'csharp', '.cpp': 'cpp', '.c': 'c', '.h': 'c', '.hpp': 'cpp' }; return languageMap[ext] || null; } /** * Select which languages to analyze based on options and availability */ selectLanguages(filesByLanguage, options) { const discoveredLanguages = Object.keys(filesByLanguage); // Map discovered languages to runtime names const languageToRuntime = this.mapLanguageToRuntime(); // If specific languages requested, filter to those if (options.languages && options.languages.length > 0) { return options.languages.filter(lang => { const runtimeName = languageToRuntime[lang] || lang; return discoveredLanguages.includes(lang) && this.runtimeManager.hasRuntime(runtimeName); }); } // Otherwise, analyze all discovered languages that have available runtimes return discoveredLanguages.filter(lang => { const runtimeName = languageToRuntime[lang] || lang; return this.runtimeManager.hasRuntime(runtimeName); }); } /** * Map language names to runtime names */ mapLanguageToRuntime() { return { 'typescript': 'node', 'javascript': 'node', 'go': 'go', 'python': 'python', 'rust': 'rust' }; } /** * Analyze files for a specific language */ async analyzeLanguage(language, files, options) { console.log(`[LanguageOrchestrator] Analyzing ${files.length} ${language} files`); // Map language to runtime name const languageToRuntime = this.mapLanguageToRuntime(); const runtimeName = languageToRuntime[language] || language; // Use the mapped runtime name for analysis const result = await this.runtimeManager.spawnAnalyzer(runtimeName, files, { analyzers: options.analyzers, minSeverity: options.minSeverity, timeout: options.timeout, language: language // Pass original language for context }); return { language, result: result || { violations: [], indexEntries: [], metrics: { filesAnalyzed: 0, executionTime: 0 }, errors: [`No analyzer available for ${language} (runtime: ${runtimeName})`] } }; } /** * Merge results from multiple language analyses */ mergeLanguageResults(results, languages) { const merged = { violations: [], errors: [], crossLanguageViolations: [], metrics: { totalFiles: 0, totalViolations: 0, languagesAnalyzed: languages, executionTime: 0, crossLanguageReferences: 0, apiContractsChecked: 0 }, languageStats: new Map(), indexEntries: [] }; for (const { language, result } of results) { // Merge violations merged.violations.push(...result.violations); // Merge errors if (result.errors) { merged.errors.push(...result.errors); } // Merge index entries if (result.indexEntries) { merged.indexEntries.push(...result.indexEntries); } // Calculate language stats merged.languageStats.set(language, { filesAnalyzed: result.metrics.filesAnalyzed, violations: result.violations.length, functions: result.indexEntries?.filter(e => e.type === 'function').length || 0, classes: result.indexEntries?.filter(e => e.type === 'class').length || 0, interfaces: result.indexEntries?.filter(e => e.type === 'interface').length || 0, executionTime: result.metrics.executionTime }); // Update totals merged.metrics.totalFiles += result.metrics.filesAnalyzed; merged.metrics.totalViolations += result.violations.length; } return merged; } /** * Build cross-references between languages */ async buildCrossReferences(results) { // This is a placeholder for cross-reference building logic // In future phases, this will analyze: // - Function calls across language boundaries // - API endpoints and their consumers // - Shared type definitions // - Import/export relationships console.log('[LanguageOrchestrator] Building cross-references (placeholder)'); return []; } /** * Detect violations that span multiple languages */ async detectCrossLanguageViolations(results, crossReferences) { console.log('[LanguageOrchestrator] Detecting cross-language violations...'); const violations = []; // Collect all entities from results const allEntities = []; for (const { result } of results) { if (result.indexEntries) { allEntities.push(...result.indexEntries); } } // Import and run API contract analysis try { const { APIContractAnalyzer } = await import('../analyzers/cross-language/APIContractAnalyzer.js'); const endpoints = APIContractAnalyzer.extractEndpoints(allEntities); const apiCalls = APIContractAnalyzer.extractAPICalls(allEntities); if (endpoints.length > 0 || apiCalls.length > 0) { const contractAnalyzer = new APIContractAnalyzer(); const contractViolations = await contractAnalyzer.analyzeContracts(endpoints, apiCalls); // Convert to CrossLanguageViolation format for (const violation of contractViolations) { violations.push({ ...violation, crossLanguageType: 'api-mismatch', relatedFiles: [violation.file, ...(violation.endpoint ? [violation.endpoint.file] : [])], relatedLanguages: [ violation.call?.language || 'unknown', violation.endpoint?.language || 'unknown' ].filter(lang => lang !== 'unknown') }); } } } catch (error) { console.warn('[LanguageOrchestrator] Failed to run API contract analysis:', error); } // Import and run schema validation try { const { SchemaValidator } = await import('../analyzers/cross-language/SchemaValidator.js'); const schemas = SchemaValidator.extractSchemas(allEntities); if (schemas.length > 0) { const schemaValidator = new SchemaValidator(); const schemaViolations = await schemaValidator.validateSchemas(schemas); // Convert to CrossLanguageViolation format for (const violation of schemaViolations) { violations.push({ ...violation, crossLanguageType: 'type-mismatch', relatedFiles: violation.schemas.map(s => s.file), relatedLanguages: violation.schemas.map(s => s.language) }); } } } catch (error) { console.warn('[LanguageOrchestrator] Failed to run schema validation:', error); } console.log(`[LanguageOrchestrator] Found ${violations.length} cross-language violations`); return violations; } /** * Validate API contracts between frontend and backend */ async validateAPIContracts(results) { // Placeholder for API contract validation console.log('[LanguageOrchestrator] Validating API contracts (placeholder)'); return []; } /** * Generate dependency graph across languages */ async generateDependencyGraph(results, crossReferences) { console.log('[LanguageOrchestrator] Generating cross-language dependency graph...'); try { const { DependencyGraphBuilder } = await import('../analyzers/cross-language/DependencyGraphBuilder.js'); // Collect all entities const allEntities = []; for (const { result } of results) { if (result.indexEntries) { allEntities.push(...result.indexEntries); } } // Build the dependency graph const graphBuilder = new DependencyGraphBuilder({ includeInternalDependencies: true, includeExternalDependencies: true, includeTestFiles: false, clusterByPackage: true }); const graph = await graphBuilder.buildGraph(allEntities, crossReferences); console.log(`[LanguageOrchestrator] Generated dependency graph with ${graph.nodes.length} nodes and ${graph.edges.length} edges`); return graph; } catch (error) { console.warn('[LanguageOrchestrator] Failed to generate dependency graph:', error); return { nodes: [], edges: [], cycles: [], metrics: { totalNodes: 0, totalEdges: 0, cycleCount: 0, averageDepth: 0, maxDepth: 0, stronglyConnectedComponents: 0 } }; } } /** * Update the unified code index */ async updateCodeIndex(indexEntries, crossReferences) { // Placeholder for index updates console.log('[LanguageOrchestrator] Updating code index (placeholder)'); } /** * Get orchestrator statistics */ getStats() { const runtimeStats = this.runtimeManager.getStats(); return { runtimes: runtimeStats, capabilities: { crossLanguageAnalysis: true, apiContractValidation: true, dependencyGraphGeneration: true, unifiedIndexing: true } }; } } //# sourceMappingURL=LanguageOrchestrator.js.map