/** * Agentic QE v3 - Root Cause Analyzer Service * Analyzes defects to identify root causes and contributing factors */ import { Result, ok, err } from '../../../shared/types'; import { MemoryBackend } from '../../../kernel/interfaces'; import { RootCauseRequest, RootCauseAnalysis, ContributingFactor, TimelineEvent, } from '../interfaces'; /** * Interface for the root cause analyzer service */ export interface IRootCauseAnalyzerService { analyzeRootCause(request: RootCauseRequest): Promise>; findRelatedDefects(defectId: string): Promise>; generateTimeline(defectId: string): Promise>; suggestRemediation(rootCause: string): Promise; } /** * Configuration for the root cause analyzer */ export interface RootCauseAnalyzerConfig { maxTimelineEvents: number; maxRelatedFiles: number; minConfidenceThreshold: number; analyzerNamespace: string; enableDeepAnalysis: boolean; } const DEFAULT_CONFIG: RootCauseAnalyzerConfig = { maxTimelineEvents: 20, maxRelatedFiles: 10, minConfidenceThreshold: 0.3, analyzerNamespace: 'defect-intelligence:root-cause', enableDeepAnalysis: true, }; /** * Known root cause categories with analysis patterns */ const ROOT_CAUSE_CATEGORIES: Record< string, { symptoms: string[]; causes: string[]; impact: 'high' | 'medium' | 'low'; remediation: string[]; } > = { 'logic-error': { symptoms: ['wrong result', 'incorrect output', 'unexpected behavior', 'calculation'], causes: [ 'Incorrect algorithm implementation', 'Missing edge case handling', 'Wrong operator usage', 'Off-by-one error', ], impact: 'high', remediation: [ 'Review algorithm logic step by step', 'Add comprehensive unit tests', 'Use property-based testing for edge cases', ], }, 'null-reference': { symptoms: ['null', 'undefined', 'cannot read property', 'NullPointerException'], causes: [ 'Missing null check', 'Uninitialized variable', 'Race condition in initialization', 'API returning null unexpectedly', ], impact: 'high', remediation: [ 'Add defensive null checks', 'Use Optional/Maybe pattern', 'Initialize variables at declaration', ], }, 'concurrency': { symptoms: ['race', 'deadlock', 'intermittent', 'timing', 'thread'], causes: [ 'Missing synchronization', 'Shared mutable state', 'Lock ordering issue', 'Async operation not awaited', ], impact: 'high', remediation: [ 'Use immutable data structures', 'Implement proper locking strategy', 'Add synchronization primitives', ], }, 'resource-management': { symptoms: ['memory', 'leak', 'connection', 'file handle', 'exhausted'], causes: [ 'Resource not properly released', 'Missing cleanup in error path', 'Connection pool misconfiguration', 'Circular reference preventing GC', ], impact: 'medium', remediation: [ 'Use try-finally or using blocks', 'Implement IDisposable pattern', 'Configure resource pools properly', ], }, 'configuration': { symptoms: ['config', 'environment', 'setting', 'property', 'parameter'], causes: [ 'Wrong configuration value', 'Missing required setting', 'Environment mismatch', 'Hardcoded values', ], impact: 'medium', remediation: [ 'Validate configuration at startup', 'Use configuration schema validation', 'Document all required settings', ], }, 'integration': { symptoms: ['api', 'service', 'external', 'timeout', 'connection'], causes: [ 'External service change', 'API contract violation', 'Network configuration issue', 'Missing retry logic', ], impact: 'high', remediation: [ 'Implement circuit breaker pattern', 'Add contract tests', 'Use retry with exponential backoff', ], }, 'data-integrity': { symptoms: ['corrupt', 'invalid', 'constraint', 'duplicate', 'data'], causes: [ 'Missing validation', 'Database constraint violated', 'Race condition in data update', 'Inconsistent state transitions', ], impact: 'high', remediation: [ 'Add input validation at all layers', 'Use database transactions properly', 'Implement optimistic locking', ], }, 'security': { symptoms: ['injection', 'auth', 'permission', 'access', 'security'], causes: [ 'Missing input sanitization', 'Broken authentication', 'Insufficient authorization checks', 'Sensitive data exposure', ], impact: 'high', remediation: [ 'Sanitize all user inputs', 'Implement proper authentication', 'Follow principle of least privilege', ], }, }; /** * Root Cause Analyzer Service Implementation * Uses symptom analysis and heuristics to identify root causes */ export class RootCauseAnalyzerService implements IRootCauseAnalyzerService { private readonly config: RootCauseAnalyzerConfig; constructor( private readonly memory: MemoryBackend, config: Partial = {} ) { this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Analyze root cause of a defect */ async analyzeRootCause( request: RootCauseRequest ): Promise> { try { const { defectId, symptoms, context = {} } = request; if (symptoms.length === 0) { return err(new Error('No symptoms provided for analysis')); } // Identify the root cause category const categoryAnalysis = this.identifyRootCauseCategory(symptoms); // Calculate confidence based on symptom matches const confidence = this.calculateConfidence(categoryAnalysis, symptoms); if (confidence < this.config.minConfidenceThreshold) { return ok({ defectId, rootCause: 'Unable to determine root cause with sufficient confidence', confidence, contributingFactors: [], relatedFiles: [], recommendations: ['Provide more symptoms for better analysis'], timeline: [], }); } // Identify contributing factors const contributingFactors = await this.identifyContributingFactors( categoryAnalysis.category, symptoms, context ); // Find related files const relatedFiles = await this.findRelatedFiles(defectId, symptoms); // Generate recommendations const recommendations = this.generateRecommendations( categoryAnalysis, contributingFactors ); // Build timeline const timeline = await this.generateTimeline(defectId); // Store analysis for future reference await this.storeAnalysis({ defectId, rootCause: categoryAnalysis.rootCause, category: categoryAnalysis.category, confidence, symptoms, timestamp: new Date(), }); return ok({ defectId, rootCause: categoryAnalysis.rootCause, confidence, contributingFactors, relatedFiles, recommendations, timeline: timeline.success ? timeline.value : [], }); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } /** * Find defects related to a given defect */ async findRelatedDefects(defectId: string): Promise> { try { // Look for defects with similar root causes const analysis = await this.memory.get<{ category: string }>( `${this.config.analyzerNamespace}:analysis:${defectId}` ); if (!analysis) { return ok([]); } // Search for other defects in the same category const keys = await this.memory.search( `${this.config.analyzerNamespace}:analysis:*`, 50 ); const relatedDefects: string[] = []; for (const key of keys) { const otherAnalysis = await this.memory.get<{ defectId: string; category: string; }>(key); if ( otherAnalysis && otherAnalysis.defectId !== defectId && otherAnalysis.category === analysis.category ) { relatedDefects.push(otherAnalysis.defectId); } } return ok(relatedDefects); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } /** * Generate timeline of events related to a defect */ async generateTimeline(defectId: string): Promise> { try { const timeline: TimelineEvent[] = []; // Look for stored events const eventsKey = `${this.config.analyzerNamespace}:events:${defectId}`; const storedEvents = await this.memory.get(eventsKey); if (storedEvents) { timeline.push(...storedEvents); } // Look for related changes in code intelligence const changes = await this.memory.search( `code-intelligence:change:*`, this.config.maxTimelineEvents ); for (const changeKey of changes) { const change = await this.memory.get<{ timestamp: string; description: string; file: string; }>(changeKey); if (change) { timeline.push({ timestamp: new Date(change.timestamp), event: `Code change: ${change.description}`, relevance: 0.5, }); } } // Sort by timestamp and limit const sortedTimeline = timeline .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) .slice(0, this.config.maxTimelineEvents); return ok(sortedTimeline); } catch (error) { return err(error instanceof Error ? error : new Error(String(error))); } } /** * Suggest remediation steps for a root cause */ async suggestRemediation(rootCause: string): Promise { const remediation: string[] = []; // Match root cause to known categories for (const [category, data] of Object.entries(ROOT_CAUSE_CATEGORIES)) { if ( rootCause.toLowerCase().includes(category) || data.causes.some((c) => rootCause.toLowerCase().includes(c.toLowerCase())) ) { remediation.push(...data.remediation); } } if (remediation.length === 0) { remediation.push('Review the affected code thoroughly'); remediation.push('Add tests to prevent regression'); remediation.push('Document the fix for future reference'); } return [...new Set(remediation)]; } // ============================================================================ // Private Helper Methods // ============================================================================ private identifyRootCauseCategory( symptoms: string[] ): { category: string; rootCause: string; matchScore: number } { let bestMatch = { category: 'unknown', rootCause: 'Undetermined root cause', matchScore: 0, }; const symptomText = symptoms.join(' ').toLowerCase(); for (const [category, data] of Object.entries(ROOT_CAUSE_CATEGORIES)) { let matchScore = 0; for (const symptom of data.symptoms) { if (symptomText.includes(symptom.toLowerCase())) { matchScore += 1; } } // Normalize by number of symptoms matchScore = matchScore / data.symptoms.length; if (matchScore > bestMatch.matchScore) { // Select the most likely cause from the category const likelyCause = this.selectLikelyCause(data.causes, symptomText); bestMatch = { category, rootCause: likelyCause, matchScore, }; } } return bestMatch; } private selectLikelyCause(causes: string[], symptomText: string): string { // Score each cause by word overlap with symptoms let bestCause = causes[0]; let bestScore = 0; for (const cause of causes) { const words = cause.toLowerCase().split(/\s+/); let score = 0; for (const word of words) { if (word.length > 3 && symptomText.includes(word)) { score += 1; } } if (score > bestScore) { bestScore = score; bestCause = cause; } } return bestCause; } private calculateConfidence( categoryAnalysis: { matchScore: number }, symptoms: string[] ): number { // Base confidence from match score let confidence = categoryAnalysis.matchScore; // Adjust based on number of symptoms if (symptoms.length >= 3) { confidence *= 1.1; } else if (symptoms.length === 1) { confidence *= 0.8; } // Cap at 1.0 return Math.min(1, Math.max(0, confidence)); } private async identifyContributingFactors( category: string, symptoms: string[], context: Record ): Promise { const factors: ContributingFactor[] = []; const categoryData = ROOT_CAUSE_CATEGORIES[category]; if (!categoryData) { return factors; } // Add factors based on category factors.push({ factor: `Category: ${category}`, impact: categoryData.impact, evidence: symptoms.slice(0, 3), }); // Add context-based factors if (context.recentChanges) { factors.push({ factor: 'Recent code changes', impact: 'high', evidence: ['Code was recently modified'], }); } if (context.highTraffic) { factors.push({ factor: 'High traffic conditions', impact: 'medium', evidence: ['Occurred during peak load'], }); } if (context.environment) { factors.push({ factor: `Environment: ${context.environment}`, impact: 'medium', evidence: [`Observed in ${context.environment} environment`], }); } // Look for historical patterns const historicalFactors = await this.getHistoricalFactors(category); factors.push(...historicalFactors); return factors; } private async getHistoricalFactors( category: string ): Promise { const factors: ContributingFactor[] = []; // Search for historical analyses in the same category const historicalKey = `${this.config.analyzerNamespace}:history:${category}`; const history = await this.memory.get<{ occurrences: number; commonFactors: string[]; }>(historicalKey); if (history && history.occurrences > 1) { factors.push({ factor: 'Recurring issue pattern', impact: 'high', evidence: [ `Similar issues occurred ${history.occurrences} times`, ...history.commonFactors.slice(0, 2), ], }); } return factors; } private async findRelatedFiles( _defectId: string, symptoms: string[] ): Promise { const files: Set = new Set(); // Extract file paths from symptoms for (const symptom of symptoms) { const filePatterns = symptom.match(/[\w\-]+\.(ts|js|tsx|jsx|py|java|go)/g); if (filePatterns) { filePatterns.forEach((f) => files.add(f)); } // Look for path-like patterns const pathPatterns = symptom.match(/(?:src|lib|app)\/[\w\/\-]+/g); if (pathPatterns) { pathPatterns.forEach((p) => files.add(p)); } } // Look for files from code intelligence const codeIntelKeys = await this.memory.search( 'code-intelligence:file:*', this.config.maxRelatedFiles ); for (const key of codeIntelKeys.slice(0, this.config.maxRelatedFiles - files.size)) { const fileInfo = await this.memory.get<{ path: string }>(key); if (fileInfo) { files.add(fileInfo.path); } } return Array.from(files).slice(0, this.config.maxRelatedFiles); } private generateRecommendations( categoryAnalysis: { category: string; rootCause: string }, contributingFactors: ContributingFactor[] ): string[] { const recommendations: string[] = []; const categoryData = ROOT_CAUSE_CATEGORIES[categoryAnalysis.category]; // Add category-specific remediation if (categoryData) { recommendations.push(...categoryData.remediation); } // Add factor-specific recommendations for (const factor of contributingFactors) { if (factor.impact === 'high') { recommendations.push(`Address: ${factor.factor}`); } } // Add general recommendations recommendations.push('Add regression tests for this scenario'); recommendations.push('Document the root cause and fix'); return [...new Set(recommendations)].slice(0, 8); } private async storeAnalysis(analysis: { defectId: string; rootCause: string; category: string; confidence: number; symptoms: string[]; timestamp: Date; }): Promise { // Store the analysis await this.memory.set( `${this.config.analyzerNamespace}:analysis:${analysis.defectId}`, analysis, { namespace: 'defect-intelligence', persist: true } ); // Update historical data for the category const historyKey = `${this.config.analyzerNamespace}:history:${analysis.category}`; const history = (await this.memory.get<{ occurrences: number; commonFactors: string[]; }>(historyKey)) || { occurrences: 0, commonFactors: [] }; history.occurrences += 1; // Add new symptoms to common factors for (const symptom of analysis.symptoms.slice(0, 2)) { if (!history.commonFactors.includes(symptom)) { history.commonFactors.push(symptom); } } history.commonFactors = history.commonFactors.slice(0, 10); await this.memory.set(historyKey, history, { namespace: 'defect-intelligence', persist: true, }); } }