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.
- package/.github/copilot-instructions.md +108 -0
- package/.idea/aianalyzer.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/API_REFERENCE.md +244 -0
- package/ENHANCEMENTS.md +282 -0
- package/README.md +179 -0
- package/USAGE.md +189 -0
- package/analysis.txt +0 -0
- package/bin/cli.js +135 -0
- package/docs/SONARCLOUD_ANALYSIS_COVERED.md +144 -0
- package/docs/SonarCloud_Presentation_Points.md +81 -0
- package/docs/UI_IMPROVEMENTS.md +117 -0
- package/package-lock_cmd.json +542 -0
- package/package.json +44 -0
- package/package_command.json +16 -0
- package/public/analysis-options.json +31 -0
- package/public/images/README.txt +2 -0
- package/public/images/rws-logo.png +0 -0
- package/public/index.html +2433 -0
- package/repositories.example.txt +17 -0
- package/sample-repos.txt +20 -0
- package/src/analyzers/accessibility.js +47 -0
- package/src/analyzers/cicd-enhanced.js +113 -0
- package/src/analyzers/codeReview-enhanced.js +599 -0
- package/src/analyzers/codeReview-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/codeReview.js +171 -0
- package/src/analyzers/codeReview.js:Zone.Identifier +3 -0
- package/src/analyzers/documentation-enhanced.js +137 -0
- package/src/analyzers/performance-enhanced.js +747 -0
- package/src/analyzers/performance-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/performance.js +211 -0
- package/src/analyzers/performance.js:Zone.Identifier +3 -0
- package/src/analyzers/performance_cmd.js +216 -0
- package/src/analyzers/quality-enhanced.js +386 -0
- package/src/analyzers/quality-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/quality.js +92 -0
- package/src/analyzers/quality.js:Zone.Identifier +3 -0
- package/src/analyzers/security-enhanced.js +512 -0
- package/src/analyzers/security-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/snyk-ai.js:Zone.Identifier +3 -0
- package/src/analyzers/sonarcloud.js +928 -0
- package/src/analyzers/vulnerability.js +185 -0
- package/src/analyzers/vulnerability.js:Zone.Identifier +3 -0
- package/src/cli.js:Zone.Identifier +3 -0
- package/src/config.js +43 -0
- package/src/core/analyzerEngine.js +68 -0
- package/src/core/reportGenerator.js +21 -0
- package/src/gemini.js +321 -0
- package/src/github/client.js +124 -0
- package/src/github/client.js:Zone.Identifier +3 -0
- package/src/index.js +93 -0
- package/src/index_cmd.js +130 -0
- package/src/openai.js +297 -0
- package/src/report/generator.js +459 -0
- package/src/report/generator_cmd.js +459 -0
- package/src/report/pdf-generator.js +387 -0
- package/src/report/pdf-generator.js:Zone.Identifier +3 -0
- package/src/server.js +431 -0
- package/src/server.js:Zone.Identifier +3 -0
- package/src/server_cmd.js +434 -0
- package/src/sonarcloud/client.js +365 -0
- package/src/sonarcloud/scanner.js +171 -0
- 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;
|