pr-vibe
Version:
AI-powered PR review responder that vibes with CodeRabbit, DeepSource, and other bots to automate repetitive feedback
375 lines (331 loc) • 12.1 kB
JavaScript
/**
* Decision Engine - Core logic for analyzing PR comments
*/
// Pattern definitions for different issue types
const PATTERNS = {
SECURITY: {
CRITICAL: [
/hardcoded.*(api|secret|key|token|password)/i,
/sql\s*injection/i,
/exposed?\s*(credentials?|secrets?)/i,
/plain\s*text\s*password/i
],
HIGH: [
/missing\s*(auth|validation|sanitization)/i,
/cors\s*wildcard|\*/i,
/eval\(|new Function\(/
]
},
VALID_PATTERNS: [
{
pattern: /console\.(log|error|warn).*lambda/i,
reason: 'Console logging is valid for CloudWatch in Lambda functions'
},
{
pattern: /any.*type.*(stripe|webhook|external\s*api)/i,
reason: 'Using any type is acceptable for complex external API payloads'
},
{
pattern: /hardcoded.*(table|bucket).*name/i,
condition: (comment) => comment.path?.includes('infrastructure') || comment.path?.includes('cdk'),
reason: 'Hardcoded resource names are valid in infrastructure code'
}
]
};
export function analyzeComment(comment) {
const body = comment.body.toLowerCase();
const path = comment.path || '';
// If comment is from Claude Code with high confidence, use its analysis
if (comment.author && /claude.*code|claude\[bot\]/i.test(comment.author) && comment.claudeAnalysis) {
const { category, priority, confidence } = comment.claudeAnalysis;
// Map Claude's categories to our actions
const actionMap = {
'MUST_FIX': 'AUTO_FIX',
'SUGGESTION': 'DEFER',
'NITPICK': 'NIT'
};
return {
action: actionMap[category] || 'DISCUSS',
reason: 'Claude Code has already analyzed this issue',
explanation: `Claude Code (${confidence}% confidence): ${comment.body}`,
confidence: confidence / 100 || 0.85,
priority: priority || getPriorityLevel(category),
category: category || 'GENERAL',
skipLLMAnalysis: true // Skip re-analyzing with LLM
};
}
// Check for valid patterns first
for (const validPattern of PATTERNS.VALID_PATTERNS) {
if (validPattern.pattern.test(body)) {
if (!validPattern.condition || validPattern.condition(comment)) {
return {
action: 'REJECT',
reason: validPattern.reason,
explanation: `This is a valid pattern in our codebase. ${validPattern.reason}`,
confidence: 0.95,
priority: PRIORITY_LEVELS.SUGGESTION,
category: 'PATTERN_MATCH'
};
}
}
}
// Check for security issues
for (const pattern of PATTERNS.SECURITY.CRITICAL) {
if (pattern.test(body)) {
const suggestedFix = generateSecurityFix(body, path);
return {
action: suggestedFix ? 'AUTO_FIX' : 'ESCALATE',
severity: 'CRITICAL',
reason: suggestedFix
? 'Critical security vulnerability must be fixed immediately'
: 'Critical security issue requires human review',
suggestedFix: suggestedFix,
confidence: suggestedFix ? 0.90 : 0.95,
priority: PRIORITY_LEVELS.MUST_FIX,
category: 'SECURITY'
};
}
}
// Check for high priority issues
for (const pattern of PATTERNS.SECURITY.HIGH) {
if (pattern.test(body)) {
const suggestedFix = generateSecurityFix(body, path);
return {
action: suggestedFix ? 'AUTO_FIX' : 'ESCALATE',
severity: 'HIGH',
reason: suggestedFix
? 'Security issue that should be fixed'
: 'Security issue requires human review',
suggestedFix: suggestedFix,
confidence: suggestedFix ? 0.85 : 0.90,
priority: PRIORITY_LEVELS.MUST_FIX,
category: 'SECURITY'
};
}
}
// Check for architectural discussions
if (/refactor|restructur|architect|design\s*pattern/i.test(body)) {
return {
action: 'DISCUSS',
reason: 'Architectural changes require discussion',
suggestedReply: 'Thanks for the suggestion. Could you provide more context on the benefits of this approach for our use case?',
confidence: 0.70,
priority: PRIORITY_LEVELS.SUGGESTION,
category: 'ARCHITECTURE'
};
}
// Check for future work/enhancement suggestions
if (/future|enhancement|nice\s*to\s*have|consider|roadmap|later|eventually|long[\s-]?term/i.test(body)) {
return {
action: 'DEFER',
reason: 'Enhancement suggestion for future consideration',
suggestedReply: 'Thanks for the suggestion! This is a good idea for future improvement. I\'ll create a tracking issue for this.',
confidence: 0.75,
severity: 'LOW',
priority: PRIORITY_LEVELS.SUGGESTION,
category: 'ENHANCEMENT'
};
}
// Check for non-critical improvements
if (/could\s*be|might\s*want|consider|suggestion|improvement|optimize|enhance/i.test(body)) {
// If it's not a security or critical issue, defer it
if (!/(security|critical|vulnerability|risk)/i.test(body)) {
return {
action: 'DEFER',
reason: 'Non-critical improvement suggestion',
suggestedReply: 'Good suggestion! I\'ll track this for future improvement.',
confidence: 0.65,
severity: 'LOW',
priority: PRIORITY_LEVELS.SUGGESTION,
category: 'IMPROVEMENT'
};
}
}
// Check if this is marked as a nit comment
if (comment.isNit) {
return {
action: 'NIT',
reason: 'Minor style/cosmetic suggestion',
suggestedReply: 'Thanks for the suggestion. We\'ll consider this for future improvements.',
confidence: 0.80,
severity: 'LOW',
priority: PRIORITY_LEVELS.NITPICK,
category: 'NITPICK'
};
}
// Default to discussion for unclear items
return {
action: 'DISCUSS',
reason: 'Need more context to make a decision',
suggestedReply: 'Could you clarify the preferred approach here?',
confidence: 0.50,
priority: PRIORITY_LEVELS.SUGGESTION,
category: 'GENERAL'
};
}
// Priority levels for categorization
export const PRIORITY_LEVELS = {
MUST_FIX: 'must-fix',
SUGGESTION: 'suggestion',
NITPICK: 'nitpick'
};
// Map categories to priority levels
export function getPriorityLevel(category) {
const priorityMap = {
'SECURITY': PRIORITY_LEVELS.MUST_FIX,
'CRITICAL': PRIORITY_LEVELS.MUST_FIX,
'BREAKING': PRIORITY_LEVELS.MUST_FIX,
'BUG': PRIORITY_LEVELS.MUST_FIX,
'PERFORMANCE': PRIORITY_LEVELS.SUGGESTION,
'CODE_QUALITY': PRIORITY_LEVELS.SUGGESTION,
'TYPE_SAFETY': PRIORITY_LEVELS.SUGGESTION,
'STYLE': PRIORITY_LEVELS.NITPICK,
'DEBUG': PRIORITY_LEVELS.NITPICK,
'NITPICK': PRIORITY_LEVELS.NITPICK,
'GENERAL': PRIORITY_LEVELS.SUGGESTION
};
return priorityMap[category] || PRIORITY_LEVELS.SUGGESTION;
}
export function categorizeIssue(description, isNit = false) {
const desc = description.toLowerCase();
// If marked as nit, categorize appropriately
if (isNit) {
return 'NITPICK';
}
// Check for style/formatting issues FIRST (before security)
if (/type-only\s*import|prefer\s*type\s*import|import\s*type/i.test(desc)) {
return 'STYLE';
}
if (/console\.(log|error|warn|debug)|remove\s*console|debug\s*(statement|logging)/i.test(desc)) {
return 'DEBUG';
}
if (/empty\s*(catch|block)|catch\s*block.*empty/i.test(desc)) {
return 'CODE_QUALITY';
}
if (/\b(formatting|indent|indentation|spacing|semicolon|quotes|lint|eslint|prettier)\b/i.test(desc)) {
return 'STYLE';
}
// Check for nit patterns before checking security
if (/\bnit\b|minor|trivial|style|cosmetic|consider|suggestion/i.test(desc)) {
return 'NITPICK';
}
// NOW check for actual security issues (with more specific patterns)
if (/hardcoded.*(api|secret|key|token|password)/i.test(desc) ||
/exposed?\s*(credentials?|secrets?|api\s*key|token)/i.test(desc) ||
/sql\s*injection|xss|cross-site|vulnerability|security\s*(risk|issue|vulnerability)/i.test(desc) ||
/plain\s*text\s*password|unencrypted\s*password/i.test(desc)) {
return 'SECURITY';
}
// Check for auth issues separately (to avoid false positives from file paths)
if (/missing\s*(auth|authentication|authorization)/i.test(desc) ||
/broken\s*(auth|authentication)/i.test(desc) ||
/bypass\s*(auth|authentication)/i.test(desc)) {
return 'SECURITY';
}
if (/performance|n\+1|slow|inefficient|optimize/i.test(desc)) {
return 'PERFORMANCE';
}
if (/unused|complex|refactor|clean|quality/i.test(desc)) {
return 'CODE_QUALITY';
}
if (/type|typescript|any/i.test(desc)) {
return 'TYPE_SAFETY';
}
return 'GENERAL';
}
function generateSecurityFix(issue, path) {
if (/hardcoded.*(api|key|token)/i.test(issue)) {
return `Move to environment variable:
const API_KEY = process.env.API_KEY;
if (!API_KEY) {
throw new Error('API_KEY environment variable is required');
}`;
}
if (/sql\s*injection/i.test(issue)) {
return `Use parameterized queries:
// Instead of: query = \`SELECT * FROM users WHERE id = \${userId}\`
const query = 'SELECT * FROM users WHERE id = ?';
db.query(query, [userId], callback);`;
}
if (/missing\s*validation/i.test(issue)) {
return `Add input validation:
if (!input || typeof input !== 'string') {
return res.status(400).json({ error: 'Invalid input' });
}
// Sanitize input
const sanitized = input.trim().replace(/[^a-zA-Z0-9]/g, '');`;
}
// Return null to indicate no auto-fix available
// This prevents replacing working code with TODO placeholders
return null;
}
// Helper for batch analysis
export function analyzeMultipleComments(comments) {
return comments.map(comment => ({
comment,
analysis: analyzeComment(comment),
category: categorizeIssue(comment.body, comment.isNit)
}));
}
// Generate appropriate commit message based on category
export function generateCommitMessageForCategory(category, description) {
const categoryMessages = {
'STYLE': 'style: Apply code formatting and style improvements',
'DEBUG': 'chore: Remove debug statements and console logs',
'CODE_QUALITY': 'refactor: Improve code quality and maintainability',
'TYPE_SAFETY': 'types: Fix TypeScript type issues',
'PERFORMANCE': 'perf: Apply performance optimizations',
'SECURITY': 'security: Fix security vulnerabilities',
'NITPICK': 'style: Apply minor style suggestions',
'GENERAL': 'fix: Apply code review feedback'
};
return categoryMessages[category] || categoryMessages.GENERAL;
}
// Generate accurate description based on category and issue type
export function generateAccurateDescription(category, issueDescription) {
// Extract key information from the issue description
const desc = issueDescription.toLowerCase();
if (category === 'STYLE') {
if (desc.includes('type-only') || desc.includes('import type')) {
return 'Convert to type-only imports for better tree-shaking';
}
if (desc.includes('formatting') || desc.includes('indent')) {
return 'Fix code formatting and indentation';
}
return 'Apply style improvements';
}
if (category === 'DEBUG') {
if (desc.includes('console.log')) {
return 'Remove console.log statements';
}
if (desc.includes('debug')) {
return 'Remove debug code';
}
return 'Clean up debug statements';
}
if (category === 'CODE_QUALITY') {
if (desc.includes('empty catch')) {
return 'Add error handling or comments to empty catch blocks';
}
if (desc.includes('unused')) {
return 'Remove unused code';
}
if (desc.includes('complex')) {
return 'Simplify complex code';
}
return 'Improve code quality';
}
if (category === 'SECURITY') {
if (desc.includes('api key') || desc.includes('token')) {
return 'Move sensitive credentials to environment variables';
}
if (desc.includes('sql injection')) {
return 'Fix SQL injection vulnerability';
}
if (desc.includes('xss')) {
return 'Fix XSS vulnerability';
}
return 'Fix security vulnerability';
}
return issueDescription;
}