UNPKG

aios-core

Version:

Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework

703 lines (599 loc) 21.9 kB
const fs = require('fs').promises; const path = require('path'); const chalk = require('chalk'); /** * Dependency impact analyzer for Synkra AIOS framework * Analyzes how component modifications affect dependent components */ class DependencyImpactAnalyzer { constructor(options = {}) { this.rootPath = options.rootPath || process.cwd(); this.dependencyGraph = new Map(); this.reverseDependencyGraph = new Map(); this.componentRegistry = new Map(); this.analysisCache = new Map(); this.maxAnalysisDepth = 10; } /** * Initialize the dependency analyzer */ async initialize() { try { console.log(chalk.gray('Initializing dependency impact analyzer...')); // Build comprehensive dependency graph await this.buildDependencyGraph(); // Build reverse dependency graph for impact analysis await this.buildReverseDependencyGraph(); // Register all components for quick lookup await this.registerAllComponents(); console.log(chalk.green('✅ Dependency impact analyzer initialized')); console.log(chalk.gray(` Components registered: ${this.componentRegistry.size}`)); console.log(chalk.gray(` Dependencies mapped: ${this.dependencyGraph.size}`)); return true; } catch (error) { console.error(chalk.red(`Failed to initialize dependency analyzer: ${error.message}`)); throw error; } } /** * Analyze dependency impact of a component modification */ async analyzeDependencyImpact(targetComponent, options = {}) { const analysisId = `impact-${Date.now()}`; try { console.log(chalk.blue(`🔍 Analyzing dependency impact for: ${targetComponent.path}`)); const config = { depth: options.depth || 'medium', includeTests: options.includeTests || false, excludeExternal: options.excludeExternal || false, modificationType: options.modificationType || 'modify', ...options, }; // Determine analysis depth const maxDepth = this.getAnalysisDepth(config.depth); // Find all components that depend on the target const directDependents = await this.findDirectDependents(targetComponent); // Perform recursive dependency analysis const affectedComponents = await this.analyzeRecursiveDependencies( targetComponent, directDependents, maxDepth, config, ); // Calculate impact scores for each affected component const scoredComponents = await this.calculateImpactScores( targetComponent, affectedComponents, config, ); // Categorize impact by severity const impactCategories = this.categorizeImpactBySeverity(scoredComponents); // Generate dependency recommendations const recommendations = await this.generateDependencyRecommendations( targetComponent, scoredComponents, config, ); const result = { analysisId: analysisId, targetComponent: { path: targetComponent.path, type: targetComponent.type, }, analysisConfig: config, affectedComponents: scoredComponents, impactCategories: impactCategories, recommendations: recommendations, statistics: { totalComponents: scoredComponents.length, directDependents: directDependents.length, highImpactComponents: scoredComponents.filter(c => c.impactScore >= 8).length, mediumImpactComponents: scoredComponents.filter(c => c.impactScore >= 5 && c.impactScore < 8).length, lowImpactComponents: scoredComponents.filter(c => c.impactScore < 5).length, }, analysisTimestamp: new Date().toISOString(), }; // Cache analysis results this.analysisCache.set(analysisId, result); console.log(chalk.green('✅ Dependency analysis completed')); console.log(chalk.gray(` Affected components: ${result.statistics.totalComponents}`)); console.log(chalk.gray(` High impact: ${result.statistics.highImpactComponents}`)); return result; } catch (error) { console.error(chalk.red(`Dependency analysis failed: ${error.message}`)); throw error; } } /** * Build comprehensive dependency graph from all components */ async buildDependencyGraph() { const componentTypes = ['agents', 'tasks', 'workflows', 'utils']; for (const type of componentTypes) { const typeDir = path.join(this.rootPath, 'aios-core', type); try { const files = await fs.readdir(typeDir); for (const file of files) { const filePath = path.join(typeDir, file); const stats = await fs.stat(filePath); if (stats.isFile()) { await this.analyzeDependenciesForFile(filePath, type.slice(0, -1)); } } } catch (error) { // Directory doesn't exist, skip continue; } } // Also analyze test files if requested await this.analyzeTestDependencies(); } /** * Analyze dependencies for a specific file */ async analyzeDependenciesForFile(filePath, componentType) { try { const content = await fs.readFile(filePath, 'utf-8'); const relativePath = path.relative(this.rootPath, filePath); const dependencies = this.extractDependenciesFromContent(content, filePath); // Store in dependency graph this.dependencyGraph.set(relativePath, { path: relativePath, type: componentType, dependencies: dependencies, lastAnalyzed: new Date().toISOString(), }); } catch (error) { console.warn(chalk.yellow(`Failed to analyze dependencies for ${filePath}: ${error.message}`)); } } /** * Extract dependencies from file content */ extractDependenciesFromContent(content, filePath) { const dependencies = { internal: [], external: [], framework: [], tests: [], }; // Extract require statements const requireMatches = content.match(/require\s*\(\s*['"](.*?)['"]\s*\)/g) || []; requireMatches.forEach(match => { const dep = match.match(/require\s*\(\s*['"](.*?)['"]\s*\)/)[1]; this.categorizeDependency(dep, dependencies, filePath); }); // Extract import statements const importMatches = content.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g) || []; importMatches.forEach(match => { const dep = match.match(/from\s+['"](.*?)['"]/)[1]; this.categorizeDependency(dep, dependencies, filePath); }); // Extract AIOS-specific references const agentRefs = content.match(/agent[_-]?name\s*[:=]\s*['"](.*?)['"]/gi) || []; agentRefs.forEach(match => { const agentName = match.match(/['"](.*?)['"]/)[1]; dependencies.framework.push(`agents/${agentName}`); }); const taskRefs = content.match(/task[_-]?name\s*[:=]\s*['"](.*?)['"]/gi) || []; taskRefs.forEach(match => { const taskName = match.match(/['"](.*?)['"]/)[1]; dependencies.framework.push(`tasks/${taskName}`); }); // Extract workflow references const workflowRefs = content.match(/workflow[_-]?name\s*[:=]\s*['"](.*?)['"]/gi) || []; workflowRefs.forEach(match => { const workflowName = match.match(/['"](.*?)['"]/)[1]; dependencies.framework.push(`workflows/${workflowName}`); }); return dependencies; } /** * Categorize a dependency by type */ categorizeDependency(dep, dependencies, filePath) { if (dep.startsWith('./') || dep.startsWith('../')) { // Resolve relative path to absolute const resolvedPath = path.resolve(path.dirname(filePath), dep); const relativePath = path.relative(this.rootPath, resolvedPath); dependencies.internal.push(relativePath); } else if (dep.includes('aios-core/') || dep.includes('/aios-core/')) { dependencies.framework.push(dep); } else if (dep.includes('test') || dep.includes('spec')) { dependencies.tests.push(dep); } else if (!dep.startsWith('node:') && !dep.startsWith('fs') && !dep.startsWith('path')) { dependencies.external.push(dep); } } /** * Build reverse dependency graph for impact analysis */ async buildReverseDependencyGraph() { for (const [componentPath, componentData] of this.dependencyGraph) { const allDeps = [ ...componentData.dependencies.internal, ...componentData.dependencies.framework, ]; for (const dep of allDeps) { if (!this.reverseDependencyGraph.has(dep)) { this.reverseDependencyGraph.set(dep, []); } this.reverseDependencyGraph.get(dep).push({ path: componentPath, type: componentData.type, dependencyType: componentData.dependencies.internal.includes(dep) ? 'internal' : 'framework', }); } } } /** * Register all components for quick lookup */ async registerAllComponents() { for (const [componentPath, componentData] of this.dependencyGraph) { this.componentRegistry.set(componentPath, { path: componentPath, type: componentData.type, dependencies: componentData.dependencies, dependents: this.reverseDependencyGraph.get(componentPath) || [], }); } } /** * Find components that directly depend on the target */ async findDirectDependents(targetComponent) { const dependents = []; const targetPath = targetComponent.path; // Check reverse dependency graph if (this.reverseDependencyGraph.has(targetPath)) { dependents.push(...this.reverseDependencyGraph.get(targetPath)); } // Also check for components that might reference this component by name const componentName = path.basename(targetPath, path.extname(targetPath)); for (const [componentPath, componentData] of this.dependencyGraph) { if (componentPath === targetPath) continue; const content = await this.getComponentContent(componentPath); if (content && this.containsReferenceTo(content, componentName, targetComponent.type)) { dependents.push({ path: componentPath, type: componentData.type, dependencyType: 'reference', }); } } return dependents; } /** * Perform recursive dependency analysis */ async analyzeRecursiveDependencies(targetComponent, directDependents, maxDepth, config) { const visited = new Set(); const affectedComponents = []; const queue = directDependents.map(dep => ({ ...dep, depth: 1 })); visited.add(targetComponent.path); while (queue.length > 0 && affectedComponents.length < 1000) { // Safety limit const current = queue.shift(); if (visited.has(current.path) || current.depth > maxDepth) { continue; } visited.add(current.path); affectedComponents.push(current); // Find dependents of current component const currentDependents = await this.findDirectDependents(current); for (const dependent of currentDependents) { if (!visited.has(dependent.path)) { queue.push({ ...dependent, depth: current.depth + 1 }); } } } return affectedComponents; } /** * Calculate impact scores for affected components */ async calculateImpactScores(targetComponent, affectedComponents, config) { const scoredComponents = []; for (const component of affectedComponents) { const impactScore = await this.calculateComponentImpactScore( targetComponent, component, config, ); scoredComponents.push({ ...component, impactScore: impactScore.score, impactFactors: impactScore.factors, severity: this.categorizeImpactSeverity(impactScore.score), reason: impactScore.primaryReason, }); } return scoredComponents.sort((a, b) => b.impactScore - a.impactScore); } /** * Calculate impact score for a specific component */ async calculateComponentImpactScore(targetComponent, affectedComponent, config) { const factors = { dependencyType: 0, componentCriticality: 0, modificationRisk: 0, propagationDepth: 0, usageFrequency: 0, }; // Factor 1: Dependency type weight switch (affectedComponent.dependencyType) { case 'internal': factors.dependencyType = 3; break; case 'framework': factors.dependencyType = 2; break; case 'reference': factors.dependencyType = 1; break; } // Factor 2: Component criticality (based on type and usage) factors.componentCriticality = this.assessComponentCriticality(affectedComponent); // Factor 3: Modification risk based on modification type factors.modificationRisk = this.assessModificationRisk(config.modificationType); // Factor 4: Propagation depth penalty factors.propagationDepth = Math.max(0, 3 - (affectedComponent.depth || 1)); // Factor 5: Usage frequency (estimate based on dependents) const componentData = this.componentRegistry.get(affectedComponent.path); if (componentData) { factors.usageFrequency = Math.min(3, componentData.dependents.length / 5); } // Calculate weighted score (0-10 scale) const totalScore = Object.values(factors).reduce((sum, factor) => sum + factor, 0); const normalizedScore = Math.min(10, Math.round((totalScore / 15) * 10)); // Determine primary reason for impact const primaryReason = this.determinePrimaryImpactReason(factors, config); return { score: normalizedScore, factors: factors, primaryReason: primaryReason, }; } /** * Assess component criticality */ assessComponentCriticality(component) { // Higher criticality for core framework components if (component.type === 'util' && component.path.includes('core')) { return 3; } if (component.type === 'agent' || component.type === 'workflow') { return 2; } if (component.type === 'task') { return 1.5; } return 1; } /** * Assess modification risk */ assessModificationRisk(modificationType) { switch (modificationType) { case 'remove': return 4; case 'deprecate': return 3; case 'refactor': return 2; case 'modify': return 1; default: return 1; } } /** * Determine primary reason for impact */ determinePrimaryImpactReason(factors, config) { const maxFactor = Math.max(...Object.values(factors)); if (factors.modificationRisk === maxFactor && config.modificationType === 'remove') { return 'Component removal will break dependent functionality'; } if (factors.componentCriticality === maxFactor) { return 'Critical component with high framework dependency'; } if (factors.dependencyType === maxFactor) { return 'Direct internal dependency requiring code changes'; } if (factors.usageFrequency === maxFactor) { return 'Widely used component affecting multiple dependents'; } return 'Component modification may require updates'; } /** * Categorize impact by severity */ categorizeImpactBySeverity(scoredComponents) { return { critical: scoredComponents.filter(c => c.impactScore >= 9), high: scoredComponents.filter(c => c.impactScore >= 7 && c.impactScore < 9), medium: scoredComponents.filter(c => c.impactScore >= 4 && c.impactScore < 7), low: scoredComponents.filter(c => c.impactScore < 4), }; } /** * Categorize impact severity */ categorizeImpactSeverity(score) { if (score >= 9) return 'critical'; if (score >= 7) return 'high'; if (score >= 4) return 'medium'; return 'low'; } /** * Generate dependency recommendations */ async generateDependencyRecommendations(targetComponent, scoredComponents, config) { const recommendations = []; const criticalComponents = scoredComponents.filter(c => c.impactScore >= 9); const highImpactComponents = scoredComponents.filter(c => c.impactScore >= 7 && c.impactScore < 9); // Critical impact recommendations if (criticalComponents.length > 0) { recommendations.push({ priority: 'critical', title: 'Review Critical Impact Components', description: `${criticalComponents.length} components have critical dependency on the target. Consider gradual migration or deprecation strategy.`, affectedComponents: criticalComponents.slice(0, 5).map(c => c.path), actionRequired: true, }); } // High impact recommendations if (highImpactComponents.length > 0) { recommendations.push({ priority: 'high', title: 'Update High Impact Components', description: `${highImpactComponents.length} components require updates to maintain compatibility.`, affectedComponents: highImpactComponents.slice(0, 5).map(c => c.path), actionRequired: true, }); } // Modification-specific recommendations if (config.modificationType === 'remove') { recommendations.push({ priority: 'critical', title: 'Plan Component Removal Strategy', description: 'Removing this component requires careful migration of all dependent functionality.', actionRequired: true, suggestedActions: [ 'Create migration guide for dependent components', 'Implement deprecation warnings in advance', 'Provide alternative component recommendations', 'Test all dependent components after removal', ], }); } if (config.modificationType === 'refactor') { recommendations.push({ priority: 'medium', title: 'Coordinate Refactoring Changes', description: 'Refactoring may require interface updates in dependent components.', actionRequired: false, suggestedActions: [ 'Review and update component interfaces', 'Update documentation for API changes', 'Run comprehensive testing on dependent components', ], }); } // Testing recommendations if (scoredComponents.length > 5) { recommendations.push({ priority: 'medium', title: 'Comprehensive Testing Required', description: `Large impact scope (${scoredComponents.length} components) requires extensive testing.`, actionRequired: true, suggestedActions: [ 'Run full integration test suite', 'Perform regression testing on affected components', 'Consider staged rollout approach', ], }); } return recommendations; } /** * Analyze test dependencies */ async analyzeTestDependencies() { const testDir = path.join(this.rootPath, 'tests'); try { await this.analyzeDirectoryRecursively(testDir, 'test'); } catch (error) { // Tests directory doesn't exist, skip } } /** * Analyze directory recursively for dependencies */ async analyzeDirectoryRecursively(dir, componentType) { try { const files = await fs.readdir(dir); for (const file of files) { const filePath = path.join(dir, file); const stats = await fs.stat(filePath); if (stats.isDirectory()) { await this.analyzeDirectoryRecursively(filePath, componentType); } else if (stats.isFile() && (file.endsWith('.js') || file.endsWith('.md'))) { await this.analyzeDependenciesForFile(filePath, componentType); } } } catch (error) { // Skip directories that can't be read } } /** * Get component content */ async getComponentContent(componentPath) { try { const fullPath = path.resolve(this.rootPath, componentPath); return await fs.readFile(fullPath, 'utf-8'); } catch (error) { return null; } } /** * Check if content contains reference to component */ containsReferenceTo(content, componentName, componentType) { const patterns = [ new RegExp(`${componentName}`, 'i'), new RegExp(`${componentType}[_-]?name.*${componentName}`, 'i'), new RegExp(`require.*${componentName}`, 'i'), new RegExp(`import.*${componentName}`, 'i'), ]; return patterns.some(pattern => pattern.test(content)); } /** * Get analysis depth from config */ getAnalysisDepth(depth) { switch (depth) { case 'shallow': return 2; case 'medium': return 4; case 'deep': return 8; default: return 4; } } /** * Get cached analysis */ getCachedAnalysis(analysisId) { return this.analysisCache.get(analysisId); } /** * Clear analysis cache */ clearCache() { this.analysisCache.clear(); console.log(chalk.gray('Dependency analysis cache cleared')); } /** * Get analysis statistics */ getAnalysisStats() { return { totalComponents: this.componentRegistry.size, totalDependencies: this.dependencyGraph.size, averageDependencies: Array.from(this.dependencyGraph.values()).reduce((sum, comp) => { const totalDeps = comp.dependencies.internal.length + comp.dependencies.framework.length + comp.dependencies.external.length; return sum + totalDeps; }, 0) / this.dependencyGraph.size, cachedAnalyses: this.analysisCache.size, }; } } module.exports = DependencyImpactAnalyzer;