UNPKG

aios-core

Version:

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

674 lines (558 loc) 19.5 kB
const fs = require('fs').promises; const path = require('path'); const chalk = require('chalk'); /** * Usage tracker for AIOS-FULLSTACK framework components * Tracks component usage patterns for deprecation warnings and impact analysis */ class UsageTracker { constructor(options = {}) { this.rootPath = options.rootPath || process.cwd(); this.usageDir = path.join(this.rootPath, '.aios', 'usage'); this.usageCache = new Map(); this.scanPatterns = this.initializeScanPatterns(); } /** * Initialize usage tracker */ async initialize() { try { // Create usage directory await fs.mkdir(this.usageDir, { recursive: true }); console.log(chalk.green('✅ Usage tracker initialized')); return true; } catch (_error) { console.error(chalk.red(`Failed to initialize usage tracker: ${error.message}`)); throw error; } } /** * Analyze component usage across the codebase */ async analyzeComponentUsage(componentId, options = {}) { const analysis = { component_id: componentId, analysis_timestamp: new Date().toISOString(), total_references: 0, usage_locations: [], dependent_components: [], external_references: [], usage_patterns: {}, risk_assessment: { impact_level: 'low', affected_areas: [], migration_complexity: 'simple' } }; try { console.log(chalk.blue(`🔍 Analyzing usage for component: ${componentId}`)); // Get component information const component = await this.getComponentInfo(componentId); if (!component) { throw new Error(`Component not found: ${componentId}`); } // Scan for direct references const directReferences = await this.scanForDirectReferences(component, options); analysis.usage_locations = directReferences; analysis.total_references = directReferences.length; // Analyze dependency relationships const dependencies = await this.analyzeDependencyRelationships(component, options); analysis.dependent_components = dependencies; // Check for external references (imports, configurations, etc.) const externalRefs = await this.scanForExternalReferences(component, options); analysis.external_references = externalRefs; // Analyze usage patterns analysis.usage_patterns = await this.analyzeUsagePatterns(component, directReferences); // Assess migration risk analysis.risk_assessment = this.assessMigrationRisk(analysis); // Cache results this.usageCache.set(componentId, analysis); // Save detailed analysis await this.saveUsageAnalysis(analysis); console.log(chalk.green(`✅ Usage analysis completed for ${componentId}`)); console.log(chalk.gray(` Total references: ${analysis.total_references}`)); console.log(chalk.gray(` Dependent components: ${analysis.dependent_components.length}`)); console.log(chalk.gray(` Impact level: ${analysis.risk_assessment.impact_level}`)); return analysis; } catch (_error) { console.error(chalk.red(`Usage analysis failed for ${componentId}: ${error.message}`)); throw error; } } /** * Track usage changes over time for deprecated components */ async trackUsageChanges(componentId, baselineAnalysis) { const currentAnalysis = await this.analyzeComponentUsage(componentId); const changes = { component_id: componentId, comparison_timestamp: new Date().toISOString(), baseline_timestamp: baselineAnalysis.analysis_timestamp, changes: { total_references: { before: baselineAnalysis.total_references, after: currentAnalysis.total_references, change: currentAnalysis.total_references - baselineAnalysis.total_references }, dependent_components: { before: baselineAnalysis.dependent_components.length, after: currentAnalysis.dependent_components.length, change: currentAnalysis.dependent_components.length - baselineAnalysis.dependent_components.length }, new_usages: this.findNewUsages(baselineAnalysis.usage_locations, currentAnalysis.usage_locations), removed_usages: this.findRemovedUsages(baselineAnalysis.usage_locations, currentAnalysis.usage_locations) }, trend: this.calculateUsageTrend(baselineAnalysis, currentAnalysis), migration_progress: this.calculateMigrationProgress(baselineAnalysis, currentAnalysis) }; // Save change tracking await this.saveUsageChanges(changes); return changes; } /** * Generate usage warnings for deprecated components */ async generateUsageWarnings(componentId, deprecationInfo) { const analysis = await this.analyzeComponentUsage(componentId); const warnings = []; for (const _usage of analysis.usage_locations) { const warning = { type: 'deprecation_warning', component_id: componentId, usage_location: { file: usage.file, line: usage.line, context: usage.context }, message: this.generateWarningMessage(componentId, deprecationInfo, usage), severity: this.calculateWarningSeverity(deprecationInfo, usage), suggested_actions: this.generateSuggestedActions(componentId, deprecationInfo, usage) }; warnings.push(warning); } // Save warnings for later processing await this.saveUsageWarnings(componentId, warnings); return warnings; } /** * Get usage statistics for reporting */ async getUsageStatistics(componentIds = null) { const stats = { generated_at: new Date().toISOString(), total_components_analyzed: 0, high_usage_components: [], zero_usage_components: [], usage_distribution: {}, dependency_graph: {} }; const componentsToAnalyze = componentIds || await this.getAllTrackedComponents(); for (const componentId of componentsToAnalyze) { try { const analysis = await this.getOrAnalyzeUsage(componentId); stats.total_components_analyzed++; // Categorize by usage level if (analysis.total_references === 0) { stats.zero_usage_components.push(componentId); } else if (analysis.total_references >= 10) { stats.high_usage_components.push({ component_id: componentId, reference_count: analysis.total_references, impact_level: analysis.risk_assessment.impact_level }); } // Add to usage distribution const usageRange = this.categorizeUsageLevel(analysis.total_references); stats.usage_distribution[usageRange] = (stats.usage_distribution[usageRange] || 0) + 1; // Add to dependency graph stats.dependency_graph[componentId] = analysis.dependent_components.map(dep => dep.component_id); } catch (_error) { console.warn(chalk.yellow(`Failed to analyze usage for ${componentId}: ${error.message}`)); } } return stats; } /** * Scan for direct references to a component */ async scanForDirectReferences(component, options = {}) { const references = []; const scanPaths = await this.getScanPaths(_options); for (const scanPath of scanPaths) { try { const pathReferences = await this.scanPath(scanPath, component); references.push(...pathReferences); } catch (_error) { console.warn(chalk.yellow(`Failed to scan path ${scanPath}: ${error.message}`)); } } return references; } /** * Scan a specific path for component references */ async scanPath(scanPath, component) { const references = []; const files = await this.getFilesToScan(scanPath); for (const file of files) { try { const fileReferences = await this.scanFile(file, component); references.push(...fileReferences); } catch (_error) { // Skip files that can't be read continue; } } return references; } /** * Scan a single file for component references */ async scanFile(filePath, component) { const references = []; try { const content = await fs.readFile(filePath, 'utf-8'); const lines = content.split('\n'); for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNumber = i + 1; // Check for various reference patterns const matches = this.findReferencesInLine(line, component); for (const match of matches) { references.push({ file: filePath, line: lineNumber, column: match.column, context: line.trim(), reference_type: match.type, match_text: match.text }); } } } catch (_error) { // File couldn't be read, skip } return references; } /** * Find references in a single line of code */ findReferencesInLine(line, component) { const matches = []; const patterns = this.scanPatterns[component.type] || this.scanPatterns.default; for (const pattern of patterns) { const regex = new RegExp(pattern.pattern.replace('{component_name}', component.name), pattern.flags || 'gi'); let match; while ((match = regex.exec(line)) !== null) { matches.push({ type: pattern.type, text: match[0], column: match.index, confidence: pattern.confidence || 0.8 }); } } return matches; } /** * Analyze dependency relationships */ async analyzeDependencyRelationships(component, options = {}) { const dependencies = []; // This would analyze manifest files, import statements, etc. // For now, return empty array as placeholder return dependencies; } /** * Scan for external references (config files, documentation, etc.) */ async scanForExternalReferences(component, options = {}) { const externalRefs = []; // Check configuration files const configRefs = await this.scanConfigurationFiles(component); externalRefs.push(...configRefs); // Check documentation const docRefs = await this.scanDocumentationFiles(component); externalRefs.push(...docRefs); return externalRefs; } /** * Analyze usage patterns */ async analyzeUsagePatterns(component, references) { const patterns = { by_file_type: {}, by_usage_type: {}, temporal_distribution: {}, complexity_indicators: {} }; for (const ref of references) { // Group by file extension const ext = path.extname(ref.file); patterns.by_file_type[ext] = (patterns.by_file_type[ext] || 0) + 1; // Group by reference type patterns.by_usage_type[ref.reference_type] = (patterns.by_usage_type[ref.reference_type] || 0) + 1; } return patterns; } /** * Assess migration risk based on usage analysis */ assessMigrationRisk(analysis) { let impactLevel = 'low'; let migrationComplexity = 'simple'; const affectedAreas = []; // Assess based on total references if (analysis.total_references > 20) { impactLevel = 'high'; migrationComplexity = 'complex'; } else if (analysis.total_references > 10) { impactLevel = 'medium'; migrationComplexity = 'moderate'; } // Check for complex usage patterns if (analysis.dependent_components.length > 5) { migrationComplexity = 'complex'; affectedAreas.push('component_dependencies'); } if (analysis.external_references.length > 0) { affectedAreas.push('external_configuration'); } return { impact_level: impactLevel, affected_areas: affectedAreas, migration_complexity: migrationComplexity }; } // Helper methods async getComponentInfo(componentId) { // This would typically integrate with the component registry // For now, return a basic component structure const [type, name] = componentId.split('/'); return { id: componentId, type: type, name: name, file_path: `aios-core/${type}s/${name}.md` }; } async getScanPaths(_options) { const defaultPaths = [ path.join(this.rootPath, 'aios-core'), path.join(this.rootPath, 'src'), path.join(this.rootPath, 'lib') ]; if (options.scanPaths) { return options.scanPaths; } // Filter paths that exist const existingPaths = []; for (const scanPath of defaultPaths) { try { await fs.access(scanPath); existingPaths.push(scanPath); } catch (_error) { // Path doesn't exist, skip } } return existingPaths; } async getFilesToScan(scanPath) { const files = []; try { const entries = await fs.readdir(scanPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(scanPath, entry.name); if (entry.isDirectory()) { // Recursively scan subdirectories const subFiles = await this.getFilesToScan(fullPath); files.push(...subFiles); } else if (this.shouldScanFile(entry.name)) { files.push(fullPath); } } } catch (_error) { // Can't read directory, skip } return files; } shouldScanFile(filename) { const scanExtensions = ['.js', '.ts', '.md', '.yaml', '.yml', '.json']; const ext = path.extname(filename); return scanExtensions.includes(ext); } async scanConfigurationFiles(component) { const configRefs = []; const configFiles = [ 'package.json', '.aiosrc', 'aios.config.js', 'manifest.yaml' ]; for (const configFile of configFiles) { const configPath = path.join(this.rootPath, configFile); try { const refs = await this.scanFile(configPath, component); configRefs.push(...refs.map(ref => ({ ...ref, source: 'configuration' }))); } catch (_error) { // Config file doesn't exist or can't be read } } return configRefs; } async scanDocumentationFiles(component) { const docRefs = []; const docsPath = path.join(this.rootPath, 'docs'); try { const files = await this.getFilesToScan(docsPath); for (const file of files) { const refs = await this.scanFile(file, component); docRefs.push(...refs.map(ref => ({ ...ref, source: 'documentation' }))); } } catch (_error) { // Docs directory doesn't exist } return docRefs; } findNewUsages(oldUsages, newUsages) { return newUsages.filter(newUsage => !oldUsages.some(oldUsage => oldUsage.file === newUsage.file && oldUsage.line === newUsage.line ) ); } findRemovedUsages(oldUsages, newUsages) { return oldUsages.filter(oldUsage => !newUsages.some(newUsage => newUsage.file === oldUsage.file && newUsage.line === oldUsage.line ) ); } calculateUsageTrend(baseline, current) { const referenceChange = current.total_references - baseline.total_references; if (referenceChange > 0) return 'increasing'; if (referenceChange < 0) return 'decreasing'; return 'stable'; } calculateMigrationProgress(baseline, current) { if (baseline.total_references === 0) return 1.0; const remainingUsages = current.total_references; const originalUsages = baseline.total_references; return Math.max(0, (originalUsages - remainingUsages) / originalUsages); } generateWarningMessage(componentId, deprecationInfo, usage) { let message = `DEPRECATED: ${componentId} is deprecated`; if (deprecationInfo.replacement) { message += ` - use ${deprecationInfo.replacement} instead`; } if (deprecationInfo.removalVersion) { message += ` (will be removed in ${deprecationInfo.removalVersion})`; } return message; } calculateWarningSeverity(deprecationInfo, usage) { if (deprecationInfo.severity === 'critical') return 'error'; if (deprecationInfo.severity === 'high') return 'warning'; return 'info'; } generateSuggestedActions(componentId, deprecationInfo, usage) { const actions = []; if (deprecationInfo.replacement) { actions.push(`Replace with ${deprecationInfo.replacement}`); } if (deprecationInfo.migrationGuide) { actions.push(`See migration guide: ${deprecationInfo.migrationGuide}`); } actions.push('Update imports and references'); actions.push('Test functionality after replacement'); return actions; } categorizeUsageLevel(referenceCount) { if (referenceCount === 0) return 'zero'; if (referenceCount <= 5) return 'low'; if (referenceCount <= 15) return 'medium'; if (referenceCount <= 30) return 'high'; return 'very_high'; } async getOrAnalyzeUsage(componentId) { if (this.usageCache.has(componentId)) { return this.usageCache.get(componentId); } return await this.analyzeComponentUsage(componentId); } async getAllTrackedComponents() { // This would typically come from a component registry // For now, return empty array return []; } async saveUsageAnalysis(analysis) { const filename = `usage-${analysis.component_id.replace('/', '-')}-${Date.now()}.json`; const filePath = path.join(this.usageDir, filename); await fs.writeFile(filePath, JSON.stringify(analysis, null, 2)); } async saveUsageChanges(changes) { const filename = `changes-${changes.component_id.replace('/', '-')}-${Date.now()}.json`; const filePath = path.join(this.usageDir, filename); await fs.writeFile(filePath, JSON.stringify(changes, null, 2)); } async saveUsageWarnings(componentId, warnings) { const filename = `warnings-${componentId.replace('/', '-')}-${Date.now()}.json`; const filePath = path.join(this.usageDir, filename); await fs.writeFile(filePath, JSON.stringify(warnings, null, 2)); } initializeScanPatterns() { return { agent: [ { pattern: 'agent:\\s*{component_name}', type: 'yaml_reference', confidence: 0.9 }, { pattern: 'use\\s+{component_name}', type: 'usage_statement', confidence: 0.8 } ], task: [ { pattern: '\\*{component_name}', type: 'task_invocation', confidence: 0.9 }, { pattern: 'require\\(.*{component_name}.*\\)', type: 'import_statement', confidence: 0.8 } ], workflow: [ { pattern: 'workflow:\\s*{component_name}', type: 'workflow_reference', confidence: 0.9 } ], util: [ { pattern: 'require\\(.*{component_name}.*\\)', type: 'import_statement', confidence: 0.9 }, { pattern: 'import.*{component_name}', type: 'import_statement', confidence: 0.9 } ], default: [ { pattern: '{component_name}', type: 'general_reference', confidence: 0.6 } ] }; } } module.exports = UsageTracker;