@entro314labs/ai-changelog-generator 3.1.1 → 3.2.1

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