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
JavaScript
/**
* 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 };