@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.
- package/CHANGELOG.md +801 -0
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/ai-changelog-mcp.sh +93 -0
- package/ai-changelog.sh +103 -0
- package/bin/ai-changelog-dxt.js +35 -0
- package/bin/ai-changelog-mcp.js +34 -0
- package/bin/ai-changelog.js +18 -0
- package/package.json +135 -0
- package/src/ai-changelog-generator.js +258 -0
- package/src/application/orchestrators/changelog.orchestrator.js +730 -0
- package/src/application/services/application.service.js +301 -0
- package/src/cli.js +157 -0
- package/src/domains/ai/ai-analysis.service.js +486 -0
- package/src/domains/analysis/analysis.engine.js +445 -0
- package/src/domains/changelog/changelog.service.js +1761 -0
- package/src/domains/changelog/workspace-changelog.service.js +505 -0
- package/src/domains/git/git-repository.analyzer.js +588 -0
- package/src/domains/git/git.service.js +302 -0
- package/src/infrastructure/cli/cli.controller.js +517 -0
- package/src/infrastructure/config/configuration.manager.js +538 -0
- package/src/infrastructure/interactive/interactive-workflow.service.js +444 -0
- package/src/infrastructure/mcp/mcp-server.service.js +540 -0
- package/src/infrastructure/metrics/metrics.collector.js +362 -0
- package/src/infrastructure/providers/core/base-provider.js +184 -0
- package/src/infrastructure/providers/implementations/anthropic.js +329 -0
- package/src/infrastructure/providers/implementations/azure.js +296 -0
- package/src/infrastructure/providers/implementations/bedrock.js +393 -0
- package/src/infrastructure/providers/implementations/dummy.js +112 -0
- package/src/infrastructure/providers/implementations/google.js +320 -0
- package/src/infrastructure/providers/implementations/huggingface.js +301 -0
- package/src/infrastructure/providers/implementations/lmstudio.js +189 -0
- package/src/infrastructure/providers/implementations/mock.js +275 -0
- package/src/infrastructure/providers/implementations/ollama.js +151 -0
- package/src/infrastructure/providers/implementations/openai.js +273 -0
- package/src/infrastructure/providers/implementations/vertex.js +438 -0
- package/src/infrastructure/providers/provider-management.service.js +415 -0
- package/src/infrastructure/providers/provider-manager.service.js +363 -0
- package/src/infrastructure/providers/utils/base-provider-helpers.js +660 -0
- package/src/infrastructure/providers/utils/model-config.js +610 -0
- package/src/infrastructure/providers/utils/provider-utils.js +286 -0
- package/src/shared/constants/colors.js +370 -0
- package/src/shared/utils/cli-entry-utils.js +525 -0
- package/src/shared/utils/error-classes.js +423 -0
- package/src/shared/utils/json-utils.js +318 -0
- package/src/shared/utils/utils.js +1997 -0
- package/types/index.d.ts +464 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { GitService } from '../../domains/git/git.service.js';
|
|
2
|
+
import { AIAnalysisService } from '../../domains/ai/ai-analysis.service.js';
|
|
3
|
+
import { ChangelogService } from '../../domains/changelog/changelog.service.js';
|
|
4
|
+
import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js';
|
|
5
|
+
import { ProviderManagerService } from '../../infrastructure/providers/provider-manager.service.js';
|
|
6
|
+
import { InteractiveWorkflowService } from '../../infrastructure/interactive/interactive-workflow.service.js';
|
|
7
|
+
import colors from '../../shared/constants/colors.js';
|
|
8
|
+
|
|
9
|
+
export class ChangelogOrchestrator {
|
|
10
|
+
constructor(configManager, options = {}) {
|
|
11
|
+
this.configManager = configManager;
|
|
12
|
+
this.options = options;
|
|
13
|
+
this.analysisMode = options.analysisMode || 'standard';
|
|
14
|
+
this.metrics = {
|
|
15
|
+
startTime: Date.now(),
|
|
16
|
+
commitsProcessed: 0,
|
|
17
|
+
apiCalls: 0,
|
|
18
|
+
errors: 0,
|
|
19
|
+
batchesProcessed: 0,
|
|
20
|
+
totalTokens: 0,
|
|
21
|
+
ruleBasedFallbacks: 0,
|
|
22
|
+
cacheHits: 0
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
this.initialized = false;
|
|
26
|
+
this.initializationPromise = null;
|
|
27
|
+
|
|
28
|
+
// Start initialization
|
|
29
|
+
this.initializationPromise = this.initializeServices();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async ensureInitialized() {
|
|
33
|
+
if (!this.initialized) {
|
|
34
|
+
await this.initializationPromise;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async initializeServices() {
|
|
39
|
+
try {
|
|
40
|
+
// Initialize AI provider
|
|
41
|
+
this.providerManager = new ProviderManagerService(this.configManager.getAll(), {
|
|
42
|
+
errorHandler: { logToConsole: true }
|
|
43
|
+
});
|
|
44
|
+
this.aiProvider = this.providerManager.getActiveProvider();
|
|
45
|
+
|
|
46
|
+
// Create lightweight implementations for missing dependencies
|
|
47
|
+
this.gitManager = await this.createGitManager();
|
|
48
|
+
this.tagger = await this.createTagger();
|
|
49
|
+
this.promptEngine = await this.createPromptEngine();
|
|
50
|
+
|
|
51
|
+
// Initialize domain services with proper dependencies
|
|
52
|
+
this.gitService = new GitService(this.gitManager, this.tagger);
|
|
53
|
+
this.aiAnalysisService = new AIAnalysisService(this.aiProvider, this.promptEngine, this.tagger, this.analysisMode);
|
|
54
|
+
this.analysisEngine = new AnalysisEngine(this.gitService, this.aiAnalysisService);
|
|
55
|
+
this.changelogService = new ChangelogService(this.gitService, this.aiAnalysisService, this.analysisEngine, this.configManager);
|
|
56
|
+
this.interactiveService = new InteractiveWorkflowService(this.gitService, this.aiAnalysisService, this.changelogService);
|
|
57
|
+
|
|
58
|
+
// Only log if not in MCP server mode
|
|
59
|
+
if (!process.env.MCP_SERVER_MODE) {
|
|
60
|
+
console.log(colors.successMessage('⚙️ Services initialized'));
|
|
61
|
+
}
|
|
62
|
+
this.initialized = true;
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(colors.errorMessage('Failed to initialize services:'), error.message);
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async createGitManager() {
|
|
71
|
+
const { execSync } = await import('child_process');
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
isGitRepo: (() => {
|
|
75
|
+
try {
|
|
76
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
})(),
|
|
82
|
+
|
|
83
|
+
execGit(command) {
|
|
84
|
+
try {
|
|
85
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' });
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw new Error(`Git command failed: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
execGitSafe(command) {
|
|
92
|
+
try {
|
|
93
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' });
|
|
94
|
+
} catch {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
execGitShow(command) {
|
|
100
|
+
try {
|
|
101
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' });
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// console.warn(`Git command failed: ${command}`);
|
|
104
|
+
// console.warn(`Error: ${error.message}`);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
validateCommitHash(hash) {
|
|
110
|
+
try {
|
|
111
|
+
execSync(`git cat-file -e ${hash}`, { stdio: 'ignore' });
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
getAllBranches() {
|
|
119
|
+
try {
|
|
120
|
+
const output = execSync('git branch -a', { encoding: 'utf8' });
|
|
121
|
+
return output.split('\n').filter(Boolean).map(branch => branch.trim().replace(/^\*\s*/, ''));
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
getUnmergedCommits() {
|
|
128
|
+
try {
|
|
129
|
+
const output = execSync('git log --oneline --no-merges HEAD ^origin/main', { encoding: 'utf8' });
|
|
130
|
+
return output.split('\n').filter(Boolean);
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
getDanglingCommits() {
|
|
137
|
+
try {
|
|
138
|
+
const output = execSync('git fsck --no-reflog | grep "dangling commit"', { encoding: 'utf8' });
|
|
139
|
+
return output.split('\n').filter(Boolean);
|
|
140
|
+
} catch {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
getUntrackedFiles() {
|
|
146
|
+
try {
|
|
147
|
+
const output = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' });
|
|
148
|
+
return output.split('\n').filter(Boolean);
|
|
149
|
+
} catch {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
getRecentCommits(limit = 10) {
|
|
155
|
+
try {
|
|
156
|
+
const output = execSync(`git log --oneline -${limit}`, { encoding: 'utf8' });
|
|
157
|
+
return output.split('\n').filter(Boolean);
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
getComprehensiveAnalysis() {
|
|
164
|
+
return {
|
|
165
|
+
totalCommits: this.getRecentCommits(1000).length,
|
|
166
|
+
branches: this.getAllBranches(),
|
|
167
|
+
untrackedFiles: this.getUntrackedFiles()
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
hasFile(filename) {
|
|
172
|
+
try {
|
|
173
|
+
execSync(`test -f ${filename}`, { stdio: 'ignore' });
|
|
174
|
+
return true;
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async createTagger() {
|
|
183
|
+
const { analyzeSemanticChanges, analyzeFunctionalImpact } = await import('../../shared/utils/utils.js');
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
analyzeCommit(commit) {
|
|
187
|
+
const semanticChanges = [];
|
|
188
|
+
const breakingChanges = [];
|
|
189
|
+
const categories = [];
|
|
190
|
+
const tags = [];
|
|
191
|
+
|
|
192
|
+
// Basic analysis based on commit message
|
|
193
|
+
const message = commit.message.toLowerCase();
|
|
194
|
+
|
|
195
|
+
if (message.includes('breaking') || message.includes('!:')) {
|
|
196
|
+
breakingChanges.push('Breaking change detected in commit message');
|
|
197
|
+
categories.push('breaking');
|
|
198
|
+
tags.push('breaking');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (message.startsWith('feat')) {
|
|
202
|
+
categories.push('feature');
|
|
203
|
+
tags.push('feature');
|
|
204
|
+
} else if (message.startsWith('fix')) {
|
|
205
|
+
categories.push('fix');
|
|
206
|
+
tags.push('bugfix');
|
|
207
|
+
} else if (message.startsWith('docs')) {
|
|
208
|
+
categories.push('docs');
|
|
209
|
+
tags.push('documentation');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Analyze files if available
|
|
213
|
+
if (commit.files && commit.files.length > 0) {
|
|
214
|
+
commit.files.forEach(file => {
|
|
215
|
+
const semantic = analyzeSemanticChanges('', file.path);
|
|
216
|
+
if (semantic.frameworks) {
|
|
217
|
+
semanticChanges.push(...semantic.frameworks);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Determine importance
|
|
223
|
+
let importance = 'medium';
|
|
224
|
+
if (breakingChanges.length > 0 || commit.files?.length > 10) {
|
|
225
|
+
importance = 'high';
|
|
226
|
+
} else if (categories.includes('docs') || commit.files?.length < 3) {
|
|
227
|
+
importance = 'low';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
semanticChanges,
|
|
232
|
+
breakingChanges,
|
|
233
|
+
categories,
|
|
234
|
+
importance,
|
|
235
|
+
tags
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async createPromptEngine() {
|
|
242
|
+
const { buildEnhancedPrompt } = await import('../../shared/utils/utils.js');
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
systemPrompts: {
|
|
246
|
+
master: "You are an expert software analyst specializing in code change analysis and changelog generation.",
|
|
247
|
+
standard: "Provide clear, concise analysis focusing on the practical impact of changes.",
|
|
248
|
+
detailed: "Provide comprehensive technical analysis with detailed explanations and implications.",
|
|
249
|
+
enterprise: "Provide enterprise-grade analysis suitable for stakeholder communication and decision-making.",
|
|
250
|
+
changesAnalysis: "You are an expert at analyzing code changes and their business impact."
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
optimizeForProvider(prompt, providerName, capabilities = {}) {
|
|
254
|
+
// Simple optimization - could be enhanced based on provider capabilities
|
|
255
|
+
if (providerName?.toLowerCase().includes('claude')) {
|
|
256
|
+
return `Please analyze this carefully and provide structured output:\n\n${prompt}`;
|
|
257
|
+
} else if (providerName?.toLowerCase().includes('gpt')) {
|
|
258
|
+
return `${prompt}\n\nPlease respond in JSON format as requested.`;
|
|
259
|
+
}
|
|
260
|
+
return prompt;
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
buildRepositoryHealthPrompt(healthData, analysisMode) {
|
|
264
|
+
return `Analyze the health of this repository based on the following data:\n\n${JSON.stringify(healthData, null, 2)}\n\nProvide assessment and recommendations.`;
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async generateChangelog(version, since) {
|
|
270
|
+
try {
|
|
271
|
+
await this.ensureInitialized();
|
|
272
|
+
|
|
273
|
+
this.metrics.startTime = Date.now();
|
|
274
|
+
|
|
275
|
+
console.log('\n' + colors.processingMessage('🚀 Starting changelog generation...'));
|
|
276
|
+
|
|
277
|
+
// Validate git repository
|
|
278
|
+
if (!this.gitManager.isGitRepo) {
|
|
279
|
+
throw new Error('Not a git repository');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Generate changelog using the service
|
|
283
|
+
const result = await this.changelogService.generateChangelog(version, since);
|
|
284
|
+
|
|
285
|
+
if (!result) {
|
|
286
|
+
console.log(colors.warningMessage('No changelog generated'));
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Update metrics
|
|
291
|
+
this.updateMetrics(result);
|
|
292
|
+
|
|
293
|
+
// Display results
|
|
294
|
+
this.displayResults(result, version);
|
|
295
|
+
|
|
296
|
+
return result;
|
|
297
|
+
|
|
298
|
+
} catch (error) {
|
|
299
|
+
this.metrics.errors++;
|
|
300
|
+
console.error(colors.errorMessage('Changelog generation failed:'), error.message);
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async analyzeRepository(options = {}) {
|
|
306
|
+
try {
|
|
307
|
+
await this.ensureInitialized();
|
|
308
|
+
|
|
309
|
+
console.log(colors.processingMessage('🔍 Starting repository analysis...'));
|
|
310
|
+
|
|
311
|
+
const analysisType = options.type || 'changes';
|
|
312
|
+
const result = await this.analysisEngine.analyze(analysisType, options);
|
|
313
|
+
|
|
314
|
+
this.displayAnalysisResults(result, analysisType);
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
|
|
318
|
+
} catch (error) {
|
|
319
|
+
this.metrics.errors++;
|
|
320
|
+
console.error(colors.errorMessage('Repository analysis failed:'), error.message);
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async runInteractive() {
|
|
326
|
+
await this.ensureInitialized();
|
|
327
|
+
|
|
328
|
+
const { runInteractiveMode, selectSpecificCommits } = await import('../../shared/utils/utils.js');
|
|
329
|
+
const { confirm } = await import('@clack/prompts');
|
|
330
|
+
|
|
331
|
+
console.log(colors.processingMessage('🎮 Starting interactive mode...'));
|
|
332
|
+
|
|
333
|
+
let continueSession = true;
|
|
334
|
+
|
|
335
|
+
while (continueSession) {
|
|
336
|
+
try {
|
|
337
|
+
const result = await runInteractiveMode();
|
|
338
|
+
|
|
339
|
+
if (result.action === 'exit') {
|
|
340
|
+
console.log(colors.successMessage('👋 Goodbye!'));
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
await this.handleInteractiveAction(result.action);
|
|
345
|
+
|
|
346
|
+
// Ask if user wants to continue
|
|
347
|
+
const continueChoice = await confirm({
|
|
348
|
+
message: 'Would you like to perform another action?',
|
|
349
|
+
initialValue: true
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
continueSession = continueChoice;
|
|
353
|
+
|
|
354
|
+
} catch (error) {
|
|
355
|
+
console.error(colors.errorMessage(`Interactive mode error: ${error.message}`));
|
|
356
|
+
|
|
357
|
+
const retryChoice = await confirm({
|
|
358
|
+
message: 'Would you like to try again?',
|
|
359
|
+
initialValue: true
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
continueSession = retryChoice;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return { interactive: true, status: 'completed' };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async handleInteractiveAction(action) {
|
|
370
|
+
|
|
371
|
+
switch (action) {
|
|
372
|
+
case 'changelog-recent':
|
|
373
|
+
await this.handleRecentChangelogGeneration();
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
case 'changelog-specific':
|
|
377
|
+
await this.handleSpecificChangelogGeneration();
|
|
378
|
+
break;
|
|
379
|
+
|
|
380
|
+
case 'analyze-workdir':
|
|
381
|
+
await this.generateChangelogFromChanges();
|
|
382
|
+
break;
|
|
383
|
+
|
|
384
|
+
case 'analyze-repo':
|
|
385
|
+
await this.analyzeRepository({ type: 'comprehensive' });
|
|
386
|
+
break;
|
|
387
|
+
|
|
388
|
+
case 'commit-message':
|
|
389
|
+
await this.handleCommitMessageGeneration();
|
|
390
|
+
break;
|
|
391
|
+
|
|
392
|
+
case 'configure-providers':
|
|
393
|
+
await this.handleProviderConfiguration();
|
|
394
|
+
break;
|
|
395
|
+
|
|
396
|
+
case 'validate-config':
|
|
397
|
+
await this.validateConfiguration();
|
|
398
|
+
break;
|
|
399
|
+
|
|
400
|
+
default:
|
|
401
|
+
console.log(colors.warningMessage(`Unknown action: ${action}`));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async handleRecentChangelogGeneration() {
|
|
406
|
+
const { text } = await import('@clack/prompts');
|
|
407
|
+
|
|
408
|
+
const commitCountInput = await text({
|
|
409
|
+
message: 'How many recent commits to include?',
|
|
410
|
+
placeholder: '10',
|
|
411
|
+
validate: (value) => {
|
|
412
|
+
const num = parseInt(value);
|
|
413
|
+
if (isNaN(num) || num <= 0 || num > 100) {
|
|
414
|
+
return 'Please enter a number between 1 and 100';
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
const commitCount = parseInt(commitCountInput) || 10;
|
|
420
|
+
|
|
421
|
+
console.log(colors.processingMessage(`📝 Generating changelog for ${commitCount} recent commits...`));
|
|
422
|
+
|
|
423
|
+
const result = await this.generateChangelog({
|
|
424
|
+
version: `Recent-${commitCount}-commits`,
|
|
425
|
+
maxCommits: commitCount
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
if (result?.changelog) {
|
|
429
|
+
console.log(colors.successMessage('✅ Changelog generated successfully!'));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async handleSpecificChangelogGeneration() {
|
|
434
|
+
const { selectSpecificCommits } = await import('../../shared/utils/utils.js');
|
|
435
|
+
|
|
436
|
+
console.log(colors.infoMessage('📋 Select specific commits for changelog generation:'));
|
|
437
|
+
|
|
438
|
+
const selectedCommits = await selectSpecificCommits(30);
|
|
439
|
+
|
|
440
|
+
if (selectedCommits.length === 0) {
|
|
441
|
+
console.log(colors.warningMessage('No commits selected.'));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log(colors.processingMessage(`📝 Generating changelog for ${selectedCommits.length} selected commits...`));
|
|
446
|
+
|
|
447
|
+
const result = await this.generateChangelogFromCommits(selectedCommits);
|
|
448
|
+
|
|
449
|
+
if (result?.changelog) {
|
|
450
|
+
console.log(colors.successMessage('✅ Changelog generated successfully!'));
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async handleCommitMessageGeneration() {
|
|
455
|
+
console.log(colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...'));
|
|
456
|
+
|
|
457
|
+
// Use shared utility for getting working directory changes
|
|
458
|
+
const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js');
|
|
459
|
+
const changes = getWorkingDirectoryChanges();
|
|
460
|
+
|
|
461
|
+
if (!changes || changes.length === 0) {
|
|
462
|
+
console.log(colors.warningMessage('No uncommitted changes found.'));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const analysis = await this.interactiveService.generateCommitSuggestion();
|
|
467
|
+
|
|
468
|
+
if (analysis.success && analysis.suggestions.length > 0) {
|
|
469
|
+
const { select } = await import('@clack/prompts');
|
|
470
|
+
|
|
471
|
+
const choices = [
|
|
472
|
+
...analysis.suggestions.map((msg, index) => ({
|
|
473
|
+
value: msg,
|
|
474
|
+
label: `${index + 1}. ${msg}`
|
|
475
|
+
})),
|
|
476
|
+
{
|
|
477
|
+
value: 'CUSTOM',
|
|
478
|
+
label: '✏️ Write custom message'
|
|
479
|
+
}
|
|
480
|
+
];
|
|
481
|
+
|
|
482
|
+
const selectedMessage = await select({
|
|
483
|
+
message: 'Choose a commit message:',
|
|
484
|
+
options: choices
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
if (selectedMessage === 'CUSTOM') {
|
|
488
|
+
const { text } = await import('@clack/prompts');
|
|
489
|
+
|
|
490
|
+
const customMessage = await text({
|
|
491
|
+
message: 'Enter your commit message:',
|
|
492
|
+
validate: (input) => {
|
|
493
|
+
if (!input || input.trim().length === 0) {
|
|
494
|
+
return 'Commit message cannot be empty';
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
console.log(colors.successMessage(`📝 Custom message: ${customMessage}`));
|
|
500
|
+
} else {
|
|
501
|
+
console.log(colors.successMessage(`📝 Selected: ${selectedMessage}`));
|
|
502
|
+
}
|
|
503
|
+
} else {
|
|
504
|
+
console.log(colors.warningMessage('Could not generate commit message suggestions.'));
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async handleProviderConfiguration() {
|
|
509
|
+
const { select } = await import('@clack/prompts');
|
|
510
|
+
|
|
511
|
+
const availableProviders = this.providerManager.getAllProviders();
|
|
512
|
+
|
|
513
|
+
const choices = availableProviders.map(p => ({
|
|
514
|
+
value: p.name,
|
|
515
|
+
label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`
|
|
516
|
+
}));
|
|
517
|
+
|
|
518
|
+
const selectedProvider = await select({
|
|
519
|
+
message: 'Select provider to configure:',
|
|
520
|
+
options: choices
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
console.log(colors.infoMessage(`🔧 Configuring ${selectedProvider}...`));
|
|
524
|
+
console.log(colors.infoMessage('Please edit your .env.local file to add the required API keys.'));
|
|
525
|
+
console.log(colors.highlight(`Example for ${selectedProvider.toUpperCase()}:`));
|
|
526
|
+
|
|
527
|
+
switch (selectedProvider) {
|
|
528
|
+
case 'openai':
|
|
529
|
+
console.log(colors.code('OPENAI_API_KEY=your_api_key_here'));
|
|
530
|
+
break;
|
|
531
|
+
case 'anthropic':
|
|
532
|
+
console.log(colors.code('ANTHROPIC_API_KEY=your_api_key_here'));
|
|
533
|
+
break;
|
|
534
|
+
case 'azure':
|
|
535
|
+
console.log(colors.code('AZURE_OPENAI_API_KEY=your_api_key_here'));
|
|
536
|
+
console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_endpoint_here'));
|
|
537
|
+
break;
|
|
538
|
+
case 'google':
|
|
539
|
+
console.log(colors.code('GOOGLE_API_KEY=your_api_key_here'));
|
|
540
|
+
break;
|
|
541
|
+
default:
|
|
542
|
+
console.log(colors.code(`${selectedProvider.toUpperCase()}_API_KEY=your_api_key_here`));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async generateChangelogFromChanges(version) {
|
|
547
|
+
try {
|
|
548
|
+
await this.ensureInitialized();
|
|
549
|
+
|
|
550
|
+
console.log(colors.processingMessage('📝 Generating changelog from working directory changes...'));
|
|
551
|
+
|
|
552
|
+
const result = await this.changelogService.generateChangelogFromChanges(version);
|
|
553
|
+
|
|
554
|
+
if (result) {
|
|
555
|
+
console.log(colors.successMessage('✅ Working directory changelog generated'));
|
|
556
|
+
console.log(result.changelog);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return result;
|
|
560
|
+
|
|
561
|
+
} catch (error) {
|
|
562
|
+
this.metrics.errors++;
|
|
563
|
+
console.error(colors.errorMessage('Working directory changelog generation failed:'), error.message);
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
updateMetrics(result) {
|
|
569
|
+
if (result.analyzedCommits) {
|
|
570
|
+
this.metrics.commitsProcessed += result.analyzedCommits.length;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Get metrics from AI service
|
|
574
|
+
const aiMetrics = this.aiAnalysisService.getMetrics();
|
|
575
|
+
this.metrics.apiCalls += aiMetrics.apiCalls;
|
|
576
|
+
this.metrics.totalTokens += aiMetrics.totalTokens;
|
|
577
|
+
this.metrics.ruleBasedFallbacks += aiMetrics.ruleBasedFallbacks;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
displayResults(result, version) {
|
|
581
|
+
const { changelog, insights, analyzedCommits } = result;
|
|
582
|
+
|
|
583
|
+
console.log('\n' + colors.successMessage('✅ Changelog Generation Complete'));
|
|
584
|
+
|
|
585
|
+
if (insights) {
|
|
586
|
+
// Create a clean insights summary
|
|
587
|
+
const insightLines = [
|
|
588
|
+
`${colors.label('Total commits')}: ${colors.number(insights.totalCommits)}`,
|
|
589
|
+
`${colors.label('Complexity')}: ${this.getComplexityColor(insights.complexity)(insights.complexity)}`,
|
|
590
|
+
`${colors.label('Risk level')}: ${this.getRiskColor(insights.riskLevel)(insights.riskLevel)}`
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
if (insights.breaking) {
|
|
594
|
+
insightLines.push('');
|
|
595
|
+
insightLines.push(colors.warningMessage('⚠️ Contains breaking changes'));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (Object.keys(insights.commitTypes).length > 0) {
|
|
599
|
+
insightLines.push('');
|
|
600
|
+
insightLines.push(colors.dim('Commit types:'));
|
|
601
|
+
Object.entries(insights.commitTypes).forEach(([type, count]) => {
|
|
602
|
+
insightLines.push(` ${colors.commitType(type)}: ${colors.number(count)}`);
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
console.log(colors.box('📊 Release Insights', insightLines.join('\n')));
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Don't show changelog content in terminal - it's saved to file
|
|
610
|
+
|
|
611
|
+
this.displayMetrics();
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
getComplexityColor(complexity) {
|
|
615
|
+
const level = complexity?.toLowerCase();
|
|
616
|
+
switch (level) {
|
|
617
|
+
case 'low': return colors.success;
|
|
618
|
+
case 'medium': return colors.warning;
|
|
619
|
+
case 'high': return colors.error;
|
|
620
|
+
default: return colors.highlight;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
getRiskColor(risk) {
|
|
625
|
+
const level = risk?.toLowerCase();
|
|
626
|
+
switch (level) {
|
|
627
|
+
case 'low': return colors.riskLow;
|
|
628
|
+
case 'medium': return colors.riskMedium;
|
|
629
|
+
case 'high': return colors.riskHigh;
|
|
630
|
+
case 'critical': return colors.riskCritical;
|
|
631
|
+
default: return colors.highlight;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
displayAnalysisResults(result, type) {
|
|
636
|
+
console.log(colors.successMessage(`\n✅ ${type.charAt(0).toUpperCase() + type.slice(1)} Analysis Complete`));
|
|
637
|
+
console.log(colors.separator());
|
|
638
|
+
|
|
639
|
+
if (result.summary) {
|
|
640
|
+
console.log(colors.sectionHeader('📋 Summary'));
|
|
641
|
+
console.log(result.summary);
|
|
642
|
+
console.log('');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if (result.analysis) {
|
|
646
|
+
console.log(colors.sectionHeader('🔍 Analysis Details'));
|
|
647
|
+
if (typeof result.analysis === 'object') {
|
|
648
|
+
Object.entries(result.analysis).forEach(([key, value]) => {
|
|
649
|
+
if (typeof value === 'object') {
|
|
650
|
+
console.log(`${key}: ${JSON.stringify(value, null, 2)}`);
|
|
651
|
+
} else {
|
|
652
|
+
console.log(`${key}: ${colors.highlight(value)}`);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
} else {
|
|
656
|
+
console.log(result.analysis);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
this.displayMetrics();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
displayMetrics() {
|
|
664
|
+
const duration = Date.now() - this.metrics.startTime;
|
|
665
|
+
|
|
666
|
+
const metricLines = [
|
|
667
|
+
`${colors.label('Duration')}: ${colors.number(this.formatDuration(duration))}`,
|
|
668
|
+
`${colors.label('Commits processed')}: ${colors.number(this.metrics.commitsProcessed)}`,
|
|
669
|
+
`${colors.label('API calls')}: ${colors.number(this.metrics.apiCalls)}`,
|
|
670
|
+
`${colors.label('Total tokens')}: ${colors.number(this.metrics.totalTokens.toLocaleString())}`
|
|
671
|
+
];
|
|
672
|
+
|
|
673
|
+
if (this.metrics.ruleBasedFallbacks > 0) {
|
|
674
|
+
metricLines.push('');
|
|
675
|
+
metricLines.push(colors.warning(`⚠️ Rule-based fallbacks: ${this.metrics.ruleBasedFallbacks}`));
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
if (this.metrics.errors > 0) {
|
|
679
|
+
metricLines.push('');
|
|
680
|
+
metricLines.push(colors.error(`❌ Errors: ${this.metrics.errors}`));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
console.log(colors.box('📈 Performance Metrics', metricLines.join('\n')));
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
formatDuration(ms) {
|
|
687
|
+
if (ms < 1000) return `${ms}ms`;
|
|
688
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
689
|
+
return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Configuration methods
|
|
693
|
+
setAnalysisMode(mode) {
|
|
694
|
+
this.analysisMode = mode;
|
|
695
|
+
if (this.aiAnalysisService) {
|
|
696
|
+
this.aiAnalysisService.analysisMode = mode;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
setModelOverride(model) {
|
|
701
|
+
if (this.aiAnalysisService) {
|
|
702
|
+
this.aiAnalysisService.setModelOverride(model);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Metrics methods
|
|
707
|
+
getMetrics() {
|
|
708
|
+
return {
|
|
709
|
+
...this.metrics,
|
|
710
|
+
aiMetrics: this.aiAnalysisService?.getMetrics() || {}
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
resetMetrics() {
|
|
715
|
+
this.metrics = {
|
|
716
|
+
startTime: Date.now(),
|
|
717
|
+
commitsProcessed: 0,
|
|
718
|
+
apiCalls: 0,
|
|
719
|
+
errors: 0,
|
|
720
|
+
batchesProcessed: 0,
|
|
721
|
+
totalTokens: 0,
|
|
722
|
+
ruleBasedFallbacks: 0,
|
|
723
|
+
cacheHits: 0
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
if (this.aiAnalysisService) {
|
|
727
|
+
this.aiAnalysisService.resetMetrics();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|