@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,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
|
+
}
|