UNPKG

design-agent

Version:

Universal AI Design Review Agent - CLI tool for scanning code for design drift

281 lines (221 loc) 7.33 kB
/** * GitHub suggested changes utility for PR comments * Generates GitHub "suggested changes" markdown snippets for trivial fixes */ export function generateGitHubSuggestions(findings) { const suggestions = []; for (const finding of findings) { if (finding.autofix && finding.autofix.before && finding.autofix.after) { const suggestion = generateSuggestionSnippet(finding); if (suggestion) { suggestions.push(suggestion); } } } return suggestions; } export function generateSuggestionSnippet(finding) { const { file, line, autofix, kind, severity } = finding; // Only generate suggestions for high-confidence autofixes if (autofix.confidence < 80) return null; // Only suggest for certain types of findings const suggestableTypes = [ 'tokenDriftColor', 'spacingRawPx', 'inlineStyle', 'utilities', 'storybookProps' ]; if (!suggestableTypes.includes(kind)) return null; // Generate the suggestion markdown const suggestion = { file, line, kind, severity, confidence: autofix.confidence, description: autofix.description, before: autofix.before, after: autofix.after, markdown: generateMarkdownSuggestion(file, line, autofix, finding) }; return suggestion; } function generateMarkdownSuggestion(file, line, autofix, finding) { const { before, after, description } = autofix; const { kind, severity } = finding; // Generate GitHub suggestion markdown const suggestion = `\`\`\`suggestion ${after} \`\`\` **${description}** - **Type:** ${kind} - **Severity:** ${severity} - **Confidence:** ${autofix.confidence}%`; return suggestion; } export function generatePRComment(findings, options = {}) { const { includeSuggestions = true, maxSuggestions = 10, groupByFile = true } = options; let comment = '## 🤖 Design Review Agent\n\n'; // Summary const summary = generateSummary(findings); comment += summary + '\n\n'; // Suggestions if (includeSuggestions) { const suggestions = generateGitHubSuggestions(findings); if (suggestions.length > 0) { comment += '## 💡 Suggested Changes\n\n'; if (groupByFile) { comment += generateGroupedSuggestions(suggestions.slice(0, maxSuggestions)); } else { comment += generateFlatSuggestions(suggestions.slice(0, maxSuggestions)); } } } // Additional findings const nonSuggestableFindings = findings.filter(f => !f.autofix || f.autofix.confidence < 80 ); if (nonSuggestableFindings.length > 0) { comment += '\n## 📋 Additional Findings\n\n'; comment += generateFindingsList(nonSuggestableFindings); } return comment; } function generateSummary(findings) { const total = findings.length; const critical = findings.filter(f => f.severity === 'critical').length; const major = findings.filter(f => f.severity === 'major').length; const minor = findings.filter(f => f.severity === 'minor').length; const suggestions = findings.filter(f => f.autofix && f.autofix.confidence >= 80).length; let summary = `Found **${total}** design issues:\n`; summary += `- 🔴 **${critical}** critical\n`; summary += `- 🟡 **${major}** major\n`; summary += `- 🟢 **${minor}** minor\n`; if (suggestions > 0) { summary += `- 💡 **${suggestions}** with suggested fixes`; } return summary; } function generateGroupedSuggestions(suggestions) { const grouped = {}; for (const suggestion of suggestions) { if (!grouped[suggestion.file]) { grouped[suggestion.file] = []; } grouped[suggestion.file].push(suggestion); } let markdown = ''; for (const [file, fileSuggestions] of Object.entries(grouped)) { markdown += `### 📁 ${file}\n\n`; for (const suggestion of fileSuggestions) { markdown += `**Line ${suggestion.line}:** ${suggestion.description}\n\n`; markdown += suggestion.markdown + '\n\n'; } } return markdown; } function generateFlatSuggestions(suggestions) { let markdown = ''; for (const suggestion of suggestions) { markdown += `### ${suggestion.file}:${suggestion.line}\n\n`; markdown += suggestion.markdown + '\n\n'; } return markdown; } function generateFindingsList(findings) { let markdown = ''; for (const finding of findings) { const emoji = getSeverityEmoji(finding.severity); markdown += `${emoji} **${finding.kind}** - ${finding.msg}\n`; markdown += ` *${finding.file}:${finding.line}*\n\n`; } return markdown; } function getSeverityEmoji(severity) { switch (severity) { case 'critical': return '🔴'; case 'major': return '🟡'; case 'minor': return '🟢'; default: return '⚪'; } } export function generateInlineComment(finding) { if (!finding.autofix || finding.autofix.confidence < 80) { return null; } const { autofix } = finding; return { path: finding.file, line: finding.line, body: `**${autofix.description}** \`\`\`suggestion ${autofix.after} \`\`\` Confidence: ${autofix.confidence}%`, start_line: finding.line, start_side: 'RIGHT' }; } export function generateReviewComment(findings, options = {}) { const { includeSuggestions = true, maxSuggestions = 5 } = options; const suggestions = generateGitHubSuggestions(findings); const comments = []; for (const suggestion of suggestions.slice(0, maxSuggestions)) { const comment = generateInlineComment({ ...suggestion, autofix: { description: suggestion.description, confidence: suggestion.confidence, after: suggestion.after } }); if (comment) { comments.push(comment); } } return comments; } export function generateCommitMessage(findings) { const critical = findings.filter(f => f.severity === 'critical').length; const major = findings.filter(f => f.severity === 'major').length; const minor = findings.filter(f => f.severity === 'minor').length; let message = 'fix(design): '; if (critical > 0) { message += `fix ${critical} critical design issues`; } else if (major > 0) { message += `fix ${major} major design issues`; } else if (minor > 0) { message += `fix ${minor} minor design issues`; } else { message += 'address design findings'; } return message; } export function generateChangelogEntry(findings) { const suggestions = generateGitHubSuggestions(findings); const critical = findings.filter(f => f.severity === 'critical').length; const major = findings.filter(f => f.severity === 'major').length; const minor = findings.filter(f => f.severity === 'minor').length; let entry = '## Design Review Results\n\n'; entry += `- **${critical}** critical issues found\n`; entry += `- **${major}** major issues found\n`; entry += `- **${minor}** minor issues found\n`; if (suggestions.length > 0) { entry += `- **${suggestions.length}** suggested fixes available\n`; } if (suggestions.length > 0) { entry += '\n### Suggested Fixes\n\n'; for (const suggestion of suggestions.slice(0, 5)) { entry += `- ${suggestion.description} (${suggestion.file}:${suggestion.line})\n`; } } return entry; }