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,747 @@
1
+ /**
2
+ * Enhanced Performance & Release Velocity Analyzer
3
+ * Analyzes release frequency, development velocity, and performance patterns
4
+ */
5
+ export class PerformanceAnalyzer {
6
+ constructor(githubClient) {
7
+ this.client = githubClient;
8
+ }
9
+
10
+ /**
11
+ * Comprehensive performance and release analysis
12
+ */
13
+ async analyzePerformance(owner, repo, repoData) {
14
+ try {
15
+ const [releases, commits, codeFrequency] = await Promise.all([
16
+ this.client.getReleases(owner, repo),
17
+ this.client.getCommits(owner, repo, { perPage: 100 }),
18
+ this.client.getCodeFrequency(owner, repo).catch(() => ({})),
19
+ ]);
20
+
21
+ const releaseVelocity = this.analyzeReleaseVelocity(releases);
22
+ const developmentVelocity = this.calculateDevelopmentVelocity(codeFrequency, commits);
23
+ const releaseQuality = this.assessReleaseQuality(releases, commits);
24
+ const maintenancePattern = this.analyzeMaintenancePattern(
25
+ commits,
26
+ releases,
27
+ repoData
28
+ );
29
+
30
+ const score = this.calculatePerformanceScore(
31
+ releaseVelocity,
32
+ developmentVelocity,
33
+ releaseQuality,
34
+ maintenancePattern
35
+ );
36
+ const rating = this.getRating(score);
37
+
38
+ return {
39
+ score,
40
+ rating,
41
+ releaseVelocity,
42
+ developmentVelocity,
43
+ releaseQuality,
44
+ maintenancePattern,
45
+ performanceMetrics: {
46
+ totalReleases: releases.length,
47
+ averageReleaseCycle: this.calculateAverageReleaseCycle(releases),
48
+ timeToFirstRelease: this.calculateTimeToFirstRelease(releases),
49
+ developmentActivityTrend: this.analyzeDevelopmentTrend(commits),
50
+ updateFrequency: this.assessUpdateFrequency(commits),
51
+ },
52
+ recommendations: this.generatePerformanceRecommendations(
53
+ releaseVelocity,
54
+ developmentVelocity,
55
+ maintenancePattern
56
+ ),
57
+ details: {
58
+ releasePatterns: this.analyzeReleasePatterns(releases),
59
+ commitPatterns: this.analyzeCommitPatterns(commits),
60
+ developmentHealth: this.assessDevelopmentHealth(commits, releases),
61
+ releaseCadence: this.analyzeReleaseCadence(releases),
62
+ },
63
+ };
64
+ } catch (error) {
65
+ throw new Error(`Performance analysis failed: ${error.message}`);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Analyze release velocity
71
+ */
72
+ analyzeReleaseVelocity(releases) {
73
+ if (releases.length === 0) {
74
+ return {
75
+ totalReleases: 0,
76
+ preReleases: 0,
77
+ stableReleases: 0,
78
+ averageReleasesPerYear: 0,
79
+ averageReleasesPerMonth: 0,
80
+ releaseCadence: 'None - No releases yet',
81
+ velocity: 'STAGNANT',
82
+ status: 'NO_RELEASES',
83
+ recommendations: [
84
+ 'Create initial release version',
85
+ 'Establish release schedule',
86
+ 'Use semantic versioning (semver)',
87
+ 'Document release process',
88
+ 'Consider beta/pre-release versions',
89
+ ],
90
+ };
91
+ }
92
+
93
+ const stableReleases = releases.filter((r) => !r.prerelease).length;
94
+ const preReleases = releases.filter((r) => r.prerelease).length;
95
+ const sortedReleases = releases.sort(
96
+ (a, b) => new Date(b.published_at) - new Date(a.published_at)
97
+ );
98
+
99
+ let releaseSpanDays = 0;
100
+ if (sortedReleases.length > 1) {
101
+ releaseSpanDays =
102
+ (new Date(sortedReleases[0].published_at) -
103
+ new Date(sortedReleases[sortedReleases.length - 1].published_at)) /
104
+ (1000 * 60 * 60 * 24);
105
+ }
106
+
107
+ const yearsActive = Math.max(releaseSpanDays / 365, 1);
108
+ const releasesPerYear = Math.round((releases.length / yearsActive) * 10) / 10;
109
+ const releasesPerMonth = Math.round((releasesPerYear / 12) * 10) / 10;
110
+
111
+ return {
112
+ totalReleases: releases.length,
113
+ stableReleases,
114
+ preReleases,
115
+ averageReleasesPerYear: releasesPerYear,
116
+ averageReleasesPerMonth: releasesPerMonth,
117
+ releaseCadence: this.determineReleaseCadence(releasesPerYear),
118
+ velocity: this.assessReleaseVelocity(releasesPerYear),
119
+ daysSinceLastRelease: this.calculateDaysSinceLastRelease(sortedReleases[0]),
120
+ status: this.assessReleaseStatus(sortedReleases[0]),
121
+ recommendations: [
122
+ 'Maintain consistent release schedule',
123
+ 'Use semantic versioning strictly',
124
+ 'Automate release processes',
125
+ 'Include release notes and changelogs',
126
+ 'Tag releases in version control',
127
+ ],
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Calculate development velocity
133
+ */
134
+ calculateDevelopmentVelocity(codeFrequency, commits) {
135
+ let additions = 0;
136
+ let deletions = 0;
137
+
138
+ // Handle codeFrequency - it's an array of [timestamp, additions, deletions]
139
+ if (Array.isArray(codeFrequency)) {
140
+ codeFrequency.forEach((item) => {
141
+ if (Array.isArray(item) && item.length === 3) {
142
+ additions += item[1] || 0;
143
+ deletions += item[2] || 0;
144
+ }
145
+ });
146
+ }
147
+
148
+ const netChanges = additions - Math.abs(deletions);
149
+ const commitCount = commits.length;
150
+ const avgLinesPerCommit = commitCount > 0 ? Math.round(netChanges / commitCount) : 0;
151
+
152
+ const recentCommits = commits.slice(0, 10);
153
+ let recentActivity = 0;
154
+ if (recentCommits.length > 0) {
155
+ const oldestRecent = new Date(recentCommits[recentCommits.length - 1].commit.author.date);
156
+ const newestRecent = new Date(recentCommits[0].commit.author.date);
157
+ const daysSinceLastCommit = (Date.now() - newestRecent) / (1000 * 60 * 60 * 24);
158
+ recentActivity = Math.max(0, Math.round((30 - daysSinceLastCommit) / 3) * 10);
159
+ }
160
+
161
+ return {
162
+ totalAdditions: additions,
163
+ totalDeletions: Math.abs(deletions),
164
+ netChanges,
165
+ totalCommits: commitCount,
166
+ averageLinesPerCommit: avgLinesPerCommit,
167
+ velocity: this.assessVelocityTrend(commits),
168
+ recentActivity,
169
+ activityLevel: this.assessActivityLevel(recentActivity, commitCount),
170
+ codeStability: this.assessCodeStability(additions, deletions),
171
+ recommendations: [
172
+ 'Maintain steady development pace',
173
+ 'Monitor code churn (high deletions)',
174
+ 'Balance feature development with refactoring',
175
+ 'Keep commits focused and atomic',
176
+ 'Review large changes carefully',
177
+ ],
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Assess release quality
183
+ */
184
+ assessReleaseQuality(releases, commits) {
185
+ const recentReleases = releases.slice(0, 5);
186
+ const avgAssetsPerRelease = Math.round(
187
+ recentReleases.reduce((sum, r) => sum + (r.assets?.length || 0), 0) /
188
+ (recentReleases.length || 1)
189
+ );
190
+
191
+ const releasesWithNotes = recentReleases.filter((r) => r.body && r.body.length > 50).length;
192
+ const notesQuality =
193
+ recentReleases.length > 0
194
+ ? Math.round((releasesWithNotes / recentReleases.length) * 100)
195
+ : 0;
196
+
197
+ return {
198
+ recentReleasesAnalyzed: recentReleases.length,
199
+ averageAssetsPerRelease: avgAssetsPerRelease,
200
+ releasesWithDocumentation: releasesWithNotes,
201
+ documentationPercentage: notesQuality,
202
+ documentationQuality:
203
+ notesQuality >= 80
204
+ ? 'EXCELLENT'
205
+ : notesQuality >= 60
206
+ ? 'GOOD'
207
+ : notesQuality >= 40
208
+ ? 'FAIR'
209
+ : 'NEEDS_IMPROVEMENT',
210
+ changelogPresence: this.hasChangelog(commits),
211
+ releaseAutomation: this.detectReleaseAutomation(releases),
212
+ recommendations: [
213
+ 'Include comprehensive release notes',
214
+ 'Document breaking changes clearly',
215
+ 'Provide downloadable assets',
216
+ 'Tag commits with version numbers',
217
+ 'Consider automated release pipelines',
218
+ ],
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Analyze maintenance pattern
224
+ */
225
+ analyzeMaintenancePattern(commits, releases, repoData) {
226
+ const allCommits = commits;
227
+ const sortedCommits = allCommits
228
+ .map((c) => new Date(c.commit.author.date))
229
+ .sort((a, b) => b - a);
230
+
231
+ const daysSinceLastCommit =
232
+ sortedCommits.length > 0 ? (Date.now() - sortedCommits[0]) / (1000 * 60 * 60 * 24) : Infinity;
233
+
234
+ const maintenanceLevel =
235
+ daysSinceLastCommit < 30
236
+ ? 'ACTIVE'
237
+ : daysSinceLastCommit < 90
238
+ ? 'MODERATE'
239
+ : daysSinceLastCommit < 180
240
+ ? 'DORMANT'
241
+ : 'INACTIVE';
242
+
243
+ return {
244
+ daysSinceLastCommit: Math.round(daysSinceLastCommit),
245
+ maintenanceLevel,
246
+ status: this.describeMaintenance(maintenanceLevel),
247
+ projectedMaintenanceHealth: this.projectMaintenanceHealth(
248
+ maintenanceLevel,
249
+ daysSinceLastCommit
250
+ ),
251
+ lastUpdateIndicator: this.formatTimeAgo(daysSinceLastCommit),
252
+ updateFrequency: this.analyzeUpdateFrequency(commits),
253
+ sustainability: this.assessSustainability(commits, releases, repoData),
254
+ recommendations: [
255
+ maintenanceLevel === 'ACTIVE' ? 'Keep up regular maintenance' : 'Increase maintenance effort',
256
+ 'Respond to community issues promptly',
257
+ 'Update dependencies regularly',
258
+ 'Address security vulnerabilities quickly',
259
+ maintenanceLevel !== 'ACTIVE' ? 'Consider automation to reduce overhead' : 'Maintain current schedule',
260
+ ],
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Calculate performance score
266
+ */
267
+ calculatePerformanceScore(releaseVelocity, developmentVelocity, releaseQuality, maintenancePattern) {
268
+ let score = 50; // Base score
269
+
270
+ // Release velocity (0-20 points)
271
+ const releasesPerYear = releaseVelocity.averageReleasesPerYear;
272
+ if (releasesPerYear >= 12) {
273
+ score += 20;
274
+ } else if (releasesPerYear >= 6) {
275
+ score += 15;
276
+ } else if (releasesPerYear >= 2) {
277
+ score += 10;
278
+ } else if (releasesPerYear > 0) {
279
+ score += 5;
280
+ }
281
+
282
+ // Development velocity (0-20 points)
283
+ if (developmentVelocity.commitCount > 1000) {
284
+ score += 20;
285
+ } else if (developmentVelocity.commitCount > 500) {
286
+ score += 15;
287
+ } else if (developmentVelocity.commitCount > 100) {
288
+ score += 10;
289
+ } else if (developmentVelocity.commitCount > 10) {
290
+ score += 5;
291
+ }
292
+
293
+ // Release quality (0-15 points)
294
+ if (releaseQuality.documentationPercentage >= 80) {
295
+ score += 15;
296
+ } else if (releaseQuality.documentationPercentage >= 60) {
297
+ score += 12;
298
+ } else if (releaseQuality.documentationPercentage >= 40) {
299
+ score += 8;
300
+ } else if (releaseQuality.documentationPercentage > 0) {
301
+ score += 4;
302
+ }
303
+
304
+ // Maintenance pattern (0-15 points)
305
+ if (maintenancePattern.maintenanceLevel === 'ACTIVE') {
306
+ score += 15;
307
+ } else if (maintenancePattern.maintenanceLevel === 'MODERATE') {
308
+ score += 10;
309
+ } else if (maintenancePattern.maintenanceLevel === 'DORMANT') {
310
+ score += 5;
311
+ }
312
+
313
+ // Recency bonus (0-10 points)
314
+ if (maintenancePattern.daysSinceLastCommit < 7) {
315
+ score += 10;
316
+ } else if (maintenancePattern.daysSinceLastCommit < 30) {
317
+ score += 8;
318
+ } else if (maintenancePattern.daysSinceLastCommit < 90) {
319
+ score += 5;
320
+ } else if (maintenancePattern.daysSinceLastCommit < 180) {
321
+ score += 2;
322
+ }
323
+
324
+ return Math.min(score, 100);
325
+ }
326
+
327
+ /**
328
+ * Helper: Determine release cadence
329
+ */
330
+ determineReleaseCadence(releasesPerYear) {
331
+ if (releasesPerYear >= 12) return 'Monthly or more frequent';
332
+ if (releasesPerYear >= 6) return 'Bi-monthly';
333
+ if (releasesPerYear >= 4) return 'Quarterly';
334
+ if (releasesPerYear >= 2) return 'Semi-annual';
335
+ if (releasesPerYear >= 1) return 'Annual';
336
+ return 'Irregular';
337
+ }
338
+
339
+ /**
340
+ * Helper: Assess release velocity
341
+ */
342
+ assessReleaseVelocity(releasesPerYear) {
343
+ if (releasesPerYear >= 12) return 'EXCELLENT';
344
+ if (releasesPerYear >= 6) return 'GOOD';
345
+ if (releasesPerYear >= 2) return 'FAIR';
346
+ if (releasesPerYear > 0) return 'SLOW';
347
+ return 'STAGNANT';
348
+ }
349
+
350
+ /**
351
+ * Helper: Calculate days since last release
352
+ */
353
+ calculateDaysSinceLastRelease(lastRelease) {
354
+ if (!lastRelease) return null;
355
+ return Math.round((Date.now() - new Date(lastRelease.published_at)) / (1000 * 60 * 60 * 24));
356
+ }
357
+
358
+ /**
359
+ * Helper: Assess release status
360
+ */
361
+ assessReleaseStatus(lastRelease) {
362
+ if (!lastRelease) return 'NO_RELEASES';
363
+ const daysSince = this.calculateDaysSinceLastRelease(lastRelease);
364
+ if (daysSince < 30) return 'RECENT';
365
+ if (daysSince < 90) return 'CURRENT';
366
+ if (daysSince < 180) return 'AGING';
367
+ return 'STALE';
368
+ }
369
+
370
+ /**
371
+ * Helper: Assess velocity trend
372
+ */
373
+ assessVelocityTrend(commits) {
374
+ const recentCommits = commits.slice(0, 50);
375
+ const olderCommits = commits.slice(50, 100);
376
+
377
+ const recentRate = recentCommits.length / Math.max(1, recentCommits.length);
378
+ const olderRate = olderCommits.length / Math.max(1, olderCommits.length);
379
+
380
+ if (recentRate > olderRate * 1.2) return 'ACCELERATING';
381
+ if (recentRate < olderRate * 0.8) return 'DECELERATING';
382
+ return 'STABLE';
383
+ }
384
+
385
+ /**
386
+ * Helper: Assess activity level
387
+ */
388
+ assessActivityLevel(recentActivity, totalCommits) {
389
+ if (recentActivity > 70 || totalCommits > 1000) return 'VERY_HIGH';
390
+ if (recentActivity > 50 || totalCommits > 500) return 'HIGH';
391
+ if (recentActivity > 30 || totalCommits > 100) return 'MODERATE';
392
+ if (recentActivity > 10 || totalCommits > 20) return 'LOW';
393
+ return 'MINIMAL';
394
+ }
395
+
396
+ /**
397
+ * Helper: Assess code stability
398
+ */
399
+ assessCodeStability(additions, deletions) {
400
+ if (additions === 0) return 'UNKNOWN';
401
+ const ratio = Math.abs(deletions) / additions;
402
+ if (ratio > 0.5) return 'UNSTABLE';
403
+ if (ratio > 0.3) return 'MODERATE';
404
+ return 'STABLE';
405
+ }
406
+
407
+ /**
408
+ * Helper: Calculate average release cycle
409
+ */
410
+ calculateAverageReleaseCycle(releases) {
411
+ if (releases.length < 2) return null;
412
+
413
+ const sortedReleases = releases
414
+ .sort((a, b) => new Date(b.published_at) - new Date(a.published_at))
415
+ .slice(0, 10);
416
+
417
+ let totalDays = 0;
418
+ for (let i = 0; i < sortedReleases.length - 1; i++) {
419
+ const timeDiff = new Date(sortedReleases[i].published_at) -
420
+ new Date(sortedReleases[i + 1].published_at);
421
+ totalDays += timeDiff / (1000 * 60 * 60 * 24);
422
+ }
423
+
424
+ return Math.round(totalDays / (sortedReleases.length - 1));
425
+ }
426
+
427
+ /**
428
+ * Helper: Calculate time to first release
429
+ */
430
+ calculateTimeToFirstRelease(releases) {
431
+ if (releases.length === 0) return null;
432
+ const firstRelease = releases[releases.length - 1];
433
+ return Math.round(
434
+ (Date.now() - new Date(firstRelease.published_at)) / (1000 * 60 * 60 * 24)
435
+ );
436
+ }
437
+
438
+ /**
439
+ * Helper: Analyze development trend
440
+ */
441
+ analyzeDevelopmentTrend(commits) {
442
+ const sortedCommits = commits
443
+ .map((c) => new Date(c.commit.author.date))
444
+ .sort((a, b) => b - a);
445
+
446
+ if (sortedCommits.length === 0) return 'UNKNOWN';
447
+
448
+ const recentDate = sortedCommits[0];
449
+ const fourWeeksAgo = new Date(recentDate - 28 * 24 * 60 * 60 * 1000);
450
+ const recentCommitCount = sortedCommits.filter((d) => d > fourWeeksAgo).length;
451
+
452
+ if (recentCommitCount > 10) return 'UPWARD';
453
+ if (recentCommitCount > 3) return 'STABLE';
454
+ if (recentCommitCount > 0) return 'DECLINING';
455
+ return 'STAGNANT';
456
+ }
457
+
458
+ /**
459
+ * Helper: Assess update frequency
460
+ */
461
+ assessUpdateFrequency(commits) {
462
+ if (commits.length === 0) return 'NEVER';
463
+ if (commits.length > 1000) return 'VERY_FREQUENT';
464
+ if (commits.length > 500) return 'FREQUENT';
465
+ if (commits.length > 100) return 'REGULAR';
466
+ if (commits.length > 20) return 'OCCASIONAL';
467
+ return 'RARE';
468
+ }
469
+
470
+ /**
471
+ * Helper: Analyze release patterns
472
+ */
473
+ analyzeReleasePatterns(releases) {
474
+ return {
475
+ totalReleases: releases.length,
476
+ preReleases: releases.filter((r) => r.prerelease).length,
477
+ drafts: releases.filter((r) => r.draft).length,
478
+ tags: releases.map((r) => r.tag_name),
479
+ recentReleases: releases.slice(0, 5).map((r) => ({
480
+ tag: r.tag_name,
481
+ date: r.published_at,
482
+ prerelease: r.prerelease,
483
+ })),
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Helper: Analyze commit patterns
489
+ */
490
+ analyzeCommitPatterns(commits) {
491
+ const commitsByDay = {};
492
+ commits.forEach((c) => {
493
+ const date = new Date(c.commit.author.date).toISOString().split('T')[0];
494
+ commitsByDay[date] = (commitsByDay[date] || 0) + 1;
495
+ });
496
+
497
+ const avgCommitsPerDay = Object.keys(commitsByDay).length > 0
498
+ ? Math.round(commits.length / Object.keys(commitsByDay).length)
499
+ : 0;
500
+
501
+ return {
502
+ totalCommits: commits.length,
503
+ uniqueDays: Object.keys(commitsByDay).length,
504
+ averageCommitsPerDay: avgCommitsPerDay,
505
+ busyDays: Object.values(commitsByDay).filter((c) => c > 3).length,
506
+ quietDays: Object.values(commitsByDay).filter((c) => c === 1).length,
507
+ };
508
+ }
509
+
510
+ /**
511
+ * Helper: Assess development health
512
+ */
513
+ assessDevelopmentHealth(commits, releases) {
514
+ const healthFactors = {
515
+ commitActivity: commits.length > 100 ? 'GOOD' : commits.length > 20 ? 'FAIR' : 'POOR',
516
+ releaseActivity: releases.length > 5 ? 'GOOD' : releases.length > 1 ? 'FAIR' : 'POOR',
517
+ recentActivity: this.analyzeDevelopmentTrend(commits),
518
+ };
519
+
520
+ return healthFactors;
521
+ }
522
+
523
+ /**
524
+ * Helper: Analyze release cadence
525
+ */
526
+ analyzeReleaseCadence(releases) {
527
+ if (releases.length < 2) {
528
+ return {
529
+ status: 'INSUFFICIENT_DATA',
530
+ pattern: 'Not enough releases to determine pattern',
531
+ };
532
+ }
533
+
534
+ const sortedReleases = releases.sort(
535
+ (a, b) => new Date(b.published_at) - new Date(a.published_at)
536
+ );
537
+
538
+ const intervals = [];
539
+ for (let i = 0; i < Math.min(sortedReleases.length - 1, 5); i++) {
540
+ const interval = (new Date(sortedReleases[i].published_at) -
541
+ new Date(sortedReleases[i + 1].published_at)) /
542
+ (1000 * 60 * 60 * 24);
543
+ intervals.push(Math.round(interval));
544
+ }
545
+
546
+ const avgInterval = Math.round(intervals.reduce((a, b) => a + b, 0) / intervals.length);
547
+ const consistency = this.assessCadenceConsistency(intervals);
548
+
549
+ return {
550
+ averageIntervalDays: avgInterval,
551
+ pattern: this.determineReleaseCadence(365 / (avgInterval || 1)),
552
+ recentIntervals: intervals,
553
+ consistency,
554
+ };
555
+ }
556
+
557
+ /**
558
+ * Helper: Assess cadence consistency
559
+ */
560
+ assessCadenceConsistency(intervals) {
561
+ if (intervals.length < 2) return 'UNKNOWN';
562
+ const mean = intervals.reduce((a, b) => a + b) / intervals.length;
563
+ const variance = intervals.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / intervals.length;
564
+ const stdDev = Math.sqrt(variance);
565
+ const cv = stdDev / mean;
566
+
567
+ if (cv < 0.3) return 'CONSISTENT';
568
+ if (cv < 0.6) return 'MODERATE';
569
+ return 'IRREGULAR';
570
+ }
571
+
572
+ /**
573
+ * Helper: Has changelog
574
+ */
575
+ hasChangelog(commits) {
576
+ return commits.some((c) => c.commit.message.toLowerCase().includes('changelog'));
577
+ }
578
+
579
+ /**
580
+ * Helper: Detect release automation
581
+ */
582
+ detectReleaseAutomation(releases) {
583
+ return releases.some((r) => r.author?.login === 'github-actions' || r.author?.type === 'Bot');
584
+ }
585
+
586
+ /**
587
+ * Helper: Describe maintenance
588
+ */
589
+ describeMaintenance(level) {
590
+ const descriptions = {
591
+ ACTIVE: 'Repository is actively maintained with regular updates',
592
+ MODERATE: 'Repository is maintained but with lower frequency',
593
+ DORMANT: 'Repository has been inactive for several months',
594
+ INACTIVE: 'Repository appears to be abandoned',
595
+ };
596
+ return descriptions[level] || 'Unknown maintenance status';
597
+ }
598
+
599
+ /**
600
+ * Helper: Project maintenance health
601
+ */
602
+ projectMaintenanceHealth(level, daysSince) {
603
+ if (level === 'ACTIVE') {
604
+ return daysSince < 7 ? 'EXCELLENT' : daysSince < 30 ? 'GOOD' : 'FAIR';
605
+ }
606
+ if (level === 'MODERATE') return 'FAIR';
607
+ if (level === 'DORMANT') return 'POOR';
608
+ return 'CRITICAL';
609
+ }
610
+
611
+ /**
612
+ * Helper: Format time ago
613
+ */
614
+ formatTimeAgo(days) {
615
+ if (days < 1) return 'Today';
616
+ if (days < 7) return `${Math.round(days)} days ago`;
617
+ if (days < 30) return `${Math.round(days / 7)} weeks ago`;
618
+ if (days < 365) return `${Math.round(days / 30)} months ago`;
619
+ return `${Math.round(days / 365)} years ago`;
620
+ }
621
+
622
+ /**
623
+ * Helper: Analyze update frequency
624
+ */
625
+ analyzeUpdateFrequency(commits) {
626
+ if (commits.length === 0) return 'NEVER';
627
+
628
+ const sortedDates = commits
629
+ .map((c) => new Date(c.commit.author.date))
630
+ .sort((a, b) => b - a);
631
+
632
+ const firstDate = sortedDates[sortedDates.length - 1];
633
+ const lastDate = sortedDates[0];
634
+ const daysSpan = (lastDate - firstDate) / (1000 * 60 * 60 * 24);
635
+
636
+ if (daysSpan === 0) return 'SINGLE_COMMIT';
637
+ const frequency = Math.round((commits.length / daysSpan) * 365);
638
+
639
+ if (frequency > 200) return 'DAILY';
640
+ if (frequency > 50) return 'WEEKLY';
641
+ if (frequency > 12) return 'MONTHLY';
642
+ if (frequency > 4) return 'QUARTERLY';
643
+ return 'ANNUAL_OR_LESS';
644
+ }
645
+
646
+ /**
647
+ * Helper: Assess sustainability
648
+ */
649
+ assessSustainability(commits, releases, repoData) {
650
+ const factors = {
651
+ commitRate: commits.length > 100 ? 'SUSTAINABLE' : 'AT_RISK',
652
+ releaseRate: releases.length > 0 ? 'SUSTAINABLE' : 'AT_RISK',
653
+ documentation: repoData.description ? 'GOOD' : 'POOR',
654
+ license: repoData.license ? 'PRESENT' : 'MISSING',
655
+ };
656
+
657
+ const sustainableFactors = Object.values(factors).filter((f) => f.includes('SUSTAINABLE') || f === 'GOOD' || f === 'PRESENT').length;
658
+
659
+ return {
660
+ overallAssessment: sustainableFactors >= 3 ? 'SUSTAINABLE' : sustainableFactors >= 2 ? 'AT_RISK' : 'UNSUSTAINABLE',
661
+ factors,
662
+ };
663
+ }
664
+
665
+ /**
666
+ * Generate performance recommendations
667
+ */
668
+ generatePerformanceRecommendations(releaseVelocity, developmentVelocity, maintenancePattern) {
669
+ const recommendations = [];
670
+
671
+ if (releaseVelocity.velocity === 'STAGNANT') {
672
+ recommendations.push({
673
+ category: 'Release Strategy',
674
+ issue: 'No releases published',
675
+ action: 'Create first release and establish release schedule',
676
+ priority: 'CRITICAL',
677
+ });
678
+ } else if (releaseVelocity.velocity === 'SLOW') {
679
+ recommendations.push({
680
+ category: 'Release Strategy',
681
+ issue: 'Infrequent releases',
682
+ action: 'Increase release frequency to at least quarterly',
683
+ priority: 'HIGH',
684
+ });
685
+ }
686
+
687
+ if (developmentVelocity.commitCount < 10) {
688
+ recommendations.push({
689
+ category: 'Development Activity',
690
+ issue: 'Very low commit activity',
691
+ action: 'Increase development effort or this may be a dormant project',
692
+ priority: 'HIGH',
693
+ });
694
+ }
695
+
696
+ if (maintenancePattern.maintenanceLevel === 'DORMANT') {
697
+ recommendations.push({
698
+ category: 'Maintenance',
699
+ issue: 'Repository is dormant',
700
+ action: 'Resume maintenance or mark as archived',
701
+ priority: 'MEDIUM',
702
+ });
703
+ } else if (maintenancePattern.maintenanceLevel === 'INACTIVE') {
704
+ recommendations.push({
705
+ category: 'Maintenance',
706
+ issue: 'Repository appears abandoned',
707
+ action: 'Either resume maintenance or archive the repository',
708
+ priority: 'CRITICAL',
709
+ });
710
+ }
711
+
712
+ if (developmentVelocity.codeStability === 'UNSTABLE') {
713
+ recommendations.push({
714
+ category: 'Code Quality',
715
+ issue: 'High code churn',
716
+ action: 'Review code changes, stabilize APIs, reduce unnecessary refactoring',
717
+ priority: 'MEDIUM',
718
+ });
719
+ }
720
+
721
+ return recommendations.length > 0
722
+ ? recommendations
723
+ : [
724
+ {
725
+ category: 'Maintenance',
726
+ issue: 'Keep current pace',
727
+ action: 'Continue regular releases and maintenance',
728
+ priority: 'LOW',
729
+ },
730
+ ];
731
+ }
732
+
733
+ /**
734
+ * Get rating from score
735
+ */
736
+ getRating(score) {
737
+ if (score >= 90) return 'A+';
738
+ if (score >= 80) return 'A';
739
+ if (score >= 70) return 'B+';
740
+ if (score >= 60) return 'B';
741
+ if (score >= 50) return 'C+';
742
+ if (score >= 40) return 'C';
743
+ return 'F';
744
+ }
745
+ }
746
+
747
+ export default PerformanceAnalyzer;