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,599 @@
1
+ /**
2
+ * Enhanced Code Review & Collaboration Analyzer
3
+ * Analyzes coding style, practices, and collaboration patterns
4
+ */
5
+ export class CodeReviewAnalyzer {
6
+ constructor(githubClient) {
7
+ this.client = githubClient;
8
+ }
9
+
10
+ /**
11
+ * Comprehensive code review and collaboration analysis
12
+ */
13
+ async analyzeCodeReviewPractices(owner, repo) {
14
+ try {
15
+ const [pullRequests, commits, contributors, languages] = await Promise.all([
16
+ this.client.getPullRequests(owner, repo, { state: 'all' }),
17
+ this.client.getCommits(owner, repo, { perPage: 100 }),
18
+ this.client.getContributors(owner, repo),
19
+ this.client.getLanguages(owner, repo),
20
+ ]);
21
+
22
+ const reviewMetrics = this.calculateReviewMetrics(pullRequests);
23
+ const codingStyle = this.analyzeCodingStyle(commits, languages);
24
+ const collaborationScore = this.calculateCollaborationScore(
25
+ pullRequests,
26
+ contributors
27
+ );
28
+ const score = collaborationScore;
29
+ const rating = this.getRating(score);
30
+
31
+ return {
32
+ score,
33
+ rating,
34
+ reviewMetrics,
35
+ codingStyle,
36
+ collaborationMetrics: {
37
+ contributors: contributors.length,
38
+ activeContributors: this.getActiveContributors(contributors),
39
+ coreTeamSize: this.identifyCoreTeam(contributors),
40
+ diversityIndex: this.calculateTeamDiversity(contributors),
41
+ },
42
+ reviewPractices: this.assessReviewPractices(pullRequests),
43
+ recommendations: this.generateCollaborationRecommendations(
44
+ reviewMetrics,
45
+ codingStyle,
46
+ pullRequests
47
+ ),
48
+ details: {
49
+ prAnalysis: this.analyzePullRequestPatterns(pullRequests),
50
+ commitAnalysis: this.analyzeCommitPatterns(commits),
51
+ teamDynamics: this.analyzeTeamDynamics(pullRequests, contributors),
52
+ codeStandards: this.assessCodingStandards(languages),
53
+ },
54
+ };
55
+ } catch (error) {
56
+ throw new Error(`Code review analysis failed: ${error.message}`);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Calculate review metrics
62
+ */
63
+ calculateReviewMetrics(pullRequests) {
64
+ const totalPRs = pullRequests.length;
65
+ const closedPRs = pullRequests.filter((pr) => pr.state === 'closed').length;
66
+ const openPRs = pullRequests.filter((pr) => pr.state === 'open').length;
67
+ const mergedPRs = pullRequests.filter((pr) => pr.merged_at).length;
68
+ const draftPRs = pullRequests.filter((pr) => pr.draft).length;
69
+
70
+ let totalReviewTime = 0;
71
+ let reviewedPRs = 0;
72
+ let averageComments = 0;
73
+ let prWithApprovals = 0;
74
+
75
+ pullRequests.forEach((pr) => {
76
+ if (pr.merged_at && pr.created_at) {
77
+ const timeToMerge = new Date(pr.merged_at) - new Date(pr.created_at);
78
+ totalReviewTime += timeToMerge;
79
+ reviewedPRs++;
80
+ }
81
+ averageComments += pr.comments || 0;
82
+
83
+ // Estimate approvals (GitHub doesn't provide this directly via API)
84
+ if (pr.review_comments && pr.review_comments > 0) {
85
+ prWithApprovals++;
86
+ }
87
+ });
88
+
89
+ const avgReviewTimeHours =
90
+ reviewedPRs > 0 ? Math.round(totalReviewTime / reviewedPRs / (1000 * 60 * 60)) : 0;
91
+ const avgCommentsPerPR =
92
+ totalPRs > 0 ? (averageComments / totalPRs).toFixed(1) : 0;
93
+ const closureRate = totalPRs > 0 ? Math.round((closedPRs / totalPRs) * 100) : 0;
94
+ const approvalRate =
95
+ totalPRs > 0 ? Math.round((prWithApprovals / totalPRs) * 100) : 0;
96
+
97
+ return {
98
+ totalPullRequests: totalPRs,
99
+ closedPullRequests: closedPRs,
100
+ openPullRequests: openPRs,
101
+ mergedPullRequests: mergedPRs,
102
+ draftPullRequests: draftPRs,
103
+ averageReviewTimeHours: avgReviewTimeHours,
104
+ averageCommentsPerPR: avgCommentsPerPR,
105
+ prClosureRate: closureRate,
106
+ prApprovalRate: approvalRate,
107
+ reviewQuality: this.assessReviewQuality(avgReviewTimeHours, avgCommentsPerPR),
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Analyze coding style
113
+ */
114
+ analyzeCodingStyle(commits, languages) {
115
+ const primaryLanguages = Object.keys(languages)
116
+ .sort((a, b) => languages[b] - languages[a])
117
+ .slice(0, 3);
118
+
119
+ const commitMessages = commits.map((c) => c.commit?.message || '');
120
+ const conventionalCommits = commitMessages.filter(
121
+ (msg) =>
122
+ /^(feat|fix|docs|style|refactor|perf|test|chore)(\(.+\))?:/i.test(msg)
123
+ ).length;
124
+
125
+ const avgMessageLength =
126
+ commitMessages.length > 0
127
+ ? Math.round(commitMessages.reduce((a, b) => a + b.length, 0) / commitMessages.length)
128
+ : 0;
129
+
130
+ return {
131
+ primaryLanguages,
132
+ commitConventionAdherence: {
133
+ total: commits.length,
134
+ conventional: conventionalCommits,
135
+ percentage:
136
+ commits.length > 0
137
+ ? Math.round((conventionalCommits / commits.length) * 100)
138
+ : 0,
139
+ status:
140
+ commits.length > 0 && (conventionalCommits / commits.length) > 0.7
141
+ ? 'GOOD'
142
+ : 'NEEDS_IMPROVEMENT',
143
+ },
144
+ commitMessageQuality: {
145
+ averageLength: avgMessageLength,
146
+ status: avgMessageLength > 30 && avgMessageLength < 100 ? 'GOOD' : 'IMPROVE',
147
+ recommendation:
148
+ avgMessageLength < 30
149
+ ? 'Commit messages too short, add more context'
150
+ : avgMessageLength > 100
151
+ ? 'Commit messages too long, be more concise'
152
+ : 'Commit message length is good',
153
+ },
154
+ recommendations: [
155
+ 'Use conventional commits (feat:, fix:, docs:, etc.)',
156
+ 'Write descriptive commit messages',
157
+ 'Reference issues in commits (#123)',
158
+ 'Keep commits focused and atomic',
159
+ 'Avoid merge commits, use rebase',
160
+ ],
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Calculate collaboration score
166
+ */
167
+ calculateCollaborationScore(pullRequests, contributors) {
168
+ let score = 50;
169
+
170
+ // Multiple contributors (0-20 points)
171
+ score += Math.min(contributors.length * 2, 20);
172
+
173
+ // PR activity (0-20 points)
174
+ const totalPRs = pullRequests.length;
175
+ score += Math.min(totalPRs / 5, 20);
176
+
177
+ // PR closure rate (0-15 points)
178
+ const closedPRs = pullRequests.filter((pr) => pr.state === 'closed').length;
179
+ const closureRate = totalPRs > 0 ? (closedPRs / totalPRs) * 100 : 0;
180
+ if (closureRate > 80) {
181
+ score += 15;
182
+ } else if (closureRate > 60) {
183
+ score += 12;
184
+ } else if (closureRate > 40) {
185
+ score += 8;
186
+ } else if (closureRate > 0) {
187
+ score += 3;
188
+ }
189
+
190
+ // Review quality (0-15 points)
191
+ let avgReviewTime = 0;
192
+ pullRequests.forEach((pr) => {
193
+ if (pr.merged_at && pr.created_at) {
194
+ const timeToMerge = new Date(pr.merged_at) - new Date(pr.created_at);
195
+ avgReviewTime += timeToMerge;
196
+ }
197
+ });
198
+ const reviewedCount = pullRequests.filter((pr) => pr.merged_at).length;
199
+ avgReviewTime =
200
+ reviewedCount > 0 ? Math.round(avgReviewTime / reviewedCount / (1000 * 60 * 60)) : 0;
201
+
202
+ if (avgReviewTime < 24) {
203
+ score += 15;
204
+ } else if (avgReviewTime < 72) {
205
+ score += 12;
206
+ } else if (avgReviewTime < 168) {
207
+ score += 8;
208
+ } else {
209
+ score += 3;
210
+ }
211
+
212
+ return Math.min(score, 100);
213
+ }
214
+
215
+ /**
216
+ * Assess review practices
217
+ */
218
+ assessReviewPractices(pullRequests) {
219
+ const hasRequiredReviews = pullRequests.some((pr) => pr.requested_reviewers?.length > 0);
220
+ const hasCodeOwners = pullRequests.some((pr) => pr.labels?.some((l) => l.name === 'codeowners'));
221
+ const hasCIIntegration = pullRequests.some(
222
+ (pr) => pr.statuses_url && pr.review_comments > 0
223
+ );
224
+
225
+ const reviewComments = pullRequests
226
+ .filter((pr) => pr.merged_at)
227
+ .map((pr) => pr.review_comments || 0);
228
+ const avgReviewComments =
229
+ reviewComments.length > 0
230
+ ? Math.round(reviewComments.reduce((a, b) => a + b, 0) / reviewComments.length)
231
+ : 0;
232
+
233
+ return {
234
+ hasRequiredReviews,
235
+ hasCodeOwners,
236
+ hasCIIntegration,
237
+ averageReviewComments: avgReviewComments,
238
+ status: hasRequiredReviews && avgReviewComments > 2 ? 'STRONG' : 'NEEDS_IMPROVEMENT',
239
+ recommendations: [
240
+ 'Require pull request reviews before merging',
241
+ 'Use CODEOWNERS file for automated reviewer assignment',
242
+ 'Set minimum number of required approvals',
243
+ 'Dismiss stale review approvals on push',
244
+ 'Require status checks to pass',
245
+ ],
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Analyze pull request patterns
251
+ */
252
+ analyzePullRequestPatterns(pullRequests) {
253
+ const patterns = {
254
+ totalPRs: pullRequests.length,
255
+ averagePRSize: this.calculateAveragePRSize(pullRequests),
256
+ largestPR: this.findLargestPR(pullRequests),
257
+ smallestPR: this.findSmallestPR(pullRequests),
258
+ prsByStatus: {
259
+ merged: pullRequests.filter((pr) => pr.merged_at).length,
260
+ closed: pullRequests.filter((pr) => pr.state === 'closed' && !pr.merged_at).length,
261
+ open: pullRequests.filter((pr) => pr.state === 'open').length,
262
+ draft: pullRequests.filter((pr) => pr.draft).length,
263
+ },
264
+ recommendations: [
265
+ 'Keep PRs small and focused (200-400 lines)',
266
+ 'Avoid large batch PRs (>1000 lines)',
267
+ 'Aim for quick review cycles (< 24 hours)',
268
+ 'Require descriptive PR titles and descriptions',
269
+ 'Link related issues in PR descriptions',
270
+ ],
271
+ };
272
+ return patterns;
273
+ }
274
+
275
+ /**
276
+ * Analyze commit patterns
277
+ */
278
+ analyzeCommitPatterns(commits) {
279
+ const authors = new Set();
280
+ let totalAdditions = 0;
281
+ let totalDeletions = 0;
282
+
283
+ commits.forEach((commit) => {
284
+ if (commit.author) {
285
+ authors.add(commit.author.login);
286
+ }
287
+ });
288
+
289
+ return {
290
+ totalCommits: commits.length,
291
+ uniqueAuthors: authors.size,
292
+ averageCommitsPerAuthor:
293
+ authors.size > 0 ? Math.round(commits.length / authors.size) : 0,
294
+ commitFrequency: this.determineCommitFrequency(commits),
295
+ recommendations: [
296
+ 'Maintain regular commit cadence',
297
+ 'Encourage atomic, focused commits',
298
+ 'Use meaningful commit messages',
299
+ 'Sign commits for security',
300
+ 'Leverage squash commits for PRs',
301
+ ],
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Analyze team dynamics
307
+ */
308
+ analyzeTeamDynamics(pullRequests, contributors) {
309
+ const reviewerFrequency = {};
310
+ const authorFrequency = {};
311
+
312
+ pullRequests.forEach((pr) => {
313
+ if (pr.user) {
314
+ authorFrequency[pr.user.login] = (authorFrequency[pr.user.login] || 0) + 1;
315
+ }
316
+ });
317
+
318
+ return {
319
+ topContributors: Object.entries(authorFrequency)
320
+ .sort((a, b) => b[1] - a[1])
321
+ .slice(0, 5)
322
+ .map(([author, count]) => ({ author, contributionCount: count })),
323
+ totalContributors: contributors.length,
324
+ teamSize: this.categorizTeamSize(contributors.length),
325
+ collaborationHealth: this.assessCollaborationHealth(pullRequests, contributors),
326
+ recommendations: [
327
+ 'Distribute review responsibilities',
328
+ 'Mentor junior developers',
329
+ 'Rotate code review duties',
330
+ 'Document review standards',
331
+ 'Celebrate contributions',
332
+ ],
333
+ };
334
+ }
335
+
336
+ /**
337
+ * Assess coding standards
338
+ */
339
+ assessCodingStandards(languages) {
340
+ const standards = {};
341
+ Object.keys(languages).forEach((lang) => {
342
+ standards[lang] = this.getStandardsForLanguage(lang);
343
+ });
344
+
345
+ return {
346
+ primaryLanguageStandards: standards,
347
+ commonIssues: [
348
+ 'Inconsistent naming conventions',
349
+ 'Missing type hints/annotations',
350
+ 'Inadequate error handling',
351
+ 'Lack of input validation',
352
+ 'Insufficient code comments',
353
+ ],
354
+ recommendations: [
355
+ 'Implement linting rules (ESLint, Pylint, etc.)',
356
+ 'Use formatters (Prettier, Black, etc.)',
357
+ 'Define code style guide',
358
+ 'Enforce naming conventions',
359
+ 'Use static analysis tools',
360
+ ],
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Helper: Calculate average PR size
366
+ */
367
+ calculateAveragePRSize(pullRequests) {
368
+ const sizes = pullRequests
369
+ .map((pr) => (pr.additions || 0) + (pr.deletions || 0))
370
+ .filter((size) => size > 0);
371
+ return sizes.length > 0 ? Math.round(sizes.reduce((a, b) => a + b, 0) / sizes.length) : 0;
372
+ }
373
+
374
+ /**
375
+ * Helper: Find largest PR
376
+ */
377
+ findLargestPR(pullRequests) {
378
+ let largest = 0;
379
+ let largestPR = null;
380
+ pullRequests.forEach((pr) => {
381
+ const size = (pr.additions || 0) + (pr.deletions || 0);
382
+ if (size > largest) {
383
+ largest = size;
384
+ largestPR = { number: pr.number, size, title: pr.title };
385
+ }
386
+ });
387
+ return largestPR;
388
+ }
389
+
390
+ /**
391
+ * Helper: Find smallest PR
392
+ */
393
+ findSmallestPR(pullRequests) {
394
+ let smallest = Infinity;
395
+ let smallestPR = null;
396
+ pullRequests.forEach((pr) => {
397
+ const size = (pr.additions || 0) + (pr.deletions || 0);
398
+ if (size > 0 && size < smallest) {
399
+ smallest = size;
400
+ smallestPR = { number: pr.number, size, title: pr.title };
401
+ }
402
+ });
403
+ return smallestPR;
404
+ }
405
+
406
+ /**
407
+ * Helper: Assess review quality
408
+ */
409
+ assessReviewQuality(avgReviewTime, avgComments) {
410
+ if (avgReviewTime < 24 && avgComments > 2) {
411
+ return 'EXCELLENT';
412
+ }
413
+ if (avgReviewTime < 72 && avgComments > 1) {
414
+ return 'GOOD';
415
+ }
416
+ if (avgReviewTime < 168) {
417
+ return 'ACCEPTABLE';
418
+ }
419
+ return 'NEEDS_IMPROVEMENT';
420
+ }
421
+
422
+ /**
423
+ * Helper: Get active contributors
424
+ */
425
+ getActiveContributors(contributors) {
426
+ return contributors.filter((c) => (c.contributions || 0) > 10).length;
427
+ }
428
+
429
+ /**
430
+ * Helper: Identify core team
431
+ */
432
+ identifyCoreTeam(contributors) {
433
+ const topContributions = contributors
434
+ .map((c) => c.contributions || 0)
435
+ .sort((a, b) => b - a)
436
+ .slice(0, 3);
437
+ return topContributions.reduce((a, b) => a + b, 0);
438
+ }
439
+
440
+ /**
441
+ * Helper: Calculate team diversity
442
+ */
443
+ calculateTeamDiversity(contributors) {
444
+ return Math.min(contributors.length / 5, 10); // Score out of 10
445
+ }
446
+
447
+ /**
448
+ * Helper: Determine commit frequency
449
+ */
450
+ determineCommitFrequency(commits) {
451
+ if (commits.length === 0) return 'None';
452
+ if (commits.length > 500) return 'Very High';
453
+ if (commits.length > 100) return 'High';
454
+ if (commits.length > 20) return 'Moderate';
455
+ return 'Low';
456
+ }
457
+
458
+ /**
459
+ * Helper: Categorize team size
460
+ */
461
+ categorizTeamSize(count) {
462
+ if (count === 1) return 'Solo';
463
+ if (count <= 3) return 'Small';
464
+ if (count <= 10) return 'Medium';
465
+ if (count <= 50) return 'Large';
466
+ return 'Enterprise';
467
+ }
468
+
469
+ /**
470
+ * Helper: Assess collaboration health
471
+ */
472
+ assessCollaborationHealth(pullRequests, contributors) {
473
+ const avgPRsPerContributor = pullRequests.length / (contributors.length || 1);
474
+ if (avgPRsPerContributor > 5) return 'EXCELLENT';
475
+ if (avgPRsPerContributor > 2) return 'GOOD';
476
+ if (avgPRsPerContributor > 0) return 'FAIR';
477
+ return 'NEEDS_IMPROVEMENT';
478
+ }
479
+
480
+ /**
481
+ * Helper: Get standards for language
482
+ */
483
+ getStandardsForLanguage(language) {
484
+ const languageStandards = {
485
+ JavaScript: {
486
+ linter: 'ESLint',
487
+ formatter: 'Prettier',
488
+ testFramework: 'Jest, Mocha',
489
+ style: 'Google JavaScript Style Guide',
490
+ },
491
+ Python: {
492
+ linter: 'Pylint, Flake8',
493
+ formatter: 'Black',
494
+ testFramework: 'pytest',
495
+ style: 'PEP 8',
496
+ },
497
+ TypeScript: {
498
+ linter: 'ESLint',
499
+ formatter: 'Prettier',
500
+ testFramework: 'Jest',
501
+ style: 'Google TypeScript Style Guide',
502
+ },
503
+ Java: {
504
+ linter: 'Checkstyle',
505
+ formatter: 'Google Java Format',
506
+ testFramework: 'JUnit, Mockito',
507
+ style: 'Google Java Style Guide',
508
+ },
509
+ 'C++': {
510
+ linter: 'Clang-Tidy',
511
+ formatter: 'Clang-Format',
512
+ testFramework: 'Google Test',
513
+ style: 'Google C++ Style Guide',
514
+ },
515
+ Go: {
516
+ linter: 'GolangCI-Lint',
517
+ formatter: 'gofmt',
518
+ testFramework: 'testing package',
519
+ style: 'Effective Go',
520
+ },
521
+ };
522
+
523
+ return languageStandards[language] || {
524
+ linter: 'Not specified',
525
+ formatter: 'Not specified',
526
+ testFramework: 'Not specified',
527
+ style: 'Check community standards',
528
+ };
529
+ }
530
+
531
+ /**
532
+ * Generate collaboration recommendations
533
+ */
534
+ generateCollaborationRecommendations(reviewMetrics, codingStyle, pullRequests) {
535
+ const recommendations = [];
536
+
537
+ if (reviewMetrics.prClosureRate < 60) {
538
+ recommendations.push({
539
+ category: 'PR Management',
540
+ issue: 'Low PR closure rate',
541
+ action: 'Implement weekly triage meetings for stale PRs',
542
+ priority: 'HIGH',
543
+ });
544
+ }
545
+
546
+ if (reviewMetrics.averageReviewTimeHours > 72) {
547
+ recommendations.push({
548
+ category: 'Review Efficiency',
549
+ issue: 'Slow review turnaround',
550
+ action: 'Establish review SLAs (e.g., 24-hour response time)',
551
+ priority: 'HIGH',
552
+ });
553
+ }
554
+
555
+ if (codingStyle.commitConventionAdherence.percentage < 70) {
556
+ recommendations.push({
557
+ category: 'Coding Standards',
558
+ issue: 'Inconsistent commit messages',
559
+ action: 'Enforce conventional commits with husky/commitlint',
560
+ priority: 'MEDIUM',
561
+ });
562
+ }
563
+
564
+ if (reviewMetrics.averageCommentsPerPR < 2) {
565
+ recommendations.push({
566
+ category: 'Code Quality',
567
+ issue: 'Insufficient review comments',
568
+ action: 'Encourage thorough code reviews, use checklists',
569
+ priority: 'MEDIUM',
570
+ });
571
+ }
572
+
573
+ return recommendations.length > 0
574
+ ? recommendations
575
+ : [
576
+ {
577
+ category: 'Maintenance',
578
+ issue: 'Keep up the good work',
579
+ action: 'Maintain current practices',
580
+ priority: 'LOW',
581
+ },
582
+ ];
583
+ }
584
+
585
+ /**
586
+ * Get rating from score
587
+ */
588
+ getRating(score) {
589
+ if (score >= 90) return 'A+';
590
+ if (score >= 80) return 'A';
591
+ if (score >= 70) return 'B+';
592
+ if (score >= 60) return 'B';
593
+ if (score >= 50) return 'C+';
594
+ if (score >= 40) return 'C';
595
+ return 'F';
596
+ }
597
+ }
598
+
599
+ export default CodeReviewAnalyzer;
@@ -0,0 +1,3 @@
1
+ [ZoneTransfer]
2
+ ZoneId=3
3
+ ReferrerUrl=C:\Users\jitendra.yadav\Downloads\GitRepoAnalyzer - Shabbir.zip