woaru
Version:
Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language
1,146 lines • 54 kB
JavaScript
import * as path from 'path';
import fs from 'fs-extra';
import { FilenameHelper } from '../utils/filenameHelper.js';
import { t, initializeI18n } from '../config/i18n.js';
/**
* Security constants for input validation
*/
const SECURITY_LIMITS = {
MAX_FILE_PATH_LENGTH: 500,
MAX_BRANCH_NAME_LENGTH: 200,
MAX_COMMIT_MESSAGE_LENGTH: 1000,
MAX_ERROR_MESSAGE_LENGTH: 500,
MAX_FINDINGS_PER_REPORT: 10000,
};
/**
* Sanitizes branch names to prevent injection attacks
* @param branchName - The branch name to sanitize
* @returns Sanitized branch name safe for display
*/
function sanitizeBranchName(branchName) {
if (typeof branchName !== 'string') {
return 'unknown-branch';
}
return (branchName
.replace(/[^a-zA-Z0-9/_.-]/g, '')
.substring(0, SECURITY_LIMITS.MAX_BRANCH_NAME_LENGTH) || 'unknown-branch');
}
/**
* Sanitizes file paths to prevent information leakage
* @param filePath - The file path to sanitize
* @returns Sanitized file path safe for display
*/
function sanitizeFilePath(filePath) {
if (typeof filePath !== 'string') {
return 'unknown-file';
}
const baseName = path.basename(filePath);
return (baseName.replace(/[^a-zA-Z0-9._-]/g, '').substring(0, 100) || 'unknown-file');
}
/**
* Sanitizes commit messages to prevent XSS
* @param message - The commit message to sanitize
* @returns Sanitized commit message safe for display
*/
function sanitizeCommitMessage(message) {
if (typeof message !== 'string') {
return t('report_generator.invalid_commit_message');
}
return message
.replace(/[<>"'&]/g, '')
.substring(0, SECURITY_LIMITS.MAX_COMMIT_MESSAGE_LENGTH);
}
/**
* Sanitizes numeric values to prevent injection
* @param value - The numeric value to sanitize
* @returns Safe numeric value or 0
*/
function sanitizeNumeric(value) {
if (typeof value === 'number' && !isNaN(value) && isFinite(value)) {
return Math.max(0, Math.min(value, 999999));
}
return 0;
}
// Explain-for-humans: This class takes all the code analysis results from WOARU and converts them into readable reports in both Markdown and JSON formats, with standardized file naming.
export class ReviewReportGenerator {
reportMetrics = {
reportsGenerated: 0,
totalFindings: 0,
securityIssuesFound: 0,
averageGenerationTime: 0,
};
/**
* Generate markdown report with standardized filename
* @param data - Review report data containing all analysis results
* @param outputPath - Optional output path, will use standardized naming if not provided
* @returns The path where the report was saved
*/
/**
* Generate markdown report with comprehensive security validation
* @param data - Review report data containing all analysis results
* @param outputPath - Optional output path, will use standardized naming if not provided
* @returns The path where the report was saved
*/
async generateMarkdownReport(data, outputPath) {
const startTime = Date.now();
try {
await initializeI18n();
// Validate input data
if (!this.validateReportData(data)) {
throw new Error(t('report_generator.error_invalid_data'));
}
const markdown = await this.buildMarkdownReport(data);
if (!outputPath) {
const commandType = sanitizeFilePath(data.context?.type || 'review');
const filename = FilenameHelper.generateReportFilename(commandType);
outputPath = path.join(process.cwd(), '.woaru', 'reports', filename);
}
// Security check for output path
if (!this.isSecureOutputPath(outputPath)) {
throw new Error(t('report_generator.error_unsafe_path'));
}
await fs.ensureDir(path.dirname(outputPath));
await fs.writeFile(outputPath, markdown, 'utf8');
// Update metrics
this.updateMetrics(data, Date.now() - startTime);
return outputPath;
}
catch (error) {
console.error(t('report_generator.error_generating_markdown'), this.sanitizeError(error));
throw error;
}
}
/**
* Generate JSON report for programmatic consumption
* @param data - Review report data containing all analysis results
* @returns JSON string representation of the report
*/
/**
* Generate JSON report for programmatic consumption with security validation
* @param data - Review report data containing all analysis results
* @returns JSON string representation of the report
*/
generateJsonReport(data) {
try {
if (!this.validateReportData(data)) {
throw new Error(t('report_generator.invalid_report_data'));
}
const securitySummary = this.getSecuritySummaryFromResults(data.securityResults || []);
const result = {
summary: {
reviewType: data.context?.type || 'git',
description: data.context?.description || '',
baseBranch: data.gitDiff.baseBranch,
currentBranch: data.currentBranch,
totalChangedFiles: data.gitDiff.totalChanges,
totalQualityIssues: data.qualityResults.length,
totalSecurityIssues: securitySummary.total,
criticalVulnerabilities: securitySummary.critical,
highVulnerabilities: securitySummary.high,
totalProductionIssues: data.productionAudits.length,
commits: data.commits.length,
},
changedFiles: data.gitDiff.changedFiles.map(file => path.basename(file)),
qualityIssues: data.qualityResults,
securityFindings: this.flattenSecurityResults(data.securityResults || []),
productionAudits: data.productionAudits,
commits: data.commits,
};
// Add AI review data if available
if (data.aiReviewResults) {
const resultSummary = result.summary;
resultSummary.aiReviewEnabled = true;
const aiResults = data.aiReviewResults;
const aiSummary = 'summary' in aiResults
? aiResults.summary
: undefined;
resultSummary.aiFilesAnalyzed = aiSummary?.filesAnalyzed || 0;
resultSummary.aiTotalFindings = aiSummary?.totalFindings || 0;
resultSummary.aiEstimatedCost = aiSummary?.estimatedCost || 0;
result.aiReview = data.aiReviewResults;
}
else {
result.summary.aiReviewEnabled = false;
}
return JSON.stringify(result, null, 2);
}
catch (error) {
console.error('Error generating JSON report:', this.sanitizeError(error));
return JSON.stringify({ error: t('report_generator.failed_to_generate') }, null, 2);
}
}
/**
* Validate report data for security and completeness
*/
validateReportData(data) {
if (!data || typeof data !== 'object') {
return false;
}
// Validate required fields
if (!data.gitDiff ||
!Array.isArray(data.qualityResults) ||
!Array.isArray(data.productionAudits)) {
return false;
}
// Validate array sizes to prevent DoS
if (data.qualityResults.length > SECURITY_LIMITS.MAX_FINDINGS_PER_REPORT ||
data.productionAudits.length > SECURITY_LIMITS.MAX_FINDINGS_PER_REPORT) {
return false;
}
return true;
}
/**
* Check if output path is secure
*/
isSecureOutputPath(outputPath) {
try {
const normalized = path.normalize(outputPath);
// Check for directory traversal
if (normalized.includes('..') || normalized.includes('\x00')) {
return false;
}
// Check path length
if (normalized.length > SECURITY_LIMITS.MAX_FILE_PATH_LENGTH) {
return false;
}
return true;
}
catch {
return false;
}
}
/**
* Sanitize error messages for security
*/
sanitizeError(error) {
if (typeof error === 'string') {
return error
.replace(/\/[^\s]*\/[^\s]*/g, '[PATH]')
.substring(0, SECURITY_LIMITS.MAX_ERROR_MESSAGE_LENGTH);
}
if (error instanceof Error) {
return this.sanitizeError(error.message);
}
return 'Unknown report generation error';
}
/**
* Update report generation metrics
*/
updateMetrics(data, duration) {
this.reportMetrics.reportsGenerated++;
this.reportMetrics.totalFindings +=
data.qualityResults.length + data.productionAudits.length;
if (data.securityResults) {
this.reportMetrics.securityIssuesFound += data.securityResults.reduce((sum, result) => sum + (result.findings?.length || 0), 0);
}
this.reportMetrics.averageGenerationTime =
(this.reportMetrics.averageGenerationTime + duration) / 2;
}
/**
* Sanitize audit messages for security
*/
sanitizeAuditMessage(message) {
if (typeof message !== 'string') {
return t('report_generator.invalid_audit_message');
}
return message
.replace(/[<>"'&]/g, '')
.substring(0, SECURITY_LIMITS.MAX_ERROR_MESSAGE_LENGTH);
}
/**
* Get report generation metrics
*/
getReportMetrics() {
return { ...this.reportMetrics };
}
/**
* Reset report generation metrics
*/
resetMetrics() {
this.reportMetrics = {
reportsGenerated: 0,
totalFindings: 0,
securityIssuesFound: 0,
averageGenerationTime: 0,
};
}
/**
* Build markdown report with comprehensive security validation
*/
async buildMarkdownReport(data) {
await initializeI18n();
const lines = [];
const securitySummary = this.getSecuritySummaryFromResults(data.securityResults || []);
// Header
lines.push('# WOARU Code Review');
lines.push(t('report_generator.summary.changes_since_branch', {
branch: sanitizeBranchName(data.gitDiff.baseBranch),
}));
lines.push(t('report_generator.summary.current_branch', {
branch: sanitizeBranchName(data.currentBranch),
}));
lines.push(t('report_generator.summary.generated_at', {
date: new Date().toLocaleString('de-DE'),
}));
lines.push('');
// Security Issues FIRST (if any critical/high issues exist)
if (securitySummary.critical > 0 || securitySummary.high > 0) {
lines.push(t('report_generator.headers.critical_security_issues'));
lines.push('');
lines.push(t('report_generator.summary.security_warnings', {
critical: sanitizeNumeric(securitySummary.critical),
high: sanitizeNumeric(securitySummary.high),
}));
lines.push('');
this.addCriticalSecuritySection(lines, data.securityResults || []);
lines.push('');
}
// Summary
lines.push(t('report_generator.headers.summary'));
lines.push('');
lines.push(t('report_generator.summary.changed_files', {
count: sanitizeNumeric(data.gitDiff.totalChanges),
}));
lines.push(t('report_generator.summary.quality_problems', {
count: sanitizeNumeric(data.qualityResults.length),
}));
lines.push(t('report_generator.summary.security_problems', {
total: sanitizeNumeric(securitySummary.total),
critical: sanitizeNumeric(securitySummary.critical),
high: sanitizeNumeric(securitySummary.high),
}));
lines.push(t('report_generator.summary.production_recommendations', {
count: sanitizeNumeric(data.productionAudits.length),
}));
const aiResults = data.aiReviewResults;
if (aiResults && aiResults.summary) {
const aiSummary = aiResults.summary;
lines.push(t('report_generator.summary.ai_review', {
filesAnalyzed: sanitizeNumeric(aiSummary.filesAnalyzed),
totalFindings: sanitizeNumeric(aiSummary.totalFindings),
}));
if (sanitizeNumeric(aiSummary.estimatedCost) > 0) {
lines.push(t('report_generator.summary.ai_cost', {
cost: sanitizeNumeric(aiSummary.estimatedCost).toFixed(4),
}));
}
}
lines.push(t('report_generator.summary.commits', {
count: sanitizeNumeric(data.commits.length),
}));
lines.push('');
// Changed Files
if (data.gitDiff.changedFiles && data.gitDiff.changedFiles.length > 0) {
lines.push(`## ${t('report_generator.headers.changed_files')}`);
lines.push('');
data.gitDiff.changedFiles.slice(0, 100).forEach(file => {
const sanitizedPath = sanitizeFilePath(file);
lines.push(`- \`${sanitizedPath}\``);
});
if (data.gitDiff.changedFiles.length > 100) {
lines.push(t('report_generator.summary.more_files', {
count: data.gitDiff.changedFiles.length - 100,
}));
}
lines.push('');
}
// Security Issues (all, not just critical/high)
if (data.securityResults &&
data.securityResults.length > 0 &&
securitySummary.total > 0) {
lines.push(t('report_generator.headers.all_security_findings'));
lines.push('');
this.addAllSecuritySection(lines, data.securityResults);
}
// Quality Issues
if (data.qualityResults.length > 0) {
lines.push(`## ${t('report_generator.headers.critical_quality_issues')}`);
lines.push('');
// Group by file
const issuesByFile = this.groupQualityIssuesByFile(data.qualityResults);
Object.entries(issuesByFile).forEach(([file, results]) => {
lines.push(`### \`${file}\``);
lines.push('');
results.forEach(result => {
lines.push(`**${result.tool} - ${this.getSeverityEmoji(result.severity)} ${result.severity.toUpperCase()}:**`);
lines.push('');
// Add explanation if available
if (result.explanation) {
lines.push(t('report_generator.problem_label', {
explanation: result.explanation,
}));
lines.push('');
}
// List all issues with better formatting
lines.push(t('report_generator.found_problems'));
result.issues.forEach((issue, index) => {
lines.push(`${index + 1}. ${issue}`);
});
lines.push('');
// Add fixes if available
if (result.fixes && result.fixes.length > 0) {
lines.push(t('report_generator.headers.solution_suggestions'));
result.fixes.forEach((fix, index) => {
lines.push(`${index + 1}. ${fix}`);
});
lines.push('');
}
// Add code examples or context if available in raw_output
if (result.raw_output && this.shouldShowCodeContext(result)) {
lines.push(t('report_generator.status.code_context'));
lines.push('```');
lines.push(this.extractRelevantCodeContext(result.raw_output));
lines.push('```');
lines.push('');
}
lines.push('---');
lines.push('');
});
});
}
// SOLID Architecture Analysis
this.addSOLIDAnalysisSection(lines, data.qualityResults);
// Code Smell Analysis
this.addCodeSmellAnalysisSection(lines, data.qualityResults);
// AI Code Review Analysis
if (data.aiReviewResults) {
this.addAIReviewSection(lines, data.aiReviewResults);
}
// Production Audits
if (data.productionAudits.length > 0) {
lines.push(`## ${t('report_generator.headers.production_recommendations')}`);
lines.push('');
// Group by priority
const auditsByPriority = this.groupAuditsByPriority(data.productionAudits);
if (auditsByPriority.critical.length > 0) {
lines.push(t('report_generator.priorities.critical'));
lines.push('');
auditsByPriority.critical.forEach(audit => {
lines.push(`**${this.sanitizeAuditMessage(audit.message)}**`);
lines.push(`→ ${this.sanitizeAuditMessage(audit.recommendation)}`);
if (audit.packages && audit.packages.length > 0) {
const sanitizedPackages = audit.packages
.map(p => sanitizeFilePath(p))
.join('`, `');
lines.push(`📦 \`${sanitizedPackages}\``);
}
lines.push('');
});
}
if (auditsByPriority.high.length > 0) {
lines.push(t('report_generator.priorities.high'));
lines.push('');
auditsByPriority.high.forEach(audit => {
lines.push(`**${this.sanitizeAuditMessage(audit.message)}**`);
lines.push(`→ ${this.sanitizeAuditMessage(audit.recommendation)}`);
if (audit.packages && audit.packages.length > 0) {
const sanitizedPackages = audit.packages
.map(p => sanitizeFilePath(p))
.join('`, `');
lines.push(`📦 \`${sanitizedPackages}\``);
}
lines.push('');
});
}
if (auditsByPriority.medium.length > 0) {
lines.push(t('report_generator.priorities.medium'));
lines.push('');
auditsByPriority.medium.forEach(audit => {
lines.push(`**${this.sanitizeAuditMessage(audit.message)}**`);
lines.push(`→ ${this.sanitizeAuditMessage(audit.recommendation)}`);
lines.push('');
});
}
if (auditsByPriority.low.length > 0) {
lines.push(t('report_generator.priorities.low'));
lines.push('');
auditsByPriority.low.forEach(audit => {
lines.push(`**${this.sanitizeAuditMessage(audit.message)}**`);
lines.push(`→ ${this.sanitizeAuditMessage(audit.recommendation)}`);
lines.push('');
});
}
}
// Commits
if (data.commits && data.commits.length > 0) {
lines.push(`## ${t('report_generator.headers.commits')}`);
lines.push('');
data.commits.slice(0, 50).forEach(commit => {
const sanitizedCommit = sanitizeCommitMessage(commit);
lines.push(`- ${sanitizedCommit}`);
});
if (data.commits.length > 50) {
lines.push(t('report_generator.summary.more_commits', {
count: data.commits.length - 50,
}));
}
lines.push('');
}
// Footer
lines.push('---');
lines.push('');
lines.push('**Generiert von WOARU Review** 🚀');
lines.push(`**Basis: \`${data.gitDiff.baseBranch}\` → \`${data.currentBranch}\`**`);
return lines.join('\n');
}
groupQualityIssuesByFile(results) {
const grouped = {};
results.forEach(result => {
const file = path.basename(result.filePath);
if (!grouped[file]) {
grouped[file] = [];
}
grouped[file].push(result);
});
return grouped;
}
groupAuditsByPriority(audits) {
return {
critical: audits.filter(a => a.priority === 'critical'),
high: audits.filter(a => a.priority === 'high'),
medium: audits.filter(a => a.priority === 'medium'),
low: audits.filter(a => a.priority === 'low'),
};
}
getReportSummary(data) {
const criticalIssues = data.qualityResults.filter(r => r.severity === 'error').length;
const securitySummary = this.getSecuritySummaryFromResults(data.securityResults || []);
const highPriorityAudits = data.productionAudits.filter(a => a.priority === 'high' || a.priority === 'critical').length;
if (criticalIssues === 0 &&
securitySummary.critical === 0 &&
securitySummary.high === 0 &&
highPriorityAudits === 0) {
return t('report_generator.no_critical_issues');
}
const issues = [];
if (securitySummary.critical > 0 || securitySummary.high > 0) {
issues.push(`${securitySummary.critical + securitySummary.high} Sicherheits-Probleme`);
}
if (criticalIssues > 0) {
issues.push(`${criticalIssues} Qualitäts-Probleme`);
}
if (highPriorityAudits > 0) {
issues.push(`${highPriorityAudits} Produktions-Empfehlungen`);
}
return `⚠️ Gefunden: ${issues.join(', ')}`;
}
/**
* Add critical/high security findings section
*/
addCriticalSecuritySection(lines, securityResults) {
securityResults.forEach(result => {
if (result.error) {
lines.push(`⚠️ **${result.tool} Error:** ${result.error}`);
lines.push('');
return;
}
const criticalFindings = result.findings.filter(f => f.severity === 'critical');
const highFindings = result.findings.filter(f => f.severity === 'high');
if (criticalFindings.length > 0) {
lines.push(`### 🔴 KRITISCHE ${result.tool.toUpperCase()}-Befunde`);
lines.push('');
criticalFindings.forEach(finding => {
lines.push(`**${finding.title}**`);
if (finding.package) {
lines.push(`- **Paket:** ${finding.package}@${finding.version}`);
}
if (finding.file) {
lines.push(`- **Datei:** ${finding.file}${finding.line ? `:${finding.line}` : ''}`);
}
lines.push(`- **Schweregrad:** KRITISCH`);
if (finding.cve) {
lines.push(`- **CVE:** ${finding.cve}`);
}
if (finding.fixedIn) {
lines.push(`- **✅ Fix verfügbar:** Upgrade auf ${finding.fixedIn}`);
}
if (finding.recommendation) {
lines.push(`- **Empfehlung:** ${finding.recommendation}`);
}
lines.push('');
});
}
if (highFindings.length > 0) {
lines.push(`### 🟡 HOHE ${result.tool.toUpperCase()}-Befunde`);
lines.push('');
highFindings.forEach(finding => {
lines.push(`**${finding.title}**`);
if (finding.package) {
lines.push(`- **Paket:** ${finding.package}@${finding.version}`);
}
if (finding.file) {
lines.push(`- **Datei:** ${finding.file}${finding.line ? `:${finding.line}` : ''}`);
}
lines.push(`- **Schweregrad:** HOCH`);
if (finding.recommendation) {
lines.push(`- **Empfehlung:** ${finding.recommendation}`);
}
lines.push('');
});
}
});
}
/**
* Add all security findings section
*/
addAllSecuritySection(lines, securityResults) {
securityResults.forEach(result => {
if (result.error) {
lines.push(`⚠️ **${result.tool} Error:** ${result.error}`);
lines.push('');
return;
}
if (result.findings.length > 0) {
lines.push(`### ${this.getToolEmoji(result.tool)} ${result.tool.toUpperCase()}-Befunde`);
lines.push('');
// Group by severity
const bySeverity = {
critical: result.findings.filter(f => f.severity === 'critical'),
high: result.findings.filter(f => f.severity === 'high'),
medium: result.findings.filter(f => f.severity === 'medium'),
low: result.findings.filter(f => f.severity === 'low'),
info: result.findings.filter(f => f.severity === 'info'),
};
Object.entries(bySeverity).forEach(([severity, findings]) => {
if (findings.length > 0) {
lines.push(`#### ${this.getSeverityEmoji(severity)} ${severity.toUpperCase()} (${findings.length})`);
lines.push('');
findings.forEach(finding => {
if (finding.package) {
lines.push(`- **${finding.title}** in ${finding.package}@${finding.version}`);
}
else if (finding.file) {
lines.push(`- **${finding.title}** in ${finding.file}${finding.line ? `:${finding.line}` : ''}`);
}
else {
lines.push(`- **${finding.title}**`);
}
});
lines.push('');
}
});
}
});
}
getToolEmoji(tool) {
switch (tool.toLowerCase()) {
case 'snyk':
return '📦';
case 'gitleaks':
return '🔍';
case 'trufflehog':
return '🕵️';
case 'trivy':
return '🛡️';
default:
return '🔒';
}
}
getSeverityEmoji(severity) {
switch (severity) {
case 'critical':
case 'error':
return '🔴';
case 'high':
case 'warning':
return '🟡';
case 'medium':
return '🔵';
case 'low':
case 'info':
return '⚪';
default:
return '🔴';
}
}
/**
* Determine if code context should be shown for this result
*/
shouldShowCodeContext(result) {
// Show context for syntax errors, formatting issues, or when raw_output contains useful snippets
return (result.tool.toLowerCase().includes('eslint') ||
result.tool.toLowerCase().includes('prettier') ||
result.tool.toLowerCase().includes('ruff') ||
(result.raw_output !== undefined && result.raw_output.length < 1000)); // Don't show very long outputs
}
/**
* Extract relevant code context from raw tool output
*/
extractRelevantCodeContext(rawOutput) {
const lines = rawOutput.split('\n');
const relevantLines = [];
// Look for actual code lines (usually indented or contain code patterns)
let foundCode = false;
for (const line of lines) {
if (line.match(/^\s*(\d+:|>|\+|-)/) || // Line numbers or diff markers
line.match(/^\s{2,}/) || // Indented lines (likely code)
line.match(/[{}();,]/) // Contains code-like characters
) {
relevantLines.push(line);
foundCode = true;
}
else if (foundCode && line.trim() === '') {
// Include empty lines in code blocks
relevantLines.push(line);
}
else if (foundCode && !line.match(/^\s*[a-zA-Z]/)) {
// Stop at non-code lines after finding code
break;
}
}
// If no code found, return first few non-empty lines
if (relevantLines.length === 0) {
return lines
.filter(line => line.trim())
.slice(0, 5)
.join('\n');
}
return relevantLines.slice(0, 15).join('\n'); // Limit to 15 lines
}
/**
* Extract summary from SecurityScanResult array
*/
getSecuritySummaryFromResults(securityResults) {
const summary = {
total: 0,
critical: 0,
high: 0,
medium: 0,
low: 0,
info: 0,
};
securityResults.forEach(result => {
summary.total += result.summary.total;
summary.critical += result.summary.critical;
summary.high += result.summary.high;
summary.medium += result.summary.medium;
summary.low += result.summary.low;
summary.info += result.summary.info;
});
return summary;
}
/**
* Flatten SecurityScanResult array to SecurityFinding array for JSON output
*/
flattenSecurityResults(securityResults) {
const allFindings = [];
securityResults.forEach(result => {
allFindings.push(...result.findings);
});
return allFindings;
}
/**
* Adds SOLID Architecture Analysis section to the report
*/
addSOLIDAnalysisSection(lines, qualityResults) {
// Extract SOLID results from quality results
const solidResults = qualityResults
.map(result => result.solidResult)
.filter((solidResult) => solidResult !== undefined);
if (solidResults.length === 0) {
return; // No SOLID analysis available
}
// Calculate summary metrics
const totalViolations = solidResults.reduce((sum, result) => sum + result.violations.length, 0);
const avgSOLIDScore = solidResults.reduce((sum, result) => sum + result.metrics.solidScore, 0) /
solidResults.length;
// Only show section if there are violations or low scores
if (totalViolations === 0 && avgSOLIDScore >= 80) {
// Add short positive note
lines.push('## 🏗️ SOLID Architecture Analysis');
lines.push('');
lines.push(`✅ **Excellent SOLID Score: ${Math.round(avgSOLIDScore)}/100** - Keine Architektur-Probleme gefunden!`);
lines.push('');
return;
}
lines.push('## 🏗️ SOLID Architecture Analysis');
lines.push('');
lines.push(`📊 **SOLID Score: ${Math.round(avgSOLIDScore)}/100** (${totalViolations} Verstöße gefunden)`);
lines.push('');
// Group violations by principle
const violationsByPrinciple = this.groupSOLIDViolationsByPrinciple(solidResults);
// Show violations by principle
Object.entries(violationsByPrinciple).forEach(([principle, violations]) => {
if (violations.length === 0)
return;
const principleNames = {
SRP: 'Single Responsibility Principle',
OCP: 'Open/Closed Principle',
LSP: 'Liskov Substitution Principle',
ISP: 'Interface Segregation Principle',
DIP: 'Dependency Inversion Principle',
};
lines.push(`### 🔴 ${principleNames[principle]} (${violations.length} Verstöße)`);
lines.push('');
// Group by severity
const critical = violations.filter(v => v.severity === 'critical');
const high = violations.filter(v => v.severity === 'high');
const medium = violations.filter(v => v.severity === 'medium');
[
{ label: 'KRITISCH', violations: critical, emoji: '🔴' },
{ label: 'HOCH', violations: high, emoji: '🟡' },
{ label: 'MITTEL', violations: medium, emoji: '🔵' },
].forEach(({ label, violations: severityViolations, emoji }) => {
if (severityViolations.length === 0)
return;
lines.push(`#### ${emoji} ${label} (${severityViolations.length})`);
lines.push('');
severityViolations.slice(0, 5).forEach((violation, index) => {
lines.push(`**${index + 1}. ${violation.description}**`);
// Location info
if (violation.class) {
const location = violation.line
? `${violation.class}:${violation.line}`
: violation.class;
lines.push(`📍 **Klasse:** ${location}`);
}
if (violation.method) {
lines.push(`🔧 **Methode:** ${violation.method}`);
}
// Explanation
lines.push(`💡 **Problem:** ${violation.explanation}`);
// Impact
lines.push(`⚠️ **Auswirkung:** ${violation.impact}`);
// Suggestion
lines.push(`🔨 **Lösung:** ${violation.suggestion}`);
// Metrics if available
if (violation.metrics) {
const metrics = [];
if (violation.metrics.complexity)
metrics.push(`Komplexität: ${violation.metrics.complexity}`);
if (violation.metrics.methodCount)
metrics.push(`Methoden: ${violation.metrics.methodCount}`);
if (violation.metrics.parameters)
metrics.push(`Parameter: ${violation.metrics.parameters}`);
if (violation.metrics.linesOfCode)
metrics.push(`Zeilen: ${violation.metrics.linesOfCode}`);
if (metrics.length > 0) {
lines.push(`📊 **Metriken:** ${metrics.join(', ')}`);
}
}
lines.push('');
});
if (severityViolations.length > 5) {
lines.push(`*... und ${severityViolations.length - 5} weitere ${label}-Verstöße*`);
lines.push('');
}
});
});
// Add general SOLID recommendations
lines.push(t('report_generator.solid_recommendations'));
lines.push('');
const recommendations = this.generateSOLIDRecommendations(solidResults);
recommendations.forEach((rec, index) => {
lines.push(`${index + 1}. ${rec}`);
});
lines.push('');
}
/**
* Groups SOLID violations by principle
*/
groupSOLIDViolationsByPrinciple(solidResults) {
const grouped = {
SRP: [],
OCP: [],
LSP: [],
ISP: [],
DIP: [],
};
solidResults.forEach(result => {
result.violations.forEach(violation => {
grouped[violation.principle].push(violation);
});
});
return grouped;
}
/**
* Generates specific SOLID recommendations based on violations
*/
generateSOLIDRecommendations(solidResults) {
const recommendations = [];
const allViolations = solidResults.flatMap(result => result.violations);
// SRP recommendations
const srpViolations = allViolations.filter(v => v.principle === 'SRP');
if (srpViolations.length > 0) {
const classesWithManyMethods = srpViolations.filter(v => v.metrics?.methodCount && v.metrics.methodCount > 15).length;
if (classesWithManyMethods > 0) {
recommendations.push(`🎯 ${classesWithManyMethods} Klassen mit zu vielen Methoden gefunden - teile diese in kleinere, fokussierte Services auf`);
}
const complexClasses = srpViolations.filter(v => v.metrics?.complexity && v.metrics.complexity > 30).length;
if (complexClasses > 0) {
recommendations.push(`🔄 ${complexClasses} Klassen mit hoher Komplexität - extrahiere komplexe Logik in separate Utility-Klassen`);
}
const concernViolations = srpViolations.filter(v => v.metrics?.importConcerns && v.metrics.importConcerns.length > 3).length;
if (concernViolations > 0) {
recommendations.push(`📦 ${concernViolations} Klassen mit zu vielen verschiedenen Concerns - verwende Dependency Injection und Service-Pattern`);
}
}
// General recommendations
const avgScore = solidResults.reduce((sum, r) => sum + r.metrics.solidScore, 0) /
solidResults.length;
if (avgScore < 70) {
recommendations.push('🏗️ Führe systematisches Refactoring durch - beginne mit den kritischsten SOLID-Verstößen');
}
const filesWithManyViolations = solidResults.filter(r => r.violations.length > 5).length;
if (filesWithManyViolations > 0) {
recommendations.push(`⚠️ ${filesWithManyViolations} Dateien mit vielen SOLID-Verstößen - priorisiere diese für Architektur-Überarbeitung`);
}
// Add fallback recommendation
if (recommendations.length === 0 && allViolations.length > 0) {
recommendations.push('📚 Überprüfe die SOLID-Prinzipien Dokumentation für weitere Verbesserungsideen');
}
return recommendations;
}
/**
* Add code smell analysis section to the report
*/
addCodeSmellAnalysisSection(lines, qualityResults) {
// Collect all code smell findings from all quality results
const allCodeSmellFindings = [];
qualityResults.forEach(result => {
if (result.codeSmellFindings && result.codeSmellFindings.length > 0) {
allCodeSmellFindings.push({
filePath: result.filePath,
findings: result.codeSmellFindings,
});
}
});
if (allCodeSmellFindings.length === 0) {
return; // No code smells found
}
lines.push('## 🧼 Code Smell Analysis (WOARU Internal)');
lines.push('');
// Calculate overall statistics
const totalFindings = allCodeSmellFindings.reduce((sum, file) => sum + file.findings.length, 0);
const findingsByType = allCodeSmellFindings
.flatMap(file => file.findings)
.reduce((acc, finding) => {
acc[finding.type] = (acc[finding.type] || 0) + 1;
return acc;
}, {});
const criticalFindings = allCodeSmellFindings
.flatMap(file => file.findings)
.filter(f => f.severity === 'error').length;
const warningFindings = allCodeSmellFindings
.flatMap(file => file.findings)
.filter(f => f.severity === 'warning').length;
// Summary
lines.push(`📊 **Gefunden: ${totalFindings} Code Smells** (${criticalFindings} kritisch, ${warningFindings} Warnungen)`);
lines.push('');
// Summary by type
lines.push('### 📋 Verteilung nach Typ:');
Object.entries(findingsByType)
.sort(([, a], [, b]) => b - a) // Sort by count descending
.forEach(([type, count]) => {
const icon = this.getCodeSmellIcon(type);
lines.push(`- ${icon} **${type.replace('-', ' ')}**: ${count}`);
});
lines.push('');
// Detailed findings by file
allCodeSmellFindings.forEach(({ filePath, findings }) => {
lines.push(`### 📄 \`${path.basename(filePath)}\``);
lines.push('');
// Group findings by severity
const criticalFindings = findings.filter(f => f.severity === 'error');
const warningFindings = findings.filter(f => f.severity === 'warning');
const infoFindings = findings.filter(f => f.severity === 'info');
if (criticalFindings.length > 0) {
lines.push(t('report_generator.critical_problems'));
this.addCodeSmellFindingsList(lines, criticalFindings);
}
if (warningFindings.length > 0) {
lines.push(t('report_generator.warnings'));
this.addCodeSmellFindingsList(lines, warningFindings);
}
if (infoFindings.length > 0) {
lines.push(t('report_generator.information'));
this.addCodeSmellFindingsList(lines, infoFindings);
}
lines.push('');
});
// Recommendations
lines.push(t('report_generator.code_smell_recommendations'));
const recommendations = this.generateCodeSmellRecommendations(findingsByType);
recommendations.forEach(rec => {
lines.push(`- ${rec}`);
});
lines.push('');
lines.push('---');
lines.push('');
}
/**
* Add list of code smell findings
*/
addCodeSmellFindingsList(lines, findings) {
findings.forEach(finding => {
lines.push(t('report_generator.code_smell_line', {
line: finding.line,
column: finding.column,
message: finding.message,
}));
if (finding.suggestion) {
lines.push(` 💡 *${finding.suggestion}*`);
}
});
lines.push('');
}
/**
* Get icon for code smell type
*/
getCodeSmellIcon(type) {
const icons = {
complexity: '🔄',
'var-keyword': '📦',
'weak-equality': '⚖️',
'console-log': '🖨️',
'function-length': '📏',
'parameter-count': '📝',
'nested-depth': '🏗️',
'magic-number': '🔢',
'duplicate-code': '📋',
'dead-code': '💀',
};
return icons[type] || '⚠️';
}
/**
* Generate code smell recommendations
*/
generateCodeSmellRecommendations(findingsByType) {
const recommendations = [];
Object.entries(findingsByType).forEach(([type, count]) => {
switch (type) {
case 'var-keyword':
recommendations.push(`🔄 Ersetze ${count} \`var\` Deklarationen durch \`let\` oder \`const\``);
break;
case 'weak-equality':
recommendations.push(`⚖️ Verwende strikte Gleichheit (\`===\`, \`!==\`) statt schwacher Gleichheit (${count} Vorkommen)`);
break;
case 'console-log':
recommendations.push(`🖨️ Entferne ${count} Debug-Statements (\`console.log\`) vor Production`);
break;
case 'complexity':
recommendations.push(`🔄 Reduziere zyklomatische Komplexität in ${count} Funktionen durch Aufteilen`);
break;
case 'function-length':
recommendations.push(`📏 Kürze ${count} zu lange Funktionen durch Extraktion von Logik`);
break;
case 'parameter-count':
recommendations.push(`📝 Reduziere Parameter-Anzahl in ${count} Funktionen (verwende Options-Objekte)`);
break;
case 'nested-depth':
recommendations.push(`🏗️ Reduziere Verschachtelungstiefe durch Guard-Clauses oder Funktions-Extraktion`);
break;
case 'magic-number':
recommendations.push(`🔢 Extrahiere ${count} magische Zahlen in benannte Konstanten`);
break;
}
});
if (recommendations.length === 0) {
recommendations.push('✅ Keine spezifischen Code Smell Empfehlungen erforderlich');
}
return recommendations;
}
/**
* Add AI Review section to the report
*/
addAIReviewSection(lines, aiReviewResults) {
lines.push('## 🧠 AI Code Review Analysis');
lines.push('');
const codeContext = aiReviewResults.codeContext;
const aggregation = aiReviewResults.aggregation;
const meta = aiReviewResults.meta;
const results = aiReviewResults.results;
// Overview
lines.push(`🤖 **Analysiert durch Multi-LLM System** - 1 Datei, ${aggregation.totalFindings} Befunde`);
if (meta.totalEstimatedCost > 0) {
lines.push(`💰 **Geschätzte Kosten:** $${meta.totalEstimatedCost.toFixed(4)}`);
}
lines.push(`⏰ **Analysedauer:** ${meta.totalDuration}ms`);
lines.push('');
if (Object.keys(results).length === 0) {
lines.push('✅ Keine AI-basierten Befunde gefunden.');
lines.push('');
return;
}
// Process file results
lines.push(`### 📄 \`${codeContext.filePath}\` (${codeContext.language})`);
lines.push('');
// File-level summary
lines.push(`📊 **${aggregation.totalFindings} Befunde gefunden** | **Analysedauer:** ${meta.totalDuration}ms | **LLM Übereinstimmung:** ${(aggregation.llmAgreementScore * 100).toFixed(1)}%`);
lines.push('');
// Show findings by severity
if (aggregation.findingsBySeverity &&
Object.keys(aggregation.findingsBySeverity).length > 0) {
lines.push('#### 📈 Befunde nach Schweregrad:');
Object.entries(aggregation.findingsBySeverity).forEach(([severity, count]) => {
const icon = this.getAISeverityIcon(severity);
lines.push(`- ${icon} **${severity.toUpperCase()}**: ${count}`);
});
lines.push('');
}
// Show findings by category
if (aggregation.findingsByCategory &&
Object.keys(aggregation.findingsByCategory).length > 0) {
lines.push('#### 🏷️ Befunde nach Kategorie:');
Object.entries(aggregation.findingsByCategory).forEach(([category, count]) => {
const icon = this.getAICategoryIcon(category);
lines.push(`- ${icon} **${category}**: ${count}`);
});
lines.push('');
}
// Show consensus findings (issues found by multiple LLMs)
if (aggregation.consensusFindings &&
aggregation.consensusFindings.length > 0) {
lines.push('#### 🤝 Konsens-Befunde (mehrere LLMs sind sich einig):');
this.addAIFindingsList(lines, aggregation.consensusFindings, true);
}
// Show unique findings per LLM
if (aggregation.uniqueFindings &&
Object.keys(aggregation.uniqueFindings).length > 0) {
lines.push('#### 🔍 Spezifische LLM-Befunde:');
Object.entries(aggregation.uniqueFindings).forEach(([llmId, findings]) => {
if (findings.length > 0) {
lines.push(`**${llmId}** (${findings.length} einzigartige Befunde):`);
this.addAIFindingsList(lines, findings.slice(0, 3), false); // Show max 3 per LLM
if (findings.length > 3) {
lines.push(` *... und ${findings.length - 3} weitere Befunde*`);
lines.push('');
}
}
});
}
// Show LLM performance details
if (meta.llmResponseTimes &&
Object.keys(meta.llmResponseTimes).length > 1) {
lines.push('#### ⚡ LLM Performance:');
Object.entries(meta.llmResponseTimes).forEach(([llmId, responseTime]) => {
const cost = meta.estimatedCost[llmId] || 0;
const tokens = meta.tokensUsed[llmId] || 0;
const error = meta.llmErrors[llmId];
if (error) {
lines.push(`- ❌ **${llmId}**: Fehler - ${error}`);
}
else {
lines.push(`- ✅ **${llmId}**: ${responseTime}ms, ${tokens} Tokens, $${cost.toFixed(4)}`);
}
});
lines.push('');
}
lines.push('---');
lines.push('');
// Overall AI Review recommendations
lines.push(t('report_generator.ai_review_recommendations'));
const aiRecommendations = this.generateAIReviewRecommendations(aiReviewResults);
aiRecommendations.forEach(rec => {
lines.push(`- ${rec}`);
});
lines.push('');
lines.push('---');
lines.push('');
}
/**
* Add list of AI findings
*/
addAIFindingsList(lines, findings, showConfidence) {
findings.forEach(finding => {
const severityIcon = t