@entro314labs/ai-changelog-generator 3.0.5

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 (47) hide show
  1. package/CHANGELOG.md +801 -0
  2. package/LICENSE +21 -0
  3. package/README.md +393 -0
  4. package/ai-changelog-mcp.sh +93 -0
  5. package/ai-changelog.sh +103 -0
  6. package/bin/ai-changelog-dxt.js +35 -0
  7. package/bin/ai-changelog-mcp.js +34 -0
  8. package/bin/ai-changelog.js +18 -0
  9. package/package.json +135 -0
  10. package/src/ai-changelog-generator.js +258 -0
  11. package/src/application/orchestrators/changelog.orchestrator.js +730 -0
  12. package/src/application/services/application.service.js +301 -0
  13. package/src/cli.js +157 -0
  14. package/src/domains/ai/ai-analysis.service.js +486 -0
  15. package/src/domains/analysis/analysis.engine.js +445 -0
  16. package/src/domains/changelog/changelog.service.js +1761 -0
  17. package/src/domains/changelog/workspace-changelog.service.js +505 -0
  18. package/src/domains/git/git-repository.analyzer.js +588 -0
  19. package/src/domains/git/git.service.js +302 -0
  20. package/src/infrastructure/cli/cli.controller.js +517 -0
  21. package/src/infrastructure/config/configuration.manager.js +538 -0
  22. package/src/infrastructure/interactive/interactive-workflow.service.js +444 -0
  23. package/src/infrastructure/mcp/mcp-server.service.js +540 -0
  24. package/src/infrastructure/metrics/metrics.collector.js +362 -0
  25. package/src/infrastructure/providers/core/base-provider.js +184 -0
  26. package/src/infrastructure/providers/implementations/anthropic.js +329 -0
  27. package/src/infrastructure/providers/implementations/azure.js +296 -0
  28. package/src/infrastructure/providers/implementations/bedrock.js +393 -0
  29. package/src/infrastructure/providers/implementations/dummy.js +112 -0
  30. package/src/infrastructure/providers/implementations/google.js +320 -0
  31. package/src/infrastructure/providers/implementations/huggingface.js +301 -0
  32. package/src/infrastructure/providers/implementations/lmstudio.js +189 -0
  33. package/src/infrastructure/providers/implementations/mock.js +275 -0
  34. package/src/infrastructure/providers/implementations/ollama.js +151 -0
  35. package/src/infrastructure/providers/implementations/openai.js +273 -0
  36. package/src/infrastructure/providers/implementations/vertex.js +438 -0
  37. package/src/infrastructure/providers/provider-management.service.js +415 -0
  38. package/src/infrastructure/providers/provider-manager.service.js +363 -0
  39. package/src/infrastructure/providers/utils/base-provider-helpers.js +660 -0
  40. package/src/infrastructure/providers/utils/model-config.js +610 -0
  41. package/src/infrastructure/providers/utils/provider-utils.js +286 -0
  42. package/src/shared/constants/colors.js +370 -0
  43. package/src/shared/utils/cli-entry-utils.js +525 -0
  44. package/src/shared/utils/error-classes.js +423 -0
  45. package/src/shared/utils/json-utils.js +318 -0
  46. package/src/shared/utils/utils.js +1997 -0
  47. package/types/index.d.ts +464 -0
@@ -0,0 +1,538 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import yaml from 'js-yaml';
4
+ import colors from '../../shared/constants/colors.js';
5
+ import { ConfigError } from '../../shared/utils/error-classes.js';
6
+ import JsonUtils from '../../shared/utils/json-utils.js';
7
+
8
+ // Model configurations - moved from lib/utils/model-config.js
9
+ const MODEL_CONFIGS = {
10
+ 'gpt-4': { maxTokens: 8192, cost: 0.03 },
11
+ 'gpt-3.5-turbo': { maxTokens: 4096, cost: 0.002 },
12
+ 'claude-3-opus': { maxTokens: 200000, cost: 0.015 },
13
+ 'claude-3-sonnet': { maxTokens: 200000, cost: 0.003 },
14
+ 'claude-3-haiku': { maxTokens: 200000, cost: 0.00025 }
15
+ };
16
+
17
+ /**
18
+ * Unified Configuration Manager
19
+ * Consolidates config.js, model-config.js, and interactive-config.js logic
20
+ * Enhanced with YAML changelog configuration support
21
+ *
22
+ * Responsibilities:
23
+ * - Environment configuration loading
24
+ * - Model configuration management
25
+ * - Provider configuration
26
+ * - Changelog configuration (YAML)
27
+ * - Validation and recommendations
28
+ */
29
+ export class ConfigurationManager {
30
+ constructor(configPath = null, changelogConfigPath = null) {
31
+ this.configPath = configPath || this.findConfigFile();
32
+ this.changelogConfigPath = changelogConfigPath || this.findChangelogConfigFile();
33
+ this.config = this.loadConfig();
34
+ this.changelogConfig = this.loadChangelogConfig();
35
+ this.modelConfigs = MODEL_CONFIGS;
36
+ this.validate();
37
+ }
38
+
39
+ findConfigFile() {
40
+ const possiblePaths = [
41
+ '.env.local',
42
+ '.changelog.config.js',
43
+ 'changelog.config.json',
44
+ path.join(process.cwd(), '.env.local')
45
+ ];
46
+
47
+ for (const configPath of possiblePaths) {
48
+ if (fs.existsSync(configPath)) {
49
+ return configPath;
50
+ }
51
+ }
52
+
53
+ return '.env.local'; // Default
54
+ }
55
+
56
+ findChangelogConfigFile() {
57
+ const possiblePaths = [
58
+ 'ai-changelog.config.yaml',
59
+ 'ai-changelog.config.yml',
60
+ '.ai-changelog.yaml',
61
+ '.ai-changelog.yml',
62
+ 'changelog.config.yaml',
63
+ 'changelog.config.yml'
64
+ ];
65
+
66
+ for (const configPath of possiblePaths) {
67
+ if (fs.existsSync(configPath)) {
68
+ return configPath;
69
+ }
70
+ }
71
+
72
+ return null; // No changelog config found
73
+ }
74
+
75
+ loadConfig() {
76
+ const defaults = {
77
+ // AI Provider Settings
78
+ AI_PROVIDER: process.env.AI_PROVIDER || 'auto',
79
+ OPENAI_API_KEY: process.env.OPENAI_API_KEY,
80
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
81
+ GOOGLE_API_KEY: process.env.GOOGLE_API_KEY,
82
+ HUGGINGFACE_API_KEY: process.env.HUGGINGFACE_API_KEY,
83
+ OLLAMA_HOST: process.env.OLLAMA_HOST || 'http://localhost:11434',
84
+ OLLAMA_MODEL: process.env.OLLAMA_MODEL || 'llama3',
85
+
86
+ // Azure OpenAI Settings
87
+ AZURE_OPENAI_ENDPOINT: process.env.AZURE_OPENAI_ENDPOINT,
88
+ AZURE_OPENAI_KEY: process.env.AZURE_OPENAI_KEY,
89
+ AZURE_OPENAI_DEPLOYMENT_NAME: process.env.AZURE_OPENAI_DEPLOYMENT_NAME || 'gpt-4o',
90
+ AZURE_OPENAI_API_VERSION: process.env.AZURE_OPENAI_API_VERSION || '2024-10-21',
91
+
92
+ // Vertex AI Settings
93
+ VERTEX_PROJECT_ID: process.env.VERTEX_PROJECT_ID,
94
+ VERTEX_LOCATION: process.env.VERTEX_LOCATION || 'us-central1',
95
+ GOOGLE_APPLICATION_CREDENTIALS: process.env.GOOGLE_APPLICATION_CREDENTIALS,
96
+
97
+ // LM Studio Settings
98
+ LMSTUDIO_BASE_URL: process.env.LMSTUDIO_BASE_URL || 'http://localhost:1234/v1',
99
+
100
+ // General Settings
101
+ GIT_PATH: process.env.GIT_PATH || process.cwd(),
102
+ DEFAULT_ANALYSIS_MODE: process.env.DEFAULT_ANALYSIS_MODE || 'standard',
103
+ RATE_LIMIT_DELAY: parseInt(process.env.RATE_LIMIT_DELAY || '1000'),
104
+ MAX_RETRIES: parseInt(process.env.MAX_RETRIES || '3'),
105
+
106
+ // Output Settings
107
+ OUTPUT_FORMAT: process.env.OUTPUT_FORMAT || 'markdown',
108
+ INCLUDE_ATTRIBUTION: process.env.INCLUDE_ATTRIBUTION !== 'false',
109
+
110
+ // Debug Settings
111
+ DEBUG: process.env.DEBUG === 'true',
112
+ VERBOSE: process.env.VERBOSE === 'true'
113
+ };
114
+
115
+ // Load from .env.local if it exists
116
+ if (fs.existsSync(this.configPath)) {
117
+ try {
118
+ const content = fs.readFileSync(this.configPath, 'utf8');
119
+ const envVars = this.parseEnvFile(content);
120
+ Object.assign(defaults, envVars);
121
+ } catch (error) {
122
+ console.warn(colors.warningMessage(`Warning: Could not load config from ${this.configPath}`));
123
+ }
124
+ }
125
+
126
+ return defaults;
127
+ }
128
+
129
+ loadChangelogConfig() {
130
+ // Default changelog configuration based on git-conventional-commits
131
+ const defaultConfig = {
132
+ convention: {
133
+ commitTypes: [
134
+ 'feat', // Features
135
+ 'fix', // Bug fixes
136
+ 'docs', // Documentation
137
+ 'style', // Code style (formatting, missing semicolons, etc)
138
+ 'refactor', // Code refactoring
139
+ 'perf', // Performance improvements
140
+ 'test', // Tests
141
+ 'build', // Build system or external dependencies
142
+ 'ci', // CI/CD changes
143
+ 'chore', // Maintenance tasks
144
+ 'revert', // Reverting commits
145
+ 'merge' // Merge commits
146
+ ],
147
+ commitScopes: [],
148
+ releaseTagGlobPattern: 'v[0-9]*.[0-9]*.[0-9]*'
149
+ },
150
+ changelog: {
151
+ commitTypes: ['feat', 'fix', 'perf', 'refactor', 'docs'],
152
+ includeInvalidCommits: true,
153
+ commitIgnoreRegexPattern: '^WIP ',
154
+ headlines: {
155
+ feat: '🚀 Features',
156
+ fix: '🐛 Bug Fixes',
157
+ perf: '⚡ Performance Improvements',
158
+ refactor: '♻️ Refactoring',
159
+ docs: '📚 Documentation',
160
+ test: '🧪 Tests',
161
+ build: '🔧 Build System',
162
+ ci: '⚙️ CI/CD',
163
+ chore: '🔧 Maintenance',
164
+ style: '💄 Code Style',
165
+ revert: '⏪ Reverts',
166
+ merge: '🔀 Merges',
167
+ breakingChange: '🚨 BREAKING CHANGES'
168
+ },
169
+ // Link generation support
170
+ commitUrl: null,
171
+ commitRangeUrl: null,
172
+ issueUrl: null,
173
+ issueRegexPattern: '#[0-9]+'
174
+ }
175
+ };
176
+
177
+ if (!this.changelogConfigPath) {
178
+ return defaultConfig;
179
+ }
180
+
181
+ try {
182
+ const content = fs.readFileSync(this.changelogConfigPath, 'utf8');
183
+ const yamlConfig = yaml.load(content);
184
+
185
+ // Deep merge with defaults
186
+ return this.deepMergeConfig(defaultConfig, yamlConfig);
187
+ } catch (error) {
188
+ console.warn(colors.warningMessage(`Warning: Could not load changelog config from ${this.changelogConfigPath}, using defaults`));
189
+ return defaultConfig;
190
+ }
191
+ }
192
+
193
+ deepMergeConfig(defaults, override) {
194
+ const result = JSON.parse(JSON.stringify(defaults));
195
+
196
+ if (!override || typeof override !== 'object') {
197
+ return result;
198
+ }
199
+
200
+ for (const key in override) {
201
+ if (override[key] && typeof override[key] === 'object' && !Array.isArray(override[key])) {
202
+ result[key] = this.deepMergeConfig(result[key] || {}, override[key]);
203
+ } else {
204
+ result[key] = override[key];
205
+ }
206
+ }
207
+
208
+ return result;
209
+ }
210
+
211
+ parseEnvFile(content) {
212
+ const envVars = {};
213
+ const lines = content.split('\n');
214
+
215
+ for (const line of lines) {
216
+ const trimmed = line.trim();
217
+ if (trimmed && !trimmed.startsWith('#')) {
218
+ const [key, ...valueParts] = trimmed.split('=');
219
+ if (key && valueParts.length > 0) {
220
+ const value = valueParts.join('=').replace(/^["']|["']$/g, '');
221
+ envVars[key.trim()] = value;
222
+ }
223
+ }
224
+ }
225
+
226
+ return envVars;
227
+ }
228
+
229
+ validate() {
230
+ const issues = [];
231
+ const recommendations = [];
232
+
233
+ // Check for AI provider configuration
234
+ const hasAnyProvider = this.hasOpenAI() || this.hasAnthropic() ||
235
+ this.hasGoogle() || this.hasHuggingFace() ||
236
+ this.hasOllama() || this.hasAzureOpenAI() ||
237
+ this.hasVertexAI() || this.hasLMStudio();
238
+
239
+ if (!hasAnyProvider) {
240
+ issues.push('No AI provider configured');
241
+ recommendations.push('Configure at least one AI provider (OpenAI, Anthropic, Google, etc.)');
242
+ }
243
+
244
+ // Git path validation
245
+ if (!fs.existsSync(this.config.GIT_PATH)) {
246
+ issues.push('Git path does not exist');
247
+ recommendations.push('Set GIT_PATH to a valid git repository');
248
+ }
249
+
250
+ // Provider-specific validations
251
+ if (this.config.AI_PROVIDER === 'azure' && !this.hasAzureOpenAI()) {
252
+ issues.push('Azure OpenAI selected but not properly configured');
253
+ recommendations.push('Set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_KEY');
254
+ }
255
+
256
+ if (this.config.AI_PROVIDER === 'vertex' && !this.hasVertexAI()) {
257
+ issues.push('Vertex AI selected but not properly configured');
258
+ recommendations.push('Set VERTEX_PROJECT_ID and GOOGLE_APPLICATION_CREDENTIALS');
259
+ }
260
+
261
+ this.validationResult = { issues, recommendations };
262
+ }
263
+
264
+ // Provider availability checks
265
+ hasOpenAI() {
266
+ return !!this.config.OPENAI_API_KEY;
267
+ }
268
+
269
+ hasAnthropic() {
270
+ return !!this.config.ANTHROPIC_API_KEY;
271
+ }
272
+
273
+ hasGoogle() {
274
+ return !!this.config.GOOGLE_API_KEY;
275
+ }
276
+
277
+ hasHuggingFace() {
278
+ return !!this.config.HUGGINGFACE_API_KEY;
279
+ }
280
+
281
+ hasOllama() {
282
+ return !!this.config.OLLAMA_HOST;
283
+ }
284
+
285
+ hasAzureOpenAI() {
286
+ return !!(this.config.AZURE_OPENAI_ENDPOINT && this.config.AZURE_OPENAI_KEY);
287
+ }
288
+
289
+ hasVertexAI() {
290
+ return !!(this.config.VERTEX_PROJECT_ID && this.config.GOOGLE_APPLICATION_CREDENTIALS);
291
+ }
292
+
293
+ hasLMStudio() {
294
+ return !!this.config.LMSTUDIO_BASE_URL;
295
+ }
296
+
297
+ // Model configuration methods
298
+ getOptimalModelConfig(analysisMode = 'standard', complexity = 'medium') {
299
+ const modes = {
300
+ simple: 'simple',
301
+ standard: 'standard',
302
+ detailed: 'complex',
303
+ enterprise: 'complex'
304
+ };
305
+
306
+ const modelType = modes[analysisMode] || 'standard';
307
+
308
+ // Return configuration for the active provider
309
+ const provider = this.getActiveProvider();
310
+ const providerConfig = this.modelConfigs[provider];
311
+
312
+ if (!providerConfig) {
313
+ return { model: 'auto', capabilities: {} };
314
+ }
315
+
316
+ const modelKey = `${modelType}Model`;
317
+ const model = providerConfig[modelKey] || providerConfig.standardModel;
318
+
319
+ return {
320
+ model,
321
+ capabilities: providerConfig.capabilities || {},
322
+ contextWindow: providerConfig.contextWindow || 8192,
323
+ maxTokens: providerConfig.maxTokens || 4096
324
+ };
325
+ }
326
+
327
+ getModelRecommendation(commitInfo) {
328
+ const { files = 0, lines = 0, breaking = false, complex = false } = commitInfo;
329
+
330
+ // Determine complexity
331
+ let analysisMode = 'standard';
332
+
333
+ if (breaking || complex || files > 20 || lines > 500) {
334
+ analysisMode = 'detailed';
335
+ } else if (files > 5 || lines > 100) {
336
+ analysisMode = 'standard';
337
+ } else {
338
+ analysisMode = 'simple';
339
+ }
340
+
341
+ return this.getOptimalModelConfig(analysisMode);
342
+ }
343
+
344
+ getActiveProvider() {
345
+ if (this.config.AI_PROVIDER !== 'auto') {
346
+ return this.config.AI_PROVIDER;
347
+ }
348
+
349
+ // Auto-detect available provider
350
+ if (this.hasOpenAI()) return 'openai';
351
+ if (this.hasAnthropic()) return 'anthropic';
352
+ if (this.hasGoogle()) return 'google';
353
+ if (this.hasAzureOpenAI()) return 'azure';
354
+ if (this.hasVertexAI()) return 'vertex';
355
+ if (this.hasOllama()) return 'ollama';
356
+ if (this.hasHuggingFace()) return 'huggingface';
357
+ if (this.hasLMStudio()) return 'lmstudio';
358
+
359
+ return 'none';
360
+ }
361
+
362
+ // Configuration getters
363
+ get(key) {
364
+ return this.config[key];
365
+ }
366
+
367
+ getAll() {
368
+ return { ...this.config };
369
+ }
370
+
371
+ set(key, value) {
372
+ this.config[key] = value;
373
+ }
374
+
375
+ // Provider configuration
376
+ getProviderConfig(providerName) {
377
+ const configs = {
378
+ openai: {
379
+ apiKey: this.config.OPENAI_API_KEY,
380
+ baseURL: 'https://api.openai.com/v1'
381
+ },
382
+ anthropic: {
383
+ apiKey: this.config.ANTHROPIC_API_KEY,
384
+ baseURL: 'https://api.anthropic.com/v1'
385
+ },
386
+ google: {
387
+ apiKey: this.config.GOOGLE_API_KEY,
388
+ baseURL: 'https://generativelanguage.googleapis.com/v1'
389
+ },
390
+ azure: {
391
+ apiKey: this.config.AZURE_OPENAI_KEY,
392
+ endpoint: this.config.AZURE_OPENAI_ENDPOINT,
393
+ deploymentName: this.config.AZURE_OPENAI_DEPLOYMENT_NAME,
394
+ apiVersion: this.config.AZURE_OPENAI_API_VERSION
395
+ },
396
+ vertex: {
397
+ projectId: this.config.VERTEX_PROJECT_ID,
398
+ location: this.config.VERTEX_LOCATION,
399
+ credentials: this.config.GOOGLE_APPLICATION_CREDENTIALS
400
+ },
401
+ ollama: {
402
+ host: this.config.OLLAMA_HOST,
403
+ model: this.config.OLLAMA_MODEL
404
+ },
405
+ huggingface: {
406
+ apiKey: this.config.HUGGINGFACE_API_KEY,
407
+ baseURL: 'https://api-inference.huggingface.co'
408
+ },
409
+ lmstudio: {
410
+ baseURL: this.config.LMSTUDIO_BASE_URL
411
+ }
412
+ };
413
+
414
+ return configs[providerName] || {};
415
+ }
416
+
417
+ // Environment setup helpers
418
+ getRequiredEnvVars(provider) {
419
+ const requirements = {
420
+ openai: ['OPENAI_API_KEY'],
421
+ anthropic: ['ANTHROPIC_API_KEY'],
422
+ google: ['GOOGLE_API_KEY'],
423
+ azure: ['AZURE_OPENAI_ENDPOINT', 'AZURE_OPENAI_KEY'],
424
+ vertex: ['VERTEX_PROJECT_ID', 'GOOGLE_APPLICATION_CREDENTIALS'],
425
+ huggingface: ['HUGGINGFACE_API_KEY'],
426
+ ollama: ['OLLAMA_HOST'],
427
+ lmstudio: ['LMSTUDIO_BASE_URL']
428
+ };
429
+
430
+ return requirements[provider] || [];
431
+ }
432
+
433
+ validateProvider(providerName) {
434
+ const required = this.getRequiredEnvVars(providerName);
435
+ const missing = required.filter(key => !this.config[key]);
436
+
437
+ return {
438
+ valid: missing.length === 0,
439
+ missing,
440
+ configured: required.filter(key => !!this.config[key])
441
+ };
442
+ }
443
+
444
+ // Configuration update methods
445
+ async updateConfig(updates) {
446
+ Object.assign(this.config, updates);
447
+ await this.saveConfig();
448
+ this.validate();
449
+ }
450
+
451
+ async saveConfig() {
452
+ try {
453
+ const envContent = Object.entries(this.config)
454
+ .map(([key, value]) => `${key}=${value || ''}`)
455
+ .join('\n');
456
+
457
+ await fs.promises.writeFile(this.configPath, envContent, 'utf8');
458
+ console.log(colors.successMessage(`✅ Configuration saved to ${this.configPath}`));
459
+ } catch (error) {
460
+ console.error(colors.errorMessage(`Failed to save configuration: ${error.message}`));
461
+ throw error;
462
+ }
463
+ }
464
+
465
+ // Validation results
466
+ getValidationResult() {
467
+ return this.validationResult;
468
+ }
469
+
470
+ isValid() {
471
+ return this.validationResult.issues.length === 0;
472
+ }
473
+
474
+ // Debug and logging
475
+ logConfiguration() {
476
+ if (!this.config.DEBUG) return;
477
+
478
+ console.log(colors.header('🔧 Configuration Debug:'));
479
+ console.log(`Config path: ${colors.file(this.configPath)}`);
480
+ console.log(`Changelog config path: ${colors.file(this.changelogConfigPath || 'default')}`);
481
+ console.log(`Active provider: ${colors.highlight(this.getActiveProvider())}`);
482
+ console.log(`Git path: ${colors.file(this.config.GIT_PATH)}`);
483
+
484
+ if (this.validationResult.issues.length > 0) {
485
+ console.log(colors.warningMessage('Issues:'));
486
+ this.validationResult.issues.forEach(issue => {
487
+ console.log(` - ${issue}`);
488
+ });
489
+ }
490
+ }
491
+
492
+ // Changelog configuration getters
493
+ getChangelogConfig() {
494
+ return this.changelogConfig;
495
+ }
496
+
497
+ getConventionConfig() {
498
+ return this.changelogConfig.convention;
499
+ }
500
+
501
+ getCommitTypes() {
502
+ return this.changelogConfig.convention.commitTypes;
503
+ }
504
+
505
+ getChangelogCommitTypes() {
506
+ return this.changelogConfig.changelog.commitTypes;
507
+ }
508
+
509
+ getHeadlines() {
510
+ return this.changelogConfig.changelog.headlines;
511
+ }
512
+
513
+ getCommitUrl() {
514
+ return this.changelogConfig.changelog.commitUrl;
515
+ }
516
+
517
+ getCommitRangeUrl() {
518
+ return this.changelogConfig.changelog.commitRangeUrl;
519
+ }
520
+
521
+ getIssueUrl() {
522
+ return this.changelogConfig.changelog.issueUrl;
523
+ }
524
+
525
+ getIssueRegexPattern() {
526
+ const pattern = this.changelogConfig.changelog.issueRegexPattern;
527
+ return pattern ? new RegExp(pattern, 'g') : /#[0-9]+/g;
528
+ }
529
+
530
+ shouldIncludeInvalidCommits() {
531
+ return this.changelogConfig.changelog.includeInvalidCommits;
532
+ }
533
+
534
+ getCommitIgnoreRegex() {
535
+ const pattern = this.changelogConfig.changelog.commitIgnoreRegexPattern;
536
+ return pattern ? new RegExp(pattern) : /^WIP /;
537
+ }
538
+ }