@entro314labs/ai-changelog-generator 3.7.1 → 3.8.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.
@@ -41,7 +41,8 @@ export class GitService {
41
41
  .trim()
42
42
  .split(' ')
43
43
  isMergeCommit = parents.length > 1
44
- } catch {
44
+ } catch (error) {
45
+ console.warn(colors.warningMessage(`Failed to check parent commits: ${error.message}`))
45
46
  isMergeCommit = false
46
47
  }
47
48
  }
@@ -203,7 +204,8 @@ export class GitService {
203
204
  } else {
204
205
  diff = 'New empty file created'
205
206
  }
206
- } catch {
207
+ } catch (error) {
208
+ console.warn(colors.warningMessage(`Failed to read new file content: ${error.message}`))
207
209
  diff = 'New file created (binary or inaccessible)'
208
210
  }
209
211
  } else if (status === 'D') {
@@ -217,7 +219,10 @@ export class GitService {
217
219
  } else {
218
220
  diff = 'File deleted from working directory (content was empty)'
219
221
  }
220
- } catch {
222
+ } catch (error) {
223
+ console.warn(
224
+ colors.warningMessage(`Failed to read deleted file content: ${error.message}`)
225
+ )
221
226
  diff = 'File deleted from working directory (content unavailable)'
222
227
  }
223
228
  } else if (status === 'M' || status.includes('M')) {
@@ -230,7 +235,8 @@ export class GitService {
230
235
  const stagedDiff = this.gitManager.execGitSafe(`git diff --cached -- "${filePath}"`)
231
236
  diff = stagedDiff || 'No diff available (binary or identical)'
232
237
  }
233
- } catch {
238
+ } catch (error) {
239
+ console.warn(colors.warningMessage(`Failed to generate diff: ${error.message}`))
234
240
  diff = 'Modified file (diff unavailable)'
235
241
  }
236
242
  } else if (status === 'R') {
@@ -250,7 +256,8 @@ export class GitService {
250
256
  try {
251
257
  const headResult = this.gitManager.execGitSafe(`git show HEAD:"${filePath}"`)
252
258
  beforeContent = headResult ? headResult.slice(0, 1000) : ''
253
- } catch {
259
+ } catch (error) {
260
+ console.warn(colors.warningMessage(`Failed to get before content: ${error.message}`))
254
261
  beforeContent = ''
255
262
  }
256
263
  // afterContent stays empty for deleted files
@@ -260,7 +267,10 @@ export class GitService {
260
267
  // Get HEAD version
261
268
  const headResult = this.gitManager.execGitSafe(`git show HEAD:"${filePath}"`)
262
269
  beforeContent = headResult ? headResult.slice(0, 1000) : ''
263
- } catch {
270
+ } catch (error) {
271
+ console.warn(
272
+ colors.warningMessage(`Failed to get before content (modified): ${error.message}`)
273
+ )
264
274
  beforeContent = ''
265
275
  }
266
276
 
@@ -268,7 +278,8 @@ export class GitService {
268
278
  // Get current working directory version
269
279
  const currentResult = this.gitManager.execGitSafe(`cat "${filePath}"`)
270
280
  afterContent = currentResult ? currentResult.slice(0, 1000) : ''
271
- } catch {
281
+ } catch (error) {
282
+ console.warn(colors.warningMessage(`Failed to get after content: ${error.message}`))
272
283
  afterContent = ''
273
284
  }
274
285
  }
@@ -301,7 +312,11 @@ export class GitService {
301
312
  language: 'unknown',
302
313
  importance: 'medium',
303
314
  complexity: { score: 1 },
304
- semanticChanges: { changeType: 'unknown', patterns: [], frameworks: [] },
315
+ semanticChanges: {
316
+ changeType: 'unknown',
317
+ patterns: [],
318
+ frameworks: [],
319
+ },
305
320
  functionalImpact: { scope: 'local', severity: 'low' },
306
321
  }
307
322
  }
@@ -328,7 +343,8 @@ export class GitService {
328
343
  }
329
344
 
330
345
  return { files: 0, insertions: 0, deletions: 0 }
331
- } catch {
346
+ } catch (error) {
347
+ console.warn(colors.warningMessage(`Failed to get diff stats: ${error.message}`))
332
348
  return { files: 0, insertions: 0, deletions: 0 }
333
349
  }
334
350
  }
@@ -420,7 +436,9 @@ export class GitService {
420
436
  category: categorizeFile(filePath),
421
437
  language: detectLanguage(filePath),
422
438
  importance: assessFileImportance(filePath, status),
423
- complexity: { level: changes > 100 ? 'high' : changes > 20 ? 'medium' : 'low' },
439
+ complexity: {
440
+ level: changes > 100 ? 'high' : changes > 20 ? 'medium' : 'low',
441
+ },
424
442
  semanticChanges: { patterns: ['merge-commit'] },
425
443
  functionalImpact: { level: changes > 50 ? 'high' : 'medium' },
426
444
  isMergeCommit: true,
@@ -662,7 +680,9 @@ export class GitService {
662
680
  details.push(techDetail)
663
681
  }
664
682
  }
665
- } catch (_error) {}
683
+ } catch (error) {
684
+ console.warn(colors.warningMessage(`Failed to extract technical details: ${error.message}`))
685
+ }
666
686
  }
667
687
 
668
688
  return details
@@ -830,7 +850,10 @@ export class GitService {
830
850
  .filter((line) => line.trim())
831
851
  .map((line) => {
832
852
  const parts = line.split(' ')
833
- return { size: parseInt(parts[1], 10), path: parts.slice(2).join(' ') }
853
+ return {
854
+ size: parseInt(parts[1], 10),
855
+ path: parts.slice(2).join(' '),
856
+ }
834
857
  })
835
858
  .filter((file) => file.size > 10 * 1024 * 1024) // 10MB threshold
836
859
 
@@ -1101,7 +1124,7 @@ ${
1101
1124
  )
1102
1125
  if (firstCommitOutput.trim()) {
1103
1126
  const firstCommitDate = new Date(firstCommitOutput.trim())
1104
- const ageInDays = Math.floor((new Date() - firstCommitDate) / (1000 * 60 * 60 * 24))
1127
+ const ageInDays = Math.floor((Date.now() - firstCommitDate) / (1000 * 60 * 60 * 24))
1105
1128
  analysis.metrics.repositoryAgeInDays = ageInDays
1106
1129
  }
1107
1130
  } catch (error) {
@@ -306,7 +306,10 @@ export class CLIController {
306
306
  .option('output', { alias: 'o', type: 'string', description: 'Output file path' })
307
307
  .option('since', { type: 'string', description: 'Analyze changes since this git ref' })
308
308
  .option('author', { alias: 'a', type: 'string', description: 'Filter commits by author' })
309
- .option('tag-range', { type: 'string', description: 'Generate changelog between tags (v1.0..v2.0)' })
309
+ .option('tag-range', {
310
+ type: 'string',
311
+ description: 'Generate changelog between tags (v1.0..v2.0)',
312
+ })
310
313
  .option('silent', { type: 'boolean', description: 'Suppress non-essential output' })
311
314
  .option('dry-run', { type: 'boolean', description: 'Preview without writing files' })
312
315
  .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode' })
@@ -544,35 +547,33 @@ class WorkingDirCommand extends BaseCommand {
544
547
  class FromCommitsCommand extends BaseCommand {
545
548
  async execute(argv, appService) {
546
549
  const _config = this.processStandardFlags(argv, appService)
547
- console.log(colors.processingMessage(`🔍 Generating changelog from commits: ${argv.commits.join(', ')}`))
550
+ EnhancedConsole.processing(`Generating changelog from commits: ${argv.commits.join(', ')}`)
548
551
 
549
552
  try {
550
553
  const result = await appService.generateChangelogFromCommits(argv.commits)
551
554
 
552
555
  if (result?.changelog) {
553
- console.log(colors.successMessage('\n✅ Changelog generated successfully!'))
554
- console.log(colors.dim('─'.repeat(50)))
556
+ EnhancedConsole.success('Changelog generated successfully!')
557
+ EnhancedConsole.divider()
555
558
  console.log(result.changelog)
556
559
  } else {
557
- console.log(colors.warningMessage('No changelog could be generated from the specified commits.'))
560
+ EnhancedConsole.warn('No changelog could be generated from the specified commits.')
558
561
  }
559
562
  } catch (error) {
560
- console.error(colors.errorMessage(`Error generating changelog: ${error.message}`))
563
+ EnhancedConsole.error(`Error generating changelog: ${error.message}`)
561
564
  }
562
565
  }
563
566
  }
564
567
 
565
568
  class CommitMessageCommand extends BaseCommand {
566
569
  async execute(_argv, appService) {
567
- console.log(
568
- colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...')
569
- )
570
+ EnhancedConsole.processing('Analyzing current changes for commit message suggestions...')
570
571
 
571
572
  try {
572
573
  const result = await appService.generateCommitMessage()
573
574
 
574
575
  if (result?.suggestions && result.suggestions.length > 0) {
575
- console.log(colors.successMessage('\n✅ Generated commit message suggestions:'))
576
+ EnhancedConsole.success('Generated commit message suggestions:')
576
577
  result.suggestions.forEach((suggestion, index) => {
577
578
  console.log(`${colors.number(index + 1)}. ${colors.highlight(suggestion)}`)
578
579
  })
@@ -581,18 +582,18 @@ class CommitMessageCommand extends BaseCommand {
581
582
  console.log(colors.dim(`\nContext: ${result.context}`))
582
583
  }
583
584
  } else {
584
- console.log(colors.warningMessage('No commit message suggestions could be generated.'))
585
- console.log(colors.infoMessage('Make sure you have uncommitted changes.'))
585
+ EnhancedConsole.warn('No commit message suggestions could be generated.')
586
+ EnhancedConsole.info('Make sure you have uncommitted changes.')
586
587
  }
587
588
  } catch (error) {
588
- console.error(colors.errorMessage(`Error generating commit message: ${error.message}`))
589
+ EnhancedConsole.error(`Error generating commit message: ${error.message}`)
589
590
  }
590
591
  }
591
592
  }
592
593
 
593
594
  class CommitCommand extends BaseCommand {
594
595
  async execute(argv, appService) {
595
- console.log(colors.processingMessage('🚀 Starting interactive commit workflow...'))
596
+ EnhancedConsole.processing('Starting interactive commit workflow...')
596
597
 
597
598
  try {
598
599
  // Process flags and model override
@@ -611,29 +612,25 @@ class CommitCommand extends BaseCommand {
611
612
 
612
613
  if (result?.success) {
613
614
  if (argv.dryRun) {
614
- console.log(colors.successMessage('Commit workflow completed (dry-run mode)'))
615
+ EnhancedConsole.success('Commit workflow completed (dry-run mode)')
615
616
  console.log(colors.highlight(`Proposed commit message:\n${result.commitMessage}`))
616
617
  } else {
617
- console.log(colors.successMessage('Changes committed successfully!'))
618
+ EnhancedConsole.success('Changes committed successfully!')
618
619
  console.log(colors.highlight(`Commit: ${result.commitHash}`))
619
620
  console.log(colors.dim(`Message: ${result.commitMessage}`))
620
621
  }
621
622
  } else {
622
- console.log(colors.warningMessage('Commit workflow cancelled or no changes to commit.'))
623
+ EnhancedConsole.warn('Commit workflow cancelled or no changes to commit.')
623
624
  }
624
625
  } catch (error) {
625
- console.error(colors.errorMessage(`Commit workflow failed: ${error.message}`))
626
+ EnhancedConsole.error(`Commit workflow failed: ${error.message}`)
626
627
 
627
628
  // Provide helpful suggestions based on error type
628
629
  if (error.message.includes('No changes')) {
629
- console.log(
630
- colors.infoMessage('💡 Try making some changes first, then run the commit command.')
631
- )
630
+ EnhancedConsole.info('Try making some changes first, then run the commit command.')
632
631
  } else if (error.message.includes('git')) {
633
- console.log(
634
- colors.infoMessage(
635
- '💡 Make sure you are in a git repository and git is properly configured.'
636
- )
632
+ EnhancedConsole.info(
633
+ 'Make sure you have uncommitted changes.'
637
634
  )
638
635
  }
639
636
  }
@@ -664,8 +661,8 @@ class ProvidersCommand extends BaseCommand {
664
661
  await this.listModels(appService, argv.provider)
665
662
  break
666
663
  default:
667
- console.log(colors.errorMessage('Unknown provider subcommand'))
668
- console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate, status, models'))
664
+ EnhancedConsole.error('Unknown provider subcommand')
665
+ EnhancedConsole.info('Available subcommands: list, switch, configure, validate, status, models')
669
666
  }
670
667
  }
671
668
 
@@ -702,8 +699,8 @@ class ProvidersCommand extends BaseCommand {
702
699
 
703
700
  async switchProvider(appService, providerName) {
704
701
  if (!providerName) {
705
- console.log(colors.errorMessage('Please specify a provider name'))
706
- console.log(colors.infoMessage('Usage: ai-changelog providers switch <provider>'))
702
+ EnhancedConsole.error('Please specify a provider name')
703
+ EnhancedConsole.info('Usage: ai-changelog providers switch <provider>')
707
704
  return
708
705
  }
709
706
 
@@ -711,12 +708,10 @@ class ProvidersCommand extends BaseCommand {
711
708
  const result = await appService.switchProvider(providerName)
712
709
 
713
710
  if (result.success) {
714
- console.log(colors.successMessage(`✅ Switched to ${providerName} provider`))
711
+ EnhancedConsole.success(`Switched to ${providerName} provider`)
715
712
  } else {
716
- console.log(colors.errorMessage(`❌ Failed to switch provider: ${result.error}`))
717
- console.log(
718
- colors.infoMessage('Use "ai-changelog providers list" to see available providers')
719
- )
713
+ EnhancedConsole.error(`Failed to switch provider: ${result.error}`)
714
+ EnhancedConsole.info('Use "ai-changelog providers list" to see available providers')
720
715
  }
721
716
  } catch (error) {
722
717
  EnhancedConsole.error(`Error switching provider: ${error.message}`)
@@ -743,7 +738,7 @@ class ProvidersCommand extends BaseCommand {
743
738
  }
744
739
 
745
740
  console.log(colors.header(`\n🔧 Configuring ${providerName.toUpperCase()} Provider`))
746
- console.log(colors.infoMessage('Please add the following to your .env.local file:\n'))
741
+ EnhancedConsole.info('Please add the following to your .env.local file:\n')
747
742
 
748
743
  switch (providerName.toLowerCase()) {
749
744
  case 'openai':
@@ -776,7 +771,7 @@ class ProvidersCommand extends BaseCommand {
776
771
  console.log(colors.code(`${providerName.toUpperCase()}_API_KEY=your_api_key_here`))
777
772
  }
778
773
 
779
- console.log(colors.infoMessage('\nAfter adding the configuration, run:'))
774
+ EnhancedConsole.info('\nAfter adding the configuration, run:')
780
775
  console.log(colors.highlight(`ai-changelog providers validate ${providerName}`))
781
776
  } catch (error) {
782
777
  EnhancedConsole.error(`Error configuring provider: ${error.message}`)
@@ -895,7 +890,9 @@ class ProvidersCommand extends BaseCommand {
895
890
 
896
891
  // Summary
897
892
  const healthy = healthResults.filter((r) => r.status === 'healthy').length
898
- const unhealthy = healthResults.filter((r) => ['unhealthy', 'error'].includes(r.status)).length
893
+ const unhealthy = healthResults.filter((r) =>
894
+ ['unhealthy', 'error'].includes(r.status)
895
+ ).length
899
896
  const unconfigured = healthResults.filter((r) => r.status === 'unconfigured').length
900
897
 
901
898
  console.log(colors.dim('─'.repeat(40)))
@@ -918,7 +915,9 @@ class ProvidersCommand extends BaseCommand {
918
915
  const provider = providers.find((p) => p.name.toLowerCase() === providerName.toLowerCase())
919
916
  if (!provider) {
920
917
  console.log(colors.errorMessage(`Provider '${providerName}' not found.`))
921
- console.log(colors.infoMessage('Use "ai-changelog providers list" to see available providers.'))
918
+ console.log(
919
+ colors.infoMessage('Use "ai-changelog providers list" to see available providers.')
920
+ )
922
921
  return
923
922
  }
924
923
 
@@ -974,13 +973,31 @@ class ProvidersCommand extends BaseCommand {
974
973
 
975
974
  getKnownModelsForProvider(providerName) {
976
975
  const knownModels = {
977
- openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo', 'o1', 'o1-mini', 'o3-mini'],
978
- anthropic: ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'],
976
+ openai: [
977
+ 'gpt-4o',
978
+ 'gpt-4o-mini',
979
+ 'gpt-4-turbo',
980
+ 'gpt-4',
981
+ 'gpt-3.5-turbo',
982
+ 'o1',
983
+ 'o1-mini',
984
+ 'o3-mini',
985
+ ],
986
+ anthropic: [
987
+ 'claude-sonnet-4-20250514',
988
+ 'claude-3-5-sonnet-20241022',
989
+ 'claude-3-opus-20240229',
990
+ 'claude-3-haiku-20240307',
991
+ ],
979
992
  google: ['gemini-2.0-flash-exp', 'gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro'],
980
993
  azure: ['gpt-4o', 'gpt-4-turbo', 'gpt-35-turbo'],
981
994
  ollama: ['llama3.2', 'llama3.1', 'mistral', 'codellama', 'deepseek-coder'],
982
995
  lmstudio: ['local-model'],
983
- bedrock: ['anthropic.claude-3-sonnet', 'anthropic.claude-3-haiku', 'amazon.titan-text-express'],
996
+ bedrock: [
997
+ 'anthropic.claude-3-sonnet',
998
+ 'anthropic.claude-3-haiku',
999
+ 'amazon.titan-text-express',
1000
+ ],
984
1001
  vertex: ['gemini-1.5-pro', 'gemini-1.5-flash'],
985
1002
  huggingface: ['mistralai/Mistral-7B-Instruct-v0.2', 'google/flan-t5-xxl'],
986
1003
  }
@@ -1109,7 +1126,9 @@ class StashCommand extends BaseCommand {
1109
1126
  console.log(colors.header('\n📋 Stash Changelog:\n'))
1110
1127
  console.log(`## Stashed Changes\n`)
1111
1128
  console.log(`**Message:** ${details.message}`)
1112
- console.log(`**Stats:** ${details.stats.filesChanged} files, +${details.stats.insertions}/-${details.stats.deletions}`)
1129
+ console.log(
1130
+ `**Stats:** ${details.stats.filesChanged} files, +${details.stats.insertions}/-${details.stats.deletions}`
1131
+ )
1113
1132
 
1114
1133
  console.log(`\n**Files affected:**`)
1115
1134
  details.files.forEach((f) => {
@@ -111,9 +111,11 @@ export class ConfigurationManager {
111
111
  const content = fs.readFileSync(this.configPath, 'utf8')
112
112
  const envVars = this.parseEnvFile(content)
113
113
  Object.assign(defaults, envVars)
114
- } catch (_error) {
114
+ } catch (error) {
115
115
  console.warn(
116
- colors.warningMessage(`Warning: Could not load config from ${this.configPath}`)
116
+ colors.warningMessage(
117
+ `Warning: Could not load config from ${this.configPath}: ${error.message}`
118
+ )
117
119
  )
118
120
  }
119
121
  }
@@ -179,10 +181,10 @@ export class ConfigurationManager {
179
181
 
180
182
  // Deep merge with defaults
181
183
  return this.deepMergeConfig(defaultConfig, yamlConfig)
182
- } catch (_error) {
184
+ } catch (error) {
183
185
  console.warn(
184
186
  colors.warningMessage(
185
- `Warning: Could not load changelog config from ${this.changelogConfigPath}, using defaults`
187
+ `Warning: Could not load changelog config from ${this.changelogConfigPath}: ${error.message}, using defaults`
186
188
  )
187
189
  )
188
190
  return defaultConfig
@@ -1,4 +1,5 @@
1
1
  import process from 'node:process'
2
+ import { execSync } from 'node:child_process'
2
3
 
3
4
  import colors from '../../shared/constants/colors.js'
4
5
  import { EnhancedConsole, SimpleSpinner } from '../../shared/utils/cli-ui.js'
@@ -101,28 +102,40 @@ export class InteractiveWorkflowService {
101
102
  // Use AI to suggest better commit message
102
103
  if (this.aiAnalysisService.hasAI) {
103
104
  const prompt = message
104
- ? `Improve this commit message following conventional commit format and best practices:
105
+ ? `Improve this commit message following conventional commit format.
105
106
 
106
107
  Original: "${message}"
107
108
 
109
+ Provide EXACTLY 3 alternative commit messages, one per line, numbered 1-3.
110
+ Do NOT include any other text, explanations, or conversational phrases.
111
+
108
112
  Requirements:
109
- - Use conventional commit format (type(scope): description)
113
+ - Use conventional commit format: type(scope): description
110
114
  - Keep first line under 72 characters
111
115
  - Use imperative mood
112
116
  - Be specific and descriptive
113
117
 
114
- Provide 3 alternative suggestions.`
115
- : `Suggest a commit message for these changes:
118
+ Example output format:
119
+ 1. feat(auth): add OAuth2 authentication support
120
+ 2. fix(api): resolve timeout in user endpoint
121
+ 3. refactor(db): optimize query performance`
122
+ : `Generate commit messages for these changes:
116
123
 
117
124
  ${analysisContext}
118
125
 
126
+ Provide EXACTLY 3 commit message suggestions, one per line, numbered 1-3.
127
+ Do NOT include any other text, explanations, or conversational phrases.
128
+
119
129
  Requirements:
120
- - Use conventional commit format (type(scope): description)
130
+ - Use conventional commit format: type(scope): description
121
131
  - Keep first line under 72 characters
122
132
  - Use imperative mood
123
133
  - Be specific and descriptive
124
134
 
125
- Provide 3 suggestions.`
135
+ Example output format:
136
+ 1. feat(auth): add OAuth2 authentication support
137
+ 2. fix(api): resolve timeout in user endpoint
138
+ 3. refactor(db): optimize query performance`
126
139
 
127
140
  const response = await this.aiAnalysisService.aiProvider.generateCompletion(
128
141
  [
@@ -195,6 +208,71 @@ Provide 3 suggestions.`
195
208
  summary += ` (${details.join(', ')})`
196
209
  }
197
210
 
211
+ // Add actual change summaries from git diff for better context
212
+ summary += '\n\nKey changes:\n'
213
+
214
+ try {
215
+ // Get a concise summary of actual changes, limiting to top 10 files
216
+ const filesToAnalyze = changes.slice(0, 10)
217
+
218
+ filesToAnalyze.forEach((change) => {
219
+ const filePath = change.filePath || change.path
220
+ const status = change.status
221
+
222
+ try {
223
+ if (status === 'M' || status === 'MM') {
224
+ // For modified files, get a brief diff stat
225
+ const diffStat = execSync(`git diff HEAD -- "${filePath}" | head -50`, {
226
+ encoding: 'utf8',
227
+ maxBuffer: 1024 * 100,
228
+ })
229
+
230
+ if (diffStat) {
231
+ // Extract some meaningful info from diff
232
+ const addedLines = (diffStat.match(/^\+(?!\+)/gm) || []).length
233
+ const removedLines = (diffStat.match(/^-(?!-)/gm) || []).length
234
+
235
+ // Try to extract function/class names or key changes
236
+ const meaningfulLines = diffStat
237
+ .split('\n')
238
+ .filter(
239
+ (line) =>
240
+ line.startsWith('+') &&
241
+ (line.includes('function') ||
242
+ line.includes('class') ||
243
+ line.includes('const') ||
244
+ line.includes('export') ||
245
+ line.includes('import') ||
246
+ line.includes('async') ||
247
+ line.includes('//'))
248
+ )
249
+ .slice(0, 2)
250
+ .map((line) => line.substring(1).trim())
251
+
252
+ if (meaningfulLines.length > 0) {
253
+ summary += `- ${filePath}: +${addedLines}/-${removedLines} lines (${meaningfulLines[0].substring(0, 60)}...)\n`
254
+ } else {
255
+ summary += `- ${filePath}: +${addedLines}/-${removedLines} lines\n`
256
+ }
257
+ }
258
+ } else if (status === 'A' || status === '??') {
259
+ summary += `- ${filePath}: New file\n`
260
+ } else if (status === 'D') {
261
+ summary += `- ${filePath}: Deleted\n`
262
+ }
263
+ } catch (fileError) {
264
+ // Skip files that can't be diffed
265
+ }
266
+ })
267
+
268
+ if (changes.length > 10) {
269
+ summary += `... and ${changes.length - 10} more files\n`
270
+ }
271
+ } catch (error) {
272
+ // If diff analysis fails, fall back to basic summary
273
+ console.warn('Could not analyze diffs:', error.message)
274
+ }
275
+
198
276
  return summary
199
277
  }
200
278
 
@@ -380,17 +458,46 @@ Provide 3 suggestions.`
380
458
  }
381
459
 
382
460
  parseCommitSuggestions(content) {
383
- // Parse AI response to extract suggestions
461
+ // Parse AI response to extract valid commit message suggestions
384
462
  const lines = content.split('\n').filter((line) => line.trim())
385
463
  const suggestions = []
386
464
 
465
+ // Valid conventional commit types
466
+ const validTypes = [
467
+ 'feat',
468
+ 'fix',
469
+ 'docs',
470
+ 'style',
471
+ 'refactor',
472
+ 'test',
473
+ 'chore',
474
+ 'perf',
475
+ 'ci',
476
+ 'build',
477
+ 'revert',
478
+ ]
479
+
387
480
  lines.forEach((line) => {
388
481
  // Remove numbering and clean up
389
- const cleaned = line
390
- .replace(/^\d+\.\s*/, '')
391
- .replace(/^-\s*/, '')
392
- .trim()
393
- if (cleaned && cleaned.length > 10 && cleaned.includes(':')) {
482
+ const cleaned = line.replace(/^\d+\.\s*/, '').replace(/^-\s*/, '').trim()
483
+
484
+ // Must be a reasonable length
485
+ if (!cleaned || cleaned.length < 10 || cleaned.length > 100) {
486
+ return
487
+ }
488
+
489
+ // Must have a colon (conventional format)
490
+ if (!cleaned.includes(':')) {
491
+ return
492
+ }
493
+
494
+ // Must start with a valid conventional commit type
495
+ const startsWithValidType = validTypes.some((type) => {
496
+ const pattern = new RegExp(`^${type}(\\(|:)`, 'i')
497
+ return pattern.test(cleaned)
498
+ })
499
+
500
+ if (startsWithValidType) {
394
501
  suggestions.push(cleaned)
395
502
  }
396
503
  })
@@ -5,7 +5,8 @@
5
5
  * Provides Model Context Protocol interface for changelog generation
6
6
  */
7
7
 
8
- import { execSync } from 'node:child_process'
8
+ import { exec } from 'node:child_process'
9
+ import { promisify } from 'node:util'
9
10
  import fs from 'node:fs'
10
11
  import path, { dirname } from 'node:path'
11
12
  import process from 'node:process'
@@ -18,8 +19,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
18
19
  import { ChangelogOrchestrator } from '../../application/orchestrators/changelog.orchestrator.js'
19
20
  // Import application services
20
21
  import { ApplicationService } from '../../application/services/application.service.js'
21
- import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
22
- import { GitService } from '../../domains/git/git.service.js'
22
+
23
23
  import { ConfigurationManager } from '../config/configuration.manager.js'
24
24
  import { ProviderManagerService } from '../providers/provider-manager.service.js'
25
25
 
@@ -312,7 +312,7 @@ class AIChangelogMCPServer {
312
312
 
313
313
  let result
314
314
 
315
- if (source === 'working-dir' || (source === 'auto' && this.hasWorkingDirectoryChanges())) {
315
+ if (source === 'working-dir' || (source === 'auto' && (await this.hasWorkingDirectoryChanges()))) {
316
316
  // Generate from working directory changes using ChangelogService
317
317
  // Access the changelogService through the orchestrator
318
318
  await this.changelogOrchestrator.ensureInitialized()
@@ -324,7 +324,7 @@ class AIChangelogMCPServer {
324
324
  })
325
325
 
326
326
  // Format result to match expected structure
327
- if (result && result.changelog) {
327
+ if (result?.changelog) {
328
328
  result = {
329
329
  content: result.changelog,
330
330
  metadata: {
@@ -522,12 +522,15 @@ class AIChangelogMCPServer {
522
522
  }
523
523
  }
524
524
 
525
- hasWorkingDirectoryChanges() {
525
+ async hasWorkingDirectoryChanges() {
526
526
  try {
527
- // Simple check for working directory changes
528
- const result = execSync('git status --porcelain', { encoding: 'utf8' })
529
- return result.trim().length > 0
530
- } catch (_error) {
527
+ const execAsync = promisify(exec)
528
+ const { stdout } = await execAsync('git status --porcelain', { encoding: 'utf8' })
529
+ return stdout.trim().length > 0
530
+ } catch (error) {
531
+ // If git is not installed or not a git repo, we can assume no changes for our purpose
532
+ // but we should log the error for debugging
533
+ console.warn(`[MCP] Git check warning: ${error.message}`)
531
534
  return false
532
535
  }
533
536
  }
@@ -87,7 +87,8 @@ export class BaseProvider {
87
87
  async selectOptimalModel(commitInfo) {
88
88
  try {
89
89
  return this.getModelRecommendation(commitInfo)
90
- } catch (_error) {
90
+ } catch (error) {
91
+ console.warn(`Warning: Model selection failed: ${error.message}. Using default model.`)
91
92
  return { model: this.getDefaultModel(), reason: 'fallback' }
92
93
  }
93
94
  }
@@ -14,7 +14,9 @@ function addWarningToCache(key) {
14
14
  warningCache.clear()
15
15
  // Keep last 80% of entries
16
16
  const keepCount = Math.floor(MAX_WARNING_CACHE_SIZE * 0.8)
17
- entries.slice(-keepCount).forEach((entry) => warningCache.add(entry))
17
+ entries.slice(-keepCount).forEach((entry) => {
18
+ warningCache.add(entry)
19
+ })
18
20
  }
19
21
  warningCache.add(key)
20
22
  }
@@ -483,7 +483,7 @@ export class CommitMessageValidationService {
483
483
  * Display validation results with colors
484
484
  */
485
485
  displayValidationResults(validationResult) {
486
- const { valid, errors, warnings, suggestions, score, summary } = validationResult
486
+ const { valid, errors, warnings, suggestions, summary } = validationResult
487
487
 
488
488
  EnhancedConsole.section('🔍 Commit Message Validation')
489
489
  console.log(colors.secondary(`${summary}`))