@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,201 +1,297 @@
1
- import yargs from 'yargs/yargs';
2
- import { hideBin } from 'yargs/helpers';
3
- import { access } from 'fs/promises';
4
- import path from 'path';
5
- import { ApplicationService } from '../../application/services/application.service.js';
6
- import colors from '../../shared/constants/colors.js';
7
- import { formatDuration, promptForConfig } from '../../shared/utils/utils.js';
8
- import { setupProcessErrorHandlers, handleCLIError, getDefaultStartupTips } from '../../shared/utils/cli-entry-utils.js';
1
+ import { access } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import { hideBin } from 'yargs/helpers'
6
+ import yargs from 'yargs/yargs'
7
+
8
+ import { ApplicationService } from '../../application/services/application.service.js'
9
+ import colors from '../../shared/constants/colors.js'
10
+ import { handleCLIError, setupProcessErrorHandlers } from '../../shared/utils/cli-entry-utils.js'
11
+ import { EnhancedConsole } from '../../shared/utils/cli-ui.js'
12
+ import { formatDuration, promptForConfig } from '../../shared/utils/utils.js'
9
13
 
10
14
  export class CLIController {
11
15
  constructor() {
12
- this.commands = new Map();
13
- this.appService = null;
14
- this.startTime = Date.now();
16
+ this.commands = new Map()
17
+ this.appService = null
18
+ this.startTime = Date.now()
15
19
 
16
20
  // Setup enhanced error handling
17
21
  setupProcessErrorHandlers('AI Changelog Generator', {
18
22
  gracefulShutdown: true,
19
23
  logErrors: true,
20
- showStack: process.env.DEBUG === 'true'
21
- });
24
+ showStack: process.env.DEBUG === 'true',
25
+ })
22
26
 
23
- this.registerCommands();
27
+ this.registerCommands()
24
28
  }
25
29
 
26
30
  registerCommands() {
27
31
  // Register all available commands
28
- this.commands.set('default', new DefaultCommand());
29
- this.commands.set('init', new InitCommand());
30
- this.commands.set('validate', new ValidateCommand());
31
- this.commands.set('analyze', new AnalyzeCommand());
32
- this.commands.set('analyze-commits', new AnalyzeCommitsCommand());
33
- this.commands.set('git-info', new GitInfoCommand());
34
- this.commands.set('health', new HealthCommand());
35
- this.commands.set('branches', new BranchesCommand());
36
- this.commands.set('comprehensive', new ComprehensiveCommand());
37
- this.commands.set('untracked', new UntrackedCommand());
38
- this.commands.set('working-dir', new WorkingDirCommand());
39
- this.commands.set('from-commits', new FromCommitsCommand());
40
- this.commands.set('commit-message', new CommitMessageCommand());
41
- this.commands.set('commit', new CommitCommand());
42
- this.commands.set('providers', new ProvidersCommand());
32
+ this.commands.set('default', new DefaultCommand())
33
+ this.commands.set('init', new InitCommand())
34
+ this.commands.set('validate', new ValidateCommand())
35
+ this.commands.set('analyze', new AnalyzeCommand())
36
+ this.commands.set('analyze-commits', new AnalyzeCommitsCommand())
37
+ this.commands.set('git-info', new GitInfoCommand())
38
+ this.commands.set('health', new HealthCommand())
39
+ this.commands.set('branches', new BranchesCommand())
40
+ this.commands.set('comprehensive', new ComprehensiveCommand())
41
+ this.commands.set('untracked', new UntrackedCommand())
42
+ this.commands.set('working-dir', new WorkingDirCommand())
43
+ this.commands.set('from-commits', new FromCommitsCommand())
44
+ this.commands.set('commit-message', new CommitMessageCommand())
45
+ this.commands.set('commit', new CommitCommand())
46
+ this.commands.set('providers', new ProvidersCommand())
43
47
  }
44
48
 
45
49
  async runCLI() {
46
50
  try {
47
51
  // Ensure config exists
48
- await this.ensureConfig();
52
+ await this.ensureConfig()
49
53
 
50
- const argv = await this.setupYargs();
54
+ const argv = await this.setupYargs()
51
55
 
52
56
  // Initialize application service with CLI options
53
57
  this.appService = new ApplicationService({
54
58
  dryRun: argv.dryRun,
55
59
  noColor: argv.noColor,
56
- silent: argv.silent
57
- });
60
+ silent: argv.silent,
61
+ })
58
62
 
59
63
  // Execute command
60
- const commandName = argv._[0] || 'default';
61
- const command = this.commands.get(commandName);
64
+ const commandName = argv._[0] || 'default'
65
+ const command = this.commands.get(commandName)
62
66
 
63
67
  if (!command) {
64
- throw new Error(`Unknown command: ${commandName}`);
68
+ throw new Error(`Unknown command: ${commandName}`)
65
69
  }
66
70
 
67
- await command.execute(argv, this.appService);
71
+ await command.execute(argv, this.appService)
68
72
 
69
73
  // Show completion metrics
70
- await this.showMetrics();
71
-
74
+ await this.showMetrics()
72
75
  } catch (error) {
73
76
  handleCLIError(error, 'run CLI application', {
74
77
  exitOnError: false,
75
78
  showTips: true,
76
- showStack: process.env.DEBUG === 'true'
77
- });
78
- process.exitCode = 1;
79
+ showStack: process.env.DEBUG === 'true',
80
+ })
81
+ process.exitCode = 1
79
82
  }
80
83
  }
81
84
 
82
85
  async ensureConfig() {
83
- const configPath = path.join(process.cwd(), '.env.local');
86
+ const configPath = path.join(process.cwd(), '.env.local')
84
87
  try {
85
- await access(configPath);
88
+ await access(configPath)
86
89
  } catch {
87
- await promptForConfig();
90
+ await promptForConfig()
88
91
  }
89
92
  }
90
93
 
91
94
  setupYargs() {
92
- return yargs(hideBin(process.argv))
93
- .scriptName("ai-changelog")
94
- .usage(`${colors.header('AI Changelog Generator')} - ${colors.secondary('Automatically generate changelogs from your git commits using AI.')}\n\n${colors.header('Usage:')} $0 [command] [options]`)
95
-
96
- // Default command
97
- .command('$0', 'Generate a changelog from git commits (default command).', (yargs) => {
98
- yargs
99
- .option('interactive', { alias: 'i', type: 'boolean', description: 'Choose commits interactively.' })
100
- .option('release-version', { alias: 'v', type: 'string', description: 'Set the release version (e.g., 1.2.3).' })
101
- .option('since', { alias: 's', type: 'string', description: 'Generate changelog since a specific git ref (tag/commit).' })
102
- .option('model', { alias: 'm', type: 'string', description: 'Override the default model.' })
103
- .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode.' })
104
- .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode.' })
105
- .option('dry-run', { type: 'boolean', description: 'Preview changelog without writing to file.' })
106
- .option('no-attribution', { type: 'boolean', description: 'Disable the attribution footer.' });
107
- })
108
-
109
- // Analysis commands
110
- .command('init', 'Run interactive setup to configure the tool.')
111
- .command('validate', 'Validate your configuration and connectivity.')
112
- .command('analyze', 'Analyze current working directory changes.', this.createStandardOptions)
113
- .command('analyze-commits <limit>', 'Analyze recent commits with detailed information.', (yargs) => {
114
- this.createStandardOptions(yargs)
115
- .positional('limit', { type: 'number', default: 10, description: 'Number of commits to analyze' });
116
- })
117
- .command('git-info', 'Display comprehensive repository information and statistics.', this.createStandardOptions)
118
- .command('health', 'Assess repository health and commit quality.', this.createStandardOptions)
119
- .command('branches', 'Analyze all branches and unmerged commits.', this.createStandardOptions)
120
- .command('comprehensive', 'Comprehensive analysis including dangling commits.', this.createStandardOptions)
121
- .command('untracked', 'Include untracked files analysis.', this.createStandardOptions)
122
- .command('working-dir', 'Generate changelog from working directory changes.', this.createStandardOptions)
123
- .command('from-commits <commits...>', 'Generate changelog from specific commit hashes.', (yargs) => {
124
- yargs.positional('commits', { describe: 'Commit hashes to analyze', type: 'string' });
125
- })
126
-
127
- // Utility commands
128
- .command('commit-message', 'Generate a commit message for current changes.')
129
- .command('commit', 'Interactive commit workflow with AI-generated messages.', (yargs) => {
130
- yargs
131
- .option('interactive', { alias: 'i', type: 'boolean', default: true, description: 'Use interactive staging (default).' })
132
- .option('all', { alias: 'a', type: 'boolean', description: 'Automatically stage all changes.' })
133
- .option('message', { alias: 'm', type: 'string', description: 'Use provided commit message (skip AI generation).' })
134
- .option('dry-run', { type: 'boolean', description: 'Preview commit message without committing.' })
135
- .option('editor', { alias: 'e', type: 'boolean', description: 'Open editor to review/edit commit message.' })
136
- .option('model', { type: 'string', description: 'Override the default AI model.' });
137
- })
138
- .command('providers', 'Manage AI providers.', (yargs) => {
139
- yargs
140
- .command('list', 'List available providers.')
141
- .command('switch <provider>', 'Switch to a different provider.')
142
- .command('configure [provider]', 'Configure AI provider settings.')
143
- .command('validate [provider]', 'Validate provider models and capabilities.')
144
- .demandCommand(1, 'Please specify a provider subcommand.');
145
- })
146
-
147
- // Global options
148
- .option('no-color', { type: 'boolean', description: 'Disable colored output.' })
149
- .option('silent', { type: 'boolean', description: 'Suppress non-essential output.' })
150
- .help('h')
151
- .alias('h', 'help')
152
- .epilogue(`For more information, visit ${colors.highlight('https://github.com/entro314-labs/ai-changelog-generator')}`)
153
- .demandCommand(0)
154
- .strict()
155
- .parse();
95
+ return (
96
+ yargs(hideBin(process.argv))
97
+ .scriptName('ai-changelog')
98
+ .usage(
99
+ `${colors.header('AI Changelog Generator')} - ${colors.secondary('Automatically generate changelogs from your git commits using AI.')}\n\n${colors.header('Usage:')} $0 [command] [options]`
100
+ )
101
+
102
+ // Default command
103
+ .command('$0', 'Generate a changelog from git commits (default command).', (yargs) => {
104
+ yargs
105
+ .option('interactive', {
106
+ alias: 'i',
107
+ type: 'boolean',
108
+ description: 'Choose commits interactively.',
109
+ })
110
+ .option('release-version', {
111
+ alias: 'v',
112
+ type: 'string',
113
+ description: 'Set the release version (e.g., 1.2.3).',
114
+ })
115
+ .option('since', {
116
+ alias: 's',
117
+ type: 'string',
118
+ description: 'Generate changelog since a specific git ref (tag/commit).',
119
+ })
120
+ .option('model', {
121
+ alias: 'm',
122
+ type: 'string',
123
+ description: 'Override the default model.',
124
+ })
125
+ .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode.' })
126
+ .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode.' })
127
+ .option('dry-run', {
128
+ type: 'boolean',
129
+ description: 'Preview changelog without writing to file.',
130
+ })
131
+ .option('no-attribution', {
132
+ type: 'boolean',
133
+ description: 'Disable the attribution footer.',
134
+ })
135
+ })
136
+
137
+ // Analysis commands
138
+ .command('init', 'Run interactive setup to configure the tool.')
139
+ .command('validate', 'Validate your configuration and connectivity.')
140
+ .command(
141
+ 'analyze',
142
+ 'Analyze current working directory changes.',
143
+ this.createStandardOptions
144
+ )
145
+ .command(
146
+ 'analyze-commits <limit>',
147
+ 'Analyze recent commits with detailed information.',
148
+ (yargs) => {
149
+ this.createStandardOptions(yargs).positional('limit', {
150
+ type: 'number',
151
+ default: 10,
152
+ description: 'Number of commits to analyze',
153
+ })
154
+ }
155
+ )
156
+ .command(
157
+ 'git-info',
158
+ 'Display comprehensive repository information and statistics.',
159
+ this.createStandardOptions
160
+ )
161
+ .command(
162
+ 'health',
163
+ 'Assess repository health and commit quality.',
164
+ this.createStandardOptions
165
+ )
166
+ .command(
167
+ 'branches',
168
+ 'Analyze all branches and unmerged commits.',
169
+ this.createStandardOptions
170
+ )
171
+ .command(
172
+ 'comprehensive',
173
+ 'Comprehensive analysis including dangling commits.',
174
+ this.createStandardOptions
175
+ )
176
+ .command('untracked', 'Include untracked files analysis.', this.createStandardOptions)
177
+ .command(
178
+ 'working-dir',
179
+ 'Generate changelog from working directory changes.',
180
+ this.createStandardOptions
181
+ )
182
+ .command(
183
+ 'from-commits <commits...>',
184
+ 'Generate changelog from specific commit hashes.',
185
+ (yargs) => {
186
+ yargs.positional('commits', { describe: 'Commit hashes to analyze', type: 'string' })
187
+ }
188
+ )
189
+
190
+ // Utility commands
191
+ .command('commit-message', 'Generate a commit message for current changes.')
192
+ .command('commit', 'Interactive commit workflow with AI-generated messages.', (yargs) => {
193
+ yargs
194
+ .option('interactive', {
195
+ alias: 'i',
196
+ type: 'boolean',
197
+ default: true,
198
+ description: 'Use interactive staging (default).',
199
+ })
200
+ .option('all', {
201
+ alias: 'a',
202
+ type: 'boolean',
203
+ description: 'Automatically stage all changes.',
204
+ })
205
+ .option('message', {
206
+ alias: 'm',
207
+ type: 'string',
208
+ description: 'Use provided commit message (skip AI generation).',
209
+ })
210
+ .option('dry-run', {
211
+ type: 'boolean',
212
+ description: 'Preview commit message without committing.',
213
+ })
214
+ .option('editor', {
215
+ alias: 'e',
216
+ type: 'boolean',
217
+ description: 'Open editor to review/edit commit message.',
218
+ })
219
+ .option('model', { type: 'string', description: 'Override the default AI model.' })
220
+ })
221
+ .command('providers', 'Manage AI providers.', (yargs) => {
222
+ yargs
223
+ .command('list', 'List available providers.')
224
+ .command('switch <provider>', 'Switch to a different provider.')
225
+ .command('configure [provider]', 'Configure AI provider settings.')
226
+ .command('validate [provider]', 'Validate provider models and capabilities.')
227
+ .demandCommand(1, 'Please specify a provider subcommand.')
228
+ })
229
+
230
+ // Global options
231
+ .option('no-color', { type: 'boolean', description: 'Disable colored output.' })
232
+ .option('silent', { type: 'boolean', description: 'Suppress non-essential output.' })
233
+ .help('h')
234
+ .alias('h', 'help')
235
+ .epilogue(
236
+ `For more information, visit ${colors.highlight('https://github.com/entro314-labs/ai-changelog-generator')}`
237
+ )
238
+ .demandCommand(0)
239
+ .strict()
240
+ .parse()
241
+ )
156
242
  }
157
243
 
158
244
  createStandardOptions(yargs) {
159
245
  return yargs
160
- .option('format', { alias: 'f', type: 'string', choices: ['markdown', 'json'], default: 'markdown', description: 'Output format' })
246
+ .option('format', {
247
+ alias: 'f',
248
+ type: 'string',
249
+ choices: ['markdown', 'json'],
250
+ default: 'markdown',
251
+ description: 'Output format',
252
+ })
161
253
  .option('output', { alias: 'o', type: 'string', description: 'Output file path' })
162
254
  .option('since', { type: 'string', description: 'Analyze changes since this git ref' })
163
255
  .option('silent', { type: 'boolean', description: 'Suppress non-essential output' })
164
256
  .option('dry-run', { type: 'boolean', description: 'Preview without writing files' })
165
257
  .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode' })
166
258
  .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode' })
167
- .option('model', { alias: 'm', type: 'string', description: 'Override the default model' });
259
+ .option('model', { alias: 'm', type: 'string', description: 'Override the default model' })
168
260
  }
169
261
 
170
262
  async showMetrics() {
171
- if (!this.appService || this.appService.options.silent) return;
263
+ if (!this.appService || this.appService.options.silent) {
264
+ return
265
+ }
172
266
 
173
- const endTime = Date.now();
174
- const metrics = this.appService.getMetrics();
267
+ const endTime = Date.now()
268
+ const metrics = this.appService.getMetrics()
175
269
 
176
- const summaryLines = [
177
- `${colors.label('Total time')}: ${colors.value(formatDuration(endTime - this.startTime))}`,
178
- `${colors.label('Commits processed')}: ${colors.number(metrics.commitsProcessed || 0)}`
179
- ];
270
+ const summaryData = {
271
+ 'Total time': formatDuration(endTime - this.startTime),
272
+ 'Commits processed': metrics.commitsProcessed || 0,
273
+ }
180
274
 
181
275
  if (metrics.apiCalls > 0) {
182
- summaryLines.push(`${colors.label('AI calls')}: ${colors.number(metrics.apiCalls)}`);
183
- summaryLines.push(`${colors.label('Total tokens')}: ${colors.number((metrics.totalTokens || 0).toLocaleString())}`);
276
+ summaryData['AI calls'] = metrics.apiCalls
277
+ summaryData['Total tokens'] = (metrics.totalTokens || 0).toLocaleString()
184
278
  }
185
279
 
186
280
  if (metrics.errors > 0) {
187
- summaryLines.push('');
188
- summaryLines.push(colors.error(`❌ Errors: ${metrics.errors}`));
281
+ summaryData.Errors = colors.error(`${metrics.errors}`)
189
282
  }
190
283
 
191
- console.log(colors.box('📊 Session Summary', summaryLines.join('\n')));
284
+ EnhancedConsole.box('📊 Session Summary', colors.formatMetrics(summaryData), {
285
+ borderStyle: 'rounded',
286
+ borderColor: 'info',
287
+ })
192
288
  }
193
289
  }
194
290
 
195
291
  // Base command class
196
292
  class BaseCommand {
197
- async execute(argv, appService) {
198
- throw new Error('Command execute method not implemented');
293
+ async execute(_argv, _appService) {
294
+ throw new Error('Command execute method not implemented')
199
295
  }
200
296
 
201
297
  processStandardFlags(argv, appService) {
@@ -203,58 +299,64 @@ class BaseCommand {
203
299
  format: argv.format || 'markdown',
204
300
  output: argv.output,
205
301
  since: argv.since,
206
- silent: argv.silent || false,
207
- dryRun: argv.dryRun || false
208
- };
302
+ silent: argv.silent,
303
+ dryRun: argv.dryRun,
304
+ }
209
305
 
210
306
  // Apply analysis mode
211
- if (argv.detailed) appService.setAnalysisMode('detailed');
212
- if (argv.enterprise) appService.setAnalysisMode('enterprise');
213
- if (argv.model) appService.setModelOverride(argv.model);
307
+ if (argv.detailed) {
308
+ appService.setAnalysisMode('detailed')
309
+ }
310
+ if (argv.enterprise) {
311
+ appService.setAnalysisMode('enterprise')
312
+ }
313
+ if (argv.model) {
314
+ appService.setModelOverride(argv.model)
315
+ }
214
316
 
215
- return config;
317
+ return config
216
318
  }
217
319
  }
218
320
 
219
321
  // Command implementations
220
322
  class DefaultCommand extends BaseCommand {
221
323
  async execute(argv, appService) {
222
- const config = this.processStandardFlags(argv, appService);
324
+ const _config = this.processStandardFlags(argv, appService)
223
325
 
224
326
  if (argv.interactive) {
225
- await appService.runInteractive();
327
+ await appService.runInteractive()
226
328
  } else {
227
329
  await appService.generateChangelog({
228
330
  version: argv.releaseVersion,
229
- since: argv.since
230
- });
331
+ since: argv.since,
332
+ })
231
333
  }
232
334
  }
233
335
  }
234
336
 
235
337
  class InitCommand extends BaseCommand {
236
- async execute(argv, appService) {
237
- await promptForConfig();
338
+ async execute(_argv, _appService) {
339
+ await promptForConfig()
238
340
  }
239
341
  }
240
342
 
241
343
  class ValidateCommand extends BaseCommand {
242
- async execute(argv, appService) {
243
- const validation = await appService.validateConfiguration();
344
+ async execute(_argv, appService) {
345
+ const validation = await appService.validateConfiguration()
244
346
 
245
347
  if (validation.valid) {
246
- console.log(colors.successMessage('✅ Configuration is valid'));
348
+ console.log(colors.successMessage('✅ Configuration is valid'))
247
349
  } else {
248
- console.log(colors.errorMessage('❌ Configuration has issues:'));
249
- validation.issues.forEach(issue => {
250
- console.log(` - ${issue}`);
251
- });
350
+ console.log(colors.errorMessage('❌ Configuration has issues:'))
351
+ validation.issues.forEach((issue) => {
352
+ console.log(` - ${issue}`)
353
+ })
252
354
 
253
355
  if (validation.recommendations.length > 0) {
254
- console.log(colors.infoMessage('\n💡 Recommendations:'));
255
- validation.recommendations.forEach(rec => {
256
- console.log(` - ${rec}`);
257
- });
356
+ console.log(colors.infoMessage('\n💡 Recommendations:'))
357
+ validation.recommendations.forEach((rec) => {
358
+ console.log(` - ${rec}`)
359
+ })
258
360
  }
259
361
  }
260
362
  }
@@ -262,131 +364,141 @@ class ValidateCommand extends BaseCommand {
262
364
 
263
365
  class AnalyzeCommand extends BaseCommand {
264
366
  async execute(argv, appService) {
265
- const config = this.processStandardFlags(argv, appService);
266
- await appService.analyzeCurrentChanges();
367
+ const _config = this.processStandardFlags(argv, appService)
368
+ await appService.analyzeCurrentChanges()
267
369
  }
268
370
  }
269
371
 
270
372
  class AnalyzeCommitsCommand extends BaseCommand {
271
373
  async execute(argv, appService) {
272
- const config = this.processStandardFlags(argv, appService);
273
- await appService.analyzeRecentCommits(argv.limit || 10);
374
+ const _config = this.processStandardFlags(argv, appService)
375
+ await appService.analyzeRecentCommits(argv.limit || 10)
274
376
  }
275
377
  }
276
378
 
277
379
  class GitInfoCommand extends BaseCommand {
278
380
  async execute(argv, appService) {
279
- const config = this.processStandardFlags(argv, appService);
280
- await appService.analyzeRepository({ type: 'git-info', ...config });
381
+ const config = this.processStandardFlags(argv, appService)
382
+ await appService.analyzeRepository({ type: 'git-info', ...config })
281
383
  }
282
384
  }
283
385
 
284
386
  class HealthCommand extends BaseCommand {
285
387
  async execute(argv, appService) {
286
- const config = this.processStandardFlags(argv, appService);
287
- await appService.assessHealth(config);
388
+ const config = this.processStandardFlags(argv, appService)
389
+ await appService.assessHealth(config)
288
390
  }
289
391
  }
290
392
 
291
393
  class BranchesCommand extends BaseCommand {
292
394
  async execute(argv, appService) {
293
- const config = this.processStandardFlags(argv, appService);
294
- await appService.analyzeRepository({ type: 'branches', ...config });
395
+ const config = this.processStandardFlags(argv, appService)
396
+ await appService.analyzeRepository({ type: 'branches', ...config })
295
397
  }
296
398
  }
297
399
 
298
400
  class ComprehensiveCommand extends BaseCommand {
299
401
  async execute(argv, appService) {
300
- const config = this.processStandardFlags(argv, appService);
301
- await appService.analyzeRepository({ type: 'comprehensive', ...config });
402
+ const config = this.processStandardFlags(argv, appService)
403
+ await appService.analyzeRepository({ type: 'comprehensive', ...config })
302
404
  }
303
405
  }
304
406
 
305
407
  class UntrackedCommand extends BaseCommand {
306
408
  async execute(argv, appService) {
307
- const config = this.processStandardFlags(argv, appService);
308
- await appService.analyzeRepository({ type: 'untracked', ...config });
409
+ const config = this.processStandardFlags(argv, appService)
410
+ await appService.analyzeRepository({ type: 'untracked', ...config })
309
411
  }
310
412
  }
311
413
 
312
414
  class WorkingDirCommand extends BaseCommand {
313
415
  async execute(argv, appService) {
314
- const config = this.processStandardFlags(argv, appService);
315
- await appService.generateChangelogFromChanges(argv.releaseVersion);
416
+ const _config = this.processStandardFlags(argv, appService)
417
+ await appService.generateChangelogFromChanges(argv.releaseVersion)
316
418
  }
317
419
  }
318
420
 
319
421
  class FromCommitsCommand extends BaseCommand {
320
422
  async execute(argv, appService) {
321
- const config = this.processStandardFlags(argv, appService);
423
+ const _config = this.processStandardFlags(argv, appService)
322
424
  // Implementation would generate changelog from specific commits
323
- console.log(colors.infoMessage(`Generating changelog from commits: ${argv.commits.join(', ')}`));
425
+ console.log(colors.infoMessage(`Generating changelog from commits: ${argv.commits.join(', ')}`))
324
426
  }
325
427
  }
326
428
 
327
429
  class CommitMessageCommand extends BaseCommand {
328
- async execute(argv, appService) {
329
- console.log(colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...'));
430
+ async execute(_argv, appService) {
431
+ console.log(
432
+ colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...')
433
+ )
330
434
 
331
435
  try {
332
- const result = await appService.generateCommitMessage();
436
+ const result = await appService.generateCommitMessage()
333
437
 
334
- if (result && result.suggestions && result.suggestions.length > 0) {
335
- console.log(colors.successMessage('\n✅ Generated commit message suggestions:'));
438
+ if (result?.suggestions && result.suggestions.length > 0) {
439
+ console.log(colors.successMessage('\n✅ Generated commit message suggestions:'))
336
440
  result.suggestions.forEach((suggestion, index) => {
337
- console.log(`${colors.number(index + 1)}. ${colors.highlight(suggestion)}`);
338
- });
441
+ console.log(`${colors.number(index + 1)}. ${colors.highlight(suggestion)}`)
442
+ })
339
443
 
340
444
  if (result.context) {
341
- console.log(colors.dim(`\nContext: ${result.context}`));
445
+ console.log(colors.dim(`\nContext: ${result.context}`))
342
446
  }
343
447
  } else {
344
- console.log(colors.warningMessage('No commit message suggestions could be generated.'));
345
- console.log(colors.infoMessage('Make sure you have uncommitted changes.'));
448
+ console.log(colors.warningMessage('No commit message suggestions could be generated.'))
449
+ console.log(colors.infoMessage('Make sure you have uncommitted changes.'))
346
450
  }
347
451
  } catch (error) {
348
- console.error(colors.errorMessage(`Error generating commit message: ${error.message}`));
452
+ console.error(colors.errorMessage(`Error generating commit message: ${error.message}`))
349
453
  }
350
454
  }
351
455
  }
352
456
 
353
457
  class CommitCommand extends BaseCommand {
354
458
  async execute(argv, appService) {
355
- console.log(colors.processingMessage('🚀 Starting interactive commit workflow...'));
459
+ console.log(colors.processingMessage('🚀 Starting interactive commit workflow...'))
356
460
 
357
461
  try {
358
462
  // Process flags and model override
359
- if (argv.model) appService.setModelOverride(argv.model);
463
+ if (argv.model) {
464
+ appService.setModelOverride(argv.model)
465
+ }
360
466
 
361
467
  // Execute the commit workflow
362
468
  const result = await appService.executeCommitWorkflow({
363
469
  interactive: argv.interactive !== false, // Default to true unless explicitly false
364
- stageAll: argv.all || false,
470
+ stageAll: argv.all,
365
471
  customMessage: argv.message,
366
- dryRun: argv.dryRun || false,
367
- useEditor: argv.editor || false
368
- });
472
+ dryRun: argv.dryRun,
473
+ useEditor: argv.editor,
474
+ })
369
475
 
370
- if (result && result.success) {
476
+ if (result?.success) {
371
477
  if (argv.dryRun) {
372
- console.log(colors.successMessage('✅ Commit workflow completed (dry-run mode)'));
373
- console.log(colors.highlight(`Proposed commit message:\n${result.commitMessage}`));
478
+ console.log(colors.successMessage('✅ Commit workflow completed (dry-run mode)'))
479
+ console.log(colors.highlight(`Proposed commit message:\n${result.commitMessage}`))
374
480
  } else {
375
- console.log(colors.successMessage('✅ Changes committed successfully!'));
376
- console.log(colors.highlight(`Commit: ${result.commitHash}`));
377
- console.log(colors.dim(`Message: ${result.commitMessage}`));
481
+ console.log(colors.successMessage('✅ Changes committed successfully!'))
482
+ console.log(colors.highlight(`Commit: ${result.commitHash}`))
483
+ console.log(colors.dim(`Message: ${result.commitMessage}`))
378
484
  }
379
485
  } else {
380
- console.log(colors.warningMessage('Commit workflow cancelled or no changes to commit.'));
486
+ console.log(colors.warningMessage('Commit workflow cancelled or no changes to commit.'))
381
487
  }
382
488
  } catch (error) {
383
- console.error(colors.errorMessage(`Commit workflow failed: ${error.message}`));
384
-
489
+ console.error(colors.errorMessage(`Commit workflow failed: ${error.message}`))
490
+
385
491
  // Provide helpful suggestions based on error type
386
492
  if (error.message.includes('No changes')) {
387
- console.log(colors.infoMessage('💡 Try making some changes first, then run the commit command.'));
493
+ console.log(
494
+ colors.infoMessage('💡 Try making some changes first, then run the commit command.')
495
+ )
388
496
  } else if (error.message.includes('git')) {
389
- console.log(colors.infoMessage('💡 Make sure you are in a git repository and git is properly configured.'));
497
+ console.log(
498
+ colors.infoMessage(
499
+ '💡 Make sure you are in a git repository and git is properly configured.'
500
+ )
501
+ )
390
502
  }
391
503
  }
392
504
  }
@@ -394,176 +506,180 @@ class CommitCommand extends BaseCommand {
394
506
 
395
507
  class ProvidersCommand extends BaseCommand {
396
508
  async execute(argv, appService) {
397
- const subcommand = argv._[1];
509
+ const subcommand = argv._[1]
398
510
 
399
511
  switch (subcommand) {
400
512
  case 'list':
401
- await this.listProviders(appService);
402
- break;
513
+ await this.listProviders(appService)
514
+ break
403
515
  case 'switch':
404
- await this.switchProvider(appService, argv.provider);
405
- break;
516
+ await this.switchProvider(appService, argv.provider)
517
+ break
406
518
  case 'configure':
407
- await this.configureProvider(appService, argv.provider);
408
- break;
519
+ await this.configureProvider(appService, argv.provider)
520
+ break
409
521
  case 'validate':
410
- await this.validateProvider(appService, argv.provider);
411
- break;
522
+ await this.validateProvider(appService, argv.provider)
523
+ break
412
524
  default:
413
- console.log(colors.errorMessage('Unknown provider subcommand'));
414
- console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate'));
525
+ console.log(colors.errorMessage('Unknown provider subcommand'))
526
+ console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate'))
415
527
  }
416
528
  }
417
529
 
418
530
  async listProviders(appService) {
419
531
  try {
420
- const providers = await appService.listProviders();
532
+ const providers = await appService.listProviders()
421
533
 
422
- console.log(colors.header('\n🤖 Available AI Providers:'));
534
+ console.log(colors.header('\n🤖 Available AI Providers:'))
423
535
 
424
- providers.forEach(provider => {
425
- const status = provider.available ? '✅ Available' : '⚠️ Needs configuration';
426
- const activeIndicator = provider.active ? ' 🎯 (Active)' : '';
536
+ providers.forEach((provider) => {
537
+ const status = provider.available ? '✅ Available' : '⚠️ Needs configuration'
538
+ const activeIndicator = provider.active ? ' 🎯 (Active)' : ''
427
539
 
428
- console.log(` ${colors.highlight(provider.name)} - ${status}${activeIndicator}`);
540
+ console.log(` ${colors.highlight(provider.name)} - ${status}${activeIndicator}`)
429
541
 
430
542
  if (provider.capabilities && Object.keys(provider.capabilities).length > 0) {
431
543
  const caps = Object.entries(provider.capabilities)
432
- .filter(([key, value]) => value === true)
544
+ .filter(([_key, value]) => value === true)
433
545
  .map(([key]) => key)
434
- .join(', ');
546
+ .join(', ')
435
547
  if (caps) {
436
- console.log(` ${colors.dim(`Capabilities: ${caps}`)}`);
548
+ console.log(` ${colors.dim(`Capabilities: ${caps}`)}`)
437
549
  }
438
550
  }
439
- });
440
-
441
- console.log(colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider'));
551
+ })
442
552
 
553
+ console.log(
554
+ colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider')
555
+ )
443
556
  } catch (error) {
444
- console.error(colors.errorMessage(`Error listing providers: ${error.message}`));
557
+ console.error(colors.errorMessage(`Error listing providers: ${error.message}`))
445
558
  }
446
559
  }
447
560
 
448
561
  async switchProvider(appService, providerName) {
449
562
  if (!providerName) {
450
- console.log(colors.errorMessage('Please specify a provider name'));
451
- console.log(colors.infoMessage('Usage: ai-changelog providers switch <provider>'));
452
- return;
563
+ console.log(colors.errorMessage('Please specify a provider name'))
564
+ console.log(colors.infoMessage('Usage: ai-changelog providers switch <provider>'))
565
+ return
453
566
  }
454
567
 
455
568
  try {
456
- const result = await appService.switchProvider(providerName);
569
+ const result = await appService.switchProvider(providerName)
457
570
 
458
571
  if (result.success) {
459
- console.log(colors.successMessage(`✅ Switched to ${providerName} provider`));
572
+ console.log(colors.successMessage(`✅ Switched to ${providerName} provider`))
460
573
  } else {
461
- console.log(colors.errorMessage(`❌ Failed to switch provider: ${result.error}`));
462
- console.log(colors.infoMessage('Use "ai-changelog providers list" to see available providers'));
574
+ console.log(colors.errorMessage(`❌ Failed to switch provider: ${result.error}`))
575
+ console.log(
576
+ colors.infoMessage('Use "ai-changelog providers list" to see available providers')
577
+ )
463
578
  }
464
579
  } catch (error) {
465
- console.error(colors.errorMessage(`Error switching provider: ${error.message}`));
580
+ console.error(colors.errorMessage(`Error switching provider: ${error.message}`))
466
581
  }
467
582
  }
468
583
 
469
584
  async configureProvider(appService, providerName) {
470
- const { select } = await import('@clack/prompts');
585
+ const { select } = await import('@clack/prompts')
471
586
 
472
587
  try {
473
588
  // If no provider specified, let user choose
474
589
  if (!providerName) {
475
- const providers = await appService.listProviders();
590
+ const providers = await appService.listProviders()
476
591
 
477
- const choices = providers.map(p => ({
592
+ const choices = providers.map((p) => ({
478
593
  value: p.name,
479
- label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`
480
- }));
594
+ label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`,
595
+ }))
481
596
 
482
597
  providerName = await select({
483
598
  message: 'Select provider to configure:',
484
- options: choices
485
- });
599
+ options: choices,
600
+ })
486
601
  }
487
602
 
488
- console.log(colors.header(`\n🔧 Configuring ${providerName.toUpperCase()} Provider`));
489
- console.log(colors.infoMessage('Please add the following to your .env.local file:\n'));
603
+ console.log(colors.header(`\n🔧 Configuring ${providerName.toUpperCase()} Provider`))
604
+ console.log(colors.infoMessage('Please add the following to your .env.local file:\n'))
490
605
 
491
606
  switch (providerName.toLowerCase()) {
492
607
  case 'openai':
493
- console.log(colors.code('OPENAI_API_KEY=your_openai_api_key_here'));
494
- console.log(colors.dim('Get your API key from: https://platform.openai.com/api-keys'));
495
- break;
608
+ console.log(colors.code('OPENAI_API_KEY=your_openai_api_key_here'))
609
+ console.log(colors.dim('Get your API key from: https://platform.openai.com/api-keys'))
610
+ break
496
611
 
497
612
  case 'anthropic':
498
- console.log(colors.code('ANTHROPIC_API_KEY=your_anthropic_api_key_here'));
499
- console.log(colors.dim('Get your API key from: https://console.anthropic.com/'));
500
- break;
613
+ console.log(colors.code('ANTHROPIC_API_KEY=your_anthropic_api_key_here'))
614
+ console.log(colors.dim('Get your API key from: https://console.anthropic.com/'))
615
+ break
501
616
 
502
617
  case 'azure':
503
- console.log(colors.code('AZURE_OPENAI_API_KEY=your_azure_api_key_here'));
504
- console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_azure_endpoint_here'));
505
- console.log(colors.dim('Get from your Azure OpenAI resource in Azure portal'));
506
- break;
618
+ console.log(colors.code('AZURE_OPENAI_API_KEY=your_azure_api_key_here'))
619
+ console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_azure_endpoint_here'))
620
+ console.log(colors.dim('Get from your Azure OpenAI resource in Azure portal'))
621
+ break
507
622
 
508
623
  case 'google':
509
- console.log(colors.code('GOOGLE_API_KEY=your_google_api_key_here'));
510
- console.log(colors.dim('Get your API key from: https://aistudio.google.com/app/apikey'));
511
- break;
624
+ console.log(colors.code('GOOGLE_API_KEY=your_google_api_key_here'))
625
+ console.log(colors.dim('Get your API key from: https://aistudio.google.com/app/apikey'))
626
+ break
512
627
 
513
628
  case 'ollama':
514
- console.log(colors.code('OLLAMA_HOST=http://localhost:11434'));
515
- console.log(colors.dim('Make sure Ollama is running: ollama serve'));
516
- break;
629
+ console.log(colors.code('OLLAMA_HOST=http://localhost:11434'))
630
+ console.log(colors.dim('Make sure Ollama is running: ollama serve'))
631
+ break
517
632
 
518
633
  default:
519
- console.log(colors.code(`${providerName.toUpperCase()}_API_KEY=your_api_key_here`));
634
+ console.log(colors.code(`${providerName.toUpperCase()}_API_KEY=your_api_key_here`))
520
635
  }
521
636
 
522
- console.log(colors.infoMessage('\nAfter adding the configuration, run:'));
523
- console.log(colors.highlight(`ai-changelog providers validate ${providerName}`));
524
-
637
+ console.log(colors.infoMessage('\nAfter adding the configuration, run:'))
638
+ console.log(colors.highlight(`ai-changelog providers validate ${providerName}`))
525
639
  } catch (error) {
526
- console.error(colors.errorMessage(`Error configuring provider: ${error.message}`));
640
+ console.error(colors.errorMessage(`Error configuring provider: ${error.message}`))
527
641
  }
528
642
  }
529
643
 
530
644
  async validateProvider(appService, providerName) {
531
645
  try {
532
646
  if (!providerName) {
533
- console.log(colors.processingMessage('🔍 Validating all configured providers...'));
534
- const result = await appService.validateAllProviders();
647
+ console.log(colors.processingMessage('🔍 Validating all configured providers...'))
648
+ const result = await appService.validateAllProviders()
535
649
 
536
- console.log(colors.header('\n📊 Provider Validation Results:'));
650
+ console.log(colors.header('\n📊 Provider Validation Results:'))
537
651
 
538
652
  Object.entries(result).forEach(([name, validation]) => {
539
- const status = validation.success ? '✅ Valid' : '❌ Invalid';
540
- console.log(` ${colors.highlight(name)}: ${status}`);
653
+ const status = validation.success ? '✅ Valid' : '❌ Invalid'
654
+ console.log(` ${colors.highlight(name)}: ${status}`)
541
655
 
542
656
  if (!validation.success) {
543
- console.log(` ${colors.errorMessage(validation.error)}`);
657
+ console.log(` ${colors.errorMessage(validation.error)}`)
544
658
  }
545
- });
546
-
659
+ })
547
660
  } else {
548
- console.log(colors.processingMessage(`🔍 Validating ${providerName} provider...`));
549
- const result = await appService.validateProvider(providerName);
661
+ console.log(colors.processingMessage(`🔍 Validating ${providerName} provider...`))
662
+ const result = await appService.validateProvider(providerName)
550
663
 
551
664
  if (result.success) {
552
- console.log(colors.successMessage(`✅ ${providerName} provider is configured correctly`));
665
+ console.log(colors.successMessage(`✅ ${providerName} provider is configured correctly`))
553
666
  if (result.model) {
554
- console.log(colors.dim(` Default model: ${result.model}`));
667
+ console.log(colors.dim(` Default model: ${result.model}`))
555
668
  }
556
669
  } else {
557
- console.log(colors.errorMessage(`❌ ${providerName} validation failed: ${result.error}`));
558
- console.log(colors.infoMessage(`Use "ai-changelog providers configure ${providerName}" for setup instructions`));
670
+ console.log(colors.errorMessage(`❌ ${providerName} validation failed: ${result.error}`))
671
+ console.log(
672
+ colors.infoMessage(
673
+ `Use "ai-changelog providers configure ${providerName}" for setup instructions`
674
+ )
675
+ )
559
676
  }
560
677
  }
561
-
562
678
  } catch (error) {
563
- console.error(colors.errorMessage(`Error validating provider: ${error.message}`));
679
+ console.error(colors.errorMessage(`Error validating provider: ${error.message}`))
564
680
  }
565
681
  }
566
682
  }
567
683
 
568
684
  // Export the controller
569
- export default CLIController;
685
+ export default CLIController