@entro314labs/ai-changelog-generator 3.7.1 → 3.8.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.
@@ -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}`))
@@ -285,6 +285,7 @@ class Colors {
285
285
  // Helper to get actual visible length of string (without ANSI codes)
286
286
  getVisibleLength(str) {
287
287
  // Remove ANSI escape sequences to get actual visible length
288
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Needed for ANSI codes
288
289
  return str.replace(/\x1b\[[0-9;]*m/g, '').length
289
290
  }
290
291
 
@@ -616,7 +617,7 @@ class Colors {
616
617
  return ''
617
618
  }
618
619
 
619
- const { headers = Object.keys(data[0]), align = 'left', padding = 1 } = options
620
+ const { headers = Object.keys(data[0]), align = 'left' } = options
620
621
  const rows = data.map((row) => headers.map((header) => String(row[header] || '')))
621
622
 
622
623
  // Calculate column widths
@@ -29,7 +29,9 @@ export function handleCLIError(error, context = 'application', options = {}) {
29
29
  if (showTips) {
30
30
  const tips = getContextualTips(error, context)
31
31
  if (tips.length > 0) {
32
- tips.forEach((tip) => console.error(colors.infoMessage(`šŸ’” Tip: ${tip}`)))
32
+ tips.forEach((tip) => {
33
+ console.error(colors.infoMessage(`šŸ’” Tip: ${tip}`))
34
+ })
33
35
  }
34
36
  }
35
37
 
@@ -191,41 +191,44 @@ export class MultiProgress {
191
191
  }
192
192
  }
193
193
 
194
+ import { format } from 'node:util'
195
+
194
196
  /**
195
197
  * Enhanced console with better formatting
196
198
  */
197
199
  export class EnhancedConsole {
198
- static log(message, type = 'info') {
200
+ static log(type = 'info', ...args) {
201
+ const message = format(...args)
199
202
  console.log(colors.statusSymbol(type, message))
200
203
  }
201
204
 
202
- static success(message) {
203
- EnhancedConsole.log(message, 'success')
205
+ static success(...args) {
206
+ EnhancedConsole.log('success', ...args)
204
207
  }
205
208
 
206
- static error(message) {
207
- EnhancedConsole.log(message, 'error')
209
+ static error(...args) {
210
+ EnhancedConsole.log('error', ...args)
208
211
  }
209
212
 
210
- static warn(message) {
211
- EnhancedConsole.log(message, 'warning')
213
+ static warn(...args) {
214
+ EnhancedConsole.log('warning', ...args)
212
215
  }
213
216
 
214
- static info(message) {
215
- EnhancedConsole.log(message, 'info')
217
+ static info(...args) {
218
+ EnhancedConsole.log('info', ...args)
216
219
  }
217
220
 
218
- static processing(message) {
219
- EnhancedConsole.log(message, 'processing')
221
+ static processing(...args) {
222
+ EnhancedConsole.log('processing', ...args)
220
223
  }
221
224
 
222
- static ai(message) {
223
- EnhancedConsole.log(message, 'ai')
225
+ static ai(...args) {
226
+ EnhancedConsole.log('ai', ...args)
224
227
  }
225
228
 
226
229
  static metrics(data, title = 'Metrics') {
227
230
  if (typeof data === 'string') {
228
- EnhancedConsole.log(data, 'metrics')
231
+ EnhancedConsole.log('metrics', data)
229
232
  } else if (data && Object.keys(data).length > 0) {
230
233
  EnhancedConsole.section(title)
231
234
  console.log(colors.formatMetrics(data))
@@ -101,7 +101,7 @@ export class DiffProcessor {
101
101
  * Process a single file's diff with intelligent compression
102
102
  */
103
103
  processFileDiff(file, options = {}) {
104
- const { budget = 4000, patterns = {}, isHighPriority = false } = options // Increased from 1500
104
+ const { budget = 4000, _patterns = {}, isHighPriority = false } = options // Increased from 1500
105
105
 
106
106
  if (!file.diff || file.diff === 'No diff available') {
107
107
  return {
@@ -120,17 +120,7 @@ export class DiffProcessor {
120
120
  }
121
121
 
122
122
  // Temporarily disable bulk pattern matching to preserve more detail
123
- // TODO: Make this smarter - only apply to truly repetitive patterns
124
- // const patternMatch = this.matchesBulkPattern(file, patterns)
125
- // if (patternMatch) {
126
- // return {
127
- // ...file,
128
- // diff: `[Bulk ${patternMatch.type}]: ${patternMatch.description}`,
129
- // processed: true,
130
- // compressionApplied: true,
131
- // bulkPattern: patternMatch.type,
132
- // }
133
- // }
123
+ // Pattern matching logic removed for cleanup
134
124
 
135
125
  // Apply intelligent truncation if still too large
136
126
  if (processedDiff.length > budget) {
@@ -370,7 +360,7 @@ export class DiffProcessor {
370
360
  * Apply intelligent truncation that preserves structure
371
361
  */
372
362
  intelligentTruncation(diff, budget, options = {}) {
373
- const { preserveStructure = false, filePath = '' } = options
363
+ const { preserveStructure = false, _filePath = '' } = options
374
364
 
375
365
  if (diff.length <= budget) {
376
366
  return diff