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