git-repo-analyzer-test 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.github/copilot-instructions.md +108 -0
  2. package/.idea/aianalyzer.iml +9 -0
  3. package/.idea/misc.xml +6 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/API_REFERENCE.md +244 -0
  7. package/ENHANCEMENTS.md +282 -0
  8. package/README.md +179 -0
  9. package/USAGE.md +189 -0
  10. package/analysis.txt +0 -0
  11. package/bin/cli.js +135 -0
  12. package/docs/SONARCLOUD_ANALYSIS_COVERED.md +144 -0
  13. package/docs/SonarCloud_Presentation_Points.md +81 -0
  14. package/docs/UI_IMPROVEMENTS.md +117 -0
  15. package/package-lock_cmd.json +542 -0
  16. package/package.json +44 -0
  17. package/package_command.json +16 -0
  18. package/public/analysis-options.json +31 -0
  19. package/public/images/README.txt +2 -0
  20. package/public/images/rws-logo.png +0 -0
  21. package/public/index.html +2433 -0
  22. package/repositories.example.txt +17 -0
  23. package/sample-repos.txt +20 -0
  24. package/src/analyzers/accessibility.js +47 -0
  25. package/src/analyzers/cicd-enhanced.js +113 -0
  26. package/src/analyzers/codeReview-enhanced.js +599 -0
  27. package/src/analyzers/codeReview-enhanced.js:Zone.Identifier +3 -0
  28. package/src/analyzers/codeReview.js +171 -0
  29. package/src/analyzers/codeReview.js:Zone.Identifier +3 -0
  30. package/src/analyzers/documentation-enhanced.js +137 -0
  31. package/src/analyzers/performance-enhanced.js +747 -0
  32. package/src/analyzers/performance-enhanced.js:Zone.Identifier +3 -0
  33. package/src/analyzers/performance.js +211 -0
  34. package/src/analyzers/performance.js:Zone.Identifier +3 -0
  35. package/src/analyzers/performance_cmd.js +216 -0
  36. package/src/analyzers/quality-enhanced.js +386 -0
  37. package/src/analyzers/quality-enhanced.js:Zone.Identifier +3 -0
  38. package/src/analyzers/quality.js +92 -0
  39. package/src/analyzers/quality.js:Zone.Identifier +3 -0
  40. package/src/analyzers/security-enhanced.js +512 -0
  41. package/src/analyzers/security-enhanced.js:Zone.Identifier +3 -0
  42. package/src/analyzers/snyk-ai.js:Zone.Identifier +3 -0
  43. package/src/analyzers/sonarcloud.js +928 -0
  44. package/src/analyzers/vulnerability.js +185 -0
  45. package/src/analyzers/vulnerability.js:Zone.Identifier +3 -0
  46. package/src/cli.js:Zone.Identifier +3 -0
  47. package/src/config.js +43 -0
  48. package/src/core/analyzerEngine.js +68 -0
  49. package/src/core/reportGenerator.js +21 -0
  50. package/src/gemini.js +321 -0
  51. package/src/github/client.js +124 -0
  52. package/src/github/client.js:Zone.Identifier +3 -0
  53. package/src/index.js +93 -0
  54. package/src/index_cmd.js +130 -0
  55. package/src/openai.js +297 -0
  56. package/src/report/generator.js +459 -0
  57. package/src/report/generator_cmd.js +459 -0
  58. package/src/report/pdf-generator.js +387 -0
  59. package/src/report/pdf-generator.js:Zone.Identifier +3 -0
  60. package/src/server.js +431 -0
  61. package/src/server.js:Zone.Identifier +3 -0
  62. package/src/server_cmd.js +434 -0
  63. package/src/sonarcloud/client.js +365 -0
  64. package/src/sonarcloud/scanner.js +171 -0
  65. package/src.zip +0 -0
@@ -0,0 +1,459 @@
1
+ import { table } from 'table';
2
+ import chalk from 'chalk';
3
+
4
+ export class ReportGenerator {
5
+ /**
6
+ * Generate comprehensive analysis report
7
+ */
8
+ static generateReport(repoName, analysis) {
9
+ const summary = this.generateSummary(analysis);
10
+ const report = {
11
+ timestamp: new Date().toISOString(),
12
+ repository: repoName,
13
+ summary,
14
+ /** Project overall: single score (0–10), rating (A+–F), and 0–100 scale for display */
15
+ overall: {
16
+ score: summary.overallScore,
17
+ score100: summary.overallScore100,
18
+ rating: summary.overallRating,
19
+ },
20
+ detailed: analysis,
21
+ };
22
+
23
+ return report;
24
+ }
25
+
26
+ /**
27
+ * Generate text-based report for console output
28
+ */
29
+ static generateTextReport(repoName, analysis) {
30
+ let output = '';
31
+
32
+ output += chalk.bold.cyan(`\nšŸ“Š GitHub Repository Analysis Report\n`);
33
+ output += chalk.gray(`Repository: ${repoName}\n`);
34
+ output += chalk.gray(`Generated: ${new Date().toLocaleString()}\n`);
35
+
36
+ // Overall Score
37
+ output += this.generateOverallScore(analysis);
38
+
39
+ // Quality Section (skip if Sonar-only run)
40
+ if (analysis.quality) output += this.generateQualitySection(analysis.quality);
41
+
42
+ // Security Section (skip if Sonar-only run)
43
+ if (analysis.security) output += this.generateSecuritySection(analysis.security);
44
+
45
+ // SonarCloud Section (always in final report)
46
+ if (analysis.sonarCloud) {
47
+ output += this.generateSonarCloudSection(analysis.sonarCloud);
48
+ } else {
49
+ output += chalk.bold.cyan(`\nšŸ“Š SonarCloud Code Quality\n`);
50
+ output += chalk.gray('Unavailable. Add SONAR_TOKEN and analyze a repo that exists on SonarCloud to include in the report.\n');
51
+ }
52
+
53
+ // Code Review Section (skip if Sonar-only run)
54
+ if (analysis.codeReview) output += this.generateCodeReviewSection(analysis.codeReview);
55
+
56
+ // Performance Section (skip if Sonar-only run)
57
+ if (analysis.performance) output += this.generatePerformanceSection(analysis.performance);
58
+
59
+ // Summary & Recommendations
60
+ output += this.generateSummarySection(analysis);
61
+
62
+ return output;
63
+ }
64
+
65
+ /**
66
+ * Generate overall score visualization (includes SonarCloud when available).
67
+ * Supports Sonar-only runs when quality, security, codeReview, performance are undefined.
68
+ */
69
+ static generateOverallScore(analysis) {
70
+ const scores = [];
71
+ if (analysis.quality?.score != null) scores.push(analysis.quality.score);
72
+ if (analysis.security?.score != null) scores.push(analysis.security.score);
73
+ if (analysis.codeReview?.score != null) scores.push(analysis.codeReview.score);
74
+ if (analysis.performance?.score != null) scores.push(analysis.performance.score);
75
+ if (analysis.sonarCloud?.available && analysis.sonarCloud.score != null) {
76
+ scores.push(analysis.sonarCloud.score);
77
+ }
78
+ if (scores.length === 0) {
79
+ return chalk.bold.yellow('\nšŸŽÆ Project Overall: Not available (no scores from any analyzer)\n');
80
+ }
81
+ const overallScore10 = scores.reduce((a, b) => a + b, 0) / scores.length;
82
+ const overallScore = Math.round(overallScore10);
83
+ const overallRating = this.getRating(overallScore);
84
+
85
+ let output = chalk.bold.yellow(`\nšŸŽÆ Project Overall: ${overallScore}/10 (${overallRating})\n`);
86
+ output += this.getScoreBar(Math.round(overallScore10 * 10)) + '\n';
87
+
88
+ return output;
89
+ }
90
+
91
+ /**
92
+ * Generate quality analysis section
93
+ */
94
+ static generateQualitySection(quality) {
95
+ let output = chalk.bold.cyan(`\n⭐ Code Quality Analysis\n`);
96
+ output += `Score: ${chalk.green(quality.score + '/10')} - Rating: ${chalk.bold(quality.rating)}\n\n`;
97
+
98
+ if (quality.issues && quality.issues.length > 0) {
99
+ output += chalk.bold(`Found ${quality.issues.length} Issue(s):\n`);
100
+ const issuesData = [['Severity', 'Type', 'Location', 'Recommendation']];
101
+
102
+ quality.issues.forEach((issue) => {
103
+ issuesData.push([
104
+ issue.severity,
105
+ issue.type,
106
+ issue.location,
107
+ issue.recommendation.substring(0, 50) + '...',
108
+ ]);
109
+ });
110
+
111
+ output += table(issuesData) + '\n';
112
+ }
113
+
114
+ if (quality.recommendations && quality.recommendations.length > 0) {
115
+ output += chalk.bold(`Recommendations:\n`);
116
+ quality.recommendations.forEach((rec, idx) => {
117
+ output += `${idx + 1}. ${rec.action}\n`;
118
+ });
119
+ output += '\n';
120
+ }
121
+
122
+ return output;
123
+ }
124
+
125
+ /**
126
+ * Generate security section
127
+ */
128
+ static generateSecuritySection(security) {
129
+ let output = chalk.bold.red(`\nšŸ”’ Security & Vulnerabilities Analysis\n`);
130
+ output += `Score: ${chalk.green(security.score + '/10')} - Rating: ${chalk.bold(security.rating)}\n`;
131
+ output += `Risk Level: ${this.getRiskLevelColor(security.riskLevel)(security.riskLevel)}\n\n`;
132
+
133
+ if (security.owasp10Vulnerabilities && security.owasp10Vulnerabilities.length > 0) {
134
+ output += chalk.bold(`OWASP Top 10 Findings (${security.owasp10Vulnerabilities.length}):\n`);
135
+
136
+ security.owasp10Vulnerabilities.forEach((vuln) => {
137
+ output += chalk.bold(`\n${vuln.rank}: ${vuln.title}\n`);
138
+ output += `Severity: ${this.getSeverityColor(vuln.severity)(vuln.severity)}\n`;
139
+ output += `Location: ${vuln.location}\n`;
140
+ output += `Description: ${vuln.description}\n`;
141
+
142
+ if (vuln.remediation && vuln.remediation.length > 0) {
143
+ output += chalk.yellow(`Remediation:\n`);
144
+ vuln.remediation.forEach((step, idx) => {
145
+ output += ` ${idx + 1}. ${step}\n`;
146
+ });
147
+ }
148
+ });
149
+ output += '\n';
150
+ }
151
+
152
+ if (security.recommendations && security.recommendations.length > 0) {
153
+ output += chalk.bold(`Recommendations:\n`);
154
+ security.recommendations.forEach((rec, idx) => {
155
+ output += `${idx + 1}. [${rec.priority}] ${rec.title}: ${rec.action}\n`;
156
+ });
157
+ output += '\n';
158
+ }
159
+
160
+ return output;
161
+ }
162
+
163
+ /**
164
+ * Generate code review section
165
+ */
166
+ static generateCodeReviewSection(codeReview) {
167
+ let output = chalk.bold.cyan(`\nšŸ‘„ Code Review & Collaboration Analysis\n`);
168
+ output += `Score: ${chalk.green(codeReview.score + '/10')} - Rating: ${chalk.bold(codeReview.rating)}\n\n`;
169
+
170
+ const metricsData = [
171
+ ['Metric', 'Value'],
172
+ ['Total PRs', codeReview.reviewMetrics.totalPullRequests.toString()],
173
+ ['Merged PRs', codeReview.reviewMetrics.mergedPullRequests.toString()],
174
+ ['Open PRs', codeReview.reviewMetrics.openPullRequests.toString()],
175
+ ['Closure Rate', codeReview.reviewMetrics.prClosureRate + '%'],
176
+ ['Avg Review Time', codeReview.reviewMetrics.averageReviewTimeHours + ' hours'],
177
+ ['Contributors', codeReview.collaborationMetrics.contributors.toString()],
178
+ ];
179
+
180
+ output += table(metricsData) + '\n';
181
+
182
+ if (codeReview.codingStyle && codeReview.codingStyle.commitConventionAdherence) {
183
+ const adherence = codeReview.codingStyle.commitConventionAdherence;
184
+ output += chalk.bold(`Commit Convention Adherence:\n`);
185
+ output += `${adherence.percentage}% follow conventional format (${adherence.conventional}/${adherence.total})\n`;
186
+ output += `Status: ${adherence.status}\n\n`;
187
+ }
188
+
189
+ if (codeReview.recommendations && codeReview.recommendations.length > 0) {
190
+ output += chalk.bold(`Recommendations:\n`);
191
+ codeReview.recommendations.forEach((rec, idx) => {
192
+ output += `${idx + 1}. [${rec.priority}] ${rec.category}: ${rec.action}\n`;
193
+ });
194
+ output += '\n';
195
+ }
196
+
197
+ return output;
198
+ }
199
+
200
+ /**
201
+ * Generate performance section
202
+ */
203
+ static generatePerformanceSection(performance) {
204
+ let output = chalk.bold.cyan(`\n⚔ Performance & Release Velocity\n`);
205
+ output += `Score: ${chalk.green(performance.score + '/10')} - Rating: ${chalk.bold(performance.rating)}\n\n`;
206
+
207
+ const metricsData = [
208
+ ['Metric', 'Value'],
209
+ ['Total Releases', performance.releaseVelocity.totalReleases.toString()],
210
+ ['Releases/Year', performance.releaseVelocity.averageReleasesPerYear.toString()],
211
+ ['Release Cadence', performance.releaseVelocity.releaseCadence],
212
+ ['Days Since Last Release', performance.releaseVelocity.daysSinceLastRelease?.toString() || 'N/A'],
213
+ ['Total Commits', performance.developmentVelocity.totalCommits.toString()],
214
+ ['Maintenance Level', performance.maintenancePattern.maintenanceLevel],
215
+ ];
216
+
217
+ output += table(metricsData) + '\n';
218
+
219
+ if (performance.maintenancePattern) {
220
+ output += chalk.bold(`Maintenance Status:\n`);
221
+ output += `Level: ${performance.maintenancePattern.maintenanceLevel}\n`;
222
+ output += `Last Update: ${performance.maintenancePattern.lastUpdateIndicator}\n`;
223
+ output += `Status: ${performance.maintenancePattern.status}\n\n`;
224
+ }
225
+
226
+ if (performance.recommendations && performance.recommendations.length > 0) {
227
+ output += chalk.bold(`Recommendations:\n`);
228
+ performance.recommendations.forEach((rec, idx) => {
229
+ output += `${idx + 1}. [${rec.priority}] ${rec.category}: ${rec.action}\n`;
230
+ });
231
+ output += '\n';
232
+ }
233
+
234
+ return output;
235
+ }
236
+
237
+ /**
238
+ * Generate summary section. Supports Sonar-only runs (undefined quality/security/codeReview/performance).
239
+ */
240
+ static generateSummarySection(analysis) {
241
+ let output = chalk.bold.cyan(`\nšŸ“‹ Summary\n`);
242
+
243
+ const summary = this.generateSummary(analysis);
244
+ const summaryData = [['Category', 'Score', 'Rating', 'Status']];
245
+ if (analysis.quality) {
246
+ summaryData.push(['Code Quality', (summary.qualityScore != null ? summary.qualityScore + '/10' : 'Not available'), analysis.quality.rating || 'Not available', 'Complete']);
247
+ }
248
+ if (analysis.security) {
249
+ summaryData.push(['Security', (summary.securityScore != null ? summary.securityScore + '/10' : 'Not available'), analysis.security.rating || 'Not available', analysis.security.riskLevel || 'Not available']);
250
+ }
251
+ if (analysis.codeReview) {
252
+ summaryData.push(['Collaboration', (summary.collaborationScore != null ? summary.collaborationScore + '/10' : 'Not available'), analysis.codeReview.rating || 'Not available', 'Complete']);
253
+ }
254
+ if (analysis.performance) {
255
+ summaryData.push(['Performance', (summary.performanceScore != null ? summary.performanceScore + '/10' : 'Not available'), analysis.performance.rating || 'Not available', analysis.performance.maintenancePattern?.maintenanceLevel ?? 'Not available']);
256
+ }
257
+ if (analysis.sonarCloud) {
258
+ const sc = analysis.sonarCloud;
259
+ const scScore = sc.available && sc.score != null ? sc.score + '/10' : 'Not available';
260
+ const scRating = sc.rating || 'Not available';
261
+ const scStatus = sc.available ? (sc.qualityGate?.status || 'Complete') : (sc.unavailableReason || 'Not available');
262
+ summaryData.push(['SonarCloud', scScore, scRating, scStatus]);
263
+ }
264
+ output += table(summaryData) + '\n';
265
+ output += chalk.bold.green(`\nāœ“ Analysis Complete\n`);
266
+
267
+ return output;
268
+ }
269
+
270
+ /**
271
+ * Generate summary statistics (includes SonarCloud when available for final report).
272
+ * Supports Sonar-only runs when quality, security, codeReview, performance are undefined.
273
+ */
274
+ static generateSummary(analysis) {
275
+ const scores = [];
276
+ if (analysis.quality?.score != null) scores.push(analysis.quality.score);
277
+ if (analysis.security?.score != null) scores.push(analysis.security.score);
278
+ if (analysis.codeReview?.score != null) scores.push(analysis.codeReview.score);
279
+ if (analysis.performance?.score != null) scores.push(analysis.performance.score);
280
+ if (analysis.sonarCloud?.available && analysis.sonarCloud.score != null) {
281
+ scores.push(analysis.sonarCloud.score);
282
+ }
283
+ if (scores.length === 0) {
284
+ return {
285
+ qualityScore: null,
286
+ securityScore: null,
287
+ collaborationScore: null,
288
+ performanceScore: null,
289
+ sonarCloudScore: null,
290
+ overallScore: null,
291
+ overallRating: null,
292
+ overallScore100: null,
293
+ healthStatus: 'Unavailable',
294
+ };
295
+ }
296
+ const overallScore10 = scores.reduce((a, b) => a + b, 0) / scores.length;
297
+ const overallScore = Math.round(overallScore10);
298
+ const overallRating = this.getRating(overallScore);
299
+ return {
300
+ qualityScore: analysis.quality?.score ?? null,
301
+ securityScore: analysis.security?.score ?? null,
302
+ collaborationScore: analysis.codeReview?.score ?? null,
303
+ performanceScore: analysis.performance?.score ?? null,
304
+ sonarCloudScore: analysis.sonarCloud?.available && analysis.sonarCloud.score != null ? analysis.sonarCloud.score : null,
305
+ overallScore,
306
+ overallRating,
307
+ overallScore100: Math.round(overallScore10 * 10),
308
+ healthStatus: overallScore >= 9 ? 'Excellent' : overallScore >= 7 ? 'Good' : overallScore >= 5 ? 'Fair' : 'Poor',
309
+ };
310
+ }
311
+
312
+ /**
313
+ * Helper: Get rating from score
314
+ */
315
+ static getRating(score) {
316
+ const n = Number(score);
317
+ if (Number.isNaN(n)) return 'Unavailable';
318
+ if (n >= 9) return 'A+';
319
+ if (n >= 8) return 'A';
320
+ if (n >= 7) return 'B+';
321
+ if (n >= 6) return 'B';
322
+ if (n >= 5) return 'C+';
323
+ if (n >= 4) return 'C';
324
+ return 'F';
325
+ }
326
+
327
+ /**
328
+ * Helper: Get score bar visualization
329
+ */
330
+ static getScoreBar(percentage) {
331
+ // Ensure percentage is in valid range
332
+ percentage = Math.max(0, Math.min(percentage, 100));
333
+
334
+ const filled = Math.max(0, Math.min(Math.round(percentage / 5), 20));
335
+ const empty = Math.max(0, 20 - filled);
336
+ const bar = 'ā–ˆ'.repeat(Math.max(0, filled)) + 'ā–‘'.repeat(Math.max(0, empty));
337
+
338
+ let color = chalk.green;
339
+ if (percentage < 40) color = chalk.red;
340
+ else if (percentage < 60) color = chalk.yellow;
341
+ else if (percentage < 80) color = chalk.cyan;
342
+
343
+ return color(`[${bar}] ${Math.round(percentage)}%`);
344
+ }
345
+
346
+ /**
347
+ * Helper: Get severity color
348
+ */
349
+ static getSeverityColor(severity) {
350
+ switch (severity) {
351
+ case 'CRITICAL':
352
+ return chalk.bgRed.white;
353
+ case 'HIGH':
354
+ return chalk.red;
355
+ case 'MEDIUM':
356
+ return chalk.yellow;
357
+ case 'LOW':
358
+ return chalk.green;
359
+ default:
360
+ return chalk.gray;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Helper: Get risk level color
366
+ */
367
+ static getRiskLevelColor(riskLevel) {
368
+ switch (riskLevel) {
369
+ case 'CRITICAL':
370
+ return chalk.bgRed.white;
371
+ case 'HIGH':
372
+ return chalk.red;
373
+ case 'MEDIUM':
374
+ return chalk.yellow;
375
+ case 'LOW':
376
+ return chalk.green;
377
+ default:
378
+ chalk.gray;
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Generate SonarCloud code quality section.
384
+ * Data is our combined summary from free-plan APIs (quality gate + measures/component); no single "Overall Code" API.
385
+ */
386
+ static generateSonarCloudSection(sonarData) {
387
+ let output = '';
388
+ output += chalk.bold.cyan(`\nšŸ“Š SonarCloud Code Quality (from API: Quality Gate + metrics)\n`);
389
+ if (!sonarData.available) {
390
+ output += chalk.gray(`Unavailable. ${sonarData.unavailableReason || 'Configure SONAR_TOKEN and add project on SonarCloud.'}\n`);
391
+ return output;
392
+ }
393
+ const summary = sonarData.overallSummary;
394
+ if (summary) {
395
+ output += chalk.gray(`Overall: ${summary.status === 'Passed' ? chalk.green(summary.status) : summary.status === 'Failed' ? chalk.red(summary.status) : summary.status}\n`);
396
+ }
397
+ output += chalk.gray(`Score: ${sonarData.score != null ? sonarData.score + '/10' : 'Not available'} - Rating: ${sonarData.rating || 'Not available'}\n`);
398
+ // output += chalk.gray(`Quality Gate: ${sonarData.qualityGate?.status === 'OK' ? chalk.green('Passed') : sonarData.qualityGate?.status === 'ERROR' ? chalk.red('Failed') : 'Unknown'}\n`);
399
+ if (sonarData.projectKey) output += chalk.gray(`Project: ${sonarData.projectKey}\n`);
400
+ const m = sonarData.metrics || {};
401
+ const metricsData = [
402
+ ['Metric', 'Value'],
403
+ ['Lines of Code', (m.ncloc ?? m.lines ?? 0).toString()],
404
+ ['Bugs', (m.bugs ?? 0).toString()],
405
+ ['Vulnerabilities', (m.vulnerabilities ?? 0).toString()],
406
+ // ['Code Smells', (m.codeSmells ?? 0).toString()],
407
+ ['Security Hotspots', (m.securityHotspots ?? 0).toString()],
408
+ ['Duplication', (m.duplicatedLinesDensity != null ? m.duplicatedLinesDensity + '%' : 'N/A')],
409
+ ['Coverage', m.coverage != null ? m.coverage + '%' : 'N/A'],
410
+ ['Complexity', (m.complexity ?? 0).toString()],
411
+ ];
412
+ output += table(metricsData) + '\n';
413
+
414
+ if (sonarData.issues?.items?.length > 0) {
415
+ output += chalk.bold(`Issues (first 5 of ${sonarData.issues.total} total):\n`);
416
+ sonarData.issues.items.slice(0, 5).forEach((issue) => {
417
+ output += ` [${issue.severity}] ${issue.type}: ${issue.message?.substring(0, 60)}...\n`;
418
+ });
419
+ output += '\n';
420
+ }
421
+ if (sonarData.recommendations?.length > 0) {
422
+ output += chalk.bold(`Recommendations:\n`);
423
+ sonarData.recommendations.forEach((rec, idx) => {
424
+ output += `${idx + 1}. [${rec.priority}] ${rec.category}: ${rec.action}\n`;
425
+ });
426
+ }
427
+ return output;
428
+ }
429
+
430
+ /**
431
+ * Convert CVSS score to severity level
432
+ */
433
+ static cvssToSeverity(cvss) {
434
+ if (cvss >= 9) return 'CRITICAL';
435
+ if (cvss >= 7) return 'HIGH';
436
+ if (cvss >= 4) return 'MEDIUM';
437
+ return 'LOW';
438
+ }
439
+
440
+ /**
441
+ * Get color function for severity
442
+ */
443
+ static getSeverityColor(severity) {
444
+ switch (severity?.toUpperCase()) {
445
+ case 'CRITICAL':
446
+ return chalk.bgRed.white;
447
+ case 'HIGH':
448
+ return chalk.red;
449
+ case 'MEDIUM':
450
+ return chalk.yellow;
451
+ case 'LOW':
452
+ return chalk.green;
453
+ default:
454
+ return chalk.gray;
455
+ }
456
+ }
457
+ }
458
+
459
+ export default ReportGenerator;