@entro314labs/ai-changelog-generator 3.2.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +21 -1
  3. package/ai-changelog-mcp.sh +0 -0
  4. package/ai-changelog.sh +0 -0
  5. package/bin/ai-changelog-dxt.js +6 -3
  6. package/manifest.json +177 -0
  7. package/package.json +76 -81
  8. package/src/ai-changelog-generator.js +5 -4
  9. package/src/application/orchestrators/changelog.orchestrator.js +19 -203
  10. package/src/cli.js +16 -5
  11. package/src/domains/ai/ai-analysis.service.js +2 -0
  12. package/src/domains/analysis/analysis.engine.js +714 -37
  13. package/src/domains/changelog/changelog.service.js +623 -32
  14. package/src/domains/changelog/workspace-changelog.service.js +445 -622
  15. package/src/domains/git/commit-tagger.js +552 -0
  16. package/src/domains/git/git-manager.js +357 -0
  17. package/src/domains/git/git.service.js +865 -16
  18. package/src/infrastructure/cli/cli.controller.js +14 -9
  19. package/src/infrastructure/config/configuration.manager.js +25 -11
  20. package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
  21. package/src/infrastructure/mcp/mcp-server.service.js +105 -32
  22. package/src/infrastructure/providers/core/base-provider.js +1 -1
  23. package/src/infrastructure/providers/implementations/anthropic.js +16 -173
  24. package/src/infrastructure/providers/implementations/azure.js +16 -63
  25. package/src/infrastructure/providers/implementations/dummy.js +13 -16
  26. package/src/infrastructure/providers/implementations/mock.js +13 -26
  27. package/src/infrastructure/providers/implementations/ollama.js +12 -4
  28. package/src/infrastructure/providers/implementations/openai.js +13 -165
  29. package/src/infrastructure/providers/provider-management.service.js +126 -412
  30. package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
  31. package/src/shared/utils/cli-ui.js +8 -10
  32. package/src/shared/utils/diff-processor.js +21 -19
  33. package/src/shared/utils/error-classes.js +33 -0
  34. package/src/shared/utils/utils.js +83 -63
  35. package/types/index.d.ts +61 -68
  36. package/src/domains/git/git-repository.analyzer.js +0 -678
@@ -83,6 +83,11 @@ export class CLIController {
83
83
  }
84
84
 
85
85
  async ensureConfig() {
86
+ // Skip config setup in test environments to prevent hanging
87
+ if (process.env.NODE_ENV === 'test' || process.env.CI) {
88
+ return
89
+ }
90
+
86
91
  const configPath = path.join(process.cwd(), '.env.local')
87
92
  try {
88
93
  await access(configPath)
@@ -345,17 +350,17 @@ class ValidateCommand extends BaseCommand {
345
350
  const validation = await appService.validateConfiguration()
346
351
 
347
352
  if (validation.valid) {
348
- console.log(colors.successMessage('✅ Configuration is valid'))
353
+ EnhancedConsole.success('✅ Configuration is valid')
349
354
  } else {
350
- console.log(colors.errorMessage('❌ Configuration has issues:'))
355
+ EnhancedConsole.error('❌ Configuration has issues:')
351
356
  validation.issues.forEach((issue) => {
352
- console.log(` - ${issue}`)
357
+ EnhancedConsole.log(` - ${issue}`)
353
358
  })
354
359
 
355
360
  if (validation.recommendations.length > 0) {
356
- console.log(colors.infoMessage('\n💡 Recommendations:'))
361
+ EnhancedConsole.info('\n💡 Recommendations:')
357
362
  validation.recommendations.forEach((rec) => {
358
- console.log(` - ${rec}`)
363
+ EnhancedConsole.log(` - ${rec}`)
359
364
  })
360
365
  }
361
366
  }
@@ -554,7 +559,7 @@ class ProvidersCommand extends BaseCommand {
554
559
  colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider')
555
560
  )
556
561
  } catch (error) {
557
- console.error(colors.errorMessage(`Error listing providers: ${error.message}`))
562
+ EnhancedConsole.error(`Error listing providers: ${error.message}`)
558
563
  }
559
564
  }
560
565
 
@@ -577,7 +582,7 @@ class ProvidersCommand extends BaseCommand {
577
582
  )
578
583
  }
579
584
  } catch (error) {
580
- console.error(colors.errorMessage(`Error switching provider: ${error.message}`))
585
+ EnhancedConsole.error(`Error switching provider: ${error.message}`)
581
586
  }
582
587
  }
583
588
 
@@ -637,7 +642,7 @@ class ProvidersCommand extends BaseCommand {
637
642
  console.log(colors.infoMessage('\nAfter adding the configuration, run:'))
638
643
  console.log(colors.highlight(`ai-changelog providers validate ${providerName}`))
639
644
  } catch (error) {
640
- console.error(colors.errorMessage(`Error configuring provider: ${error.message}`))
645
+ EnhancedConsole.error(`Error configuring provider: ${error.message}`)
641
646
  }
642
647
  }
643
648
 
@@ -676,7 +681,7 @@ class ProvidersCommand extends BaseCommand {
676
681
  }
677
682
  }
678
683
  } catch (error) {
679
- console.error(colors.errorMessage(`Error validating provider: ${error.message}`))
684
+ EnhancedConsole.error(`Error validating provider: ${error.message}`)
680
685
  }
681
686
  }
682
687
  }
@@ -5,15 +5,7 @@ import process from 'node:process'
5
5
  import yaml from 'js-yaml'
6
6
 
7
7
  import colors from '../../shared/constants/colors.js'
8
-
9
- // Model configurations - moved from lib/utils/model-config.js
10
- const MODEL_CONFIGS = {
11
- 'gpt-4': { maxTokens: 8192, cost: 0.03 },
12
- 'gpt-3.5-turbo': { maxTokens: 4096, cost: 0.002 },
13
- 'claude-3-opus': { maxTokens: 200000, cost: 0.015 },
14
- 'claude-3-sonnet': { maxTokens: 200000, cost: 0.003 },
15
- 'claude-3-haiku': { maxTokens: 200000, cost: 0.00025 },
16
- }
8
+ import { MODEL_CONFIGS } from '../providers/utils/model-config.js'
17
9
 
18
10
  /**
19
11
  * Unified Configuration Manager
@@ -151,7 +143,7 @@ export class ConfigurationManager {
151
143
  releaseTagGlobPattern: 'v[0-9]*.[0-9]*.[0-9]*',
152
144
  },
153
145
  changelog: {
154
- commitTypes: ['feat', 'fix', 'perf', 'refactor', 'docs'],
146
+ commitTypes: ['feat', 'fix', 'perf', 'refactor', 'docs', 'merge'],
155
147
  includeInvalidCommits: true,
156
148
  commitIgnoreRegexPattern: '^WIP ',
157
149
  headlines: {
@@ -529,7 +521,21 @@ export class ConfigurationManager {
529
521
  }
530
522
 
531
523
  getCommitTypes() {
532
- return this.changelogConfig?.convention?.commitTypes || ['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert']
524
+ return (
525
+ this.changelogConfig?.convention?.commitTypes || [
526
+ 'feat',
527
+ 'fix',
528
+ 'docs',
529
+ 'style',
530
+ 'refactor',
531
+ 'perf',
532
+ 'test',
533
+ 'build',
534
+ 'ci',
535
+ 'chore',
536
+ 'revert',
537
+ ]
538
+ )
533
539
  }
534
540
 
535
541
  getChangelogCommitTypes() {
@@ -565,4 +571,12 @@ export class ConfigurationManager {
565
571
  const pattern = this.changelogConfig.changelog.commitIgnoreRegexPattern
566
572
  return pattern ? new RegExp(pattern) : /^WIP /
567
573
  }
574
+
575
+ /**
576
+ * Get the complete configuration object
577
+ * @returns {Object} Configuration object
578
+ */
579
+ getConfig() {
580
+ return this.config || {}
581
+ }
568
582
  }
@@ -198,6 +198,10 @@ Provide 3 suggestions.`
198
198
  return summary
199
199
  }
200
200
 
201
+ async generateChangelogForCommitHashes(commitHashes) {
202
+ return await this.generateChangelogForCommits(commitHashes)
203
+ }
204
+
201
205
  async selectSpecificCommits() {
202
206
  return await selectSpecificCommits()
203
207
  }
@@ -207,7 +211,10 @@ Provide 3 suggestions.`
207
211
  const spinner = new SimpleSpinner(`Generating changelog for recent ${count} commits...`)
208
212
  spinner.start()
209
213
 
210
- const commits = await this.gitService.getCommitsSince(null)
214
+ const commits =
215
+ (await this.gitService.getCommitsSince?.(null)) ||
216
+ (await this.gitService.getCommitAnalysis?.()) ||
217
+ []
211
218
  const recentCommits = commits.slice(0, count)
212
219
 
213
220
  if (recentCommits.length === 0) {
@@ -9,6 +9,7 @@ import fs from 'node:fs'
9
9
  import path, { dirname } from 'node:path'
10
10
  import process from 'node:process'
11
11
  import { fileURLToPath } from 'node:url'
12
+ import { execSync } from 'node:child_process'
12
13
 
13
14
  import { Server } from '@modelcontextprotocol/sdk/server/index.js'
14
15
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
@@ -18,9 +19,9 @@ import { ChangelogOrchestrator } from '../../application/orchestrators/changelog
18
19
  // Import application services
19
20
  import { ApplicationService } from '../../application/services/application.service.js'
20
21
  import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
21
- import { GitRepositoryAnalyzer } from '../../domains/git/git-repository.analyzer.js'
22
+ import { GitService } from '../../domains/git/git.service.js'
22
23
  import { ConfigurationManager } from '../config/configuration.manager.js'
23
- import { ProviderManagementService } from '../providers/provider-management.service.js'
24
+ import { ProviderManagerService } from '../providers/provider-manager.service.js'
24
25
 
25
26
  const __filename = fileURLToPath(import.meta.url)
26
27
  const __dirname = dirname(__filename)
@@ -61,12 +62,17 @@ class AIChangelogMCPServer {
61
62
  // Set MCP server mode to suppress verbose logging
62
63
  process.env.MCP_SERVER_MODE = 'true'
63
64
 
65
+ // Initialize configuration
64
66
  this.configManager = new ConfigurationManager()
67
+
68
+ // Initialize application service
65
69
  this.applicationService = new ApplicationService()
70
+
71
+ // Initialize orchestrator with proper dependencies (it will create its own services)
66
72
  this.changelogOrchestrator = new ChangelogOrchestrator(this.configManager)
67
- this.gitAnalyzer = new GitRepositoryAnalyzer()
68
- this.analysisEngine = new AnalysisEngine()
69
- this.providerService = new ProviderManagementService()
73
+
74
+ // Initialize provider service
75
+ this.providerService = new ProviderManagerService(this.configManager.getAll())
70
76
 
71
77
  // Log available configuration
72
78
  const hasProvider = process.env.AI_PROVIDER
@@ -80,6 +86,9 @@ class AIChangelogMCPServer {
80
86
  } else {
81
87
  console.error(`[MCP] Configured with provider: ${process.env.AI_PROVIDER}`)
82
88
  }
89
+
90
+ // Wait for orchestrator to initialize its services
91
+ this.initPromise = this.changelogOrchestrator.ensureInitialized()
83
92
  } catch (error) {
84
93
  console.error('[MCP] Failed to initialize services:', error.message)
85
94
  console.error('[MCP] Server will start but tools may require configuration')
@@ -296,27 +305,55 @@ class AIChangelogMCPServer {
296
305
  } = args
297
306
 
298
307
  try {
308
+ // Ensure services are initialized
309
+ if (this.initPromise) {
310
+ await this.initPromise
311
+ }
312
+
299
313
  let result
300
314
 
301
315
  if (source === 'working-dir' || (source === 'auto' && this.hasWorkingDirectoryChanges())) {
302
- // Generate from working directory changes
303
- result = await this.changelogOrchestrator.generateWorkspaceChangelog({
304
- version,
316
+ // Generate from working directory changes using ChangelogService
317
+ // Access the changelogService through the orchestrator
318
+ await this.changelogOrchestrator.ensureInitialized()
319
+ const changelogService = this.changelogOrchestrator.changelogService
320
+
321
+ result = await changelogService.generateWorkspaceChangelog(version, {
305
322
  analysisMode,
306
323
  includeAttribution,
307
324
  })
325
+
326
+ // Format result to match expected structure
327
+ if (result && result.changelog) {
328
+ result = {
329
+ content: result.changelog,
330
+ metadata: {
331
+ version,
332
+ source: 'working-directory',
333
+ filesProcessed: result.filesProcessed,
334
+ filesSkipped: result.filesSkipped,
335
+ }
336
+ }
337
+ }
308
338
  } else {
309
- // Generate from commits
310
- result = await this.changelogOrchestrator.generateChangelog({
311
- version,
312
- since,
313
- analysisMode,
314
- includeAttribution,
315
- })
339
+ // Generate from commits - use orchestrator's generateChangelog(version, since)
340
+ result = await this.changelogOrchestrator.generateChangelog(version, since)
341
+
342
+ // Wrap result if needed
343
+ if (result && typeof result === 'string') {
344
+ result = {
345
+ content: result,
346
+ metadata: {
347
+ version,
348
+ since,
349
+ source: 'commits'
350
+ }
351
+ }
352
+ }
316
353
  }
317
354
 
318
355
  // Write file if requested
319
- if (writeFile && result.content) {
356
+ if (writeFile && result && result.content) {
320
357
  const changelogPath = path.join(process.cwd(), 'AI_CHANGELOG.md')
321
358
  try {
322
359
  fs.writeFileSync(changelogPath, result.content, 'utf8')
@@ -330,10 +367,10 @@ class AIChangelogMCPServer {
330
367
  content: [
331
368
  {
332
369
  type: 'text',
333
- text: result.content || 'No changelog content generated',
370
+ text: result?.content || 'No changelog content generated',
334
371
  },
335
372
  ],
336
- metadata: result.metadata,
373
+ metadata: result?.metadata,
337
374
  }
338
375
  } catch (error) {
339
376
  throw new Error(`Changelog generation failed: ${error.message}`)
@@ -344,23 +381,30 @@ class AIChangelogMCPServer {
344
381
  const { analysisType = 'comprehensive', includeRecommendations = true, commitLimit = 50 } = args
345
382
 
346
383
  try {
384
+ // Ensure services are initialized
385
+ if (this.initPromise) {
386
+ await this.initPromise
387
+ }
388
+ await this.changelogOrchestrator.ensureInitialized()
389
+
347
390
  let result
348
391
 
349
392
  switch (analysisType) {
350
393
  case 'health':
351
- result = await this.gitAnalyzer.assessRepositoryHealth(includeRecommendations)
394
+ result = await this.changelogOrchestrator.gitService.assessRepositoryHealth(includeRecommendations)
352
395
  break
353
396
  case 'commits':
354
- result = await this.analysisEngine.analyzeRecentCommits(commitLimit)
397
+ result = await this.changelogOrchestrator.analysisEngine.analyzeRecentCommits(commitLimit)
355
398
  break
356
399
  case 'branches':
357
- result = await this.gitAnalyzer.analyzeBranches()
400
+ result = await this.changelogOrchestrator.gitService.analyzeBranches()
358
401
  break
359
402
  case 'working-dir':
360
- result = await this.analysisEngine.analyzeCurrentChanges()
403
+ result = await this.changelogOrchestrator.analysisEngine.analyzeCurrentChanges()
361
404
  break
362
405
  default:
363
- result = await this.gitAnalyzer.analyzeComprehensive(includeRecommendations)
406
+ // Comprehensive analysis
407
+ result = await this.changelogOrchestrator.analyzeRepository({ type: analysisType })
364
408
  break
365
409
  }
366
410
 
@@ -381,7 +425,13 @@ class AIChangelogMCPServer {
381
425
  const { includeAIAnalysis = true, includeAttribution = true } = args
382
426
 
383
427
  try {
384
- const result = await this.analysisEngine.analyzeCurrentChanges({
428
+ // Ensure services are initialized
429
+ if (this.initPromise) {
430
+ await this.initPromise
431
+ }
432
+ await this.changelogOrchestrator.ensureInitialized()
433
+
434
+ const result = await this.changelogOrchestrator.analysisEngine.analyzeCurrentChanges({
385
435
  includeAIAnalysis,
386
436
  includeAttribution,
387
437
  })
@@ -419,15 +469,39 @@ class AIChangelogMCPServer {
419
469
  result += `\n${testResult}`
420
470
  }
421
471
  break
422
- case 'test':
423
- result = await this.providerService.testCurrentProvider()
472
+ case 'test': {
473
+ const activeProvider = this.providerService.getActiveProvider()
474
+ if (!activeProvider) {
475
+ throw new Error('No active provider found')
476
+ }
477
+ result = await this.providerService.testProvider(activeProvider.getName())
424
478
  break
425
- case 'configure':
426
- result = await this.providerService.configureProvider(provider)
479
+ }
480
+ case 'configure': {
481
+ if (!provider) {
482
+ throw new Error('Provider required for configure action')
483
+ }
484
+ const providerData = this.providerService.findProviderByName(provider)
485
+ if (!providerData) {
486
+ throw new Error(`Provider '${provider}' not found`)
487
+ }
488
+ result = {
489
+ name: provider,
490
+ available: providerData.available,
491
+ configuration: providerData.instance.getConfiguration
492
+ ? providerData.instance.getConfiguration()
493
+ : {},
494
+ requiredVars: providerData.instance.getRequiredEnvVars
495
+ ? providerData.instance.getRequiredEnvVars()
496
+ : [],
497
+ }
427
498
  break
428
- case 'validate':
429
- result = await this.providerService.validateModels(provider)
499
+ }
500
+ case 'validate': {
501
+ const validationResults = await this.providerService.validateAll()
502
+ result = validationResults
430
503
  break
504
+ }
431
505
  default:
432
506
  throw new Error(`Unknown action: ${action}`)
433
507
  }
@@ -436,7 +510,7 @@ class AIChangelogMCPServer {
436
510
  content: [
437
511
  {
438
512
  type: 'text',
439
- text: result,
513
+ text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
440
514
  },
441
515
  ],
442
516
  }
@@ -448,7 +522,6 @@ class AIChangelogMCPServer {
448
522
  hasWorkingDirectoryChanges() {
449
523
  try {
450
524
  // Simple check for working directory changes
451
- const { execSync } = require('node:child_process')
452
525
  const result = execSync('git status --porcelain', { encoding: 'utf8' })
453
526
  return result.trim().length > 0
454
527
  } catch (_error) {
@@ -2,7 +2,7 @@
2
2
  * Abstract Base Provider for AI models.
3
3
  * Defines the interface that all provider plugins must implement.
4
4
  */
5
- import { AbstractMethodError } from '../../../shared/utils/utils.js'
5
+ import { AbstractMethodError } from '../../../shared/utils/error-classes.js'
6
6
 
7
7
  export class BaseProvider {
8
8
  constructor(config) {
@@ -2,7 +2,6 @@ import Anthropic from '@anthropic-ai/sdk'
2
2
 
3
3
  import { BaseProvider } from '../core/base-provider.js'
4
4
  import { applyMixins, ProviderResponseHandler } from '../utils/base-provider-helpers.js'
5
- import { buildClientOptions } from '../utils/provider-utils.js'
6
5
 
7
6
  export class AnthropicProvider extends BaseProvider {
8
7
  constructor(config) {
@@ -14,7 +13,7 @@ export class AnthropicProvider extends BaseProvider {
14
13
  }
15
14
 
16
15
  initializeClient() {
17
- const clientOptions = buildClientOptions(this.getProviderConfig(), {
16
+ const clientOptions = this.buildClientOptions({
18
17
  timeout: 60000,
19
18
  maxRetries: 2,
20
19
  defaultHeaders: {
@@ -24,7 +23,7 @@ export class AnthropicProvider extends BaseProvider {
24
23
  })
25
24
 
26
25
  this.anthropic = new Anthropic({
27
- apiKey: clientOptions.apiKey,
26
+ apiKey: clientOptions.ANTHROPIC_API_KEY,
28
27
  baseURL: clientOptions.baseURL,
29
28
  timeout: clientOptions.timeout,
30
29
  maxRetries: clientOptions.maxRetries,
@@ -45,7 +44,8 @@ export class AnthropicProvider extends BaseProvider {
45
44
  }
46
45
 
47
46
  getDefaultModel() {
48
- return 'claude-sonnet-4-20250514'
47
+ const modelConfig = this.getProviderModelConfig()
48
+ return modelConfig.standardModel
49
49
  }
50
50
 
51
51
  async generateCompletion(messages, options = {}) {
@@ -115,127 +115,32 @@ export class AnthropicProvider extends BaseProvider {
115
115
 
116
116
  async getAvailableModels() {
117
117
  // Anthropic doesn't provide a models endpoint, return known models
118
+ // Use direct model names to avoid circular dependency with mixins
118
119
  return [
119
- {
120
- name: 'claude-sonnet-4-20250514',
121
- id: 'claude-sonnet-4-20250514',
122
- description: 'Claude Sonnet 4 - Latest balanced model (2025)',
123
- contextWindow: 200000,
124
- capabilities: {
125
- reasoning: true,
126
- function_calling: true,
127
- json_mode: true,
128
- multimodal: true,
129
- largeContext: true,
130
- toolUse: true,
131
- },
132
- },
133
120
  {
134
121
  name: 'claude-opus-4-20250514',
135
122
  id: 'claude-opus-4-20250514',
136
123
  description: 'Claude Opus 4 - Most capable model for complex tasks (2025)',
137
- contextWindow: 200000,
138
- capabilities: {
139
- reasoning: true,
140
- function_calling: true,
141
- json_mode: true,
142
- multimodal: true,
143
- largeContext: true,
144
- toolUse: true,
145
- advancedReasoning: true,
146
- },
124
+ },
125
+ {
126
+ name: 'claude-sonnet-4-20250514',
127
+ id: 'claude-sonnet-4-20250514',
128
+ description: 'Claude Sonnet 4 - Latest balanced model (2025)',
147
129
  },
148
130
  {
149
131
  name: 'claude-3.7-sonnet-20250219',
150
132
  id: 'claude-3.7-sonnet-20250219',
151
133
  description: 'Claude 3.7 Sonnet - Enhanced reasoning capabilities',
152
- contextWindow: 200000,
153
- capabilities: {
154
- reasoning: true,
155
- function_calling: true,
156
- json_mode: true,
157
- multimodal: true,
158
- largeContext: true,
159
- toolUse: true,
160
- },
161
- },
162
- {
163
- name: 'claude-3-5-sonnet-20241022',
164
- id: 'claude-3-5-sonnet-20241022',
165
- description: 'Claude 3.5 Sonnet - Previous generation',
166
- contextWindow: 200000,
167
- capabilities: {
168
- reasoning: true,
169
- function_calling: true,
170
- json_mode: true,
171
- multimodal: true,
172
- },
173
134
  },
174
135
  {
175
136
  name: 'claude-3-5-haiku-20241022',
176
137
  id: 'claude-3-5-haiku-20241022',
177
138
  description: 'Claude 3.5 Haiku - Fast and efficient',
178
- contextWindow: 200000,
179
- capabilities: {
180
- reasoning: true,
181
- function_calling: true,
182
- json_mode: true,
183
- multimodal: true,
184
- },
185
139
  },
186
140
  ]
187
141
  }
188
142
 
189
- async validateModelAvailability(modelName) {
190
- try {
191
- const models = await this.getAvailableModels()
192
- const model = models.find((m) => m.name === modelName)
193
-
194
- if (model) {
195
- return {
196
- available: true,
197
- model: modelName,
198
- capabilities: model.capabilities,
199
- contextWindow: model.contextWindow,
200
- }
201
- }
202
- const availableModels = models.map((m) => m.name)
203
- return {
204
- available: false,
205
- error: `Model '${modelName}' not available`,
206
- alternatives: availableModels.slice(0, 5),
207
- }
208
- } catch (error) {
209
- return {
210
- available: false,
211
- error: error.message,
212
- alternatives: [
213
- 'claude-sonnet-4-20250514',
214
- 'claude-opus-4-20250514',
215
- 'claude-3.7-sonnet-20250219',
216
- ],
217
- }
218
- }
219
- }
220
-
221
- async testConnection() {
222
- try {
223
- const response = await this.generateCompletion([{ role: 'user', content: 'Hello' }], {
224
- max_tokens: 5,
225
- })
226
-
227
- return {
228
- success: true,
229
- model: response.model,
230
- message: 'Connection successful',
231
- }
232
- } catch (error) {
233
- return {
234
- success: false,
235
- error: error.message,
236
- }
237
- }
238
- }
143
+ // testConnection() and validateModelAvailability() now provided by mixins
239
144
 
240
145
  async testModel(modelName) {
241
146
  try {
@@ -258,73 +163,11 @@ export class AnthropicProvider extends BaseProvider {
258
163
  }
259
164
  }
260
165
 
261
- getCapabilities(_modelName) {
262
- return {
263
- completion: true,
264
- streaming: true,
265
- function_calling: true,
266
- json_mode: true,
267
- reasoning: true,
268
- multimodal: true,
269
- large_context: true,
270
- }
271
- }
272
-
273
- getModelRecommendation(commitDetails) {
274
- const { files = 0, lines = 0, breaking = false, complex = false } = commitDetails
275
-
276
- // Use the most capable model for complex or breaking changes
277
- if (breaking || complex || files > 20 || lines > 1000) {
278
- return {
279
- model: 'claude-3-5-sonnet-20241022',
280
- reason: 'Complex or breaking change requiring advanced reasoning',
281
- }
282
- }
283
-
284
- // Use standard model for medium changes
285
- if (files > 5 || lines > 200) {
286
- return {
287
- model: 'claude-3-sonnet-20240229',
288
- reason: 'Medium-sized change requiring good analysis',
289
- }
290
- }
291
-
292
- // Use efficient model for small changes
293
- return {
294
- model: 'claude-3-haiku-20240307',
295
- reason: 'Small change, optimized for efficiency',
296
- }
297
- }
298
-
299
- async selectOptimalModel(commitInfo) {
300
- const recommendation = this.getModelRecommendation(commitInfo)
301
- const validation = await this.validateModelAvailability(recommendation.model)
302
-
303
- if (validation.available) {
304
- return {
305
- model: recommendation.model,
306
- reason: recommendation.reason,
307
- capabilities: validation.capabilities,
308
- }
309
- }
310
- return {
311
- model: this.getDefaultModel(),
312
- reason: 'Fallback to default model',
313
- capabilities: this.getCapabilities(this.getDefaultModel()),
314
- }
315
- }
316
-
317
- getProviderModelConfig() {
318
- return {
319
- smallModel: 'claude-3-5-haiku-20241022',
320
- mediumModel: 'claude-3.7-sonnet-20250219',
321
- standardModel: 'claude-sonnet-4-20250514',
322
- complexModel: 'claude-opus-4-20250514',
323
- default: 'claude-sonnet-4-20250514',
324
- temperature: 0.3,
325
- maxTokens: 4096,
326
- }
327
- }
166
+ // All common methods now provided by mixins:
167
+ // - getCapabilities() from CapabilitiesMixin
168
+ // - getModelRecommendation() from ModelRecommendationMixin
169
+ // - selectOptimalModel() from ModelRecommendationMixin
170
+ // - getProviderModelConfig() from ConfigurationMixin
328
171
  }
329
172
 
330
173
  // Apply mixins to add standard provider functionality