// ============================================================================ // Agent 2: Code Analyzer // Deep code analysis - functions, classes, patterns, dependencies // ============================================================================ import { AgentState, CodeAnalysis, ModuleInfo, DependencyGraph, DependencyNode, DependencyEdge, CodeMetrics, CodePattern, ApiEndpoint, DatabaseOperation, ErrorPattern, } from '../types'; import { readFileContent } from '../utils/file-utils'; import { parseModule, detectApiEndpoints, detectDatabaseOperations, detectErrorPatterns, detectCodePatterns, } from '../utils/parser'; import { logger } from '../utils/logger'; export async function analyzerAgent(state: AgentState): Promise> { const startTime = Date.now(); logger.agentStart('analyzer'); try { if (!state.projectStructure) { throw new Error('Project structure not available. Scanner Agent must run first.'); } const sourceFiles = state.projectStructure.files.filter( f => f.type === 'source' && (f.language === 'typescript' || f.language === 'javascript') ); logger.agent('analyzer', `Analyzing ${sourceFiles.length} source files...`); // 1. Parse all modules const modules: ModuleInfo[] = []; const allApiEndpoints: ApiEndpoint[] = []; const allDbOperations: DatabaseOperation[] = []; const allErrorPatterns: ErrorPattern[] = []; const allCodePatterns: CodePattern[] = []; for (let i = 0; i < sourceFiles.length; i++) { const file = sourceFiles[i]; logger.progress(i + 1, sourceFiles.length, file.relativePath); const content = readFileContent(file.path); if (!content) continue; // Parse module structure const moduleInfo = parseModule(file.path); modules.push(moduleInfo); // Detect API endpoints const endpoints = detectApiEndpoints(content, file.path); allApiEndpoints.push(...endpoints); // Detect database operations const dbOps = detectDatabaseOperations(content, file.path); allDbOperations.push(...dbOps); // Detect error patterns const errPatterns = detectErrorPatterns(content, file.path); allErrorPatterns.push(...errPatterns); // Detect code patterns const codePatterns = detectCodePatterns(content, file.path); allCodePatterns.push(...codePatterns); } // 2. Build dependency graph logger.agent('analyzer', 'Building dependency graph...'); const dependencyGraph = buildDependencyGraph(modules); // 3. Detect environment variables const envVars = detectEnvironmentVariables(sourceFiles.map(f => f.path)); logger.agent('analyzer', `${envVars.length} environment variables detected`); // 4. Calculate metrics const metrics = calculateMetrics(modules); // 5. Log results logger.agent('analyzer', `${modules.length} modules analyzed`); logger.agent('analyzer', `${metrics.totalFunctions} functions, ${metrics.totalClasses} classes`); logger.agent('analyzer', `${allApiEndpoints.length} API endpoints found`); logger.agent('analyzer', `${allDbOperations.length} database operations detected`); logger.agent('analyzer', `${allCodePatterns.length} design patterns detected`); if (dependencyGraph.circularDependencies.length > 0) { logger.warning(`${dependencyGraph.circularDependencies.length} circular dependencies found!`); } logger.table( ['Metric', 'Value'], [ ['Functions', String(metrics.totalFunctions)], ['Classes', String(metrics.totalClasses)], ['Interfaces', String(metrics.totalInterfaces)], ['Avg. Complexity', metrics.averageComplexity.toFixed(1)], ['Max. Complexity', String(metrics.maxComplexity)], ['Testable Units', String(metrics.testableUnits)], ['API Endpoints', String(allApiEndpoints.length)], ['DB Operations', String(allDbOperations.length)], ] ); const codeAnalysis: CodeAnalysis = { modules, dependencyGraph, patterns: allCodePatterns, metrics, apiEndpoints: allApiEndpoints, databaseOperations: allDbOperations, environmentVariables: envVars, errorHandlingPatterns: allErrorPatterns, }; logger.agentComplete('analyzer', Date.now() - startTime); return { codeAnalysis, agentLog: [ ...state.agentLog, { agent: 'analyzer', timestamp: new Date().toISOString(), action: 'Code analyzed', details: `${modules.length} modules, ${metrics.totalFunctions} functions, ${allApiEndpoints.length} endpoints`, duration: Date.now() - startTime, status: 'complete', }, ], }; } catch (error) { const errMsg = error instanceof Error ? error.message : String(error); logger.agentError('analyzer', errMsg); return { errors: [...state.errors, `Analyzer: ${errMsg}`], agentLog: [ ...state.agentLog, { agent: 'analyzer', timestamp: new Date().toISOString(), action: 'Error', details: errMsg, status: 'error', }, ], }; } } function buildDependencyGraph(modules: ModuleInfo[]): DependencyGraph { const nodes: DependencyNode[] = []; const edges: DependencyEdge[] = []; const externalDeps = new Set(); // Create nodes for (const mod of modules) { nodes.push({ id: mod.filePath, filePath: mod.filePath, type: 'source' }); } // Create edges for (const mod of modules) { for (const imp of mod.imports) { if (imp.isExternal) { externalDeps.add(imp.moduleName); } else { edges.push({ from: mod.filePath, to: imp.moduleName, type: 'import', }); } } } // Detect circular dependencies (simple check) const circularDependencies: string[][] = []; const adjacency = new Map>(); for (const edge of edges) { if (!adjacency.has(edge.from)) adjacency.set(edge.from, new Set()); adjacency.get(edge.from)!.add(edge.to); } for (const [from, tos] of adjacency) { for (const to of tos) { const reverseEdges = adjacency.get(to); if (reverseEdges?.has(from)) { const cycle = [from, to].sort(); const cycleKey = cycle.join('->'); if (!circularDependencies.some(c => c.sort().join('->') === cycleKey)) { circularDependencies.push(cycle); } } } } return { nodes, edges, circularDependencies, externalDependencies: [...externalDeps], }; } function calculateMetrics(modules: ModuleInfo[]): CodeMetrics { let totalFunctions = 0; let totalClasses = 0; let totalInterfaces = 0; let totalComplexity = 0; let maxComplexity = 0; let funcCount = 0; for (const mod of modules) { totalFunctions += mod.functions.length; totalClasses += mod.classes.length; totalInterfaces += mod.interfaces.length; for (const func of mod.functions) { totalComplexity += func.complexity; maxComplexity = Math.max(maxComplexity, func.complexity); funcCount++; } for (const cls of mod.classes) { for (const method of cls.methods) { totalComplexity += method.complexity; maxComplexity = Math.max(maxComplexity, method.complexity); funcCount++; } } } const testableUnits = totalFunctions + modules.reduce( (sum, m) => sum + m.classes.reduce((s, c) => s + c.methods.length, 0), 0 ); return { totalFunctions, totalClasses, totalInterfaces, totalLines: 0, averageComplexity: funcCount > 0 ? totalComplexity / funcCount : 0, maxComplexity, testableUnits, coverageEstimate: 0, }; } function detectEnvironmentVariables(filePaths: string[]): string[] { const envVars = new Set(); const envRegex = /process\.env\.(\w+)|process\.env\[['"](\w+)['"]\]/g; for (const filePath of filePaths) { const content = readFileContent(filePath); let match: RegExpExecArray | null; while ((match = envRegex.exec(content)) !== null) { envVars.add(match[1] || match[2]); } } return [...envVars]; }