@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,445 @@
1
+ import { analyzeSemanticChanges, analyzeFunctionalImpact, generateAnalysisSummary, performSemanticAnalysis, assessOverallComplexity, assessRisk, assessBusinessRelevance, categorizeFile, detectLanguage, assessFileImportance, assessChangeComplexity, getWorkingDirectoryChanges } from '../../shared/utils/utils.js';
2
+ import colors from '../../shared/constants/colors.js';
3
+
4
+ export class AnalysisEngine {
5
+ constructor(gitService, aiAnalysisService, gitManager = null) {
6
+ this.gitService = gitService;
7
+ this.aiAnalysisService = aiAnalysisService;
8
+ this.gitManager = gitManager;
9
+ this.gitRepoAnalyzer = null;
10
+ }
11
+
12
+ async _ensureGitAnalyzer() {
13
+ if (!this.gitRepoAnalyzer && this.gitService?.gitManager) {
14
+ const { GitRepositoryAnalyzer } = await import('../git/git-repository.analyzer.js');
15
+ this.gitRepoAnalyzer = new GitRepositoryAnalyzer(this.gitService.gitManager, this.aiAnalysisService);
16
+ }
17
+ return this.gitRepoAnalyzer;
18
+ }
19
+
20
+ async analyze(type, config = {}) {
21
+ switch (type) {
22
+ case 'changes':
23
+ return this.analyzeCurrentChanges(config);
24
+ case 'commits':
25
+ return this.analyzeRecentCommits(config.limit || 10, config);
26
+ case 'branches':
27
+ return this.analyzeBranches(config);
28
+ case 'comprehensive':
29
+ return this.analyzeComprehensive(config);
30
+ case 'health':
31
+ return this.assessRepositoryHealth(config);
32
+ default:
33
+ throw new Error(`Unknown analysis type: ${type}`);
34
+ }
35
+ }
36
+
37
+ async analyzeCurrentChanges(config = {}) {
38
+ let enhancedChanges = [];
39
+
40
+ try {
41
+ const changes = getWorkingDirectoryChanges();
42
+
43
+ if (changes.length === 0) {
44
+ console.log(colors.infoMessage('No changes detected in working directory.'));
45
+ return { changes: [], analysis: null };
46
+ }
47
+
48
+ console.log(colors.processingMessage(`Analyzing ${changes.length} working directory changes...`));
49
+
50
+ // Enhanced file analysis with actual diff content
51
+ enhancedChanges = [];
52
+ for (let i = 0; i < changes.length; i++) {
53
+ const change = changes[i];
54
+ try {
55
+ // Show progress every 5 files or for last file to avoid spam
56
+ if (i % 5 === 0 || i === changes.length - 1) {
57
+ process.stdout.write(`\r Progress: ${i + 1}/${changes.length} files analyzed`);
58
+ }
59
+
60
+ // Use git service to get detailed diff analysis
61
+ let detailedAnalysis = null;
62
+ if (this.gitService && this.gitService.analyzeWorkingDirectoryFileChange) {
63
+ detailedAnalysis = await this.gitService.analyzeWorkingDirectoryFileChange(change.status, change.path);
64
+ }
65
+
66
+ if (detailedAnalysis) {
67
+ // Use the rich analysis from git service
68
+ enhancedChanges.push({
69
+ ...change,
70
+ ...detailedAnalysis,
71
+ // Preserve original fields for compatibility
72
+ filePath: detailedAnalysis.filePath || change.path,
73
+ path: change.path
74
+ });
75
+ } else {
76
+ // Fallback to basic analysis
77
+ enhancedChanges.push({
78
+ ...change,
79
+ category: categorizeFile(change.path),
80
+ language: detectLanguage(change.path),
81
+ importance: assessFileImportance(change.path, change.status),
82
+ complexity: assessChangeComplexity(change.diff || ''),
83
+ diff: change.diff || '',
84
+ semanticChanges: { changeType: 'unknown', patterns: [], frameworks: [] },
85
+ functionalImpact: { scope: 'local', severity: 'low' }
86
+ });
87
+ }
88
+ } catch (error) {
89
+ console.error(`Error processing change ${i}:`, change, error.message);
90
+ enhancedChanges.push({
91
+ ...change,
92
+ category: 'other',
93
+ language: 'Unknown',
94
+ importance: 'medium',
95
+ complexity: { score: 1 },
96
+ diff: 'Analysis failed',
97
+ semanticChanges: { changeType: 'unknown', patterns: [], frameworks: [] },
98
+ functionalImpact: { scope: 'local', severity: 'low' }
99
+ });
100
+ }
101
+ }
102
+
103
+ // Clear the progress line
104
+ process.stdout.write('\n');
105
+
106
+ // AI analysis if available
107
+ let aiAnalysis = null;
108
+ if (this.aiAnalysisService.hasAI) {
109
+ try {
110
+ aiAnalysis = await this.aiAnalysisService.analyzeChanges(enhancedChanges, 'working-directory');
111
+ } catch (error) {
112
+ console.error('Error in AI analysis:', error.message);
113
+ aiAnalysis = null;
114
+ }
115
+ }
116
+
117
+ // Rule-based analysis
118
+ let ruleBasedAnalysis = null;
119
+ try {
120
+ ruleBasedAnalysis = this.analyzeChangesRuleBased(enhancedChanges);
121
+ } catch (error) {
122
+ console.error('Error in rule-based analysis:', error.message);
123
+ ruleBasedAnalysis = {
124
+ summary: 'Analysis failed',
125
+ category: 'other',
126
+ impact: 'medium',
127
+ userFacing: false
128
+ };
129
+ }
130
+
131
+ // Combine analyses
132
+ let finalAnalysis = null;
133
+ try {
134
+ finalAnalysis = this.combineAnalyses(aiAnalysis, ruleBasedAnalysis);
135
+ } catch (error) {
136
+ console.error('Error combining analyses:', error.message);
137
+ finalAnalysis = {
138
+ summary: `${enhancedChanges.length} working directory changes detected`,
139
+ category: 'working-directory',
140
+ impact: 'medium',
141
+ userFacing: false
142
+ };
143
+ }
144
+
145
+ return {
146
+ changes: enhancedChanges,
147
+ analysis: finalAnalysis,
148
+ summary: generateAnalysisSummary(enhancedChanges, finalAnalysis)
149
+ };
150
+
151
+ } catch (error) {
152
+ console.error(colors.errorMessage('Error analyzing current changes:'), error.message);
153
+ // Return enhanced changes if they were created before the error
154
+ return {
155
+ changes: enhancedChanges,
156
+ analysis: enhancedChanges.length > 0 ? {
157
+ summary: `${enhancedChanges.length} working directory changes detected (analysis failed)`,
158
+ category: 'working-directory',
159
+ impact: 'medium',
160
+ userFacing: false
161
+ } : null,
162
+ error: error.message
163
+ };
164
+ }
165
+ }
166
+
167
+ async analyzeRecentCommits(limit = 10, config = {}) {
168
+ try {
169
+ console.log(colors.processingMessage(`Analyzing recent ${limit} commits...`));
170
+
171
+ const commits = await this.gitService.getCommitsSince(null);
172
+ const recentCommits = commits.slice(0, limit);
173
+
174
+ if (recentCommits.length === 0) {
175
+ console.log(colors.infoMessage('No recent commits found.'));
176
+ return { commits: [], analysis: null };
177
+ }
178
+
179
+ // Analyze each commit
180
+ const analyzedCommits = [];
181
+ for (const commit of recentCommits) {
182
+ const analysis = await this.gitService.getCommitAnalysis(commit.hash);
183
+ if (analysis) {
184
+ // Enhanced analysis
185
+ const enhancedAnalysis = await this.enhanceCommitAnalysis(analysis);
186
+ analyzedCommits.push(enhancedAnalysis);
187
+ }
188
+ }
189
+
190
+ // Generate overall analysis
191
+ const overallAnalysis = this.generateOverallCommitAnalysis(analyzedCommits);
192
+
193
+ return {
194
+ commits: analyzedCommits,
195
+ analysis: overallAnalysis,
196
+ summary: this.generateCommitsSummary(analyzedCommits, overallAnalysis)
197
+ };
198
+
199
+ } catch (error) {
200
+ console.error(colors.errorMessage('Error analyzing recent commits:'), error.message);
201
+ return { commits: [], analysis: null, error: error.message };
202
+ }
203
+ }
204
+
205
+ async enhanceCommitAnalysis(commitAnalysis) {
206
+ try {
207
+ // Add complexity assessment
208
+ const complexity = assessOverallComplexity(
209
+ commitAnalysis.files.map(f => f.diff).join('\n'),
210
+ commitAnalysis.files.length
211
+ );
212
+
213
+ // Add risk assessment
214
+ const risk = assessRisk(
215
+ commitAnalysis.files.map(f => f.diff).join('\n'),
216
+ commitAnalysis.files.length,
217
+ commitAnalysis.subject
218
+ );
219
+
220
+ // Add business relevance
221
+ const businessRelevance = assessBusinessRelevance(
222
+ commitAnalysis.subject,
223
+ commitAnalysis.files.map(f => f.filePath)
224
+ );
225
+
226
+ // Enhanced semantic analysis
227
+ const semanticAnalysis = performSemanticAnalysis(
228
+ commitAnalysis.files,
229
+ commitAnalysis.subject
230
+ );
231
+
232
+ return {
233
+ ...commitAnalysis,
234
+ complexity,
235
+ risk,
236
+ businessRelevance,
237
+ semanticAnalysis: {
238
+ ...commitAnalysis.semanticAnalysis,
239
+ ...semanticAnalysis
240
+ }
241
+ };
242
+
243
+ } catch (error) {
244
+ console.warn(colors.warningMessage(`Failed to enhance analysis for commit ${commitAnalysis.hash}:`, error.message));
245
+ return commitAnalysis;
246
+ }
247
+ }
248
+
249
+ analyzeChangesRuleBased(changes) {
250
+ const categories = this.categorizeChanges(changes);
251
+ const complexity = this.assessOverallChangeComplexity(changes);
252
+ const risk = this.assessOverallChangeRisk(changes);
253
+
254
+ return {
255
+ categories,
256
+ complexity,
257
+ risk,
258
+ totalFiles: changes.length,
259
+ primaryCategory: Object.keys(categories)[0] || 'other',
260
+ impact: this.assessChangeImpact(changes),
261
+ userFacing: this.hasUserFacingChanges(changes)
262
+ };
263
+ }
264
+
265
+ categorizeChanges(changes) {
266
+ const categories = {};
267
+ changes.forEach(change => {
268
+ const category = change.category || 'other';
269
+ if (!categories[category]) categories[category] = [];
270
+ categories[category].push(change);
271
+ });
272
+ return categories;
273
+ }
274
+
275
+ assessOverallChangeComplexity(changes) {
276
+ const totalComplexity = changes.reduce((sum, change) => {
277
+ return sum + (change.complexity?.score || 0);
278
+ }, 0);
279
+
280
+ const avgComplexity = totalComplexity / changes.length;
281
+
282
+ if (avgComplexity > 0.7) return 'high';
283
+ if (avgComplexity > 0.4) return 'medium';
284
+ return 'low';
285
+ }
286
+
287
+ assessOverallChangeRisk(changes) {
288
+ const riskFactors = changes.filter(change =>
289
+ change.importance === 'critical' ||
290
+ change.category === 'core' ||
291
+ change.status === 'D' // Deletions are risky
292
+ );
293
+
294
+ if (riskFactors.length > changes.length * 0.3) return 'high';
295
+ if (riskFactors.length > 0) return 'medium';
296
+ return 'low';
297
+ }
298
+
299
+ assessChangeImpact(changes) {
300
+ if (changes.length > 20) return 'high';
301
+ if (changes.length > 5) return 'medium';
302
+ return 'low';
303
+ }
304
+
305
+ hasUserFacingChanges(changes) {
306
+ return changes.some(change =>
307
+ change.category === 'ui' ||
308
+ change.category === 'frontend' ||
309
+ (change.path && change.path.includes('/component/')) ||
310
+ (change.path && change.path.includes('/page/')) ||
311
+ (change.path && change.path.includes('/ui/'))
312
+ );
313
+ }
314
+
315
+ generateOverallCommitAnalysis(commits) {
316
+ const types = {};
317
+ let totalFiles = 0;
318
+ let totalLines = 0;
319
+ let breakingChanges = 0;
320
+ let highRiskCommits = 0;
321
+
322
+ commits.forEach(commit => {
323
+ const type = commit.categories?.[0] || 'other';
324
+ types[type] = (types[type] || 0) + 1;
325
+
326
+ totalFiles += commit.files?.length || 0;
327
+ totalLines += (commit.diffStats?.insertions || 0) + (commit.diffStats?.deletions || 0);
328
+
329
+ if (commit.breakingChanges?.length > 0) breakingChanges++;
330
+ if (commit.risk === 'high') highRiskCommits++;
331
+ });
332
+
333
+ return {
334
+ totalCommits: commits.length,
335
+ commitTypes: types,
336
+ totalFiles,
337
+ totalLines,
338
+ breakingChanges,
339
+ highRiskCommits,
340
+ riskLevel: this.calculateOverallRisk(commits),
341
+ complexity: this.calculateOverallComplexity(commits),
342
+ trends: this.analyzeTrends(commits)
343
+ };
344
+ }
345
+
346
+ calculateOverallRisk(commits) {
347
+ const highRisk = commits.filter(c => c.risk === 'high').length;
348
+ const ratio = highRisk / commits.length;
349
+
350
+ if (ratio > 0.3) return 'high';
351
+ if (ratio > 0.1) return 'medium';
352
+ return 'low';
353
+ }
354
+
355
+ calculateOverallComplexity(commits) {
356
+ const avgFiles = commits.reduce((sum, c) => sum + (c.files?.length || 0), 0) / commits.length;
357
+ const avgLines = commits.reduce((sum, c) => sum + ((c.diffStats?.insertions || 0) + (c.diffStats?.deletions || 0)), 0) / commits.length;
358
+
359
+ if (avgFiles > 15 || avgLines > 200) return 'high';
360
+ if (avgFiles > 5 || avgLines > 50) return 'medium';
361
+ return 'low';
362
+ }
363
+
364
+ analyzeTrends(commits) {
365
+ // Simple trend analysis based on recent patterns
366
+ const recentCommits = commits.slice(0, Math.min(5, commits.length));
367
+ const categories = recentCommits.map(c => c.categories?.[0] || 'other');
368
+
369
+ const categoryCount = {};
370
+ categories.forEach(cat => {
371
+ categoryCount[cat] = (categoryCount[cat] || 0) + 1;
372
+ });
373
+
374
+ const dominantCategory = Object.entries(categoryCount)
375
+ .sort(([,a], [,b]) => b - a)[0];
376
+
377
+ return {
378
+ dominantCategory: dominantCategory?.[0] || 'mixed',
379
+ frequency: dominantCategory?.[1] || 0,
380
+ pattern: this.identifyPattern(categories)
381
+ };
382
+ }
383
+
384
+ identifyPattern(categories) {
385
+ if (categories.every(cat => cat === categories[0])) return 'focused';
386
+ if (new Set(categories).size === categories.length) return 'diverse';
387
+ return 'mixed';
388
+ }
389
+
390
+ combineAnalyses(aiAnalysis, ruleBasedAnalysis) {
391
+ if (!aiAnalysis) return ruleBasedAnalysis;
392
+
393
+ return {
394
+ ...ruleBasedAnalysis,
395
+ aiInsights: aiAnalysis,
396
+ confidence: aiAnalysis ? 'high' : 'medium',
397
+ source: aiAnalysis ? 'ai+rules' : 'rules'
398
+ };
399
+ }
400
+
401
+ generateCommitsSummary(commits, analysis) {
402
+ const { totalCommits, commitTypes, riskLevel, complexity } = analysis;
403
+
404
+ let summary = `Analyzed ${totalCommits} recent commits. `;
405
+
406
+ if (Object.keys(commitTypes).length > 0) {
407
+ const dominantType = Object.entries(commitTypes)
408
+ .sort(([,a], [,b]) => b - a)[0];
409
+ summary += `Primary activity: ${dominantType[1]} ${dominantType[0]} commits. `;
410
+ }
411
+
412
+ summary += `Risk level: ${riskLevel}. Complexity: ${complexity}.`;
413
+
414
+ if (analysis.breakingChanges > 0) {
415
+ summary += ` ⚠️ ${analysis.breakingChanges} commits contain breaking changes.`;
416
+ }
417
+
418
+ return summary;
419
+ }
420
+
421
+ // Delegate to GitRepositoryAnalyzer for specialized analysis
422
+ async analyzeBranches(config) {
423
+ const analyzer = await this._ensureGitAnalyzer();
424
+ if (analyzer) {
425
+ return await analyzer.analyzeBranches(config?.format || 'markdown');
426
+ }
427
+ return { branches: [], analysis: 'Git analyzer not available' };
428
+ }
429
+
430
+ async analyzeComprehensive(config) {
431
+ const analyzer = await this._ensureGitAnalyzer();
432
+ if (analyzer) {
433
+ return await analyzer.analyzeComprehensive(config?.format || 'markdown');
434
+ }
435
+ return { comprehensive: true, analysis: 'Git analyzer not available' };
436
+ }
437
+
438
+ async assessRepositoryHealth(config) {
439
+ const analyzer = await this._ensureGitAnalyzer();
440
+ if (analyzer) {
441
+ return await analyzer.assessRepositoryHealth(config || {});
442
+ }
443
+ return { health: 'unknown', analysis: 'Git analyzer not available' };
444
+ }
445
+ }