shipdeck
Version:
Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.
655 lines (545 loc) • 20.8 kB
JavaScript
/**
* Tier 2 Retry Engine with AI Feedback
*
* Handles 20 Tier 2 violations through intelligent retry with AI feedback:
* - Complex type issues
* - Performance patterns
* - Test coverage improvements
* - Architecture guidance
* - Pattern validation
*
* Improves quality 80% of the time through iterative AI assistance.
*/
const EventEmitter = require('events');
class TierTwoRetryEngine extends EventEmitter {
constructor(config = {}) {
super();
this.config = {
maxRetries: config.maxRetries || 3,
retryDelay: config.retryDelay || 1000,
aiModel: config.aiModel || 'claude-3-sonnet',
improvementThreshold: config.improvementThreshold || 0.8, // 80% success rate
contextualFeedback: config.contextualFeedback !== false,
learningEnabled: config.learningEnabled !== false,
...config
};
// AI feedback generators
this.feedbackGenerators = this._initializeFeedbackGenerators();
// Learning system for pattern recognition
this.patternLearning = {
successfulPatterns: new Map(),
commonFailures: new Map(),
improvementStrategies: new Map()
};
// Statistics
this.stats = {
totalRetries: 0,
successfulRetries: 0,
failedRetries: 0,
averageRetryCount: 0,
improvementRate: 0,
feedbackQuality: 0
};
}
/**
* Retry violations with AI feedback
*/
async retryWithFeedback(artifact, violations, context = {}) {
const startTime = Date.now();
console.log(`🔄 Retrying ${violations.length} Tier 2 violations with AI feedback`);
try {
let currentArtifact = this._cloneArtifact(artifact);
const retryResults = [];
const improvedViolations = [];
const remainingViolations = [];
let totalRetryCount = 0;
// Process violations in groups based on similarity
const violationGroups = this._groupSimilarViolations(violations);
for (const [groupType, groupViolations] of Object.entries(violationGroups)) {
console.log(` 🎯 Processing ${groupViolations.length} ${groupType} violations`);
const groupResult = await this._retryViolationGroup(
currentArtifact,
groupType,
groupViolations,
context
);
if (groupResult.success) {
currentArtifact = groupResult.improvedArtifact;
improvedViolations.push(...groupResult.improvedViolations);
retryResults.push(groupResult);
}
remainingViolations.push(...groupResult.remainingViolations);
totalRetryCount += groupResult.retryCount;
}
const processingTime = Date.now() - startTime;
const improvementCount = improvedViolations.length;
const successRate = violations.length > 0 ? improvementCount / violations.length : 0;
// Update statistics
this.stats.totalRetries += totalRetryCount;
this.stats.successfulRetries += improvementCount;
this.stats.failedRetries += remainingViolations.length;
this._updateAverageRetryCount(totalRetryCount);
this._updateImprovementRate(successRate);
console.log(`✅ Tier 2 retry complete: ${improvementCount}/${violations.length} improved (${Math.round(successRate * 100)}%) in ${processingTime}ms`);
this.emit('retry:completed', {
originalViolations: violations.length,
improvedCount: improvementCount,
remainingCount: remainingViolations.length,
successRate,
processingTime
});
return {
success: successRate >= this.config.improvementThreshold,
improvedArtifact: currentArtifact,
retryCount: totalRetryCount,
improvedCount: improvementCount,
remainingViolations,
retryResults,
successRate,
processingTime
};
} catch (error) {
console.error(`❌ Tier 2 retry failed: ${error.message}`);
this.emit('retry:failed', {
error: error.message,
violationsCount: violations.length
});
return {
success: false,
error: error.message,
improvedArtifact: artifact,
retryCount: 0,
improvedCount: 0,
remainingViolations: violations
};
}
}
/**
* Retry a group of similar violations
*/
async _retryViolationGroup(artifact, groupType, violations, context) {
let currentArtifact = artifact;
const improvedViolations = [];
const remainingViolations = [...violations];
let retryCount = 0;
for (let attempt = 1; attempt <= this.config.maxRetries && remainingViolations.length > 0; attempt++) {
retryCount++;
console.log(` 🔄 Attempt ${attempt}/${this.config.maxRetries} for ${remainingViolations.length} ${groupType} violations`);
try {
// Generate contextual feedback for current violations
const feedback = await this._generateFeedback(
currentArtifact,
remainingViolations,
context,
attempt
);
// Apply AI-guided improvements
const improvementResult = await this._applyAIGuidedImprovements(
currentArtifact,
remainingViolations,
feedback,
context
);
if (improvementResult.success) {
currentArtifact = improvementResult.improvedArtifact;
// Re-evaluate violations to see which ones were resolved
const stillViolating = await this._reevaluateViolations(
currentArtifact,
remainingViolations,
context
);
// Track which violations were improved
const resolvedViolations = remainingViolations.filter(
v => !stillViolating.some(sv => sv.rule.key === v.rule.key)
);
improvedViolations.push(...resolvedViolations);
remainingViolations.splice(0, remainingViolations.length, ...stillViolating);
console.log(` ✅ Resolved ${resolvedViolations.length} violations, ${stillViolating.length} remain`);
// If we made progress, record the successful pattern
if (resolvedViolations.length > 0) {
this._recordSuccessfulPattern(groupType, feedback, resolvedViolations);
}
} else {
console.log(` ⚠️ No improvement in attempt ${attempt}: ${improvementResult.error}`);
this._recordFailurePattern(groupType, feedback, improvementResult.error);
}
// Add delay before next retry
if (attempt < this.config.maxRetries && remainingViolations.length > 0) {
await new Promise(resolve => setTimeout(resolve, this.config.retryDelay));
}
} catch (error) {
console.warn(` ❌ Retry attempt ${attempt} failed: ${error.message}`);
}
}
return {
success: improvedViolations.length > 0,
improvedArtifact: currentArtifact,
improvedViolations,
remainingViolations,
retryCount
};
}
/**
* Generate contextual AI feedback for violations
*/
async _generateFeedback(artifact, violations, context, attemptNumber) {
const code = this._extractCode(artifact);
// Build comprehensive feedback context
const feedbackContext = {
code: this._sanitizeCodeForFeedback(code),
violations: violations.map(v => ({
rule: v.rule.name,
category: v.rule.category,
severity: v.rule.severity,
message: v.message,
suggestion: v.suggestion,
position: v.position
})),
context,
attemptNumber,
previousPatterns: this._getRelevantPatterns(violations),
codeContext: this._extractCodeContext(code, violations)
};
// Generate specific feedback based on violation types
const feedbackPrompts = violations.map(violation => {
const generator = this.feedbackGenerators[violation.rule.name] || this.feedbackGenerators.default;
return generator(feedbackContext, violation);
});
return {
general: await this._generateGeneralFeedback(feedbackContext),
specific: feedbackPrompts,
patterns: this._suggestPatterns(violations),
examples: this._getExamples(violations),
priority: this._prioritizeViolations(violations)
};
}
/**
* Apply AI-guided improvements to the artifact
*/
async _applyAIGuidedImprovements(artifact, violations, feedback, context) {
try {
// This would integrate with an actual AI service
// For now, we'll simulate intelligent improvements based on patterns
const improvementStrategy = this._selectImprovementStrategy(violations, feedback);
const result = await this._executeImprovementStrategy(
artifact,
violations,
improvementStrategy,
context
);
return result;
} catch (error) {
return {
success: false,
error: error.message,
improvedArtifact: artifact
};
}
}
/**
* Select improvement strategy based on violations and feedback
*/
_selectImprovementStrategy(violations, feedback) {
const violationTypes = violations.map(v => v.rule.category);
const primaryCategory = this._getMostCommonCategory(violationTypes);
// Strategy selection based on category and patterns
const strategies = {
typescript: 'type_safety_enhancement',
react: 'component_optimization',
nextjs: 'framework_compliance',
database: 'query_optimization',
security: 'security_hardening',
performance: 'performance_tuning',
testing: 'test_enhancement'
};
return strategies[primaryCategory] || 'general_improvement';
}
/**
* Execute the selected improvement strategy
*/
async _executeImprovementStrategy(artifact, violations, strategy, context) {
const code = this._extractCode(artifact);
let improvedCode = code;
try {
switch (strategy) {
case 'type_safety_enhancement':
improvedCode = await this._enhanceTypeSafety(code, violations, context);
break;
case 'component_optimization':
improvedCode = await this._optimizeReactComponents(code, violations, context);
break;
case 'framework_compliance':
improvedCode = await this._improveFrameworkCompliance(code, violations, context);
break;
case 'query_optimization':
improvedCode = await this._optimizeDatabaseQueries(code, violations, context);
break;
case 'security_hardening':
improvedCode = await this._hardenSecurity(code, violations, context);
break;
case 'performance_tuning':
improvedCode = await this._tunePerformance(code, violations, context);
break;
case 'test_enhancement':
improvedCode = await this._enhanceTests(code, violations, context);
break;
default:
improvedCode = await this._generalImprovements(code, violations, context);
}
return {
success: improvedCode !== code,
improvedArtifact: this._updateArtifactCode(artifact, improvedCode),
strategy
};
} catch (error) {
return {
success: false,
error: error.message,
improvedArtifact: artifact
};
}
}
// Strategy implementations
async _enhanceTypeSafety(code, violations, context) {
let improved = code;
for (const violation of violations) {
if (violation.rule.name === 'explicit-return-types') {
// Add return type annotations
improved = this._addReturnTypes(improved);
} else if (violation.rule.name === 'strict-null-checks') {
// Add null checks and optional chaining
improved = this._addNullChecks(improved);
}
}
return improved;
}
async _optimizeReactComponents(code, violations, context) {
let improved = code;
for (const violation of violations) {
if (violation.rule.name === 'memoize-expensive') {
improved = this._addMemoization(improved);
} else if (violation.rule.name === 'error-boundaries') {
improved = this._addErrorBoundaries(improved);
}
}
return improved;
}
async _improveFrameworkCompliance(code, violations, context) {
let improved = code;
for (const violation of violations) {
if (violation.rule.name === 'nextjs-15-async-params') {
improved = this._makeParamsAsync(improved);
} else if (violation.rule.name === 'metadata-api') {
improved = this._addMetadata(improved);
}
}
return improved;
}
// Implementation helpers for strategies
_addReturnTypes(code) {
// Add return type annotations to functions missing them
return code.replace(
/function\s+(\w+)\s*\([^)]*\)\s*\{/g,
'function $1(): any {' // This would be more intelligent in real implementation
);
}
_addNullChecks(code) {
// Add optional chaining where deep property access is found
return code.replace(
/(\w+)\.(\w+)\.(\w+)/g,
'$1?.$2?.$3'
);
}
_addMemoization(code) {
// Wrap expensive operations in useMemo
const expensiveOps = /\.(map|filter|reduce)\([^)]+\)\.(map|filter|reduce)/g;
return code.replace(expensiveOps, 'useMemo(() => $&, [dependency])');
}
_addErrorBoundaries(code) {
// This would add error boundary wrappers
return code; // Placeholder
}
_makeParamsAsync(code) {
// Make page component functions async
return code.replace(
/export\s+default\s+function\s+(\w+)\s*\(\s*\{\s*params\s*\}/g,
'export default async function $1({ params }'
);
}
_addMetadata(code) {
// Add metadata export
if (!code.includes('export const metadata') && !code.includes('export async function generateMetadata')) {
const metadataExport = `
export const metadata = {
title: 'Page Title',
description: 'Page description'
};
`;
return metadataExport + code;
}
return code;
}
async _optimizeDatabaseQueries(code, violations, context) {
return code; // Placeholder
}
async _hardenSecurity(code, violations, context) {
return code; // Placeholder
}
async _tunePerformance(code, violations, context) {
return code; // Placeholder
}
async _enhanceTests(code, violations, context) {
return code; // Placeholder
}
async _generalImprovements(code, violations, context) {
return code; // Placeholder
}
/**
* Re-evaluate violations after improvements
*/
async _reevaluateViolations(artifact, violations, context) {
const stillViolating = [];
const code = this._extractCode(artifact);
for (const violation of violations) {
try {
const ruleCheck = violation.rule.check;
if (ruleCheck && typeof ruleCheck === 'function') {
const newViolations = ruleCheck(code, context);
// If rule still triggers, keep the violation
if (newViolations && newViolations.length > 0) {
stillViolating.push(violation);
}
}
} catch (error) {
// If we can't re-evaluate, assume it's still violating
stillViolating.push(violation);
}
}
return stillViolating;
}
// Utility and helper methods
_groupSimilarViolations(violations) {
const groups = {};
violations.forEach(violation => {
const category = violation.rule.category || 'general';
if (!groups[category]) {
groups[category] = [];
}
groups[category].push(violation);
});
return groups;
}
_initializeFeedbackGenerators() {
return {
'explicit-return-types': (context, violation) => `Add explicit return type annotation for function at line ${violation.line}`,
'strict-null-checks': (context, violation) => `Add null safety check using optional chaining or conditional`,
'memoize-expensive': (context, violation) => `Wrap expensive computation in useMemo with appropriate dependencies`,
'error-boundaries': (context, violation) => `Add ErrorBoundary component or error.tsx file for error handling`,
'nextjs-15-async-params': (context, violation) => `Make page component async to handle params in Next.js 15`,
'metadata-api': (context, violation) => `Export metadata object or generateMetadata function for SEO`,
default: (context, violation) => `Address ${violation.rule.name}: ${violation.suggestion || violation.message}`
};
}
async _generateGeneralFeedback(context) {
return `Based on ${context.violations.length} violations, focus on: ${context.violations.map(v => v.category).join(', ')}`;
}
_suggestPatterns(violations) {
return violations.map(v => `Consider ${v.rule.category} best practices`);
}
_getExamples(violations) {
return violations.map(v => `Example fix for ${v.rule.name}`);
}
_prioritizeViolations(violations) {
return violations.sort((a, b) => {
const severityOrder = { critical: 4, error: 3, warning: 2, info: 1 };
return (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0);
});
}
_extractCodeContext(code, violations) {
return violations.map(v => {
if (v.line) {
const lines = code.split('\n');
const start = Math.max(0, v.line - 3);
const end = Math.min(lines.length, v.line + 3);
return lines.slice(start, end).join('\n');
}
return '';
});
}
_sanitizeCodeForFeedback(code) {
return code.substring(0, 1000); // Limit code length for feedback
}
_getRelevantPatterns(violations) {
const categories = violations.map(v => v.rule.category);
return Array.from(this.patternLearning.successfulPatterns.entries())
.filter(([category]) => categories.includes(category));
}
_getMostCommonCategory(categories) {
const counts = {};
categories.forEach(cat => counts[cat] = (counts[cat] || 0) + 1);
return Object.keys(counts).reduce((a, b) => counts[a] > counts[b] ? a : b);
}
_recordSuccessfulPattern(groupType, feedback, resolvedViolations) {
if (!this.patternLearning.successfulPatterns.has(groupType)) {
this.patternLearning.successfulPatterns.set(groupType, []);
}
this.patternLearning.successfulPatterns.get(groupType).push({
feedback,
resolvedViolations: resolvedViolations.map(v => v.rule.name),
timestamp: Date.now()
});
}
_recordFailurePattern(groupType, feedback, error) {
if (!this.patternLearning.commonFailures.has(groupType)) {
this.patternLearning.commonFailures.set(groupType, []);
}
this.patternLearning.commonFailures.get(groupType).push({
feedback,
error,
timestamp: Date.now()
});
}
_extractCode(artifact) {
if (typeof artifact === 'string') return artifact;
if (artifact.content) return artifact.content;
if (artifact.files?.[0]?.content) return artifact.files[0].content;
return '';
}
_updateArtifactCode(artifact, newCode) {
if (typeof artifact === 'string') return newCode;
const updated = { ...artifact };
if (artifact.content) {
updated.content = newCode;
} else if (artifact.files?.[0]) {
updated.files = [...artifact.files];
updated.files[0] = { ...updated.files[0], content: newCode };
}
return updated;
}
_cloneArtifact(artifact) {
if (typeof artifact === 'string') return artifact;
return JSON.parse(JSON.stringify(artifact));
}
_updateAverageRetryCount(newCount) {
const totalCalls = Math.max(1, this.stats.totalRetries / newCount);
const currentTotal = this.stats.averageRetryCount * (totalCalls - 1);
this.stats.averageRetryCount = (currentTotal + newCount) / totalCalls;
}
_updateImprovementRate(newRate) {
const alpha = 0.1; // Exponential moving average
this.stats.improvementRate = alpha * newRate + (1 - alpha) * this.stats.improvementRate;
}
getStatistics() {
return {
...this.stats,
learnedPatterns: this.patternLearning.successfulPatterns.size,
commonFailures: this.patternLearning.commonFailures.size,
config: {
maxRetries: this.config.maxRetries,
improvementThreshold: this.config.improvementThreshold,
aiModel: this.config.aiModel
}
};
}
}
module.exports = { TierTwoRetryEngine };