@entro314labs/ai-changelog-generator 3.2.1 → 3.6.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/CHANGELOG.md +42 -2
- package/README.md +21 -1
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +6 -3
- package/manifest.json +177 -0
- package/package.json +76 -81
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +19 -203
- package/src/cli.js +16 -5
- package/src/domains/ai/ai-analysis.service.js +2 -0
- package/src/domains/analysis/analysis.engine.js +714 -37
- package/src/domains/changelog/changelog.service.js +623 -32
- package/src/domains/changelog/workspace-changelog.service.js +445 -622
- package/src/domains/git/commit-tagger.js +552 -0
- package/src/domains/git/git-manager.js +357 -0
- package/src/domains/git/git.service.js +865 -16
- package/src/infrastructure/cli/cli.controller.js +14 -9
- package/src/infrastructure/config/configuration.manager.js +25 -11
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +105 -32
- package/src/infrastructure/providers/core/base-provider.js +1 -1
- package/src/infrastructure/providers/implementations/anthropic.js +16 -173
- package/src/infrastructure/providers/implementations/azure.js +16 -63
- package/src/infrastructure/providers/implementations/dummy.js +13 -16
- package/src/infrastructure/providers/implementations/mock.js +13 -26
- package/src/infrastructure/providers/implementations/ollama.js +12 -4
- package/src/infrastructure/providers/implementations/openai.js +13 -165
- package/src/infrastructure/providers/provider-management.service.js +126 -412
- package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
- package/src/shared/utils/cli-ui.js +8 -10
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +83 -63
- package/types/index.d.ts +61 -68
- 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
|
-
}
|