From 06bd9cb16fa2c6e55491ada71aea977a757f8a02 Mon Sep 17 00:00:00 2001 From: Noisemaker111 Date: Sun, 30 Nov 2025 14:56:59 -0500 Subject: [PATCH] feat: consolidate duplicate issues to reduce report size Instead of repeating the same issue for each occurrence, identical issues (same type, file, code, message, severity) are now consolidated into a single entry with a 'location' array containing all line:column positions. Changes: - Added ConsolidatedIssue interface with location array - Added consolidateIssues() method to group identical issues - Updated exportResults() to use consolidated format - bySeverity counts remain occurrence-based (backwards compatible) - byType now includes both 'occurrences' and 'uniqueIssues' counts - Renamed totalIssues to uniqueIssues for clarity This reduces JSON output size while retaining all information and maintaining backwards compatibility for bySeverity consumers. --- ai-slop-detector.ts | 100 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/ai-slop-detector.ts b/ai-slop-detector.ts index f43fda5..7369537 100644 --- a/ai-slop-detector.ts +++ b/ai-slop-detector.ts @@ -31,6 +31,15 @@ interface AISlopIssue { severity: 'critical' | 'high' | 'medium' | 'low'; } +interface ConsolidatedIssue { + type: string; + file: string; + code: string; + message: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + location: string[]; +} + interface DetectionPattern { id: string; pattern: RegExp; @@ -682,37 +691,96 @@ class AISlopDetector { return this.issues.filter(issue => issue.severity === severity); } + /** + * Consolidate issues by grouping identical issues (same type, file, code, message, severity) + * into single entries with a location array + */ + private consolidateIssues(): ConsolidatedIssue[] { + const issueMap = new Map(); + + for (const issue of this.issues) { + // Create a unique key for grouping identical issues + const key = `${issue.type}|${issue.file}|${issue.code}|${issue.message}|${issue.severity}`; + + if (issueMap.has(key)) { + // Add location to existing consolidated issue + const existing = issueMap.get(key)!; + existing.location.push(`${issue.line}:${issue.column}`); + } else { + // Create new consolidated issue + issueMap.set(key, { + type: issue.type, + file: issue.file, + code: issue.code, + message: issue.message, + severity: issue.severity, + location: [`${issue.line}:${issue.column}`] + }); + } + } + + return Array.from(issueMap.values()); + } + /** * Export results to JSON for further processing */ exportResults(outputPath: string) { - // Group issues by severity - const bySeverity = { - critical: this.issues.filter(i => i.severity === 'critical'), - high: this.issues.filter(i => i.severity === 'high'), - medium: this.issues.filter(i => i.severity === 'medium'), - low: this.issues.filter(i => i.severity === 'low') + // Consolidate issues to avoid repetition + const consolidatedIssues = this.consolidateIssues(); + + // Helper to count occurrences from consolidated issues + const countOccurrences = (issues: ConsolidatedIssue[]) => + issues.reduce((sum, issue) => sum + issue.location.length, 0); + + // Group consolidated issues by severity + const consolidatedBySeverity = { + critical: consolidatedIssues.filter(i => i.severity === 'critical'), + high: consolidatedIssues.filter(i => i.severity === 'high'), + medium: consolidatedIssues.filter(i => i.severity === 'medium'), + low: consolidatedIssues.filter(i => i.severity === 'low') }; + // Count total occurrences (sum of all locations) + const totalOccurrences = countOccurrences(consolidatedIssues); + + // Group by type and count occurrences + const byTypeMap = new Map(); + for (const issue of consolidatedIssues) { + if (!byTypeMap.has(issue.type)) { + byTypeMap.set(issue.type, []); + } + byTypeMap.get(issue.type)!.push(issue); + } + const results = { timestamp: new Date().toISOString(), - totalIssues: this.issues.length, + // Unique consolidated issues count + uniqueIssues: consolidatedIssues.length, + // Total occurrences (backwards compatible - same as old totalIssues) + totalOccurrences: totalOccurrences, + // Occurrence counts by severity (backwards compatible) bySeverity: { - critical: bySeverity.critical.length, - high: bySeverity.high.length, - medium: bySeverity.medium.length, - low: bySeverity.low.length + critical: countOccurrences(consolidatedBySeverity.critical), + high: countOccurrences(consolidatedBySeverity.high), + medium: countOccurrences(consolidatedBySeverity.medium), + low: countOccurrences(consolidatedBySeverity.low) }, - byType: Object.entries(this.getIssuesByType()).map(([type, issues]) => ({ + // Occurrence counts by type (backwards compatible) + byType: Array.from(byTypeMap.entries()).map(([type, issues]) => ({ type, - count: issues.length, + // Total occurrences of this issue type + occurrences: countOccurrences(issues), + // Unique consolidated issues of this type + uniqueIssues: issues.length, sample: issues.slice(0, 3).map(issue => ({ file: path.relative(this.rootDir, issue.file), - line: issue.line, + locations: issue.location.slice(0, 3), code: issue.code })) })), - issues: this.issues + // Consolidated issues array (new format with location arrays) + issues: consolidatedIssues }; fs.writeFileSync(outputPath, JSON.stringify(results, null, 2)); @@ -846,4 +914,4 @@ runIfMain().catch(error => { process.exit(1); }); -export { AISlopDetector, AISlopIssue, DetectionPattern }; +export { AISlopDetector, AISlopIssue, ConsolidatedIssue, DetectionPattern };