@entro314labs/ai-changelog-generator 3.1.1 → 3.2.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 (51) hide show
  1. package/CHANGELOG.md +383 -877
  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 +83 -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 +253 -193
  15. package/src/domains/changelog/changelog.service.js +1062 -784
  16. package/src/domains/changelog/workspace-changelog.service.js +420 -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,8 @@
1
- import { runInteractiveMode, analyzeChangesForCommitMessage, selectSpecificCommits } from '../../shared/utils/utils.js';
2
- import colors from '../../shared/constants/colors.js';
1
+ import process from 'node:process'
2
+
3
+ import colors from '../../shared/constants/colors.js'
4
+ import { EnhancedConsole, SimpleSpinner } from '../../shared/utils/cli-ui.js'
5
+ import { runInteractiveMode, selectSpecificCommits } from '../../shared/utils/utils.js'
3
6
 
4
7
  /**
5
8
  * Interactive Workflow Service
@@ -13,13 +16,13 @@ import colors from '../../shared/constants/colors.js';
13
16
  */
14
17
  export class InteractiveWorkflowService {
15
18
  constructor(gitService, aiAnalysisService, changelogService) {
16
- this.gitService = gitService;
17
- this.aiAnalysisService = aiAnalysisService;
18
- this.changelogService = changelogService;
19
+ this.gitService = gitService
20
+ this.aiAnalysisService = aiAnalysisService
21
+ this.changelogService = changelogService
19
22
  }
20
23
 
21
24
  async runInteractiveMode() {
22
- return await runInteractiveMode();
25
+ return await runInteractiveMode()
23
26
  }
24
27
 
25
28
  async validateCommitMessage(message) {
@@ -27,67 +30,71 @@ export class InteractiveWorkflowService {
27
30
  return {
28
31
  valid: false,
29
32
  issues: ['Commit message is empty'],
30
- suggestions: ['Provide a descriptive commit message']
31
- };
33
+ suggestions: ['Provide a descriptive commit message'],
34
+ }
32
35
  }
33
36
 
34
- const issues = [];
35
- const suggestions = [];
37
+ const issues = []
38
+ const suggestions = []
36
39
 
37
40
  // Length validation
38
41
  if (message.length < 10) {
39
- issues.push('Commit message is too short (minimum 10 characters)');
40
- suggestions.push('Add more detail about what was changed');
42
+ issues.push('Commit message is too short (minimum 10 characters)')
43
+ suggestions.push('Add more detail about what was changed')
41
44
  }
42
45
 
43
46
  if (message.length > 72) {
44
- issues.push('Commit message first line is too long (maximum 72 characters)');
45
- suggestions.push('Keep the first line concise, add details in the body');
47
+ issues.push('Commit message first line is too long (maximum 72 characters)')
48
+ suggestions.push('Keep the first line concise, add details in the body')
46
49
  }
47
50
 
48
51
  // Format validation
49
- const lines = message.split('\n');
50
- const firstLine = lines[0];
52
+ const lines = message.split('\n')
53
+ const firstLine = lines[0]
51
54
 
52
55
  // Check for conventional commit format
53
- const conventionalPattern = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .+/;
56
+ const conventionalPattern =
57
+ /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .+/
54
58
  if (!conventionalPattern.test(firstLine)) {
55
- suggestions.push('Consider using conventional commit format: type(scope): description');
59
+ suggestions.push('Consider using conventional commit format: type(scope): description')
56
60
  }
57
61
 
58
62
  // Check for imperative mood
59
- const imperativeWords = ['add', 'fix', 'update', 'remove', 'create', 'implement', 'refactor'];
60
- const firstWord = firstLine.split(' ')[0].toLowerCase().replace(/[^a-z]/g, '');
61
-
62
- if (!imperativeWords.some(word => firstWord.startsWith(word))) {
63
- suggestions.push('Use imperative mood (e.g., "Add feature" not "Added feature")');
63
+ const imperativeWords = ['add', 'fix', 'update', 'remove', 'create', 'implement', 'refactor']
64
+ const firstWord = firstLine
65
+ .split(' ')[0]
66
+ .toLowerCase()
67
+ .replace(/[^a-z]/g, '')
68
+
69
+ if (!imperativeWords.some((word) => firstWord.startsWith(word))) {
70
+ suggestions.push('Use imperative mood (e.g., "Add feature" not "Added feature")')
64
71
  }
65
72
 
66
73
  // Check for body separation
67
74
  if (lines.length > 1 && lines[1].trim() !== '') {
68
- issues.push('Missing blank line between subject and body');
69
- suggestions.push('Add a blank line after the first line if including a body');
75
+ issues.push('Missing blank line between subject and body')
76
+ suggestions.push('Add a blank line after the first line if including a body')
70
77
  }
71
78
 
72
79
  return {
73
80
  valid: issues.length === 0,
74
81
  issues,
75
82
  suggestions,
76
- score: Math.max(0, 100 - (issues.length * 20) - (suggestions.length * 10))
77
- };
83
+ score: Math.max(0, 100 - issues.length * 20 - suggestions.length * 10),
84
+ }
78
85
  }
79
86
 
80
87
  async generateCommitSuggestion(message = null) {
81
88
  try {
82
89
  // If no message provided, analyze current changes
83
- let analysisContext = '';
90
+ let analysisContext = ''
84
91
 
85
92
  if (!message) {
86
93
  // Use the shared utility function for getting working directory changes
87
- const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js');
88
- const changes = getWorkingDirectoryChanges();
94
+ const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js')
95
+ const changes = getWorkingDirectoryChanges()
89
96
  if (changes && changes.length > 0) {
90
- analysisContext = this.analyzeChangesForCommitMessage(changes, true);
97
+ analysisContext = this.analyzeChangesForCommitMessage(changes, true)
91
98
  }
92
99
  }
93
100
 
@@ -115,258 +122,288 @@ Requirements:
115
122
  - Use imperative mood
116
123
  - Be specific and descriptive
117
124
 
118
- Provide 3 suggestions.`;
125
+ Provide 3 suggestions.`
119
126
 
120
- const response = await this.aiAnalysisService.aiProvider.generateCompletion([{
121
- role: 'user',
122
- content: prompt
123
- }], { max_tokens: 200 });
127
+ const response = await this.aiAnalysisService.aiProvider.generateCompletion(
128
+ [
129
+ {
130
+ role: 'user',
131
+ content: prompt,
132
+ },
133
+ ],
134
+ { max_tokens: 200 }
135
+ )
124
136
 
125
137
  return {
126
138
  success: true,
127
139
  original: message,
128
140
  suggestions: this.parseCommitSuggestions(response.content),
129
- context: analysisContext
130
- };
131
- } else {
132
- // Rule-based suggestions
133
- return this.generateRuleBasedCommitSuggestion(message, analysisContext);
141
+ context: analysisContext,
142
+ }
134
143
  }
135
-
144
+ // Rule-based suggestions
145
+ return this.generateRuleBasedCommitSuggestion(message, analysisContext)
136
146
  } catch (error) {
137
- console.error(colors.errorMessage(`Error generating commit suggestion: ${error.message}`));
147
+ EnhancedConsole.error(`Error generating commit suggestion: ${error.message}`)
138
148
  return {
139
149
  success: false,
140
150
  error: error.message,
141
- fallback: this.generateRuleBasedCommitSuggestion(message)
142
- };
151
+ fallback: this.generateRuleBasedCommitSuggestion(message),
152
+ }
143
153
  }
144
154
  }
145
155
 
146
156
  analyzeChangesForCommitMessage(changes, includeScope = false) {
147
157
  if (!changes || changes.length === 0) {
148
- return 'No changes detected';
158
+ return 'No changes detected'
149
159
  }
150
160
 
151
- const categories = this.categorizeChanges(changes);
152
- const primaryCategory = Object.keys(categories)[0];
153
- const fileCount = changes.length;
161
+ const categories = this.categorizeChanges(changes)
162
+ const primaryCategory = Object.keys(categories)[0]
163
+ const fileCount = changes.length
154
164
 
155
- let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`;
165
+ let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`
156
166
 
157
167
  if (primaryCategory) {
158
- summary += ` in ${primaryCategory}`;
168
+ summary += ` in ${primaryCategory}`
159
169
  }
160
170
 
161
171
  if (includeScope) {
162
- const scopes = this.extractScopes(changes);
172
+ const scopes = this.extractScopes(changes)
163
173
  if (scopes.length > 0) {
164
- summary += ` (scope: ${scopes.join(', ')})`;
174
+ summary += ` (scope: ${scopes.join(', ')})`
165
175
  }
166
176
  }
167
177
 
168
178
  // Add change details
169
- const additions = changes.filter(c => c.status === 'A').length;
170
- const modifications = changes.filter(c => c.status === 'M').length;
171
- const deletions = changes.filter(c => c.status === 'D').length;
179
+ const additions = changes.filter((c) => c.status === 'A').length
180
+ const modifications = changes.filter((c) => c.status === 'M').length
181
+ const deletions = changes.filter((c) => c.status === 'D').length
172
182
 
173
- const details = [];
174
- if (additions > 0) details.push(`${additions} added`);
175
- if (modifications > 0) details.push(`${modifications} modified`);
176
- if (deletions > 0) details.push(`${deletions} deleted`);
183
+ const details = []
184
+ if (additions > 0) {
185
+ details.push(`${additions} added`)
186
+ }
187
+ if (modifications > 0) {
188
+ details.push(`${modifications} modified`)
189
+ }
190
+ if (deletions > 0) {
191
+ details.push(`${deletions} deleted`)
192
+ }
177
193
 
178
194
  if (details.length > 0) {
179
- summary += ` (${details.join(', ')})`;
195
+ summary += ` (${details.join(', ')})`
180
196
  }
181
197
 
182
- return summary;
198
+ return summary
183
199
  }
184
200
 
185
201
  async selectSpecificCommits() {
186
- return await selectSpecificCommits();
202
+ return await selectSpecificCommits()
187
203
  }
188
204
 
189
205
  async generateChangelogForRecentCommits(count = 10) {
190
206
  try {
191
- console.log(colors.processingMessage(`šŸ“ Generating changelog for recent ${count} commits...`));
207
+ const spinner = new SimpleSpinner(`Generating changelog for recent ${count} commits...`)
208
+ spinner.start()
192
209
 
193
- const commits = await this.gitService.getCommitsSince(null);
194
- const recentCommits = commits.slice(0, count);
210
+ const commits = await this.gitService.getCommitsSince(null)
211
+ const recentCommits = commits.slice(0, count)
195
212
 
196
213
  if (recentCommits.length === 0) {
197
- console.log(colors.infoMessage('No recent commits found.'));
198
- return null;
214
+ spinner.stop()
215
+ EnhancedConsole.info('No recent commits found.')
216
+ return null
199
217
  }
200
218
 
201
219
  const result = await this.changelogService.generateChangelogBatch(
202
- recentCommits.map(c => c.hash)
203
- );
204
-
205
- console.log(colors.successMessage(`āœ… Generated changelog for ${recentCommits.length} commits`));
206
- return result;
220
+ recentCommits.map((c) => c.hash)
221
+ )
207
222
 
223
+ spinner.succeed(`Generated changelog for ${recentCommits.length} commits`)
224
+ return result
208
225
  } catch (error) {
209
- console.error(colors.errorMessage(`Error generating changelog for recent commits: ${error.message}`));
210
- throw error;
226
+ if (typeof spinner !== 'undefined') {
227
+ spinner.fail('Failed to generate changelog')
228
+ }
229
+ EnhancedConsole.error(`Error generating changelog for recent commits: ${error.message}`)
230
+ throw error
211
231
  }
212
232
  }
213
233
 
214
234
  async generateChangelogForCommits(commitHashes) {
215
235
  if (!commitHashes || commitHashes.length === 0) {
216
- console.log(colors.warningMessage('No commit hashes provided'));
217
- return null;
236
+ EnhancedConsole.warn('No commit hashes provided')
237
+ return null
218
238
  }
219
239
 
220
240
  try {
221
- console.log(colors.processingMessage(`šŸ“ Generating changelog for ${commitHashes.length} specific commits...`));
241
+ const spinner = new SimpleSpinner(
242
+ `Generating changelog for ${commitHashes.length} specific commits...`
243
+ )
244
+ spinner.start()
222
245
 
223
246
  // Validate commit hashes
224
- const validCommits = [];
247
+ const validCommits = []
225
248
  for (const hash of commitHashes) {
226
- const analysis = await this.gitService.getCommitAnalysis(hash);
249
+ const analysis = await this.gitService.getCommitAnalysis(hash)
227
250
  if (analysis) {
228
- validCommits.push(analysis);
251
+ validCommits.push(analysis)
229
252
  } else {
230
- console.warn(colors.warningMessage(`Invalid or inaccessible commit: ${hash}`));
253
+ EnhancedConsole.warn(`Invalid or inaccessible commit: ${hash}`)
231
254
  }
232
255
  }
233
256
 
234
257
  if (validCommits.length === 0) {
235
- console.log(colors.errorMessage('No valid commits found'));
236
- return null;
258
+ spinner.fail('No valid commits found')
259
+ return null
237
260
  }
238
261
 
239
262
  // Generate AI summaries for each commit
240
- const analyzedCommits = [];
263
+ const analyzedCommits = []
241
264
  for (let i = 0; i < validCommits.length; i++) {
242
- const commit = validCommits[i];
243
- console.log(colors.processingMessage(`Processing commit ${i + 1}/${validCommits.length}: ${colors.hash(commit.hash)}`));
265
+ const commit = validCommits[i]
266
+ process.stdout.write(
267
+ `\r${colors.statusSymbol('processing', `Processing commit ${i + 1}/${validCommits.length}: ${colors.hash(commit.hash)}`)}`
268
+ )
244
269
 
245
- const selectedModel = await this.aiAnalysisService.selectOptimalModel(commit);
246
- const aiSummary = await this.aiAnalysisService.generateAISummary(commit, selectedModel);
270
+ const selectedModel = await this.aiAnalysisService.selectOptimalModel(commit)
271
+ const aiSummary = await this.aiAnalysisService.generateAISummary(commit, selectedModel)
247
272
 
248
273
  if (aiSummary) {
249
274
  analyzedCommits.push({
250
275
  ...commit,
251
276
  aiSummary,
252
277
  type: aiSummary.category || 'other',
253
- breaking: aiSummary.breaking || false
254
- });
278
+ breaking: aiSummary.breaking,
279
+ })
255
280
  }
256
281
  }
257
282
 
258
283
  if (analyzedCommits.length === 0) {
259
- console.log(colors.errorMessage('No commits could be analyzed'));
260
- return null;
284
+ spinner.fail('No commits could be analyzed')
285
+ return null
261
286
  }
262
287
 
263
288
  // Generate release insights and build changelog
264
- const insights = await this.changelogService.generateReleaseInsights(analyzedCommits, 'Selected Commits');
265
- const changelog = await this.changelogService.buildChangelog(analyzedCommits, insights, 'Selected Commits');
289
+ const insights = await this.changelogService.generateReleaseInsights(
290
+ analyzedCommits,
291
+ 'Selected Commits'
292
+ )
293
+ const changelog = await this.changelogService.buildChangelog(
294
+ analyzedCommits,
295
+ insights,
296
+ 'Selected Commits'
297
+ )
266
298
 
267
- console.log(colors.successMessage(`āœ… Generated changelog for ${analyzedCommits.length} commits`));
299
+ process.stdout.write(`\r${' '.repeat(80)}\r`) // Clear processing line
300
+ spinner.succeed(`Generated changelog for ${analyzedCommits.length} commits`)
268
301
 
269
302
  return {
270
303
  changelog,
271
304
  insights,
272
305
  analyzedCommits,
273
306
  requestedCommits: commitHashes.length,
274
- processedCommits: analyzedCommits.length
275
- };
276
-
307
+ processedCommits: analyzedCommits.length,
308
+ }
277
309
  } catch (error) {
278
- console.error(colors.errorMessage(`Error generating changelog for commits: ${error.message}`));
279
- throw error;
310
+ console.error(colors.errorMessage(`Error generating changelog for commits: ${error.message}`))
311
+ throw error
280
312
  }
281
313
  }
282
314
 
283
315
  // Helper methods
284
316
  categorizeChanges(changes) {
285
- const categories = {};
286
- changes.forEach(change => {
287
- const category = this.getFileCategory(change.path);
288
- if (!categories[category]) categories[category] = [];
289
- categories[category].push(change);
290
- });
317
+ const categories = {}
318
+ changes.forEach((change) => {
319
+ const category = this.getFileCategory(change.path)
320
+ if (!categories[category]) {
321
+ categories[category] = []
322
+ }
323
+ categories[category].push(change)
324
+ })
291
325
 
292
326
  // Sort by count
293
327
  return Object.fromEntries(
294
- Object.entries(categories).sort(([,a], [,b]) => b.length - a.length)
295
- );
328
+ Object.entries(categories).sort(([, a], [, b]) => b.length - a.length)
329
+ )
296
330
  }
297
331
 
298
332
  getFileCategory(filePath) {
299
333
  if (!filePath || typeof filePath !== 'string') {
300
- return 'other';
334
+ return 'other'
301
335
  }
302
336
 
303
- const path = filePath.toLowerCase();
337
+ const path = filePath.toLowerCase()
304
338
 
305
339
  if (path.includes('/test/') || path.includes('.test.') || path.includes('.spec.')) {
306
- return 'tests';
340
+ return 'tests'
307
341
  }
308
342
  if (path.includes('/doc/') || path.endsWith('.md') || path.endsWith('.txt')) {
309
- return 'documentation';
343
+ return 'documentation'
310
344
  }
311
345
  if (path.includes('/config/') || path.endsWith('.json') || path.endsWith('.yaml')) {
312
- return 'configuration';
346
+ return 'configuration'
313
347
  }
314
348
  if (path.includes('/src/') || path.includes('/lib/')) {
315
- return 'source';
349
+ return 'source'
316
350
  }
317
351
  if (path.includes('/style/') || path.endsWith('.css') || path.endsWith('.scss')) {
318
- return 'styles';
352
+ return 'styles'
319
353
  }
320
354
 
321
- return 'other';
355
+ return 'other'
322
356
  }
323
357
 
324
358
  extractScopes(changes) {
325
- const scopes = new Set();
359
+ const scopes = new Set()
326
360
 
327
- changes.forEach(change => {
328
- const parts = change.path.split('/');
361
+ changes.forEach((change) => {
362
+ const parts = change.path.split('/')
329
363
  if (parts.length > 1) {
330
364
  // Extract directory name as scope
331
- const scope = parts[parts.length - 2];
365
+ const scope = parts.at(-2)
332
366
  if (scope && scope !== '.' && scope !== '..') {
333
- scopes.add(scope);
367
+ scopes.add(scope)
334
368
  }
335
369
  }
336
- });
370
+ })
337
371
 
338
- return Array.from(scopes).slice(0, 3); // Limit to 3 scopes
372
+ return Array.from(scopes).slice(0, 3) // Limit to 3 scopes
339
373
  }
340
374
 
341
375
  parseCommitSuggestions(content) {
342
376
  // Parse AI response to extract suggestions
343
- const lines = content.split('\n').filter(line => line.trim());
344
- const suggestions = [];
377
+ const lines = content.split('\n').filter((line) => line.trim())
378
+ const suggestions = []
345
379
 
346
- lines.forEach(line => {
380
+ lines.forEach((line) => {
347
381
  // Remove numbering and clean up
348
- const cleaned = line.replace(/^\d+\.\s*/, '').replace(/^-\s*/, '').trim();
382
+ const cleaned = line
383
+ .replace(/^\d+\.\s*/, '')
384
+ .replace(/^-\s*/, '')
385
+ .trim()
349
386
  if (cleaned && cleaned.length > 10 && cleaned.includes(':')) {
350
- suggestions.push(cleaned);
387
+ suggestions.push(cleaned)
351
388
  }
352
- });
389
+ })
353
390
 
354
- return suggestions.length > 0 ? suggestions : [content.trim()];
391
+ return suggestions.length > 0 ? suggestions : [content.trim()]
355
392
  }
356
393
 
357
394
  generateRuleBasedCommitSuggestion(message, context) {
358
- const suggestions = [];
395
+ const suggestions = []
359
396
 
360
397
  if (message) {
361
398
  // Improve existing message
362
- const improved = this.improveCommitMessage(message);
363
- suggestions.push(improved);
399
+ const improved = this.improveCommitMessage(message)
400
+ suggestions.push(improved)
364
401
  }
365
402
 
366
403
  if (context) {
367
404
  // Generate from context
368
- const fromContext = this.generateFromContext(context);
369
- suggestions.push(fromContext);
405
+ const fromContext = this.generateFromContext(context)
406
+ suggestions.push(fromContext)
370
407
  }
371
408
 
372
409
  // Add generic suggestions
@@ -374,71 +411,75 @@ Provide 3 suggestions.`;
374
411
  'feat: add new functionality',
375
412
  'fix: resolve issue with component',
376
413
  'docs: update documentation'
377
- );
414
+ )
378
415
 
379
416
  return {
380
417
  success: true,
381
418
  suggestions: suggestions.slice(0, 3),
382
- source: 'rule-based'
383
- };
419
+ source: 'rule-based',
420
+ }
384
421
  }
385
422
 
386
423
  improveCommitMessage(message) {
387
424
  // Basic improvements
388
- let improved = message.trim();
425
+ let improved = message.trim()
389
426
 
390
427
  // Add conventional commit prefix if missing
391
428
  if (!/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)/.test(improved)) {
392
- improved = `feat: ${improved}`;
429
+ improved = `feat: ${improved}`
393
430
  }
394
431
 
395
432
  // Ensure imperative mood
396
- improved = improved.replace(/^(\w+)ed\s/, '$1 ');
397
- improved = improved.replace(/^(\w+)s\s/, '$1 ');
433
+ improved = improved.replace(/^(\w+)ed\s/, '$1 ')
434
+ improved = improved.replace(/^(\w+)s\s/, '$1 ')
398
435
 
399
- return improved;
436
+ return improved
400
437
  }
401
438
 
402
439
  generateFromContext(context) {
403
440
  if (!context || typeof context !== 'string') {
404
- return 'feat: implement changes';
441
+ return 'feat: implement changes'
405
442
  }
406
443
 
407
444
  if (context.includes('test')) {
408
- return 'test: add test coverage';
445
+ return 'test: add test coverage'
409
446
  }
410
447
  if (context.includes('doc')) {
411
- return 'docs: update documentation';
448
+ return 'docs: update documentation'
412
449
  }
413
450
  if (context.includes('config')) {
414
- return 'chore: update configuration';
451
+ return 'chore: update configuration'
415
452
  }
416
453
  if (context.includes('fix') || context.includes('bug')) {
417
- return 'fix: resolve issue';
454
+ return 'fix: resolve issue'
418
455
  }
419
456
 
420
- return 'feat: implement changes';
457
+ return 'feat: implement changes'
421
458
  }
422
459
 
423
460
  // Utility method for displaying interactive results
424
461
  displayInteractiveResults(results) {
425
- if (!results) return;
462
+ if (!results) {
463
+ return
464
+ }
426
465
 
427
- console.log(colors.header('\nšŸ“Š Interactive Session Results:'));
466
+ console.log(colors.header('\nšŸ“Š Interactive Session Results:'))
428
467
 
429
468
  if (results.changelog) {
430
- console.log(colors.subheader('Generated Changelog:'));
431
- console.log(results.changelog);
469
+ console.log(colors.subheader('Generated Changelog:'))
470
+ console.log(results.changelog)
432
471
  }
433
472
 
434
473
  if (results.insights) {
435
- console.log(colors.subheader('\nšŸ“ˆ Insights:'));
436
- console.log(`Total commits: ${colors.number(results.insights.totalCommits)}`);
437
- console.log(`Risk level: ${colors.highlight(results.insights.riskLevel)}`);
474
+ console.log(colors.subheader('\nšŸ“ˆ Insights:'))
475
+ console.log(`Total commits: ${colors.number(results.insights.totalCommits)}`)
476
+ console.log(`Risk level: ${colors.highlight(results.insights.riskLevel)}`)
438
477
  }
439
478
 
440
479
  if (results.analyzedCommits) {
441
- console.log(colors.subheader(`\nšŸ” Processed ${colors.number(results.analyzedCommits.length)} commits`));
480
+ console.log(
481
+ colors.subheader(`\nšŸ” Processed ${colors.number(results.analyzedCommits.length)} commits`)
482
+ )
442
483
  }
443
484
  }
444
- }
485
+ }