UNPKG

shipdeck

Version:

Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.

604 lines (506 loc) 20.1 kB
/** * Shipdeck Ultimate Quality Gates System * * Three-tier quality enforcement system with 54 embedded rules: * - Tier 1: Auto-fixable issues (formatting, imports, linting) * - Tier 2: Retry with AI feedback (complexity, patterns, test coverage) * - Tier 3: Human approval required (architecture, security, breaking changes) * * Guarantees 95%+ MVP success rate with automatic error detection and fixing. */ const EventEmitter = require('events'); const { RuleClassifier } = require('./rule-classifier'); const { TierOneAutoFixer } = require('./tier-one-autofixer'); const { TierTwoRetryEngine } = require('./tier-two-retry'); const { TierThreeApproval } = require('./tier-three-approval'); const { QualityReporter } = require('./quality-reporter'); const { SecurityScanner } = require('./security-scanner'); const { PerformanceValidator } = require('./performance-validator'); const { TestCoverageVerifier } = require('./test-coverage-verifier'); const { RollbackTrigger } = require('./rollback-trigger'); class QualityGatesSystem extends EventEmitter { constructor(config = {}) { super(); this.config = { // Tier settings tier1AutoFix: config.tier1AutoFix !== false, // Auto-fix enabled by default tier2MaxRetries: config.tier2MaxRetries || 3, tier3RequireApproval: config.tier3RequireApproval !== false, // Performance thresholds maxGateProcessingTime: config.maxGateProcessingTime || 5000, // 5 seconds falsePositiveThreshold: config.falsePositiveThreshold || 0.01, // <1% // Integration settings dagWorkflowIntegration: config.dagWorkflowIntegration !== false, rollbackOnCriticalFailure: config.rollbackOnCriticalFailure !== false, // Success targets targetSuccessRate: config.targetSuccessRate || 0.95, // 95% ...config }; // Initialize components this.ruleClassifier = new RuleClassifier(this.config); this.tier1AutoFixer = new TierOneAutoFixer(this.config); this.tier2RetryEngine = new TierTwoRetryEngine(this.config); this.tier3Approval = new TierThreeApproval(this.config); this.qualityReporter = new QualityReporter(this.config); this.securityScanner = new SecurityScanner(this.config); this.performanceValidator = new PerformanceValidator(this.config); this.testCoverageVerifier = new TestCoverageVerifier(this.config); this.rollbackTrigger = new RollbackTrigger(this.config); // Runtime state this.statistics = { totalChecks: 0, tier1Fixes: 0, tier2Retries: 0, tier3Approvals: 0, criticalBlocks: 0, successRate: 0, averageProcessingTime: 0, falsePositiveRate: 0 }; this.activeGates = new Map(); this.gateResults = new Map(); // Setup event handlers this._setupEventHandlers(); } /** * Main entry point: Apply quality gates to code/artifact */ async applyQualityGates(artifact, context = {}) { const gateId = this._generateGateId(); const startTime = Date.now(); try { console.log(`🛡️ Applying quality gates: ${gateId}`); this.emit('gates:started', { gateId, artifact: this._sanitizeArtifact(artifact) }); this.activeGates.set(gateId, { id: gateId, artifact, context, startTime, status: 'running', currentTier: null, results: [] }); // Step 1: Classify all violations by tier const classification = await this.ruleClassifier.classifyViolations(artifact, context); if (classification.violations.length === 0) { return this._completeGateSuccess(gateId, 'No violations found'); } console.log(`📋 Found ${classification.violations.length} violations across ${classification.tierCounts.tier1 + classification.tierCounts.tier2 + classification.tierCounts.tier3} tiers`); // Step 2: Process each tier sequentially let processedArtifact = artifact; const gateResults = []; // Tier 1: Auto-fix if (classification.tierCounts.tier1 > 0) { console.log(`🔧 Processing ${classification.tierCounts.tier1} Tier 1 auto-fixes`); const tier1Result = await this._processTier1(gateId, processedArtifact, classification.violations.filter(v => v.tier === 1)); if (tier1Result.success) { processedArtifact = tier1Result.artifact; gateResults.push(tier1Result); this.statistics.tier1Fixes += tier1Result.fixedCount; } else { return this._completeGateFailure(gateId, 'Tier 1 auto-fix failed', tier1Result); } } // Tier 2: Retry with feedback if (classification.tierCounts.tier2 > 0) { console.log(`🔄 Processing ${classification.tierCounts.tier2} Tier 2 retries`); const tier2Result = await this._processTier2(gateId, processedArtifact, classification.violations.filter(v => v.tier === 2), context); if (tier2Result.success) { processedArtifact = tier2Result.artifact; gateResults.push(tier2Result); this.statistics.tier2Retries += tier2Result.retryCount; } else { return this._completeGateFailure(gateId, 'Tier 2 retry failed', tier2Result); } } // Tier 3: Human approval if (classification.tierCounts.tier3 > 0) { console.log(`⚠️ Processing ${classification.tierCounts.tier3} Tier 3 approval requests`); const tier3Result = await this._processTier3(gateId, processedArtifact, classification.violations.filter(v => v.tier === 3), context); if (tier3Result.success) { processedArtifact = tier3Result.artifact; gateResults.push(tier3Result); this.statistics.tier3Approvals += tier3Result.approvalCount; } else if (tier3Result.requiresApproval) { return this._completeGatePending(gateId, 'Awaiting human approval', tier3Result); } else { return this._completeGateFailure(gateId, 'Tier 3 approval denied', tier3Result); } } // Step 3: Final validation const finalValidation = await this._performFinalValidation(processedArtifact, context); if (!finalValidation.success) { return this._completeGateFailure(gateId, 'Final validation failed', finalValidation); } // Step 4: Success - update statistics and return const result = this._completeGateSuccess(gateId, 'All quality gates passed', { artifact: processedArtifact, gateResults, finalValidation }); this.statistics.totalChecks++; this._updateSuccessRate(); return result; } catch (error) { return this._completeGateFailure(gateId, `Quality gate error: ${error.message}`, { error }); } finally { this._recordProcessingTime(gateId, startTime); } } /** * Process Tier 1: Auto-fixable issues */ async _processTier1(gateId, artifact, violations) { this._updateGateStatus(gateId, 'tier1', 'Processing auto-fixes'); try { const result = await this.tier1AutoFixer.fixViolations(artifact, violations); this.emit('gates:tier1:completed', { gateId, fixedCount: result.fixedCount, remainingCount: result.remainingViolations.length }); if (result.remainingViolations.length > 0) { console.warn(`⚠️ ${result.remainingViolations.length} Tier 1 violations could not be auto-fixed`); } return { tier: 1, success: true, artifact: result.fixedArtifact, fixedCount: result.fixedCount, remainingViolations: result.remainingViolations, processingTime: result.processingTime }; } catch (error) { this.emit('gates:tier1:failed', { gateId, error: error.message }); return { tier: 1, success: false, error: error.message, artifact }; } } /** * Process Tier 2: Retry with AI feedback */ async _processTier2(gateId, artifact, violations, context) { this._updateGateStatus(gateId, 'tier2', 'Processing retries with AI feedback'); try { const result = await this.tier2RetryEngine.retryWithFeedback(artifact, violations, context); this.emit('gates:tier2:completed', { gateId, retryCount: result.retryCount, improvedCount: result.improvedCount, remainingCount: result.remainingViolations.length }); return { tier: 2, success: result.success, artifact: result.improvedArtifact, retryCount: result.retryCount, improvedCount: result.improvedCount, remainingViolations: result.remainingViolations, processingTime: result.processingTime }; } catch (error) { this.emit('gates:tier2:failed', { gateId, error: error.message }); return { tier: 2, success: false, error: error.message, artifact }; } } /** * Process Tier 3: Human approval workflow */ async _processTier3(gateId, artifact, violations, context) { this._updateGateStatus(gateId, 'tier3', 'Requesting human approval'); try { const result = await this.tier3Approval.requestApproval(artifact, violations, context); this.emit('gates:tier3:completed', { gateId, approvalCount: result.approvalCount, deniedCount: result.deniedCount, pendingCount: result.pendingCount }); return { tier: 3, success: result.success, requiresApproval: result.requiresApproval, artifact: result.artifact, approvalCount: result.approvalCount, deniedCount: result.deniedCount, pendingCount: result.pendingCount, riskAssessment: result.riskAssessment, processingTime: result.processingTime }; } catch (error) { this.emit('gates:tier3:failed', { gateId, error: error.message }); return { tier: 3, success: false, error: error.message, artifact }; } } /** * Perform final validation across all dimensions */ async _performFinalValidation(artifact, context) { console.log('🔍 Performing final quality validation'); const validationResults = await Promise.allSettled([ this.securityScanner.scanArtifact(artifact, context), this.performanceValidator.validatePerformance(artifact, context), this.testCoverageVerifier.verifyTestCoverage(artifact, context) ]); const securityResult = validationResults[0]; const performanceResult = validationResults[1]; const testCoverageResult = validationResults[2]; const issues = []; if (securityResult.status === 'rejected') { issues.push(`Security validation failed: ${securityResult.reason}`); } else if (!securityResult.value.passed) { issues.push(`Security issues found: ${securityResult.value.issues.length} vulnerabilities`); } if (performanceResult.status === 'rejected') { issues.push(`Performance validation failed: ${performanceResult.reason}`); } else if (!performanceResult.value.passed) { issues.push(`Performance issues found: ${performanceResult.value.issues.length} bottlenecks`); } if (testCoverageResult.status === 'rejected') { issues.push(`Test coverage validation failed: ${testCoverageResult.reason}`); } else if (!testCoverageResult.value.passed) { issues.push(`Test coverage insufficient: ${testCoverageResult.value.coverage}% < ${testCoverageResult.value.threshold}%`); } return { success: issues.length === 0, issues, securityResult: securityResult.status === 'fulfilled' ? securityResult.value : null, performanceResult: performanceResult.status === 'fulfilled' ? performanceResult.value : null, testCoverageResult: testCoverageResult.status === 'fulfilled' ? testCoverageResult.value : null }; } /** * Complete gate processing with success */ _completeGateSuccess(gateId, message, additionalData = {}) { const gate = this.activeGates.get(gateId); if (!gate) return null; gate.status = 'success'; gate.completedAt = Date.now(); gate.message = message; const result = { gateId, success: true, message, processingTime: gate.completedAt - gate.startTime, ...additionalData }; this.gateResults.set(gateId, result); this.activeGates.delete(gateId); console.log(`✅ Quality gates passed: ${gateId} - ${message}`); this.emit('gates:success', result); return result; } /** * Complete gate processing with failure */ _completeGateFailure(gateId, message, additionalData = {}) { const gate = this.activeGates.get(gateId); if (!gate) return null; gate.status = 'failed'; gate.completedAt = Date.now(); gate.message = message; const result = { gateId, success: false, message, processingTime: gate.completedAt - gate.startTime, ...additionalData }; this.gateResults.set(gateId, result); this.activeGates.delete(gateId); console.error(`❌ Quality gates failed: ${gateId} - ${message}`); this.emit('gates:failed', result); // Check if rollback is needed if (this.config.rollbackOnCriticalFailure && this._isCriticalFailure(result)) { this._triggerRollback(gateId, result); } return result; } /** * Complete gate processing with pending approval */ _completeGatePending(gateId, message, additionalData = {}) { const gate = this.activeGates.get(gateId); if (!gate) return null; gate.status = 'pending'; gate.pendingAt = Date.now(); gate.message = message; const result = { gateId, success: false, pending: true, message, processingTime: gate.pendingAt - gate.startTime, ...additionalData }; console.log(`⏳ Quality gates pending: ${gateId} - ${message}`); this.emit('gates:pending', result); return result; } /** * Resume a pending gate after approval */ async resumePendingGate(gateId, approvalDecision) { const gate = this.activeGates.get(gateId); if (!gate || gate.status !== 'pending') { throw new Error(`No pending gate found: ${gateId}`); } console.log(`🔄 Resuming pending gate: ${gateId} with decision: ${approvalDecision.approved ? 'approved' : 'denied'}`); if (approvalDecision.approved) { // Continue processing with approved changes return this._completeGateSuccess(gateId, 'Human approval granted', { approvalDecision, artifact: approvalDecision.modifiedArtifact || gate.artifact }); } else { // Reject with reason return this._completeGateFailure(gateId, `Human approval denied: ${approvalDecision.reason}`, { approvalDecision }); } } /** * Get quality gates statistics and health metrics */ getStatistics() { return { ...this.statistics, activeGates: this.activeGates.size, completedGates: this.gateResults.size, uptime: process.uptime(), memoryUsage: process.memoryUsage(), healthStatus: this._getHealthStatus() }; } /** * Get detailed quality report */ async generateQualityReport(options = {}) { return this.qualityReporter.generateReport({ statistics: this.getStatistics(), recentGates: Array.from(this.gateResults.values()).slice(-50), options }); } /** * Integration with DAG Workflow */ createWorkflowIntegration(workflow) { if (!this.config.dagWorkflowIntegration) { return null; } // Add quality gate node after each regular node const originalNodes = Array.from(workflow.nodes.values()); for (const node of originalNodes) { const qualityGateId = `quality-gate-${node.id}`; workflow.addNode({ id: qualityGateId, name: `Quality Gates for ${node.name}`, agent: 'quality-gates', dependencies: [node.id], prompt: `Apply quality gates to the output of ${node.name}`, condition: (context) => { // Only apply gates if the dependent node succeeded return context.dependencies[node.id]?.status === 'completed'; } }); // Update downstream nodes to depend on quality gate instead for (const [nodeId, nodeObj] of workflow.nodes) { if (nodeObj.dependencies.includes(node.id) && nodeId !== qualityGateId) { const depIndex = nodeObj.dependencies.indexOf(node.id); nodeObj.dependencies[depIndex] = qualityGateId; } } } return workflow; } // Private helper methods _generateGateId() { return `gate-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } _sanitizeArtifact(artifact) { // Return safe representation for logging return { type: artifact.type || 'unknown', size: typeof artifact.content === 'string' ? artifact.content.length : 'unknown', files: Array.isArray(artifact.files) ? artifact.files.length : 'unknown' }; } _updateGateStatus(gateId, tier, status) { const gate = this.activeGates.get(gateId); if (gate) { gate.currentTier = tier; gate.status = status; this.emit('gates:status', { gateId, tier, status }); } } _recordProcessingTime(gateId, startTime) { const processingTime = Date.now() - startTime; // Update average processing time const totalTime = this.statistics.averageProcessingTime * this.statistics.totalChecks + processingTime; this.statistics.averageProcessingTime = Math.round(totalTime / (this.statistics.totalChecks + 1)); // Check if within threshold if (processingTime > this.config.maxGateProcessingTime) { console.warn(`⚠️ Quality gate ${gateId} took ${processingTime}ms (threshold: ${this.config.maxGateProcessingTime}ms)`); this.emit('gates:slow', { gateId, processingTime, threshold: this.config.maxGateProcessingTime }); } } _updateSuccessRate() { const recentResults = Array.from(this.gateResults.values()).slice(-100); const successCount = recentResults.filter(r => r.success).length; this.statistics.successRate = recentResults.length > 0 ? successCount / recentResults.length : 0; } _isCriticalFailure(result) { return result.error && ( result.error.includes('security') || result.error.includes('critical') || result.error.includes('unsafe') ); } _triggerRollback(gateId, result) { console.log(`🚨 Triggering rollback for critical failure: ${gateId}`); this.rollbackTrigger.triggerRollback(gateId, result); this.emit('gates:rollback', { gateId, result }); } _getHealthStatus() { const stats = this.statistics; if (stats.successRate < 0.8) return 'unhealthy'; if (stats.averageProcessingTime > this.config.maxGateProcessingTime) return 'degraded'; if (stats.falsePositiveRate > this.config.falsePositiveThreshold) return 'degraded'; return 'healthy'; } _setupEventHandlers() { // Forward events from sub-components const components = [ this.tier1AutoFixer, this.tier2RetryEngine, this.tier3Approval, this.securityScanner, this.performanceValidator, this.testCoverageVerifier ]; components.forEach(component => { if (component && typeof component.on === 'function') { component.on('*', (eventName, data) => { this.emit(`component:${component.constructor.name.toLowerCase()}:${eventName}`, data); }); } }); } } module.exports = { QualityGatesSystem };