UNPKG

@versatil/sdlc-framework

Version:

๐Ÿš€ AI-Native SDLC framework with 11-MCP ecosystem, RAG memory, OPERA orchestration, and 6 specialized agents achieving ZERO CONTEXT LOSS. Features complete CI/CD pipeline with 7 GitHub workflows (MCP testing, security scanning, performance benchmarking),

711 lines (599 loc) โ€ข 24.4 kB
/** * Reality Validator - The Brutal Truth Engine * * Executes tests against the live VERSATIL framework to determine * what actually works vs what's just impressive architecture. * * No mercy. No architectural theater. Just brutal honesty. */ import * as fs from 'fs/promises'; import * as path from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import { EventEmitter } from 'events'; import { VERSATILLogger } from '../utils/logger.js'; import { GeneratedTestSuite } from './test-generator.js'; import { SimulationScenario, TestCase } from '../agents/simulation-qa.js'; const execAsync = promisify(exec); export interface ValidationResult { scenario: SimulationScenario; startTime: Date; endTime: Date; executionTime: number; passed: boolean; evidence: ValidationEvidence[]; error?: string; confidence: number; } export interface ValidationEvidence { type: 'agent_response' | 'mcp_response' | 'file_change' | 'error_message' | 'performance_metric' | 'log_entry'; timestamp: Date; source: string; data: any; relevant: boolean; description: string; } export interface LiveFrameworkInterface { triggerFileEdit(filePath: string, content: string): Promise<string>; callMCPTool(tool: string, params: any): Promise<any>; monitorAgentActivation(timeout: number): Promise<any[]>; checkFrameworkLogs(duration: number): Promise<string[]>; measureResponseTime(action: () => Promise<any>): Promise<{ result: any; duration: number }>; } export class RealityValidator extends EventEmitter { private logger: VERSATILLogger; private projectRoot: string; private verssaiRoot: string; private mcpServerPid?: number; private validationResults: ValidationResult[] = []; private frameworkInterface: LiveFrameworkInterface; constructor(projectRoot: string = process.cwd()) { super(); this.logger = VERSATILLogger.getInstance(); this.projectRoot = projectRoot; this.verssaiRoot = '/Users/nissimmenashe/VERSSAI-app-Sept7'; this.frameworkInterface = this.createFrameworkInterface(); this.logger.info('๐Ÿ” Reality Validator initialized', { mode: 'brutal honesty', tolerance: 'zero for vapor-ware', projectRoot: this.projectRoot }, 'RealityValidator'); } /** * Execute validation of generated test suites */ async executeValidation(testSuites: GeneratedTestSuite[]): Promise<SimulationScenario[]> { this.logger.info('โšก Starting reality validation', { totalSuites: testSuites.length, totalScenarios: testSuites.reduce((sum, suite) => sum + suite.scenarios.length, 0) }, 'RealityValidator'); const allScenarios: SimulationScenario[] = []; try { // Ensure MCP server is running for tests await this.ensureMCPServerRunning(); // Execute validation for each test suite for (const suite of testSuites) { this.logger.info(`๐Ÿงช Validating suite: ${suite.name}`, { scenarios: suite.scenarios.length, priority: suite.priority }, 'RealityValidator'); const validatedScenarios = await this.validateTestSuite(suite); allScenarios.push(...validatedScenarios); } // Generate summary statistics this.generateValidationSummary(allScenarios); } catch (error) { this.logger.error('Reality validation failed', { error: error instanceof Error ? error.message : String(error) }, 'RealityValidator'); // Return scenarios marked as failed due to validation error return testSuites.flatMap(suite => suite.scenarios.map(scenario => ({ ...scenario, status: 'failed' as const, evidence: [`Validation failed: ${error instanceof Error ? error.message : String(error)}`] })) ); } return allScenarios; } /** * Validate a single test suite */ private async validateTestSuite(suite: GeneratedTestSuite): Promise<SimulationScenario[]> { const validatedScenarios: SimulationScenario[] = []; for (const scenario of suite.scenarios) { this.logger.info(`๐ŸŽฏ Testing: ${scenario.featureName}`, { testCases: scenario.testCases.length, category: suite.category }, 'RealityValidator'); const validatedScenario = await this.validateScenario(scenario); validatedScenarios.push(validatedScenario); // Emit progress event this.emit('scenario-completed', validatedScenario); } return validatedScenarios; } /** * Validate a single scenario */ private async validateScenario(scenario: SimulationScenario): Promise<SimulationScenario> { const startTime = Date.now(); try { scenario.status = 'testing'; // Execute test cases for this scenario const testResults = await this.executeTestCases(scenario.testCases); // Collect evidence during test execution const evidence = await this.collectEvidence(scenario); // Determine if scenario passed based on test results and evidence const passed = this.evaluateScenarioSuccess(testResults, evidence); scenario.status = passed ? 'passed' : 'failed'; scenario.actualBehavior = this.describeBehavior(evidence, passed); scenario.evidence = evidence.map(e => e.description); scenario.confidence = this.calculateConfidence(testResults, evidence); const executionTime = Date.now() - startTime; this.logger.info(`${passed ? 'โœ…' : 'โŒ'} ${scenario.featureName}`, { status: scenario.status, executionTime: `${executionTime}ms`, confidence: `${Math.round(scenario.confidence * 100)}%`, evidenceCount: evidence.length }, 'RealityValidator'); return scenario; } catch (error) { scenario.status = 'failed'; scenario.actualBehavior = `Test execution failed: ${error instanceof Error ? error.message : String(error)}`; scenario.evidence = [`Execution error: ${error instanceof Error ? error.message : String(error)}`]; scenario.confidence = 0; this.logger.error(`โŒ ${scenario.featureName} failed`, { error: error instanceof Error ? error.message : String(error), executionTime: `${Date.now() - startTime}ms` }, 'RealityValidator'); return scenario; } } /** * Execute test cases for a scenario */ private async executeTestCases(testCases: TestCase[]): Promise<TestCase[]> { const executedCases: TestCase[] = []; for (const testCase of testCases) { const startTime = Date.now(); try { testCase.timestamp = Date.now(); // Execute the specific test case const result = await this.executeSpecificTest(testCase); testCase.passed = result.passed; testCase.actualResult = result.actualResult; testCase.executionTime = Date.now() - startTime; } catch (error) { testCase.passed = false; testCase.actualResult = `Test failed: ${error instanceof Error ? error.message : String(error)}`; testCase.executionTime = Date.now() - startTime; } executedCases.push(testCase); } return executedCases; } /** * Execute a specific test case based on its action */ private async executeSpecificTest(testCase: TestCase): Promise<{ passed: boolean; actualResult: string }> { // Agent Activation Tests if (testCase.action.includes('Edit') && testCase.action.includes('.tsx')) { return await this.testAgentActivation('james-frontend', '.tsx'); } if (testCase.action.includes('Edit') && testCase.action.includes('.test.ts')) { return await this.testAgentActivation('maria-qa', '.test.ts'); } // MCP Tool Tests if (testCase.action.includes('Call') && testCase.action.includes('versatil_')) { const toolMatch = testCase.action.match(/versatil_\w+/); const tool = toolMatch ? toolMatch[0] : 'versatil_analyze_project'; return await this.testMCPTool(tool); } // Context Preservation Tests if (testCase.action.includes('handoff') || testCase.action.includes('context')) { return await this.testContextPreservation(); } // Performance Tests if (testCase.action.includes('measure') || testCase.action.includes('response time')) { return await this.testPerformance(testCase.expectedResult); } // Integration Tests if (testCase.action.includes('workflow') || testCase.action.includes('sequence')) { return await this.testIntegrationWorkflow(); } // Default case - basic functionality check return await this.testBasicFunctionality(testCase); } /** * Test agent activation functionality */ private async testAgentActivation(agentId: string, fileExtension: string): Promise<{ passed: boolean; actualResult: string }> { try { // Create a test file that should trigger the agent const testFilePath = path.join(this.verssaiRoot, `simulation-test${fileExtension}`); const testContent = this.generateTestContent(fileExtension); // Trigger file edit await this.frameworkInterface.triggerFileEdit(testFilePath, testContent); // Monitor for agent activation const activations = await this.frameworkInterface.monitorAgentActivation(5000); // Check if expected agent was activated const expectedActivation = activations.find(a => a.agentId === agentId); if (expectedActivation) { return { passed: true, actualResult: `${agentId} activated successfully with response: ${JSON.stringify(expectedActivation)}` }; } else { return { passed: false, actualResult: `No activation detected for ${agentId}. Activations found: ${JSON.stringify(activations)}` }; } } catch (error) { return { passed: false, actualResult: `Agent activation test failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Test MCP tool functionality */ private async testMCPTool(tool: string): Promise<{ passed: boolean; actualResult: string }> { try { const params = this.getDefaultMCPParams(tool); const response = await this.frameworkInterface.callMCPTool(tool, params); if (response && typeof response === 'object') { return { passed: true, actualResult: `MCP tool ${tool} responded successfully: ${JSON.stringify(response)}` }; } else { return { passed: false, actualResult: `MCP tool ${tool} provided no meaningful response: ${JSON.stringify(response)}` }; } } catch (error) { return { passed: false, actualResult: `MCP tool ${tool} failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Test context preservation between agents */ private async testContextPreservation(): Promise<{ passed: boolean; actualResult: string }> { try { // This would test actual agent handoffs // For now, return based on what we know about the framework return { passed: false, actualResult: 'Context preservation not implemented - no agent handoff mechanism found' }; } catch (error) { return { passed: false, actualResult: `Context preservation test failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Test performance characteristics */ private async testPerformance(expectedCriteria: string): Promise<{ passed: boolean; actualResult: string }> { try { const { result, duration } = await this.frameworkInterface.measureResponseTime(async () => { return await this.frameworkInterface.callMCPTool('versatil_analyze_project', {}); }); // Extract expected time from criteria (e.g., "under 5 seconds") const timeMatch = expectedCriteria.match(/(\d+)\s*seconds?/i); const expectedTime = timeMatch && timeMatch[1] ? parseInt(timeMatch[1], 10) * 1000 : 5000; // Default 5 seconds const passed = duration < expectedTime; return { passed, actualResult: `Performance test: ${duration}ms (expected: under ${expectedTime}ms)` }; } catch (error) { return { passed: false, actualResult: `Performance test failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Test integration workflow */ private async testIntegrationWorkflow(): Promise<{ passed: boolean; actualResult: string }> { try { // Test complete workflow: file edit -> agent activation -> handoff -> completion const workflowSteps: string[] = []; // Step 1: Edit React component (should trigger James) const testFilePath = path.join(this.verssaiRoot, 'workflow-test.tsx'); await this.frameworkInterface.triggerFileEdit(testFilePath, 'export const Test = () => <div>Test</div>;'); workflowSteps.push('File edited'); // Step 2: Check for agent activation const activations = await this.frameworkInterface.monitorAgentActivation(3000); if (activations.length > 0) { workflowSteps.push(`Agents activated: ${activations.map(a => a.agentId).join(', ')}`); } else { workflowSteps.push('No agents activated'); } // Step 3: Check for handoffs (context preservation) // This would require actual implementation const passed = activations.length > 0; // Basic check return { passed, actualResult: `Integration workflow: ${workflowSteps.join(' โ†’ ')}` }; } catch (error) { return { passed: false, actualResult: `Integration workflow test failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Test basic functionality */ private async testBasicFunctionality(testCase: TestCase): Promise<{ passed: boolean; actualResult: string }> { // For unknown test cases, just check if the framework is responsive try { const logs = await this.frameworkInterface.checkFrameworkLogs(1000); return { passed: logs.length > 0, actualResult: `Framework responsiveness check: ${logs.length} log entries found` }; } catch (error) { return { passed: false, actualResult: `Basic functionality test failed: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Collect evidence during test execution */ private async collectEvidence(scenario: SimulationScenario): Promise<ValidationEvidence[]> { const evidence: ValidationEvidence[] = []; try { // Collect framework logs const logs = await this.frameworkInterface.checkFrameworkLogs(2000); logs.forEach(log => { evidence.push({ type: 'log_entry', timestamp: new Date(Date.now()), source: 'framework-logs', data: log, relevant: this.isLogRelevant(log, scenario), description: `Framework log: ${log.substring(0, 100)}...` }); }); // Check for file changes // This would monitor the file system for agent-generated changes // Check for error messages // This would capture any errors during test execution } catch (error) { evidence.push({ type: 'error_message', timestamp: new Date(Date.now()), source: 'evidence-collection', data: { error: error instanceof Error ? error.message : String(error) }, relevant: true, description: `Evidence collection failed: ${error instanceof Error ? error.message : String(error)}` }); } return evidence; } /** * Evaluate scenario success based on test results and evidence */ private evaluateScenarioSuccess(testResults: TestCase[], evidence: ValidationEvidence[]): boolean { // Scenario passes if majority of test cases pass AND we have positive evidence const passedTests = testResults.filter(test => test.passed).length; const totalTests = testResults.length; const testPassRate = totalTests > 0 ? passedTests / totalTests : 0; const positiveEvidence = evidence.filter(e => e.relevant && !e.description.toLowerCase().includes('error')).length; // Require at least 70% test pass rate AND some positive evidence return testPassRate >= 0.7 && positiveEvidence > 0; } /** * Describe behavior based on evidence */ private describeBehavior(evidence: ValidationEvidence[], passed: boolean): string { if (passed) { const positiveEvidence = evidence.filter(e => e.relevant && !e.description.toLowerCase().includes('error')); return `Feature working: ${positiveEvidence.map(e => e.description).join('; ')}`; } else { const negativeEvidence = evidence.filter(e => e.description.toLowerCase().includes('error') || e.description.toLowerCase().includes('failed')); if (negativeEvidence.length > 0) { return `Feature broken: ${negativeEvidence.map(e => e.description).join('; ')}`; } else { return 'Feature appears to be vapor-ware: no evidence of functionality found'; } } } /** * Calculate confidence based on test results and evidence quality */ private calculateConfidence(testResults: TestCase[], evidence: ValidationEvidence[]): number { const testConfidence = testResults.length > 0 ? testResults.filter(t => t.passed).length / testResults.length : 0; const evidenceConfidence = evidence.length > 0 ? evidence.filter(e => e.relevant).length / evidence.length : 0; return (testConfidence + evidenceConfidence) / 2; } /** * Create framework interface for live testing */ private createFrameworkInterface(): LiveFrameworkInterface { return { triggerFileEdit: async (filePath: string, content: string): Promise<string> => { await fs.writeFile(filePath, content, 'utf-8'); return filePath; }, callMCPTool: async (tool: string, params: any): Promise<any> => { try { // This would make actual MCP requests // For now, simulate based on what we know return null; // No actual MCP responses detected in our testing } catch (error) { throw new Error(`MCP tool ${tool} failed: ${error instanceof Error ? error.message : String(error)}`); } }, monitorAgentActivation: async (timeout: number): Promise<any[]> => { // Monitor framework logs/output for agent activation messages await new Promise(resolve => setTimeout(resolve, timeout)); return []; // No agent activations detected in our previous testing }, checkFrameworkLogs: async (duration: number): Promise<string[]> => { await new Promise(resolve => setTimeout(resolve, duration)); return ['Framework log entries would appear here']; }, measureResponseTime: async (action: () => Promise<any>): Promise<{ result: any; duration: number }> => { const startTime = Date.now(); const result = await action(); const duration = Date.now() - startTime; return { result, duration }; } }; } /** * Generate appropriate test content for file extension */ private generateTestContent(extension: string): string { switch (extension) { case '.tsx': return 'import React from "react";\nexport const TestComponent = () => <div>Test</div>;'; case '.test.ts': return 'describe("test", () => { it("should work", () => { expect(true).toBe(true); }); });'; case '.ts': return 'export const testFunction = () => "test";'; default: return 'console.log("test");'; } } /** * Get default parameters for MCP tools */ private getDefaultMCPParams(tool: string): any { const defaults: Record<string, Record<string, any>> = { 'versatil_analyze_project': { path: this.projectRoot }, 'versatil_activate_agent': { agent: 'james', context: 'test activation' }, 'versatil_quality_gates': { strict: false }, 'versatil_emergency_response': { error: 'test emergency' } }; return defaults[tool] || {}; } /** * Check if log entry is relevant to scenario */ private isLogRelevant(log: string, scenario: SimulationScenario): boolean { const relevantTerms = [ scenario.featureName.toLowerCase(), 'agent', 'activation', 'mcp', 'context', 'handoff' ]; return relevantTerms.some(term => log.toLowerCase().includes(term)); } /** * Ensure MCP server is running for tests */ private async ensureMCPServerRunning(): Promise<void> { try { // Check if MCP server is already running const { stdout } = await execAsync('pgrep -f "versatil-mcp"'); if (stdout.trim()) { this.logger.info('MCP server already running', { pid: stdout.trim() }, 'RealityValidator'); return; } } catch (error) { // MCP server not running, will start it } // Start MCP server for testing this.logger.info('Starting MCP server for validation', {}, 'RealityValidator'); // Implementation would start the MCP server } /** * Generate validation summary */ private generateValidationSummary(scenarios: SimulationScenario[]): void { const total = scenarios.length; const passed = scenarios.filter(s => s.status === 'passed').length; const failed = scenarios.filter(s => s.status === 'failed').length; const vapor = scenarios.filter(s => s.status === 'vapor').length; const summary = { total, passed, failed, vapor, passRate: total > 0 ? Math.round((passed / total) * 100) : 0, categories: this.summarizeByCategory(scenarios) }; this.logger.info('๐ŸŽฏ Reality Validation Summary', summary, 'RealityValidator'); // Emit summary event this.emit('validation-complete', summary); } /** * Summarize results by category */ private summarizeByCategory(scenarios: SimulationScenario[]): Record<string, any> { const categories: Record<string, any> = {}; scenarios.forEach(scenario => { const category = this.extractCategory(scenario.featureName); if (!categories[category]) { categories[category] = { total: 0, passed: 0, failed: 0 }; } categories[category].total++; if (scenario.status === 'passed') categories[category].passed++; if (scenario.status === 'failed') categories[category].failed++; }); return categories; } /** * Extract category from feature name */ private extractCategory(featureName: string): string { if (featureName.toLowerCase().includes('agent')) return 'agent-activation'; if (featureName.toLowerCase().includes('mcp')) return 'mcp-integration'; if (featureName.toLowerCase().includes('context')) return 'context-preservation'; if (featureName.toLowerCase().includes('quality')) return 'quality-gates'; if (featureName.toLowerCase().includes('integration')) return 'integration'; return 'other'; } /** * Export validation results */ async exportResults(filePath?: string): Promise<string> { const exportPath = filePath || path.join(this.projectRoot, '.versatil', 'validation-results.json'); const exportData = { timestamp: new Date(Date.now()).toISOString(), validator: 'VERSATIL Reality Validator', projectRoot: this.projectRoot, results: this.validationResults, summary: this.generateExportSummary() }; await fs.mkdir(path.dirname(exportPath), { recursive: true }); await fs.writeFile(exportPath, JSON.stringify(exportData, null, 2)); return exportPath; } /** * Generate export summary */ private generateExportSummary(): any { return { totalValidations: this.validationResults.length, successRate: this.validationResults.length > 0 ? Math.round((this.validationResults.filter(r => r.passed).length / this.validationResults.length) * 100) : 0, averageExecutionTime: this.validationResults.length > 0 ? Math.round(this.validationResults.reduce((sum, r) => sum + r.executionTime, 0) / this.validationResults.length) : 0, categories: this.summarizeByCategory(this.validationResults.map(r => r.scenario)) }; } }