@entro314labs/ai-changelog-generator 3.2.1 → 3.3.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 (32) hide show
  1. package/ai-changelog-mcp.sh +0 -0
  2. package/ai-changelog.sh +0 -0
  3. package/bin/ai-changelog-dxt.js +0 -0
  4. package/package.json +72 -80
  5. package/src/ai-changelog-generator.js +5 -4
  6. package/src/application/orchestrators/changelog.orchestrator.js +12 -202
  7. package/src/cli.js +4 -5
  8. package/src/domains/ai/ai-analysis.service.js +2 -0
  9. package/src/domains/analysis/analysis.engine.js +714 -37
  10. package/src/domains/changelog/changelog.service.js +615 -30
  11. package/src/domains/changelog/workspace-changelog.service.js +418 -627
  12. package/src/domains/git/commit-tagger.js +552 -0
  13. package/src/domains/git/git-manager.js +357 -0
  14. package/src/domains/git/git.service.js +865 -16
  15. package/src/infrastructure/cli/cli.controller.js +14 -9
  16. package/src/infrastructure/config/configuration.manager.js +24 -2
  17. package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
  18. package/src/infrastructure/mcp/mcp-server.service.js +35 -11
  19. package/src/infrastructure/providers/core/base-provider.js +1 -1
  20. package/src/infrastructure/providers/implementations/anthropic.js +16 -173
  21. package/src/infrastructure/providers/implementations/azure.js +16 -63
  22. package/src/infrastructure/providers/implementations/dummy.js +13 -16
  23. package/src/infrastructure/providers/implementations/mock.js +13 -26
  24. package/src/infrastructure/providers/implementations/ollama.js +12 -4
  25. package/src/infrastructure/providers/implementations/openai.js +13 -165
  26. package/src/infrastructure/providers/provider-management.service.js +126 -412
  27. package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
  28. package/src/shared/utils/cli-ui.js +1 -1
  29. package/src/shared/utils/diff-processor.js +21 -19
  30. package/src/shared/utils/error-classes.js +33 -0
  31. package/src/shared/utils/utils.js +65 -60
  32. package/src/domains/git/git-repository.analyzer.js +0 -678
@@ -1,678 +0,0 @@
1
- import colors from '../../shared/constants/colors.js'
2
- import { outputData } from '../../shared/utils/utils.js'
3
-
4
- /**
5
- * Git Repository Analyzer
6
- *
7
- * Repository analysis and branch management functionality
8
- * Provides comprehensive git repository analysis including:
9
- * - Branch analysis and comparison
10
- * - Untracked files detection and categorization
11
- * - Repository health assessment
12
- * - Code quality and structure analysis
13
- */
14
- export class GitRepositoryAnalyzer {
15
- constructor(gitManager, aiAnalysisService) {
16
- this.gitManager = gitManager
17
- this.aiAnalysisService = aiAnalysisService
18
- }
19
-
20
- async analyzeBranches(format = 'markdown') {
21
- if (!this.gitManager.isGitRepo) {
22
- const errorMsg = 'Not a git repository'
23
- if (format === 'json') {
24
- outputData({ error: errorMsg }, format)
25
- return
26
- }
27
- console.log(colors.errorMessage(errorMsg))
28
- return
29
- }
30
-
31
- if (format === 'markdown') {
32
- console.log(colors.processingMessage('Analyzing git branches and unmerged commits...'))
33
- }
34
-
35
- try {
36
- const branches = this.gitManager.getAllBranches()
37
- const unmergedCommits = this.gitManager.getUnmergedCommits()
38
- const danglingCommits = this.gitManager.getDanglingCommits()
39
-
40
- let aiAnalysis = null
41
- if (
42
- this.aiAnalysisService.hasAI &&
43
- (unmergedCommits.length > 0 || danglingCommits.length > 0)
44
- ) {
45
- aiAnalysis = await this.aiAnalysisService.getBranchesAIAnalysis(
46
- branches,
47
- unmergedCommits,
48
- danglingCommits
49
- )
50
- }
51
-
52
- const data = {
53
- type: 'branch_analysis',
54
- timestamp: new Date().toISOString(),
55
- branches: {
56
- local: branches.local.map((branch) => ({
57
- name: branch,
58
- current: branch === branches.current,
59
- })),
60
- remote: branches.remote,
61
- current: branches.current,
62
- summary: {
63
- localCount: branches.local.length,
64
- remoteCount: branches.remote.length,
65
- },
66
- },
67
- unmergedCommits: unmergedCommits.map((branch) => ({
68
- branch: branch.branch,
69
- commitCount: branch.commits.length,
70
- commits: branch.commits.map((commit) => ({
71
- hash: commit.hash,
72
- shortHash: commit.shortHash,
73
- subject: commit.subject,
74
- author: commit.author,
75
- date: commit.date,
76
- })),
77
- })),
78
- danglingCommits: danglingCommits.map((commit) => ({
79
- hash: commit.hash,
80
- shortHash: commit.shortHash,
81
- subject: commit.subject,
82
- author: commit.author,
83
- date: commit.date,
84
- })),
85
- summary: {
86
- totalBranches: branches.local.length + branches.remote.length,
87
- unmergedCommitCount: unmergedCommits.reduce(
88
- (sum, branch) => sum + branch.commits.length,
89
- 0
90
- ),
91
- danglingCommitCount: danglingCommits.length,
92
- hasUnmergedWork: unmergedCommits.length > 0,
93
- hasDanglingCommits: danglingCommits.length > 0,
94
- },
95
- aiAnalysis,
96
- }
97
-
98
- if (format === 'json') {
99
- outputData(data, format)
100
- return data
101
- }
102
-
103
- // Markdown format display
104
- console.log(colors.header('\n📊 Branch Analysis:'))
105
- console.log(colors.subheader(`🌿 Local branches (${colors.number(branches.local.length)}):`))
106
- branches.local.forEach((branch) => {
107
- const indicator = branch === branches.current ? '* ' : ' '
108
- const branchColor = branch === branches.current ? colors.highlight : colors.secondary
109
- console.log(`${indicator}${branchColor(branch)}`)
110
- })
111
-
112
- if (branches.remote.length > 0) {
113
- console.log(
114
- colors.subheader(`\n🌐 Remote branches (${colors.number(branches.remote.length)}):`)
115
- )
116
- branches.remote
117
- .slice(0, 10)
118
- .forEach((branch) => console.log(` - ${colors.secondary(branch)}`))
119
- if (branches.remote.length > 10) {
120
- console.log(colors.dim(` ... and ${branches.remote.length - 10} more`))
121
- }
122
- }
123
-
124
- if (unmergedCommits.length > 0) {
125
- console.log(colors.subheader('\n🔄 Unmerged commits in other branches:'))
126
- unmergedCommits.forEach((branch) => {
127
- console.log(
128
- `\n ${colors.label(branch.branch)} (${colors.number(branch.commits.length)} commits):`
129
- )
130
- branch.commits.slice(0, 3).forEach((commit) => {
131
- console.log(
132
- ` - ${colors.hash(commit.shortHash)}: ${colors.value(commit.subject)} (${colors.secondary(commit.author)})`
133
- )
134
- })
135
- if (branch.commits.length > 3) {
136
- console.log(colors.dim(` ... and ${branch.commits.length - 3} more commits`))
137
- }
138
- })
139
- } else {
140
- console.log(colors.successMessage('\n✅ No unmerged commits found'))
141
- }
142
-
143
- if (danglingCommits.length > 0) {
144
- console.log(
145
- colors.warningMessage(`\n🗑️ Dangling commits (${colors.number(danglingCommits.length)}):`)
146
- )
147
- danglingCommits.slice(0, 5).forEach((commit) => {
148
- console.log(
149
- ` - ${colors.hash(commit.shortHash)}: ${colors.value(commit.subject)} (${colors.secondary(commit.author)})`
150
- )
151
- })
152
- if (danglingCommits.length > 5) {
153
- console.log(colors.dim(` ... and ${danglingCommits.length - 5} more`))
154
- }
155
- console.log(
156
- colors.infoMessage(
157
- '\n💡 These commits are unreachable from any branch. Consider creating a branch or removing them.'
158
- )
159
- )
160
- }
161
-
162
- if (aiAnalysis) {
163
- console.log(colors.aiMessage('\n🤖 AI Analysis of branch situation:'))
164
- console.log(aiAnalysis)
165
- }
166
-
167
- return data
168
- } catch (error) {
169
- if (format === 'json') {
170
- outputData({ error: `Error analyzing branches: ${error.message}` }, format)
171
- return { error: error.message }
172
- }
173
- console.error(colors.errorMessage(`Error analyzing branches: ${error.message}`))
174
- throw error
175
- }
176
- }
177
-
178
- async analyzeComprehensive(format = 'markdown') {
179
- if (!this.gitManager.isGitRepo) {
180
- const errorMsg = 'Not a git repository'
181
- if (format === 'json') {
182
- outputData({ error: errorMsg }, format)
183
- return
184
- }
185
- console.log(colors.errorMessage(errorMsg))
186
- return
187
- }
188
-
189
- if (format === 'markdown') {
190
- console.log(colors.processingMessage('Comprehensive repository analysis...'))
191
- }
192
-
193
- try {
194
- const comprehensiveData = this.gitManager.getComprehensiveAnalysis()
195
-
196
- if (!comprehensiveData) {
197
- const errorMsg = 'Failed to get comprehensive analysis'
198
- if (format === 'json') {
199
- outputData({ error: errorMsg }, format)
200
- return
201
- }
202
- console.log(colors.errorMessage(errorMsg))
203
- return
204
- }
205
-
206
- // Get untracked files
207
- const untrackedFiles = this.gitManager.getUntrackedFiles()
208
- const untrackedCategories = this.categorizeUntrackedFiles(untrackedFiles)
209
-
210
- let aiAnalysis = null
211
- if (this.aiAnalysisService.hasAI) {
212
- aiAnalysis = await this.aiAnalysisService.getRepositoryAIAnalysis(comprehensiveData)
213
- }
214
-
215
- const data = {
216
- type: 'comprehensive_analysis',
217
- timestamp: new Date().toISOString(),
218
- repository: comprehensiveData,
219
- untrackedFiles: {
220
- files: untrackedFiles,
221
- categories: untrackedCategories,
222
- summary: {
223
- totalFiles: untrackedFiles.length,
224
- categoryCounts: Object.keys(untrackedCategories).reduce((acc, key) => {
225
- acc[key] = untrackedCategories[key].length
226
- return acc
227
- }, {}),
228
- },
229
- },
230
- aiAnalysis,
231
- }
232
-
233
- if (format === 'json') {
234
- outputData(data, format)
235
- return data
236
- }
237
-
238
- // Display comprehensive analysis
239
- console.log(colors.header('\n📊 Comprehensive Repository Analysis:'))
240
-
241
- // Repository statistics
242
- console.log(colors.subheader('\n📈 Repository Statistics:'))
243
- console.log(
244
- ` ${colors.label('Total commits')}: ${colors.number(comprehensiveData.totalCommits || 0)}`
245
- )
246
- console.log(
247
- ` ${colors.label('Contributors')}: ${colors.number(comprehensiveData.contributors?.length || 0)}`
248
- )
249
- console.log(
250
- ` ${colors.label('Files tracked')}: ${colors.number(comprehensiveData.totalFiles || 0)}`
251
- )
252
- console.log(
253
- ` ${colors.label('Branches')}: ${colors.number(comprehensiveData.branchCount || 0)}`
254
- )
255
-
256
- // Recent activity
257
- if (comprehensiveData.recentActivity) {
258
- console.log(colors.subheader('\n🔥 Recent Activity:'))
259
- console.log(
260
- ` ${colors.label('Commits this week')}: ${colors.number(comprehensiveData.recentActivity.thisWeek || 0)}`
261
- )
262
- console.log(
263
- ` ${colors.label('Commits this month')}: ${colors.number(comprehensiveData.recentActivity.thisMonth || 0)}`
264
- )
265
- }
266
-
267
- // File type breakdown
268
- if (comprehensiveData.fileTypes) {
269
- console.log(colors.subheader('\n📁 File Type Breakdown:'))
270
- Object.entries(comprehensiveData.fileTypes).forEach(([type, count]) => {
271
- console.log(` ${colors.label(type)}: ${colors.number(count)}`)
272
- })
273
- }
274
-
275
- // Untracked files summary
276
- if (untrackedFiles.length > 0) {
277
- console.log(
278
- colors.subheader(`\n📄 Untracked Files (${colors.number(untrackedFiles.length)}):`)
279
- )
280
- Object.entries(untrackedCategories).forEach(([category, files]) => {
281
- if (files.length > 0) {
282
- console.log(` ${colors.label(category)}: ${colors.number(files.length)} files`)
283
- }
284
- })
285
- }
286
-
287
- if (aiAnalysis) {
288
- console.log(colors.aiMessage('\n🤖 AI Repository Analysis:'))
289
- console.log(aiAnalysis)
290
- }
291
-
292
- return data
293
- } catch (error) {
294
- if (format === 'json') {
295
- outputData({ error: `Error in comprehensive analysis: ${error.message}` }, format)
296
- return { error: error.message }
297
- }
298
- console.error(colors.errorMessage(`Error in comprehensive analysis: ${error.message}`))
299
- throw error
300
- }
301
- }
302
-
303
- async analyzeUntrackedFiles(format = 'markdown') {
304
- if (!this.gitManager.isGitRepo) {
305
- const errorMsg = 'Not a git repository'
306
- if (format === 'json') {
307
- outputData({ error: errorMsg }, format)
308
- return
309
- }
310
- console.log(colors.errorMessage(errorMsg))
311
- return
312
- }
313
-
314
- if (format === 'markdown') {
315
- console.log(colors.processingMessage('Analyzing untracked files...'))
316
- }
317
-
318
- try {
319
- const untrackedFiles = this.gitManager.getUntrackedFiles()
320
-
321
- if (untrackedFiles.length === 0) {
322
- const message = 'No untracked files found'
323
- if (format === 'json') {
324
- outputData({ message, files: [] }, format)
325
- return { message, files: [] }
326
- }
327
- console.log(colors.successMessage('✅ No untracked files found'))
328
- return
329
- }
330
-
331
- const categories = this.categorizeUntrackedFiles(untrackedFiles)
332
- const recommendations = this.generateUntrackedRecommendations(categories)
333
-
334
- let aiAnalysis = null
335
- if (this.aiAnalysisService.hasAI && untrackedFiles.length > 0) {
336
- aiAnalysis = await this.aiAnalysisService.getUntrackedFilesAIAnalysis(categories)
337
- }
338
-
339
- const data = {
340
- type: 'untracked_files_analysis',
341
- timestamp: new Date().toISOString(),
342
- files: untrackedFiles,
343
- categories: Object.keys(categories).reduce((acc, key) => {
344
- acc[key] = {
345
- count: categories[key].length,
346
- files: categories[key],
347
- }
348
- return acc
349
- }, {}),
350
- summary: {
351
- totalFiles: untrackedFiles.length,
352
- categoryCounts: Object.keys(categories).reduce((acc, key) => {
353
- acc[key] = categories[key].length
354
- return acc
355
- }, {}),
356
- },
357
- recommendations,
358
- aiAnalysis,
359
- }
360
-
361
- if (format === 'json') {
362
- outputData(data, format)
363
- return data
364
- }
365
-
366
- // Markdown format display
367
- console.log(colors.header('\n📄 Untracked Files Analysis:'))
368
-
369
- Object.entries(categories).forEach(([category, files]) => {
370
- if (files.length > 0) {
371
- console.log(colors.subheader(`\n📁 ${category} (${colors.number(files.length)} files):`))
372
- files.slice(0, 10).forEach((file) => console.log(` - ${colors.file(file)}`))
373
- if (files.length > 10) {
374
- console.log(colors.dim(` ... and ${files.length - 10} more`))
375
- }
376
- }
377
- })
378
-
379
- // Provide recommendations
380
- if (recommendations.length > 0) {
381
- console.log(colors.infoMessage('\n💡 Recommendations:'))
382
- recommendations.forEach((rec) => console.log(` - ${rec}`))
383
- }
384
-
385
- if (aiAnalysis) {
386
- console.log(colors.aiMessage('\n🤖 AI Analysis of untracked files:'))
387
- console.log(aiAnalysis)
388
- }
389
-
390
- return data
391
- } catch (error) {
392
- if (format === 'json') {
393
- outputData({ error: `Error analyzing untracked files: ${error.message}` }, format)
394
- return { error: error.message }
395
- }
396
- console.error(colors.errorMessage(`Error analyzing untracked files: ${error.message}`))
397
- throw error
398
- }
399
- }
400
-
401
- async assessRepositoryHealth(config = {}) {
402
- if (!this.gitManager.isGitRepo) {
403
- const errorMsg = 'Not a git repository'
404
- if (config.format === 'json') {
405
- outputData({ error: errorMsg }, config.format)
406
- return
407
- }
408
- console.log(colors.errorMessage(errorMsg))
409
- return
410
- }
411
-
412
- try {
413
- console.log(colors.processingMessage('Assessing repository health...'))
414
-
415
- // Gather repository health metrics
416
- const healthMetrics = await this.gatherHealthMetrics()
417
- const healthScore = this.calculateHealthScore(healthMetrics)
418
- const recommendations = this.generateHealthRecommendations(healthMetrics)
419
-
420
- const data = {
421
- type: 'repository_health_assessment',
422
- timestamp: new Date().toISOString(),
423
- overallScore: healthScore.overall,
424
- grade: healthScore.grade,
425
- metrics: healthMetrics,
426
- recommendations,
427
- categories: {
428
- commitQuality: healthScore.commitQuality,
429
- branchHygiene: healthScore.branchHygiene,
430
- fileOrganization: healthScore.fileOrganization,
431
- activityLevel: healthScore.activityLevel,
432
- },
433
- }
434
-
435
- if (config.format === 'json') {
436
- outputData(data, config.format)
437
- return data
438
- }
439
-
440
- // Display health assessment
441
- console.log(colors.header('\n🏥 Repository Health Assessment:'))
442
-
443
- // Overall score
444
- const gradeColor = this.getGradeColor(healthScore.grade)
445
- console.log(
446
- colors.subheader(
447
- `\n📊 Overall Health: ${gradeColor(healthScore.grade)} (${colors.number(healthScore.overall)}/100)`
448
- )
449
- )
450
-
451
- // Category breakdown
452
- console.log(colors.subheader('\n📋 Category Scores:'))
453
- console.log(
454
- ` ${colors.label('Commit Quality')}: ${colors.number(healthScore.commitQuality)}/100`
455
- )
456
- console.log(
457
- ` ${colors.label('Branch Hygiene')}: ${colors.number(healthScore.branchHygiene)}/100`
458
- )
459
- console.log(
460
- ` ${colors.label('File Organization')}: ${colors.number(healthScore.fileOrganization)}/100`
461
- )
462
- console.log(
463
- ` ${colors.label('Activity Level')}: ${colors.number(healthScore.activityLevel)}/100`
464
- )
465
-
466
- // Key metrics
467
- console.log(colors.subheader('\n📈 Key Metrics:'))
468
- console.log(
469
- ` ${colors.label('Average commit message length')}: ${colors.number(healthMetrics.avgCommitMessageLength)} chars`
470
- )
471
- console.log(
472
- ` ${colors.label('Recent commits')}: ${colors.number(healthMetrics.recentCommitCount)} (last 30 days)`
473
- )
474
- console.log(
475
- ` ${colors.label('Active branches')}: ${colors.number(healthMetrics.activeBranchCount)}`
476
- )
477
- console.log(
478
- ` ${colors.label('Untracked files')}: ${colors.number(healthMetrics.untrackedFileCount)}`
479
- )
480
-
481
- // Recommendations
482
- if (recommendations.length > 0) {
483
- console.log(colors.infoMessage('\n💡 Health Recommendations:'))
484
- recommendations.forEach((rec) => console.log(` - ${rec}`))
485
- }
486
-
487
- return data
488
- } catch (error) {
489
- console.error(colors.errorMessage(`Error assessing repository health: ${error.message}`))
490
- if (config.format === 'json') {
491
- outputData({ error: error.message }, config.format)
492
- return { error: error.message }
493
- }
494
- return { error: error.message }
495
- }
496
- }
497
-
498
- // Helper methods
499
- categorizeUntrackedFiles(files) {
500
- const categories = {
501
- source: [],
502
- config: [],
503
- documentation: [],
504
- assets: [],
505
- temporary: [],
506
- other: [],
507
- }
508
-
509
- files.forEach((file) => {
510
- const ext = file.split('.').pop()?.toLowerCase()
511
- const path = file.toLowerCase()
512
-
513
- if (['js', 'ts', 'py', 'java', 'cpp', 'c', 'rs', 'go'].includes(ext)) {
514
- categories.source.push(file)
515
- } else if (
516
- ['json', 'yaml', 'yml', 'toml', 'ini', 'env'].includes(ext) ||
517
- path.includes('config')
518
- ) {
519
- categories.config.push(file)
520
- } else if (
521
- ['md', 'txt', 'rst'].includes(ext) ||
522
- path.includes('readme') ||
523
- path.includes('doc')
524
- ) {
525
- categories.documentation.push(file)
526
- } else if (['png', 'jpg', 'jpeg', 'gif', 'svg', 'css', 'scss'].includes(ext)) {
527
- categories.assets.push(file)
528
- } else if (
529
- ['tmp', 'temp', 'log', 'cache'].some((temp) => path.includes(temp)) ||
530
- file.startsWith('.')
531
- ) {
532
- categories.temporary.push(file)
533
- } else {
534
- categories.other.push(file)
535
- }
536
- })
537
-
538
- return categories
539
- }
540
-
541
- generateUntrackedRecommendations(categories) {
542
- const recommendations = []
543
-
544
- if (categories.source?.length > 0) {
545
- recommendations.push('Consider adding source files to git or updating .gitignore')
546
- }
547
- if (categories.temporary?.length > 0) {
548
- recommendations.push('Add temporary files to .gitignore to keep repository clean')
549
- }
550
- if (categories.config?.length > 0) {
551
- recommendations.push('Review configuration files - add sensitive configs to .gitignore')
552
- }
553
- if (categories.documentation?.length > 0) {
554
- recommendations.push('Consider adding documentation files to the repository')
555
- }
556
-
557
- return recommendations
558
- }
559
-
560
- gatherHealthMetrics() {
561
- const recentCommits = this.gitManager.getRecentCommits(100)
562
- const branches = this.gitManager.getAllBranches()
563
- const untrackedFiles = this.gitManager.getUntrackedFiles()
564
-
565
- return {
566
- totalCommits: recentCommits.length,
567
- recentCommitCount: recentCommits.filter(
568
- (c) => new Date(c.date) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
569
- ).length,
570
- avgCommitMessageLength:
571
- recentCommits.reduce((sum, c) => sum + c.subject.length, 0) / recentCommits.length || 0,
572
- activeBranchCount: branches.local?.length || 0,
573
- untrackedFileCount: untrackedFiles.length,
574
- hasGitignore: this.gitManager.hasFile('.gitignore'),
575
- hasReadme: this.gitManager.hasFile('README.md') || this.gitManager.hasFile('readme.md'),
576
- }
577
- }
578
-
579
- calculateHealthScore(metrics) {
580
- let commitQuality = 100
581
- let branchHygiene = 100
582
- let fileOrganization = 100
583
- let activityLevel = 100
584
-
585
- // Commit quality assessment
586
- if (metrics.avgCommitMessageLength < 10) {
587
- commitQuality -= 30
588
- } else if (metrics.avgCommitMessageLength < 20) {
589
- commitQuality -= 15
590
- }
591
-
592
- // Branch hygiene
593
- if (metrics.activeBranchCount > 10) {
594
- branchHygiene -= 20
595
- }
596
- if (metrics.activeBranchCount > 20) {
597
- branchHygiene -= 30
598
- }
599
-
600
- // File organization
601
- if (!metrics.hasGitignore) {
602
- fileOrganization -= 20
603
- }
604
- if (!metrics.hasReadme) {
605
- fileOrganization -= 15
606
- }
607
- if (metrics.untrackedFileCount > 10) {
608
- fileOrganization -= 20
609
- }
610
-
611
- // Activity level
612
- if (metrics.recentCommitCount === 0) {
613
- activityLevel -= 50
614
- } else if (metrics.recentCommitCount < 5) {
615
- activityLevel -= 20
616
- }
617
-
618
- const overall = Math.round(
619
- (commitQuality + branchHygiene + fileOrganization + activityLevel) / 4
620
- )
621
-
622
- let grade = 'F'
623
- if (overall >= 90) {
624
- grade = 'A'
625
- } else if (overall >= 80) {
626
- grade = 'B'
627
- } else if (overall >= 70) {
628
- grade = 'C'
629
- } else if (overall >= 60) {
630
- grade = 'D'
631
- }
632
-
633
- return {
634
- overall: Math.max(0, overall),
635
- commitQuality: Math.max(0, commitQuality),
636
- branchHygiene: Math.max(0, branchHygiene),
637
- fileOrganization: Math.max(0, fileOrganization),
638
- activityLevel: Math.max(0, activityLevel),
639
- grade,
640
- }
641
- }
642
-
643
- generateHealthRecommendations(metrics) {
644
- const recommendations = []
645
-
646
- if (metrics.avgCommitMessageLength < 20) {
647
- recommendations.push('Write more descriptive commit messages (aim for 20+ characters)')
648
- }
649
- if (metrics.activeBranchCount > 10) {
650
- recommendations.push('Consider cleaning up old/merged branches')
651
- }
652
- if (!metrics.hasGitignore) {
653
- recommendations.push('Add a .gitignore file to exclude unwanted files')
654
- }
655
- if (!metrics.hasReadme) {
656
- recommendations.push('Add a README.md file to document your project')
657
- }
658
- if (metrics.untrackedFileCount > 5) {
659
- recommendations.push('Review and either commit or ignore untracked files')
660
- }
661
- if (metrics.recentCommitCount < 5) {
662
- recommendations.push('Consider more frequent commits for better project tracking')
663
- }
664
-
665
- return recommendations
666
- }
667
-
668
- getGradeColor(grade) {
669
- const colors_map = {
670
- A: colors.successMessage,
671
- B: colors.successMessage,
672
- C: colors.warningMessage,
673
- D: colors.warningMessage,
674
- F: colors.errorMessage,
675
- }
676
- return colors_map[grade] || colors.infoMessage
677
- }
678
- }