@entro314labs/ai-changelog-generator 3.1.1 → 3.2.1
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 +412 -875
- 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 +91 -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 +328 -192
- package/src/domains/changelog/changelog.service.js +1174 -783
- package/src/domains/changelog/workspace-changelog.service.js +487 -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
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
* For specialized error handling, use error classes from './error-classes.js'
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
15
|
+
import { execSync } from 'node:child_process'
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
|
|
18
|
+
import colors from '../constants/colors.js'
|
|
19
|
+
import { DiffProcessor } from './diff-processor.js'
|
|
20
|
+
import JsonUtils from './json-utils.js'
|
|
20
21
|
|
|
21
22
|
// ========================================
|
|
22
23
|
// ERROR HANDLING UTILITIES
|
|
@@ -27,12 +28,12 @@ import { GitError, ValidationError, ConfigError } from './error-classes.js';
|
|
|
27
28
|
*/
|
|
28
29
|
export class AIChangelogError extends Error {
|
|
29
30
|
constructor(message, type, context = {}, originalError = null) {
|
|
30
|
-
super(message)
|
|
31
|
-
this.name = this.constructor.name
|
|
32
|
-
this.type = type
|
|
33
|
-
this.context = context
|
|
34
|
-
this.originalError = originalError
|
|
35
|
-
this.timestamp = new Date().toISOString()
|
|
31
|
+
super(message)
|
|
32
|
+
this.name = this.constructor.name
|
|
33
|
+
this.type = type
|
|
34
|
+
this.context = context
|
|
35
|
+
this.originalError = originalError
|
|
36
|
+
this.timestamp = new Date().toISOString()
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
toJSON() {
|
|
@@ -42,8 +43,8 @@ export class AIChangelogError extends Error {
|
|
|
42
43
|
type: this.type,
|
|
43
44
|
context: this.context,
|
|
44
45
|
timestamp: this.timestamp,
|
|
45
|
-
stack: this.stack
|
|
46
|
-
}
|
|
46
|
+
stack: this.stack,
|
|
47
|
+
}
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
@@ -52,9 +53,9 @@ export class AIChangelogError extends Error {
|
|
|
52
53
|
*/
|
|
53
54
|
export class AbstractMethodError extends AIChangelogError {
|
|
54
55
|
constructor(message, className, methodName, context = {}) {
|
|
55
|
-
super(message, 'abstract', { className, methodName, ...context })
|
|
56
|
-
this.className = className
|
|
57
|
-
this.methodName = methodName
|
|
56
|
+
super(message, 'abstract', { className, methodName, ...context })
|
|
57
|
+
this.className = className
|
|
58
|
+
this.methodName = methodName
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -63,8 +64,8 @@ export class AbstractMethodError extends AIChangelogError {
|
|
|
63
64
|
*/
|
|
64
65
|
export class ProviderError extends AIChangelogError {
|
|
65
66
|
constructor(message, providerName, context = {}, originalError = null) {
|
|
66
|
-
super(message, 'provider', { providerName, ...context }, originalError)
|
|
67
|
-
this.providerName = providerName
|
|
67
|
+
super(message, 'provider', { providerName, ...context }, originalError)
|
|
68
|
+
this.providerName = providerName
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -77,22 +78,22 @@ export class ProviderError extends AIChangelogError {
|
|
|
77
78
|
*/
|
|
78
79
|
export function convertSetsToArrays(obj) {
|
|
79
80
|
if (obj === null || typeof obj !== 'object') {
|
|
80
|
-
return obj
|
|
81
|
+
return obj
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
if (obj instanceof Set) {
|
|
84
|
-
return Array.from(obj)
|
|
85
|
+
return Array.from(obj)
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
if (Array.isArray(obj)) {
|
|
88
|
-
return obj.map(convertSetsToArrays)
|
|
89
|
+
return obj.map(convertSetsToArrays)
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
const result = {}
|
|
92
|
+
const result = {}
|
|
92
93
|
for (const [key, value] of Object.entries(obj)) {
|
|
93
|
-
result[key] = convertSetsToArrays(value)
|
|
94
|
+
result[key] = convertSetsToArrays(value)
|
|
94
95
|
}
|
|
95
|
-
return result
|
|
96
|
+
return result
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
@@ -101,18 +102,20 @@ export function convertSetsToArrays(obj) {
|
|
|
101
102
|
*/
|
|
102
103
|
export function extractCommitScope(message) {
|
|
103
104
|
// Enhanced regex to match conventional commits format: type(scope)!: description
|
|
104
|
-
const conventionalMatch = message.match(
|
|
105
|
+
const conventionalMatch = message.match(
|
|
106
|
+
/^(?<type>\w+)(?:\((?<scope>[^()]+)\))?(?<breaking>!)?:\s*(?<description>.+)/
|
|
107
|
+
)
|
|
105
108
|
|
|
106
109
|
if (conventionalMatch) {
|
|
107
|
-
const { type, scope, breaking, description } = conventionalMatch.groups
|
|
110
|
+
const { type, scope, breaking, description } = conventionalMatch.groups
|
|
108
111
|
|
|
109
112
|
return {
|
|
110
|
-
type
|
|
113
|
+
type,
|
|
111
114
|
scope: scope || null,
|
|
112
115
|
description: description.trim(),
|
|
113
116
|
breaking: !!breaking,
|
|
114
|
-
isConventional: true
|
|
115
|
-
}
|
|
117
|
+
isConventional: true,
|
|
118
|
+
}
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
// Fallback for non-conventional commits
|
|
@@ -121,49 +124,53 @@ export function extractCommitScope(message) {
|
|
|
121
124
|
scope: null,
|
|
122
125
|
description: message.trim(),
|
|
123
126
|
breaking: false,
|
|
124
|
-
isConventional: false
|
|
125
|
-
}
|
|
127
|
+
isConventional: false,
|
|
128
|
+
}
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
/**
|
|
129
132
|
* Parse conventional commit message with full body analysis
|
|
130
133
|
*/
|
|
131
134
|
export function parseConventionalCommit(subject, body = '') {
|
|
132
|
-
const parsed = extractCommitScope(subject)
|
|
133
|
-
const fullMessage = `${subject}\n\n${body}`.trim()
|
|
135
|
+
const parsed = extractCommitScope(subject)
|
|
136
|
+
const fullMessage = `${subject}\n\n${body}`.trim()
|
|
134
137
|
|
|
135
138
|
// Enhanced breaking change detection from body
|
|
136
|
-
const breakingChanges = []
|
|
139
|
+
const breakingChanges = []
|
|
137
140
|
|
|
138
141
|
// Check for BREAKING CHANGE: footer
|
|
139
|
-
const breakingMatch = fullMessage.match(/BREAKING CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
|
|
142
|
+
const breakingMatch = fullMessage.match(/BREAKING CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
|
|
140
143
|
if (breakingMatch) {
|
|
141
|
-
breakingChanges.push(breakingMatch[1].trim())
|
|
142
|
-
parsed.breaking = true
|
|
144
|
+
breakingChanges.push(breakingMatch[1].trim())
|
|
145
|
+
parsed.breaking = true
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
// Check for BREAKING-CHANGE: footer (alternative format)
|
|
146
|
-
const breakingAltMatch = fullMessage.match(/BREAKING-CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
|
|
149
|
+
const breakingAltMatch = fullMessage.match(/BREAKING-CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
|
|
147
150
|
if (breakingAltMatch) {
|
|
148
|
-
breakingChanges.push(breakingAltMatch[1].trim())
|
|
149
|
-
parsed.breaking = true
|
|
151
|
+
breakingChanges.push(breakingAltMatch[1].trim())
|
|
152
|
+
parsed.breaking = true
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
// Extract issue references (GitHub/GitLab format)
|
|
153
|
-
const issueRefs = []
|
|
154
|
-
const issueMatches = fullMessage.match(/#[0-9]+/g)
|
|
156
|
+
const issueRefs = []
|
|
157
|
+
const issueMatches = fullMessage.match(/#[0-9]+/g)
|
|
155
158
|
if (issueMatches) {
|
|
156
|
-
issueRefs.push(...issueMatches)
|
|
159
|
+
issueRefs.push(...issueMatches)
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
// Extract closes references
|
|
160
|
-
const closesMatches = fullMessage.match(
|
|
161
|
-
|
|
163
|
+
const closesMatches = fullMessage.match(
|
|
164
|
+
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?\s*#?([0-9]+)/gi
|
|
165
|
+
)
|
|
166
|
+
const closesRefs = []
|
|
162
167
|
if (closesMatches) {
|
|
163
|
-
closesMatches.forEach(match => {
|
|
164
|
-
const num = match.match(/([0-9]+)/)
|
|
165
|
-
if (num)
|
|
166
|
-
|
|
168
|
+
closesMatches.forEach((match) => {
|
|
169
|
+
const num = match.match(/([0-9]+)/)
|
|
170
|
+
if (num) {
|
|
171
|
+
closesRefs.push(`#${num[1]}`)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
return {
|
|
@@ -172,8 +179,8 @@ export function parseConventionalCommit(subject, body = '') {
|
|
|
172
179
|
issueReferences: [...new Set(issueRefs)],
|
|
173
180
|
closesReferences: [...new Set(closesRefs)],
|
|
174
181
|
body: body.trim(),
|
|
175
|
-
revert: subject.toLowerCase().startsWith('revert')
|
|
176
|
-
}
|
|
182
|
+
revert: subject.toLowerCase().startsWith('revert'),
|
|
183
|
+
}
|
|
177
184
|
}
|
|
178
185
|
|
|
179
186
|
/**
|
|
@@ -181,12 +188,12 @@ export function parseConventionalCommit(subject, body = '') {
|
|
|
181
188
|
*/
|
|
182
189
|
export function markdownCommitLink(commitHash, commitUrl, shortHash = true) {
|
|
183
190
|
if (!commitUrl) {
|
|
184
|
-
return shortHash ? commitHash.substring(0, 7) : commitHash
|
|
191
|
+
return shortHash ? commitHash.substring(0, 7) : commitHash
|
|
185
192
|
}
|
|
186
193
|
|
|
187
|
-
const url = commitUrl.replace('%commit%', commitHash)
|
|
188
|
-
const displayHash = shortHash ? commitHash.substring(0, 7) : commitHash
|
|
189
|
-
return `[${displayHash}](${url})
|
|
194
|
+
const url = commitUrl.replace('%commit%', commitHash)
|
|
195
|
+
const displayHash = shortHash ? commitHash.substring(0, 7) : commitHash
|
|
196
|
+
return `[${displayHash}](${url})`
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
/**
|
|
@@ -194,13 +201,11 @@ export function markdownCommitLink(commitHash, commitUrl, shortHash = true) {
|
|
|
194
201
|
*/
|
|
195
202
|
export function markdownCommitRangeLink(fromCommit, toCommit, commitRangeUrl) {
|
|
196
203
|
if (!commitRangeUrl) {
|
|
197
|
-
return `${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}
|
|
204
|
+
return `${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}`
|
|
198
205
|
}
|
|
199
206
|
|
|
200
|
-
const url = commitRangeUrl
|
|
201
|
-
|
|
202
|
-
.replace('%to%', toCommit);
|
|
203
|
-
return `[${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}](${url})`;
|
|
207
|
+
const url = commitRangeUrl.replace('%from%', fromCommit).replace('%to%', toCommit)
|
|
208
|
+
return `[${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}](${url})`
|
|
204
209
|
}
|
|
205
210
|
|
|
206
211
|
/**
|
|
@@ -208,40 +213,42 @@ export function markdownCommitRangeLink(fromCommit, toCommit, commitRangeUrl) {
|
|
|
208
213
|
*/
|
|
209
214
|
export function markdownIssueLink(issueId, issueUrl) {
|
|
210
215
|
if (!issueUrl) {
|
|
211
|
-
return issueId
|
|
216
|
+
return issueId
|
|
212
217
|
}
|
|
213
218
|
|
|
214
|
-
const cleanIssueId = issueId.replace('#', '')
|
|
215
|
-
const url = issueUrl.replace('%issue%', cleanIssueId)
|
|
216
|
-
return `[${issueId}](${url})
|
|
219
|
+
const cleanIssueId = issueId.replace('#', '')
|
|
220
|
+
const url = issueUrl.replace('%issue%', cleanIssueId)
|
|
221
|
+
return `[${issueId}](${url})`
|
|
217
222
|
}
|
|
218
223
|
|
|
219
224
|
/**
|
|
220
225
|
* Process issue references in text and convert to markdown links
|
|
221
226
|
*/
|
|
222
227
|
export function processIssueReferences(text, issueUrl, issueRegex) {
|
|
223
|
-
if (!issueUrl
|
|
228
|
+
if (!(issueUrl && text)) {
|
|
229
|
+
return text
|
|
230
|
+
}
|
|
224
231
|
|
|
225
232
|
return text.replace(issueRegex, (match) => {
|
|
226
|
-
return markdownIssueLink(match, issueUrl)
|
|
227
|
-
})
|
|
233
|
+
return markdownIssueLink(match, issueUrl)
|
|
234
|
+
})
|
|
228
235
|
}
|
|
229
236
|
|
|
230
237
|
/**
|
|
231
238
|
* Deep merge objects
|
|
232
239
|
*/
|
|
233
240
|
export function deepMerge(target, source) {
|
|
234
|
-
const result = { ...target }
|
|
241
|
+
const result = { ...target }
|
|
235
242
|
|
|
236
243
|
for (const key in source) {
|
|
237
244
|
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
238
|
-
result[key] = deepMerge(result[key] || {}, source[key])
|
|
245
|
+
result[key] = deepMerge(result[key] || {}, source[key])
|
|
239
246
|
} else {
|
|
240
|
-
result[key] = source[key]
|
|
247
|
+
result[key] = source[key]
|
|
241
248
|
}
|
|
242
249
|
}
|
|
243
250
|
|
|
244
|
-
return result
|
|
251
|
+
return result
|
|
245
252
|
}
|
|
246
253
|
|
|
247
254
|
// ========================================
|
|
@@ -252,18 +259,24 @@ export function deepMerge(target, source) {
|
|
|
252
259
|
* Format duration in human-readable format
|
|
253
260
|
*/
|
|
254
261
|
export function formatDuration(ms) {
|
|
255
|
-
if (ms < 1000)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
262
|
+
if (ms < 1000) {
|
|
263
|
+
return `${ms}ms`
|
|
264
|
+
}
|
|
265
|
+
if (ms < 60000) {
|
|
266
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
267
|
+
}
|
|
268
|
+
if (ms < 3600000) {
|
|
269
|
+
return `${(ms / 60000).toFixed(1)}m`
|
|
270
|
+
}
|
|
271
|
+
return `${(ms / 3600000).toFixed(1)}h`
|
|
259
272
|
}
|
|
260
273
|
|
|
261
274
|
/**
|
|
262
275
|
* Interactive configuration prompt (simplified)
|
|
263
276
|
*/
|
|
264
277
|
export function promptForConfig(message = 'Configure settings', defaultValue = '') {
|
|
265
|
-
console.log(colors.infoMessage(message))
|
|
266
|
-
return Promise.resolve(defaultValue)
|
|
278
|
+
console.log(colors.infoMessage(message))
|
|
279
|
+
return Promise.resolve(defaultValue)
|
|
267
280
|
}
|
|
268
281
|
|
|
269
282
|
/**
|
|
@@ -271,32 +284,36 @@ export function promptForConfig(message = 'Configure settings', defaultValue = '
|
|
|
271
284
|
*/
|
|
272
285
|
export function getHealthColor(status) {
|
|
273
286
|
const colorMap = {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
return colorMap[status] || colors.infoMessage
|
|
287
|
+
excellent: colors.successMessage,
|
|
288
|
+
good: colors.successMessage,
|
|
289
|
+
fair: colors.warningMessage,
|
|
290
|
+
poor: colors.errorMessage,
|
|
291
|
+
critical: colors.errorMessage,
|
|
292
|
+
}
|
|
293
|
+
return colorMap[status] || colors.infoMessage
|
|
281
294
|
}
|
|
282
295
|
|
|
283
296
|
/**
|
|
284
297
|
* Format file size
|
|
285
298
|
*/
|
|
286
299
|
export function formatFileSize(bytes) {
|
|
287
|
-
if (bytes === 0)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
300
|
+
if (bytes === 0) {
|
|
301
|
+
return '0 B'
|
|
302
|
+
}
|
|
303
|
+
const k = 1024
|
|
304
|
+
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
305
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
306
|
+
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
|
292
307
|
}
|
|
293
308
|
|
|
294
309
|
/**
|
|
295
310
|
* Format percentage
|
|
296
311
|
*/
|
|
297
312
|
export function formatPercentage(value, total) {
|
|
298
|
-
if (total === 0)
|
|
299
|
-
|
|
313
|
+
if (total === 0) {
|
|
314
|
+
return '0%'
|
|
315
|
+
}
|
|
316
|
+
return `${((value / total) * 100).toFixed(1)}%`
|
|
300
317
|
}
|
|
301
318
|
|
|
302
319
|
// ========================================
|
|
@@ -308,63 +325,88 @@ export function formatPercentage(value, total) {
|
|
|
308
325
|
*/
|
|
309
326
|
export function categorizeFile(filePath) {
|
|
310
327
|
if (!filePath || typeof filePath !== 'string') {
|
|
311
|
-
return 'other'
|
|
328
|
+
return 'other'
|
|
312
329
|
}
|
|
313
|
-
const path = filePath.toLowerCase()
|
|
314
|
-
const ext = path.split('.').pop()
|
|
330
|
+
const path = filePath.toLowerCase()
|
|
331
|
+
const ext = path.split('.').pop()
|
|
315
332
|
|
|
316
333
|
// Configuration files
|
|
317
|
-
if (
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
334
|
+
if (
|
|
335
|
+
path.includes('package.json') ||
|
|
336
|
+
path.includes('yarn.lock') ||
|
|
337
|
+
path.includes('pnpm-lock') ||
|
|
338
|
+
path.includes('.gitignore') ||
|
|
339
|
+
ext === 'toml' ||
|
|
340
|
+
ext === 'yaml' ||
|
|
341
|
+
ext === 'yml' ||
|
|
342
|
+
path.includes('dockerfile') ||
|
|
343
|
+
path.includes('.env')
|
|
344
|
+
) {
|
|
345
|
+
return 'configuration'
|
|
322
346
|
}
|
|
323
347
|
|
|
324
348
|
// Documentation
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
349
|
+
if (
|
|
350
|
+
ext === 'md' ||
|
|
351
|
+
ext === 'txt' ||
|
|
352
|
+
ext === 'rst' ||
|
|
353
|
+
path.includes('readme') ||
|
|
354
|
+
path.includes('changelog') ||
|
|
355
|
+
path.includes('/docs/') ||
|
|
356
|
+
path.includes('/doc/')
|
|
357
|
+
) {
|
|
358
|
+
return 'documentation'
|
|
329
359
|
}
|
|
330
360
|
|
|
331
361
|
// Test files
|
|
332
|
-
if (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
362
|
+
if (
|
|
363
|
+
path.includes('/test/') ||
|
|
364
|
+
path.includes('/tests/') ||
|
|
365
|
+
path.includes('__tests__') ||
|
|
366
|
+
path.includes('.test.') ||
|
|
367
|
+
path.includes('.spec.') ||
|
|
368
|
+
ext === 'test' ||
|
|
369
|
+
ext === 'spec'
|
|
370
|
+
) {
|
|
371
|
+
return 'tests'
|
|
336
372
|
}
|
|
337
373
|
|
|
338
374
|
// Source code
|
|
339
|
-
const sourceExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'go', 'rs', 'php']
|
|
375
|
+
const sourceExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'go', 'rs', 'php']
|
|
340
376
|
if (sourceExts.includes(ext)) {
|
|
341
377
|
if (path.includes('/src/') || path.includes('/lib/')) {
|
|
342
|
-
return 'source'
|
|
378
|
+
return 'source'
|
|
343
379
|
}
|
|
344
|
-
return 'source'
|
|
380
|
+
return 'source'
|
|
345
381
|
}
|
|
346
382
|
|
|
347
383
|
// Frontend/UI
|
|
348
|
-
const frontendExts = ['html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte']
|
|
384
|
+
const frontendExts = ['html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte']
|
|
349
385
|
if (frontendExts.includes(ext)) {
|
|
350
|
-
return 'frontend'
|
|
386
|
+
return 'frontend'
|
|
351
387
|
}
|
|
352
388
|
|
|
353
389
|
// Assets
|
|
354
|
-
const assetExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp']
|
|
390
|
+
const assetExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp']
|
|
355
391
|
if (assetExts.includes(ext)) {
|
|
356
|
-
return 'assets'
|
|
392
|
+
return 'assets'
|
|
357
393
|
}
|
|
358
394
|
|
|
359
395
|
// Build/tooling
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
396
|
+
if (
|
|
397
|
+
path.includes('webpack') ||
|
|
398
|
+
path.includes('rollup') ||
|
|
399
|
+
path.includes('vite') ||
|
|
400
|
+
path.includes('babel') ||
|
|
401
|
+
path.includes('eslint') ||
|
|
402
|
+
path.includes('prettier') ||
|
|
403
|
+
path.includes('/build/') ||
|
|
404
|
+
path.includes('/dist/')
|
|
405
|
+
) {
|
|
406
|
+
return 'build'
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return 'other'
|
|
368
410
|
}
|
|
369
411
|
|
|
370
412
|
/**
|
|
@@ -372,9 +414,9 @@ export function categorizeFile(filePath) {
|
|
|
372
414
|
*/
|
|
373
415
|
export function detectLanguage(filePath) {
|
|
374
416
|
if (!filePath || typeof filePath !== 'string') {
|
|
375
|
-
return 'Unknown'
|
|
417
|
+
return 'Unknown'
|
|
376
418
|
}
|
|
377
|
-
const ext = filePath.split('.').pop()?.toLowerCase()
|
|
419
|
+
const ext = filePath.split('.').pop()?.toLowerCase()
|
|
378
420
|
|
|
379
421
|
const langMap = {
|
|
380
422
|
js: 'JavaScript',
|
|
@@ -405,10 +447,10 @@ export function detectLanguage(filePath) {
|
|
|
405
447
|
yml: 'YAML',
|
|
406
448
|
toml: 'TOML',
|
|
407
449
|
md: 'Markdown',
|
|
408
|
-
sql: 'SQL'
|
|
409
|
-
}
|
|
450
|
+
sql: 'SQL',
|
|
451
|
+
}
|
|
410
452
|
|
|
411
|
-
return langMap[ext] || 'Unknown'
|
|
453
|
+
return langMap[ext] || 'Unknown'
|
|
412
454
|
}
|
|
413
455
|
|
|
414
456
|
/**
|
|
@@ -416,47 +458,56 @@ export function detectLanguage(filePath) {
|
|
|
416
458
|
*/
|
|
417
459
|
export function assessFileImportance(filePath, status) {
|
|
418
460
|
if (!filePath || typeof filePath !== 'string') {
|
|
419
|
-
return 'medium'
|
|
461
|
+
return 'medium'
|
|
420
462
|
}
|
|
421
|
-
const path = filePath.toLowerCase()
|
|
463
|
+
const path = filePath.toLowerCase()
|
|
422
464
|
|
|
423
465
|
// Critical files
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
466
|
+
if (
|
|
467
|
+
path.includes('package.json') ||
|
|
468
|
+
path.includes('pom.xml') ||
|
|
469
|
+
path.includes('cargo.toml') ||
|
|
470
|
+
path.includes('requirements.txt') ||
|
|
471
|
+
path.includes('dockerfile') ||
|
|
472
|
+
path.includes('docker-compose')
|
|
473
|
+
) {
|
|
474
|
+
return 'critical'
|
|
428
475
|
}
|
|
429
476
|
|
|
430
477
|
// Core source files
|
|
431
478
|
if (path.includes('/src/') || path.includes('/lib/')) {
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
|
|
479
|
+
if (
|
|
480
|
+
path.includes('index.') ||
|
|
481
|
+
path.includes('main.') ||
|
|
482
|
+
path.includes('app.') ||
|
|
483
|
+
path.includes('server.')
|
|
484
|
+
) {
|
|
485
|
+
return 'critical'
|
|
435
486
|
}
|
|
436
|
-
return 'high'
|
|
487
|
+
return 'high'
|
|
437
488
|
}
|
|
438
489
|
|
|
439
490
|
// Configuration
|
|
440
491
|
if (categorizeFile(filePath) === 'configuration') {
|
|
441
|
-
return 'high'
|
|
492
|
+
return 'high'
|
|
442
493
|
}
|
|
443
494
|
|
|
444
495
|
// Tests
|
|
445
496
|
if (categorizeFile(filePath) === 'tests') {
|
|
446
|
-
return 'medium'
|
|
497
|
+
return 'medium'
|
|
447
498
|
}
|
|
448
499
|
|
|
449
500
|
// Documentation
|
|
450
501
|
if (categorizeFile(filePath) === 'documentation') {
|
|
451
|
-
return 'low'
|
|
502
|
+
return 'low'
|
|
452
503
|
}
|
|
453
504
|
|
|
454
505
|
// File deletion is always important
|
|
455
506
|
if (status === 'D') {
|
|
456
|
-
return 'high'
|
|
507
|
+
return 'high'
|
|
457
508
|
}
|
|
458
509
|
|
|
459
|
-
return 'medium'
|
|
510
|
+
return 'medium'
|
|
460
511
|
}
|
|
461
512
|
|
|
462
513
|
// ========================================
|
|
@@ -467,18 +518,18 @@ export function assessFileImportance(filePath, status) {
|
|
|
467
518
|
* Assess overall complexity of changes
|
|
468
519
|
*/
|
|
469
520
|
export function assessOverallComplexity(diffContent, fileCount) {
|
|
470
|
-
const lines = diffContent.split('\n')
|
|
471
|
-
const addedLines = lines.filter(line => line.startsWith('+')).length
|
|
472
|
-
const deletedLines = lines.filter(line => line.startsWith('-')).length
|
|
473
|
-
const totalChanges = addedLines + deletedLines
|
|
521
|
+
const lines = diffContent.split('\n')
|
|
522
|
+
const addedLines = lines.filter((line) => line.startsWith('+')).length
|
|
523
|
+
const deletedLines = lines.filter((line) => line.startsWith('-')).length
|
|
524
|
+
const totalChanges = addedLines + deletedLines
|
|
474
525
|
|
|
475
526
|
// Complexity factors
|
|
476
|
-
let complexity = 'low'
|
|
527
|
+
let complexity = 'low'
|
|
477
528
|
|
|
478
529
|
if (fileCount > 20 || totalChanges > 500) {
|
|
479
|
-
complexity = 'high'
|
|
530
|
+
complexity = 'high'
|
|
480
531
|
} else if (fileCount > 5 || totalChanges > 100) {
|
|
481
|
-
complexity = 'medium'
|
|
532
|
+
complexity = 'medium'
|
|
482
533
|
}
|
|
483
534
|
|
|
484
535
|
// Check for complex patterns
|
|
@@ -489,39 +540,39 @@ export function assessOverallComplexity(diffContent, fileCount) {
|
|
|
489
540
|
/try\s*{/g,
|
|
490
541
|
/catch\s*\(/g,
|
|
491
542
|
/interface\s+\w+/g,
|
|
492
|
-
/type\s+\w+/g
|
|
493
|
-
]
|
|
543
|
+
/type\s+\w+/g,
|
|
544
|
+
]
|
|
494
545
|
|
|
495
546
|
const patternMatches = complexPatterns.reduce((count, pattern) => {
|
|
496
|
-
return count + (diffContent.match(pattern) || []).length
|
|
497
|
-
}, 0)
|
|
547
|
+
return count + (diffContent.match(pattern) || []).length
|
|
548
|
+
}, 0)
|
|
498
549
|
|
|
499
550
|
if (patternMatches > 10) {
|
|
500
|
-
complexity = complexity === 'low' ? 'medium' : 'high'
|
|
551
|
+
complexity = complexity === 'low' ? 'medium' : 'high'
|
|
501
552
|
}
|
|
502
553
|
|
|
503
|
-
return complexity
|
|
554
|
+
return complexity
|
|
504
555
|
}
|
|
505
556
|
|
|
506
557
|
/**
|
|
507
558
|
* Assess risk level of changes
|
|
508
559
|
*/
|
|
509
560
|
export function assessRisk(diffContent, fileCount, commitMessage) {
|
|
510
|
-
let risk = 'low'
|
|
561
|
+
let risk = 'low'
|
|
511
562
|
|
|
512
563
|
// High-risk keywords in commit message
|
|
513
|
-
const highRiskKeywords = ['breaking', 'remove', 'delete', 'deprecated', 'migration']
|
|
514
|
-
const mediumRiskKeywords = ['refactor', 'restructure', 'change', 'modify', 'update']
|
|
564
|
+
const highRiskKeywords = ['breaking', 'remove', 'delete', 'deprecated', 'migration']
|
|
565
|
+
const mediumRiskKeywords = ['refactor', 'restructure', 'change', 'modify', 'update']
|
|
515
566
|
|
|
516
567
|
if (!commitMessage || typeof commitMessage !== 'string') {
|
|
517
|
-
return risk
|
|
568
|
+
return risk // Return early with default risk level
|
|
518
569
|
}
|
|
519
|
-
const message = commitMessage.toLowerCase()
|
|
570
|
+
const message = commitMessage.toLowerCase()
|
|
520
571
|
|
|
521
|
-
if (highRiskKeywords.some(keyword => message.includes(keyword))) {
|
|
522
|
-
risk = 'high'
|
|
523
|
-
} else if (mediumRiskKeywords.some(keyword => message.includes(keyword))) {
|
|
524
|
-
risk = 'medium'
|
|
572
|
+
if (highRiskKeywords.some((keyword) => message.includes(keyword))) {
|
|
573
|
+
risk = 'high'
|
|
574
|
+
} else if (mediumRiskKeywords.some((keyword) => message.includes(keyword))) {
|
|
575
|
+
risk = 'medium'
|
|
525
576
|
}
|
|
526
577
|
|
|
527
578
|
// High-risk file patterns
|
|
@@ -534,24 +585,24 @@ export function assessRisk(diffContent, fileCount, commitMessage) {
|
|
|
534
585
|
/database/,
|
|
535
586
|
/migration/,
|
|
536
587
|
/config/,
|
|
537
|
-
/env
|
|
538
|
-
]
|
|
588
|
+
/env/,
|
|
589
|
+
]
|
|
539
590
|
|
|
540
|
-
const diffLines = diffContent.split('\n')
|
|
541
|
-
const hasHighRiskFiles = diffLines.some(line =>
|
|
542
|
-
highRiskPatterns.some(pattern => pattern.test(line))
|
|
543
|
-
)
|
|
591
|
+
const diffLines = diffContent.split('\n')
|
|
592
|
+
const hasHighRiskFiles = diffLines.some((line) =>
|
|
593
|
+
highRiskPatterns.some((pattern) => pattern.test(line))
|
|
594
|
+
)
|
|
544
595
|
|
|
545
596
|
if (hasHighRiskFiles) {
|
|
546
|
-
risk = risk === 'low' ? 'medium' : 'high'
|
|
597
|
+
risk = risk === 'low' ? 'medium' : 'high'
|
|
547
598
|
}
|
|
548
599
|
|
|
549
600
|
// Large changes are risky
|
|
550
601
|
if (fileCount > 15 || diffContent.length > 10000) {
|
|
551
|
-
risk = risk === 'low' ? 'medium' : 'high'
|
|
602
|
+
risk = risk === 'low' ? 'medium' : 'high'
|
|
552
603
|
}
|
|
553
604
|
|
|
554
|
-
return risk
|
|
605
|
+
return risk
|
|
555
606
|
}
|
|
556
607
|
|
|
557
608
|
/**
|
|
@@ -559,13 +610,13 @@ export function assessRisk(diffContent, fileCount, commitMessage) {
|
|
|
559
610
|
*/
|
|
560
611
|
export function isBreakingChange(commitMessage, diffContent) {
|
|
561
612
|
if (!commitMessage || typeof commitMessage !== 'string') {
|
|
562
|
-
return false
|
|
613
|
+
return false
|
|
563
614
|
}
|
|
564
|
-
const message = commitMessage.toLowerCase()
|
|
615
|
+
const message = commitMessage.toLowerCase()
|
|
565
616
|
|
|
566
617
|
// Check commit message for breaking indicators
|
|
567
618
|
if (message.includes('breaking') || message.includes('!:')) {
|
|
568
|
-
return true
|
|
619
|
+
return true
|
|
569
620
|
}
|
|
570
621
|
|
|
571
622
|
// Check diff for breaking patterns
|
|
@@ -573,10 +624,10 @@ export function isBreakingChange(commitMessage, diffContent) {
|
|
|
573
624
|
/function\s+\w+\s*\([^)]*\)\s*{[\s\S]*?}[\s\S]*?-/g, // Function signature changes
|
|
574
625
|
/class\s+\w+[\s\S]*?-/g, // Class changes
|
|
575
626
|
/interface\s+\w+[\s\S]*?-/g, // Interface changes
|
|
576
|
-
/export\s+[\s\S]*?-/g // Export changes
|
|
577
|
-
]
|
|
627
|
+
/export\s+[\s\S]*?-/g, // Export changes
|
|
628
|
+
]
|
|
578
629
|
|
|
579
|
-
return breakingPatterns.some(pattern => pattern.test(diffContent))
|
|
630
|
+
return breakingPatterns.some((pattern) => pattern.test(diffContent))
|
|
580
631
|
}
|
|
581
632
|
|
|
582
633
|
/**
|
|
@@ -584,33 +635,54 @@ export function isBreakingChange(commitMessage, diffContent) {
|
|
|
584
635
|
*/
|
|
585
636
|
export function assessBusinessRelevance(commitMessage, filePaths) {
|
|
586
637
|
if (!commitMessage || typeof commitMessage !== 'string') {
|
|
587
|
-
return 'low'
|
|
638
|
+
return 'low'
|
|
588
639
|
}
|
|
589
|
-
const message = commitMessage.toLowerCase()
|
|
640
|
+
const message = commitMessage.toLowerCase()
|
|
590
641
|
|
|
591
642
|
// Business-relevant keywords
|
|
592
643
|
const businessKeywords = [
|
|
593
|
-
'feature',
|
|
594
|
-
'
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
644
|
+
'feature',
|
|
645
|
+
'user',
|
|
646
|
+
'customer',
|
|
647
|
+
'client',
|
|
648
|
+
'business',
|
|
649
|
+
'revenue',
|
|
650
|
+
'payment',
|
|
651
|
+
'billing',
|
|
652
|
+
'subscription',
|
|
653
|
+
'auth',
|
|
654
|
+
'login',
|
|
655
|
+
'security',
|
|
656
|
+
]
|
|
657
|
+
|
|
658
|
+
const hasBusinessKeywords = businessKeywords.some((keyword) => message.includes(keyword))
|
|
600
659
|
|
|
601
660
|
// Business-relevant file paths
|
|
602
661
|
const businessPaths = [
|
|
603
|
-
'/api/',
|
|
604
|
-
'/
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
662
|
+
'/api/',
|
|
663
|
+
'/service/',
|
|
664
|
+
'/controller/',
|
|
665
|
+
'/model/',
|
|
666
|
+
'/auth/',
|
|
667
|
+
'/payment/',
|
|
668
|
+
'/billing/',
|
|
669
|
+
'/user/',
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
const hasBusinessFiles = filePaths.some(
|
|
673
|
+
(path) =>
|
|
674
|
+
path &&
|
|
675
|
+
typeof path === 'string' &&
|
|
676
|
+
businessPaths.some((businessPath) => path.includes(businessPath))
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
if (hasBusinessKeywords && hasBusinessFiles) {
|
|
680
|
+
return 'high'
|
|
681
|
+
}
|
|
682
|
+
if (hasBusinessKeywords || hasBusinessFiles) {
|
|
683
|
+
return 'medium'
|
|
684
|
+
}
|
|
685
|
+
return 'low'
|
|
614
686
|
}
|
|
615
687
|
|
|
616
688
|
// ========================================
|
|
@@ -622,7 +694,7 @@ export function assessBusinessRelevance(commitMessage, filePaths) {
|
|
|
622
694
|
*/
|
|
623
695
|
export function safeJsonParse(jsonString, fallback = null) {
|
|
624
696
|
// Use the advanced JsonUtils for better error handling
|
|
625
|
-
return JsonUtils.safeParse(jsonString, fallback)
|
|
697
|
+
return JsonUtils.safeParse(jsonString, fallback)
|
|
626
698
|
}
|
|
627
699
|
|
|
628
700
|
/**
|
|
@@ -630,10 +702,10 @@ export function safeJsonParse(jsonString, fallback = null) {
|
|
|
630
702
|
*/
|
|
631
703
|
export function safeJsonStringify(obj, indent = 2) {
|
|
632
704
|
try {
|
|
633
|
-
return JSON.stringify(convertSetsToArrays(obj), null, indent)
|
|
705
|
+
return JSON.stringify(convertSetsToArrays(obj), null, indent)
|
|
634
706
|
} catch (error) {
|
|
635
|
-
console.warn(colors.warningMessage(`JSON stringify error: ${error.message}`))
|
|
636
|
-
return '{}'
|
|
707
|
+
console.warn(colors.warningMessage(`JSON stringify error: ${error.message}`))
|
|
708
|
+
return '{}'
|
|
637
709
|
}
|
|
638
710
|
}
|
|
639
711
|
|
|
@@ -645,57 +717,57 @@ export function safeJsonStringify(obj, indent = 2) {
|
|
|
645
717
|
* Sleep utility for rate limiting
|
|
646
718
|
*/
|
|
647
719
|
export function sleep(ms) {
|
|
648
|
-
return new Promise(resolve => setTimeout(resolve, ms))
|
|
720
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
649
721
|
}
|
|
650
722
|
|
|
651
723
|
/**
|
|
652
724
|
* Retry utility with exponential backoff
|
|
653
725
|
*/
|
|
654
726
|
export async function retry(fn, maxRetries = 3, baseDelay = 1000) {
|
|
655
|
-
let lastError
|
|
727
|
+
let lastError
|
|
656
728
|
|
|
657
729
|
for (let i = 0; i < maxRetries; i++) {
|
|
658
730
|
try {
|
|
659
|
-
return await fn()
|
|
731
|
+
return await fn()
|
|
660
732
|
} catch (error) {
|
|
661
|
-
lastError = error
|
|
733
|
+
lastError = error
|
|
662
734
|
if (i < maxRetries - 1) {
|
|
663
|
-
const delay = baseDelay *
|
|
664
|
-
await sleep(delay)
|
|
735
|
+
const delay = baseDelay * 2 ** i
|
|
736
|
+
await sleep(delay)
|
|
665
737
|
}
|
|
666
738
|
}
|
|
667
739
|
}
|
|
668
740
|
|
|
669
|
-
throw lastError
|
|
741
|
+
throw lastError
|
|
670
742
|
}
|
|
671
743
|
|
|
672
744
|
/**
|
|
673
745
|
* Debounce utility
|
|
674
746
|
*/
|
|
675
747
|
export function debounce(func, wait) {
|
|
676
|
-
let timeout
|
|
748
|
+
let timeout
|
|
677
749
|
return function executedFunction(...args) {
|
|
678
750
|
const later = () => {
|
|
679
|
-
clearTimeout(timeout)
|
|
680
|
-
func(...args)
|
|
681
|
-
}
|
|
682
|
-
clearTimeout(timeout)
|
|
683
|
-
timeout = setTimeout(later, wait)
|
|
684
|
-
}
|
|
751
|
+
clearTimeout(timeout)
|
|
752
|
+
func(...args)
|
|
753
|
+
}
|
|
754
|
+
clearTimeout(timeout)
|
|
755
|
+
timeout = setTimeout(later, wait)
|
|
756
|
+
}
|
|
685
757
|
}
|
|
686
758
|
|
|
687
759
|
/**
|
|
688
760
|
* Throttle utility
|
|
689
761
|
*/
|
|
690
762
|
export function throttle(func, limit) {
|
|
691
|
-
let inThrottle
|
|
763
|
+
let inThrottle
|
|
692
764
|
return function executedFunction(...args) {
|
|
693
765
|
if (!inThrottle) {
|
|
694
|
-
func.apply(this, args)
|
|
695
|
-
inThrottle = true
|
|
696
|
-
setTimeout(() => inThrottle = false, limit)
|
|
766
|
+
func.apply(this, args)
|
|
767
|
+
inThrottle = true
|
|
768
|
+
setTimeout(() => (inThrottle = false), limit)
|
|
697
769
|
}
|
|
698
|
-
}
|
|
770
|
+
}
|
|
699
771
|
}
|
|
700
772
|
|
|
701
773
|
// ========================================
|
|
@@ -713,90 +785,106 @@ export function analyzeSemanticChanges(diff, filePath) {
|
|
|
713
785
|
keywords: new Set(),
|
|
714
786
|
codeElements: new Set(),
|
|
715
787
|
apiChanges: [],
|
|
716
|
-
dataChanges: []
|
|
717
|
-
}
|
|
788
|
+
dataChanges: [],
|
|
789
|
+
}
|
|
718
790
|
|
|
719
791
|
if (!diff || diff === 'Binary file or diff unavailable') {
|
|
720
|
-
return convertSetsToArrays(analysis)
|
|
792
|
+
return convertSetsToArrays(analysis)
|
|
721
793
|
}
|
|
722
794
|
|
|
723
|
-
const
|
|
724
|
-
|
|
795
|
+
const _addedLines = diff
|
|
796
|
+
.split('\n')
|
|
797
|
+
.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
798
|
+
const _removedLines = diff
|
|
799
|
+
.split('\n')
|
|
800
|
+
.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
725
801
|
|
|
726
802
|
// Framework detection (only if filePath is valid)
|
|
727
|
-
if (
|
|
728
|
-
|
|
803
|
+
if (
|
|
804
|
+
filePath &&
|
|
805
|
+
typeof filePath === 'string' &&
|
|
806
|
+
(filePath.includes('database/') ||
|
|
807
|
+
filePath.includes('sql/') ||
|
|
808
|
+
filePath.includes('migrations/'))
|
|
809
|
+
) {
|
|
810
|
+
analysis.frameworks.add('Database')
|
|
729
811
|
if (diff.includes('CREATE TABLE') || diff.includes('ALTER TABLE')) {
|
|
730
|
-
analysis.changeType = 'schema_change'
|
|
731
|
-
analysis.patterns.add('database_schema')
|
|
812
|
+
analysis.changeType = 'schema_change'
|
|
813
|
+
analysis.patterns.add('database_schema')
|
|
732
814
|
}
|
|
733
815
|
if (diff.includes('CREATE POLICY') || diff.includes('ALTER POLICY')) {
|
|
734
|
-
analysis.patterns.add('security_policy')
|
|
816
|
+
analysis.patterns.add('security_policy')
|
|
735
817
|
}
|
|
736
818
|
}
|
|
737
819
|
|
|
738
820
|
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) {
|
|
739
|
-
analysis.frameworks.add('React')
|
|
821
|
+
analysis.frameworks.add('React')
|
|
740
822
|
if (diff.includes('useState') || diff.includes('useEffect')) {
|
|
741
|
-
analysis.patterns.add('react_hooks')
|
|
823
|
+
analysis.patterns.add('react_hooks')
|
|
742
824
|
}
|
|
743
825
|
if (diff.includes('useCallback') || diff.includes('useMemo')) {
|
|
744
|
-
analysis.patterns.add('performance_optimization')
|
|
826
|
+
analysis.patterns.add('performance_optimization')
|
|
745
827
|
}
|
|
746
828
|
}
|
|
747
829
|
|
|
748
|
-
if (
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
830
|
+
if (
|
|
831
|
+
filePath &&
|
|
832
|
+
typeof filePath === 'string' &&
|
|
833
|
+
(filePath.includes('/api/') || filePath.includes('route.'))
|
|
834
|
+
) {
|
|
835
|
+
analysis.frameworks.add('API')
|
|
836
|
+
analysis.changeType = 'api_change'
|
|
837
|
+
|
|
838
|
+
;['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].forEach((method) => {
|
|
839
|
+
if (
|
|
840
|
+
diff.includes(`export async function ${method}`) ||
|
|
841
|
+
diff.includes(`app.${method.toLowerCase()}`)
|
|
842
|
+
) {
|
|
843
|
+
analysis.apiChanges.push(`${method} endpoint`)
|
|
844
|
+
analysis.patterns.add('api_endpoint')
|
|
757
845
|
}
|
|
758
|
-
})
|
|
846
|
+
})
|
|
759
847
|
}
|
|
760
848
|
|
|
761
849
|
// Code element detection
|
|
762
850
|
const codePatterns = {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
851
|
+
function_definition: /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g,
|
|
852
|
+
component_definition: /(?:export\s+)?(?:const|function)\s+(\w+Component|\w+Page|\w+Layout)/g,
|
|
853
|
+
hook_definition: /(?:export\s+)?(?:const|function)\s+(use\w+)/g,
|
|
854
|
+
type_definition: /(?:export\s+)?(?:type|interface)\s+(\w+)/g,
|
|
855
|
+
constant_definition: /(?:export\s+)?const\s+(\w+)/g,
|
|
856
|
+
}
|
|
769
857
|
|
|
770
858
|
Object.entries(codePatterns).forEach(([pattern, regex]) => {
|
|
771
|
-
const matches = [...diff.matchAll(regex)]
|
|
859
|
+
const matches = [...diff.matchAll(regex)]
|
|
772
860
|
if (matches.length > 0) {
|
|
773
|
-
analysis.patterns.add(pattern)
|
|
774
|
-
matches.forEach(match => analysis.codeElements.add(match[1]))
|
|
861
|
+
analysis.patterns.add(pattern)
|
|
862
|
+
matches.forEach((match) => analysis.codeElements.add(match[1]))
|
|
775
863
|
}
|
|
776
|
-
})
|
|
864
|
+
})
|
|
777
865
|
|
|
778
866
|
// Advanced pattern detection
|
|
779
867
|
const advancedPatterns = {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
868
|
+
error_handling: [/try\s*{/, /catch\s*\(/, /throw\s+/, /Error\(/],
|
|
869
|
+
async_operations: [/async\s+/, /await\s+/, /Promise\./, /\.then\(/],
|
|
870
|
+
data_validation: [/validate/, /schema/, /validation/, /validator/i],
|
|
871
|
+
authentication: [/auth/, /login/, /logout/, /token/, /jwt/, /session/i],
|
|
872
|
+
authorization: [/permission/, /role/, /access/, /policy/, /guard/i],
|
|
873
|
+
caching: [/cache/, /memo/, /useMemo/, /useCallback/i],
|
|
874
|
+
testing: [/test/, /spec/, /mock/, /describe/, /it\(/],
|
|
875
|
+
styling: [/className/, /css/, /styled/, /style/],
|
|
876
|
+
state_management: [/useState/, /useReducer/, /store/, /state/i],
|
|
877
|
+
routing: [/router/, /navigate/, /redirect/, /route/, /Link/],
|
|
878
|
+
data_fetching: [/fetch/, /axios/, /useQuery/, /useMutation/, /api/i],
|
|
879
|
+
}
|
|
792
880
|
|
|
793
881
|
Object.entries(advancedPatterns).forEach(([pattern, regexes]) => {
|
|
794
|
-
if (regexes.some(regex => regex.test(diff))) {
|
|
795
|
-
analysis.patterns.add(pattern)
|
|
882
|
+
if (regexes.some((regex) => regex.test(diff))) {
|
|
883
|
+
analysis.patterns.add(pattern)
|
|
796
884
|
}
|
|
797
|
-
})
|
|
885
|
+
})
|
|
798
886
|
|
|
799
|
-
return convertSetsToArrays(analysis)
|
|
887
|
+
return convertSetsToArrays(analysis)
|
|
800
888
|
}
|
|
801
889
|
|
|
802
890
|
/**
|
|
@@ -810,49 +898,57 @@ export function analyzeFunctionalImpact(diff, filePath, status) {
|
|
|
810
898
|
businessImpact: 'minimal',
|
|
811
899
|
technicalDebt: 'none',
|
|
812
900
|
migrationRequired: false,
|
|
813
|
-
backwardCompatible: true
|
|
814
|
-
}
|
|
901
|
+
backwardCompatible: true,
|
|
902
|
+
}
|
|
815
903
|
|
|
816
904
|
if (!diff || diff === 'Binary file or diff unavailable') {
|
|
817
|
-
return convertSetsToArrays(impact)
|
|
905
|
+
return convertSetsToArrays(impact)
|
|
818
906
|
}
|
|
819
907
|
|
|
820
908
|
// File deletion has high impact
|
|
821
909
|
if (status === 'D') {
|
|
822
|
-
impact.severity = 'high'
|
|
823
|
-
impact.scope = 'global'
|
|
824
|
-
impact.backwardCompatible = false
|
|
825
|
-
return convertSetsToArrays(impact)
|
|
910
|
+
impact.severity = 'high'
|
|
911
|
+
impact.scope = 'global'
|
|
912
|
+
impact.backwardCompatible = false
|
|
913
|
+
return convertSetsToArrays(impact)
|
|
826
914
|
}
|
|
827
915
|
|
|
828
|
-
const
|
|
829
|
-
|
|
916
|
+
const _addedLines = diff
|
|
917
|
+
.split('\n')
|
|
918
|
+
.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
919
|
+
const _removedLines = diff
|
|
920
|
+
.split('\n')
|
|
921
|
+
.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
830
922
|
|
|
831
923
|
// Assess scope based on file type and changes (only if filePath is valid)
|
|
832
924
|
if (filePath && typeof filePath === 'string') {
|
|
833
|
-
if (
|
|
834
|
-
|
|
835
|
-
|
|
925
|
+
if (
|
|
926
|
+
filePath.includes('/api/') ||
|
|
927
|
+
filePath.includes('/server/') ||
|
|
928
|
+
filePath.includes('/backend/')
|
|
929
|
+
) {
|
|
930
|
+
impact.scope = 'system'
|
|
931
|
+
impact.affectedSystems.add('backend')
|
|
836
932
|
|
|
837
933
|
if (diff.includes('export') || diff.includes('endpoint') || diff.includes('route')) {
|
|
838
|
-
impact.scope = 'global'
|
|
839
|
-
impact.severity = 'medium'
|
|
934
|
+
impact.scope = 'global'
|
|
935
|
+
impact.severity = 'medium'
|
|
840
936
|
}
|
|
841
937
|
}
|
|
842
938
|
|
|
843
939
|
if (filePath.includes('package.json') || filePath.includes('package-lock.json')) {
|
|
844
|
-
impact.scope = 'global'
|
|
845
|
-
impact.severity = 'high'
|
|
846
|
-
impact.affectedSystems.add('dependencies')
|
|
847
|
-
impact.migrationRequired = true
|
|
940
|
+
impact.scope = 'global'
|
|
941
|
+
impact.severity = 'high'
|
|
942
|
+
impact.affectedSystems.add('dependencies')
|
|
943
|
+
impact.migrationRequired = true
|
|
848
944
|
}
|
|
849
945
|
|
|
850
946
|
if (filePath.includes('database/') || filePath.includes('migrations/')) {
|
|
851
|
-
impact.scope = 'global'
|
|
852
|
-
impact.severity = 'high'
|
|
853
|
-
impact.affectedSystems.add('database')
|
|
854
|
-
impact.migrationRequired = true
|
|
855
|
-
impact.backwardCompatible = false
|
|
947
|
+
impact.scope = 'global'
|
|
948
|
+
impact.severity = 'high'
|
|
949
|
+
impact.affectedSystems.add('database')
|
|
950
|
+
impact.migrationRequired = true
|
|
951
|
+
impact.backwardCompatible = false
|
|
856
952
|
}
|
|
857
953
|
}
|
|
858
954
|
|
|
@@ -860,28 +956,28 @@ export function analyzeFunctionalImpact(diff, filePath, status) {
|
|
|
860
956
|
const breakingPatterns = [
|
|
861
957
|
/export\s+(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*{[\s\S]*?}-/g,
|
|
862
958
|
/export\s+(?:const|let|var)\s+\w+\s*=[\s\S]*?-/g,
|
|
863
|
-
/export\s+(?:class|interface|type)\s+\w+[\s\S]*?-/g
|
|
864
|
-
]
|
|
959
|
+
/export\s+(?:class|interface|type)\s+\w+[\s\S]*?-/g,
|
|
960
|
+
]
|
|
865
961
|
|
|
866
|
-
if (breakingPatterns.some(pattern => pattern.test(diff))) {
|
|
867
|
-
impact.backwardCompatible = false
|
|
868
|
-
impact.severity = 'high'
|
|
869
|
-
impact.businessImpact = 'high'
|
|
962
|
+
if (breakingPatterns.some((pattern) => pattern.test(diff))) {
|
|
963
|
+
impact.backwardCompatible = false
|
|
964
|
+
impact.severity = 'high'
|
|
965
|
+
impact.businessImpact = 'high'
|
|
870
966
|
}
|
|
871
967
|
|
|
872
968
|
// Performance impact
|
|
873
969
|
if (diff.includes('async') || diff.includes('await') || diff.includes('Promise')) {
|
|
874
|
-
impact.affectedSystems.add('performance')
|
|
970
|
+
impact.affectedSystems.add('performance')
|
|
875
971
|
}
|
|
876
972
|
|
|
877
973
|
// Security impact
|
|
878
974
|
if (diff.includes('auth') || diff.includes('permission') || diff.includes('security')) {
|
|
879
|
-
impact.affectedSystems.add('security')
|
|
880
|
-
impact.severity = 'high'
|
|
881
|
-
impact.businessImpact = 'high'
|
|
975
|
+
impact.affectedSystems.add('security')
|
|
976
|
+
impact.severity = 'high'
|
|
977
|
+
impact.businessImpact = 'high'
|
|
882
978
|
}
|
|
883
979
|
|
|
884
|
-
return convertSetsToArrays(impact)
|
|
980
|
+
return convertSetsToArrays(impact)
|
|
885
981
|
}
|
|
886
982
|
|
|
887
983
|
/**
|
|
@@ -896,56 +992,56 @@ export function generateAnalysisSummary(semanticAnalysis, functionalImpact) {
|
|
|
896
992
|
frameworks: Array.from(semanticAnalysis.frameworks || []),
|
|
897
993
|
patterns: Array.from(semanticAnalysis.patterns || []),
|
|
898
994
|
recommendations: [],
|
|
899
|
-
riskFactors: []
|
|
900
|
-
}
|
|
995
|
+
riskFactors: [],
|
|
996
|
+
}
|
|
901
997
|
|
|
902
998
|
// Generate primary changes based on patterns
|
|
903
|
-
if (semanticAnalysis.patterns
|
|
904
|
-
summary.primaryChanges.push('API endpoint modifications')
|
|
999
|
+
if (semanticAnalysis.patterns?.includes('api_endpoint')) {
|
|
1000
|
+
summary.primaryChanges.push('API endpoint modifications')
|
|
905
1001
|
}
|
|
906
|
-
if (semanticAnalysis.patterns
|
|
907
|
-
summary.primaryChanges.push('Database schema changes')
|
|
1002
|
+
if (semanticAnalysis.patterns?.includes('database_schema')) {
|
|
1003
|
+
summary.primaryChanges.push('Database schema changes')
|
|
908
1004
|
}
|
|
909
|
-
if (semanticAnalysis.patterns
|
|
910
|
-
summary.primaryChanges.push('React component updates')
|
|
1005
|
+
if (semanticAnalysis.patterns?.includes('react_hooks')) {
|
|
1006
|
+
summary.primaryChanges.push('React component updates')
|
|
911
1007
|
}
|
|
912
|
-
if (semanticAnalysis.patterns
|
|
913
|
-
summary.primaryChanges.push('Function implementations')
|
|
1008
|
+
if (semanticAnalysis.patterns?.includes('function_definition')) {
|
|
1009
|
+
summary.primaryChanges.push('Function implementations')
|
|
914
1010
|
}
|
|
915
1011
|
|
|
916
1012
|
// Generate recommendations
|
|
917
1013
|
if (functionalImpact.migrationRequired) {
|
|
918
|
-
summary.recommendations.push('Migration script required')
|
|
1014
|
+
summary.recommendations.push('Migration script required')
|
|
919
1015
|
}
|
|
920
1016
|
if (!functionalImpact.backwardCompatible) {
|
|
921
|
-
summary.recommendations.push('Breaking change - update dependent code')
|
|
1017
|
+
summary.recommendations.push('Breaking change - update dependent code')
|
|
922
1018
|
}
|
|
923
|
-
if (functionalImpact.affectedSystems
|
|
924
|
-
summary.recommendations.push('Security review recommended')
|
|
1019
|
+
if (functionalImpact.affectedSystems?.includes('security')) {
|
|
1020
|
+
summary.recommendations.push('Security review recommended')
|
|
925
1021
|
}
|
|
926
|
-
if (functionalImpact.affectedSystems
|
|
927
|
-
summary.recommendations.push('Performance testing advised')
|
|
1022
|
+
if (functionalImpact.affectedSystems?.includes('performance')) {
|
|
1023
|
+
summary.recommendations.push('Performance testing advised')
|
|
928
1024
|
}
|
|
929
1025
|
|
|
930
1026
|
// Identify risk factors
|
|
931
1027
|
if (functionalImpact.severity === 'high') {
|
|
932
|
-
summary.riskFactors.push('High impact changes')
|
|
1028
|
+
summary.riskFactors.push('High impact changes')
|
|
933
1029
|
}
|
|
934
1030
|
if (functionalImpact.scope === 'global') {
|
|
935
|
-
summary.riskFactors.push('System-wide effects')
|
|
1031
|
+
summary.riskFactors.push('System-wide effects')
|
|
936
1032
|
}
|
|
937
1033
|
if (semanticAnalysis.frameworks && semanticAnalysis.frameworks.length > 2) {
|
|
938
|
-
summary.riskFactors.push('Multiple framework dependencies')
|
|
1034
|
+
summary.riskFactors.push('Multiple framework dependencies')
|
|
939
1035
|
}
|
|
940
1036
|
|
|
941
|
-
return summary
|
|
1037
|
+
return summary
|
|
942
1038
|
}
|
|
943
1039
|
|
|
944
1040
|
/**
|
|
945
1041
|
* Perform semantic analysis on files and commit message
|
|
946
1042
|
*/
|
|
947
1043
|
export function performSemanticAnalysis(files, subject, body) {
|
|
948
|
-
const conventionalCommit = parseConventionalCommit(subject, body)
|
|
1044
|
+
const conventionalCommit = parseConventionalCommit(subject, body)
|
|
949
1045
|
const analysis = {
|
|
950
1046
|
commitType: conventionalCommit.type || 'unknown',
|
|
951
1047
|
scope: conventionalCommit.scope,
|
|
@@ -958,43 +1054,43 @@ export function performSemanticAnalysis(files, subject, body) {
|
|
|
958
1054
|
affectedDomains: new Set(),
|
|
959
1055
|
technicalElements: new Set(),
|
|
960
1056
|
businessElements: new Set(),
|
|
961
|
-
complexity: 'low'
|
|
962
|
-
}
|
|
1057
|
+
complexity: 'low',
|
|
1058
|
+
}
|
|
963
1059
|
|
|
964
1060
|
// Analyze files
|
|
965
|
-
files.forEach(file => {
|
|
966
|
-
const filePath = file.filePath || file.path || ''
|
|
967
|
-
const category = categorizeFile(filePath)
|
|
968
|
-
const language = detectLanguage(filePath)
|
|
1061
|
+
files.forEach((file) => {
|
|
1062
|
+
const filePath = file.filePath || file.path || ''
|
|
1063
|
+
const category = categorizeFile(filePath)
|
|
1064
|
+
const language = detectLanguage(filePath)
|
|
969
1065
|
|
|
970
|
-
analysis.affectedDomains.add(category)
|
|
971
|
-
analysis.technicalElements.add(language)
|
|
1066
|
+
analysis.affectedDomains.add(category)
|
|
1067
|
+
analysis.technicalElements.add(language)
|
|
972
1068
|
|
|
973
1069
|
// Business domain detection (only if filePath exists)
|
|
974
1070
|
if (filePath && typeof filePath === 'string') {
|
|
975
1071
|
if (filePath.includes('/user/') || filePath.includes('/customer/')) {
|
|
976
|
-
analysis.businessElements.add('user_management')
|
|
1072
|
+
analysis.businessElements.add('user_management')
|
|
977
1073
|
}
|
|
978
1074
|
if (filePath.includes('/payment/') || filePath.includes('/billing/')) {
|
|
979
|
-
analysis.businessElements.add('financial_operations')
|
|
1075
|
+
analysis.businessElements.add('financial_operations')
|
|
980
1076
|
}
|
|
981
1077
|
if (filePath.includes('/auth/') || filePath.includes('/security/')) {
|
|
982
|
-
analysis.businessElements.add('security_access')
|
|
1078
|
+
analysis.businessElements.add('security_access')
|
|
983
1079
|
}
|
|
984
1080
|
if (filePath.includes('/analytics/') || filePath.includes('/metrics/')) {
|
|
985
|
-
analysis.businessElements.add('business_intelligence')
|
|
1081
|
+
analysis.businessElements.add('business_intelligence')
|
|
986
1082
|
}
|
|
987
1083
|
}
|
|
988
|
-
})
|
|
1084
|
+
})
|
|
989
1085
|
|
|
990
1086
|
// Complexity assessment
|
|
991
1087
|
if (files.length > 10 || analysis.affectedDomains.size > 3) {
|
|
992
|
-
analysis.complexity = 'high'
|
|
1088
|
+
analysis.complexity = 'high'
|
|
993
1089
|
} else if (files.length > 3 || analysis.affectedDomains.size > 1) {
|
|
994
|
-
analysis.complexity = 'medium'
|
|
1090
|
+
analysis.complexity = 'medium'
|
|
995
1091
|
}
|
|
996
1092
|
|
|
997
|
-
return convertSetsToArrays(analysis)
|
|
1093
|
+
return convertSetsToArrays(analysis)
|
|
998
1094
|
}
|
|
999
1095
|
|
|
1000
1096
|
// ========================================
|
|
@@ -1005,102 +1101,57 @@ export function performSemanticAnalysis(files, subject, body) {
|
|
|
1005
1101
|
* Build enhanced prompt for AI analysis
|
|
1006
1102
|
*/
|
|
1007
1103
|
export function buildEnhancedPrompt(commitAnalysis, analysisMode = 'standard') {
|
|
1008
|
-
const {
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
businessRelevance: file.businessRelevance,
|
|
1051
|
-
diff: file.diff || 'No diff available' // Include actual diff content so AI can see what changed
|
|
1052
|
-
})).slice(0, fileLimit);
|
|
1053
|
-
|
|
1054
|
-
// Enhanced analysis requirements based on mode
|
|
1055
|
-
const getAnalysisRequirements = () => {
|
|
1056
|
-
const baseRequirements = [
|
|
1057
|
-
"**Primary Impact**: What does this change do for end users?",
|
|
1058
|
-
"**Technical Scope**: How does this affect the codebase architecture?",
|
|
1059
|
-
"**Business Value**: What problem does this solve or feature does this enable?",
|
|
1060
|
-
"**Risk Assessment**: What are the potential impacts of this change?"
|
|
1061
|
-
];
|
|
1062
|
-
|
|
1063
|
-
if (analysisMode === 'detailed' || analysisMode === 'enterprise') {
|
|
1064
|
-
baseRequirements.push(
|
|
1065
|
-
"**Migration Needs**: Are there any breaking changes or upgrade requirements?",
|
|
1066
|
-
"**Performance Impact**: How might this affect system performance?",
|
|
1067
|
-
"**Security Implications**: Are there any security considerations?",
|
|
1068
|
-
"**Testing Strategy**: What areas should be prioritized for testing?",
|
|
1069
|
-
"**Rollback Plan**: What would be needed if this change needs to be reverted?"
|
|
1070
|
-
);
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
|
-
if (analysisMode === 'enterprise') {
|
|
1074
|
-
baseRequirements.push(
|
|
1075
|
-
"**Operational Impact**: How does this affect deployment, monitoring, or operations?",
|
|
1076
|
-
"**Compliance**: Are there regulatory or compliance considerations?",
|
|
1077
|
-
"**Cross-team Impact**: What teams or systems might be affected?",
|
|
1078
|
-
"**Timeline Implications**: Are there dependencies or staging requirements?"
|
|
1079
|
-
);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
return baseRequirements;
|
|
1083
|
-
};
|
|
1084
|
-
|
|
1085
|
-
// Enhanced task description based on analysis mode
|
|
1086
|
-
const getTaskDescription = () => {
|
|
1087
|
-
switch (analysisMode) {
|
|
1088
|
-
case 'enterprise':
|
|
1089
|
-
return `Perform comprehensive enterprise-grade analysis of this git commit for changelog generation. Provide deep technical insights, thorough risk assessment, and detailed operational considerations suitable for enterprise deployment and stakeholder communication.`;
|
|
1090
|
-
case 'detailed':
|
|
1091
|
-
return `Perform detailed technical analysis of this git commit for changelog generation. Provide comprehensive technical insights, thorough impact assessment, and detailed migration considerations for development teams.`;
|
|
1092
|
-
default:
|
|
1093
|
-
return `Analyze this git commit for changelog generation using your reasoning capabilities.`;
|
|
1094
|
-
}
|
|
1095
|
-
};
|
|
1104
|
+
const {
|
|
1105
|
+
subject,
|
|
1106
|
+
files = [],
|
|
1107
|
+
semanticAnalysis = {},
|
|
1108
|
+
diffStats = {},
|
|
1109
|
+
complexity = {},
|
|
1110
|
+
riskAssessment = {},
|
|
1111
|
+
} = commitAnalysis
|
|
1112
|
+
|
|
1113
|
+
// Initialize DiffProcessor with analysis mode
|
|
1114
|
+
const diffProcessor = new DiffProcessor({
|
|
1115
|
+
analysisMode,
|
|
1116
|
+
enableFiltering: true,
|
|
1117
|
+
enablePatternDetection: true,
|
|
1118
|
+
})
|
|
1119
|
+
|
|
1120
|
+
// Process all files with intelligent diff compression
|
|
1121
|
+
const processedResult = diffProcessor.processFiles(files)
|
|
1122
|
+
const { processedFiles, patterns, filesProcessed, filesSkipped } = processedResult
|
|
1123
|
+
|
|
1124
|
+
const insertions = diffStats.insertions || 0
|
|
1125
|
+
const deletions = diffStats.deletions || 0
|
|
1126
|
+
|
|
1127
|
+
// Build pattern summary if patterns were detected
|
|
1128
|
+
const patternSummary =
|
|
1129
|
+
Object.keys(patterns).length > 0
|
|
1130
|
+
? `\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
|
|
1131
|
+
.map((p) => `- ${p.description}`)
|
|
1132
|
+
.join('\n')}\n`
|
|
1133
|
+
: ''
|
|
1134
|
+
|
|
1135
|
+
// Build files section with processed diffs
|
|
1136
|
+
const filesSection = processedFiles
|
|
1137
|
+
.map((file) => {
|
|
1138
|
+
if (file.isSummary) {
|
|
1139
|
+
return `\n**[REMAINING FILES SUMMARY]:**\n${file.diff}\n`
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const compressionInfo = file.compressionApplied
|
|
1143
|
+
? ` [compressed from ${file.originalSize || 'unknown'} chars]`
|
|
1144
|
+
: ''
|
|
1145
|
+
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
1096
1146
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1147
|
+
return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}\n`
|
|
1148
|
+
})
|
|
1149
|
+
.join('')
|
|
1099
1150
|
|
|
1100
1151
|
const prompt = `Analyze this git commit for changelog generation.
|
|
1101
1152
|
|
|
1102
1153
|
**COMMIT:** ${subject}
|
|
1103
|
-
**FILES:** ${files.length} files, ${insertions + deletions} lines changed
|
|
1154
|
+
**FILES:** ${files.length} files (${filesProcessed} analyzed, ${filesSkipped} summarized), ${insertions + deletions} lines changed${patternSummary}
|
|
1104
1155
|
|
|
1105
1156
|
**TARGET AUDIENCE:** End users and project stakeholders who need to understand what changed and why it matters.
|
|
1106
1157
|
|
|
@@ -1111,14 +1162,7 @@ export function buildEnhancedPrompt(commitAnalysis, analysisMode = 'standard') {
|
|
|
1111
1162
|
2. **Then, focus on user impact** - what can they do now that they couldn't before?
|
|
1112
1163
|
3. **Keep technical details minimal** - only what's necessary for understanding
|
|
1113
1164
|
|
|
1114
|
-
**
|
|
1115
|
-
${filesContext.map(file => {
|
|
1116
|
-
const diffPreview = file.diff ? file.diff.substring(0, diffLimit) + (file.diff.length > diffLimit ? '...' : '') : 'No diff available';
|
|
1117
|
-
return `
|
|
1118
|
-
**${file.path}** (${file.status}):
|
|
1119
|
-
${diffPreview}
|
|
1120
|
-
`;
|
|
1121
|
-
}).join('')}
|
|
1165
|
+
**PROCESSED DIFFS:**${filesSection}
|
|
1122
1166
|
|
|
1123
1167
|
**CATEGORIZATION RULES (STRICTLY ENFORCED):**
|
|
1124
1168
|
- **fix**: ONLY actual bug fixes - broken functionality now works correctly
|
|
@@ -1145,102 +1189,116 @@ Provide a JSON response with ONLY these fields:
|
|
|
1145
1189
|
"recommendations": ["minimal", "list"],
|
|
1146
1190
|
"breakingChanges": false,
|
|
1147
1191
|
"migrationRequired": false
|
|
1148
|
-
}
|
|
1192
|
+
}`
|
|
1149
1193
|
|
|
1150
|
-
return prompt
|
|
1194
|
+
return prompt
|
|
1151
1195
|
}
|
|
1152
1196
|
|
|
1153
1197
|
/**
|
|
1154
1198
|
* Validate and correct AI categorization and impact assessment based on commit characteristics
|
|
1155
1199
|
*/
|
|
1156
1200
|
export function validateCommitCategory(category, commitAnalysis) {
|
|
1157
|
-
const { files = [], diffStats = {}, subject = '' } = commitAnalysis
|
|
1158
|
-
const fileCount = files.length
|
|
1159
|
-
const addedFiles = files.filter(f => f.status === 'A' || f.status === '??').length
|
|
1160
|
-
const { insertions = 0, deletions = 0 } = diffStats
|
|
1201
|
+
const { files = [], diffStats = {}, subject = '' } = commitAnalysis
|
|
1202
|
+
const fileCount = files.length
|
|
1203
|
+
const addedFiles = files.filter((f) => f.status === 'A' || f.status === '??').length
|
|
1204
|
+
const { insertions = 0, deletions = 0 } = diffStats
|
|
1161
1205
|
|
|
1162
1206
|
// Rule 1: Large additions cannot be bug fixes
|
|
1163
1207
|
if (category === 'fix' && (fileCount > 10 || addedFiles > 5 || insertions > 1000)) {
|
|
1164
1208
|
// Check if it's likely a refactor vs new feature
|
|
1165
1209
|
if (deletions > insertions * 0.5) {
|
|
1166
|
-
return 'refactor'
|
|
1210
|
+
return 'refactor' // Significant deletions suggest refactoring
|
|
1167
1211
|
}
|
|
1168
|
-
return 'feature'
|
|
1212
|
+
return 'feature' // More additions suggest new functionality
|
|
1169
1213
|
}
|
|
1170
1214
|
|
|
1171
1215
|
// Rule 2: New module/class additions are features
|
|
1172
1216
|
if (category === 'fix' && addedFiles > 0) {
|
|
1173
|
-
const hasNewModules = files.some(
|
|
1174
|
-
(f
|
|
1175
|
-
|
|
1176
|
-
|
|
1217
|
+
const hasNewModules = files.some(
|
|
1218
|
+
(f) =>
|
|
1219
|
+
(f.status === 'A' || f.status === '??') &&
|
|
1220
|
+
(f.filePath.includes('.js') || f.filePath.includes('.ts') || f.filePath.includes('.py'))
|
|
1221
|
+
)
|
|
1177
1222
|
if (hasNewModules) {
|
|
1178
|
-
return 'feature'
|
|
1223
|
+
return 'feature'
|
|
1179
1224
|
}
|
|
1180
1225
|
}
|
|
1181
1226
|
|
|
1182
1227
|
// Rule 3: Documentation-only changes
|
|
1183
|
-
if (
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1228
|
+
if (
|
|
1229
|
+
category !== 'docs' &&
|
|
1230
|
+
files.every(
|
|
1231
|
+
(f) =>
|
|
1232
|
+
f.filePath.endsWith('.md') ||
|
|
1233
|
+
f.filePath.endsWith('.txt') ||
|
|
1234
|
+
f.filePath.includes('README') ||
|
|
1235
|
+
f.filePath.includes('CHANGELOG')
|
|
1236
|
+
)
|
|
1237
|
+
) {
|
|
1238
|
+
return 'docs'
|
|
1190
1239
|
}
|
|
1191
1240
|
|
|
1192
1241
|
// Rule 4: Test-only changes
|
|
1193
|
-
if (
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1242
|
+
if (
|
|
1243
|
+
category !== 'test' &&
|
|
1244
|
+
files.every(
|
|
1245
|
+
(f) =>
|
|
1246
|
+
f.filePath.includes('test') ||
|
|
1247
|
+
f.filePath.includes('spec') ||
|
|
1248
|
+
f.filePath.endsWith('.test.js') ||
|
|
1249
|
+
f.filePath.endsWith('.spec.js')
|
|
1250
|
+
)
|
|
1251
|
+
) {
|
|
1252
|
+
return 'test'
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
return category // No correction needed
|
|
1203
1256
|
}
|
|
1204
1257
|
|
|
1205
1258
|
/**
|
|
1206
1259
|
* Validate and correct impact assessment based on actual change magnitude
|
|
1207
1260
|
*/
|
|
1208
1261
|
export function validateImpactAssessment(impact, commitAnalysis) {
|
|
1209
|
-
const { files = [], diffStats = {}, subject = '' } = commitAnalysis
|
|
1210
|
-
const fileCount = files.length
|
|
1211
|
-
const addedFiles = files.filter(f => f.status === 'A' || f.status === '??').length
|
|
1212
|
-
const { insertions = 0, deletions = 0 } = diffStats
|
|
1213
|
-
const totalChanges = insertions + deletions
|
|
1262
|
+
const { files = [], diffStats = {}, subject = '' } = commitAnalysis
|
|
1263
|
+
const fileCount = files.length
|
|
1264
|
+
const addedFiles = files.filter((f) => f.status === 'A' || f.status === '??').length
|
|
1265
|
+
const { insertions = 0, deletions = 0 } = diffStats
|
|
1266
|
+
const totalChanges = insertions + deletions
|
|
1214
1267
|
|
|
1215
1268
|
// Rule 1: Large architectural changes cannot be "minimal" or "low"
|
|
1216
1269
|
if ((impact === 'minimal' || impact === 'low') && (fileCount > 50 || totalChanges > 5000)) {
|
|
1217
|
-
return 'high'
|
|
1270
|
+
return 'high'
|
|
1218
1271
|
}
|
|
1219
1272
|
|
|
1220
1273
|
// Rule 2: Major refactors or large features should be at least "medium"
|
|
1221
1274
|
if (impact === 'minimal' && (fileCount > 20 || totalChanges > 2000 || addedFiles > 10)) {
|
|
1222
|
-
return 'medium'
|
|
1275
|
+
return 'medium'
|
|
1223
1276
|
}
|
|
1224
1277
|
|
|
1225
1278
|
// Rule 3: Small changes cannot be "critical" or "high" unless breaking
|
|
1226
1279
|
if ((impact === 'critical' || impact === 'high') && fileCount <= 3 && totalChanges <= 100) {
|
|
1227
|
-
const hasBreakingIndicators =
|
|
1280
|
+
const hasBreakingIndicators =
|
|
1281
|
+
subject.includes('!') || subject.toLowerCase().includes('breaking')
|
|
1228
1282
|
if (!hasBreakingIndicators) {
|
|
1229
|
-
return 'medium'
|
|
1283
|
+
return 'medium'
|
|
1230
1284
|
}
|
|
1231
1285
|
}
|
|
1232
1286
|
|
|
1233
1287
|
// Rule 4: Documentation-only changes should be low impact
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1288
|
+
if (
|
|
1289
|
+
files.every(
|
|
1290
|
+
(f) =>
|
|
1291
|
+
f.filePath.endsWith('.md') ||
|
|
1292
|
+
f.filePath.endsWith('.txt') ||
|
|
1293
|
+
f.filePath.includes('README') ||
|
|
1294
|
+
f.filePath.includes('CHANGELOG')
|
|
1295
|
+
) &&
|
|
1296
|
+
(impact === 'critical' || impact === 'high')
|
|
1297
|
+
) {
|
|
1298
|
+
return 'low'
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
return impact // No correction needed
|
|
1244
1302
|
}
|
|
1245
1303
|
|
|
1246
1304
|
/**
|
|
@@ -1258,19 +1316,19 @@ export function parseAIResponse(content, originalCommit = {}) {
|
|
|
1258
1316
|
riskFactors: [],
|
|
1259
1317
|
recommendations: ['Consider configuring AI provider for detailed analysis'],
|
|
1260
1318
|
breakingChanges: false,
|
|
1261
|
-
migrationRequired: false
|
|
1262
|
-
}
|
|
1319
|
+
migrationRequired: false,
|
|
1320
|
+
}
|
|
1263
1321
|
}
|
|
1264
1322
|
|
|
1265
1323
|
try {
|
|
1266
1324
|
// Try to extract JSON from the response
|
|
1267
|
-
const jsonMatch = content.match(/\{[\s\S]*\}/)
|
|
1325
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/)
|
|
1268
1326
|
if (jsonMatch) {
|
|
1269
|
-
const parsed = safeJsonParse(jsonMatch[0])
|
|
1327
|
+
const parsed = safeJsonParse(jsonMatch[0])
|
|
1270
1328
|
if (parsed) {
|
|
1271
1329
|
// Validate and correct the category and impact
|
|
1272
|
-
const validatedCategory = validateCommitCategory(parsed.category || 'chore', originalCommit)
|
|
1273
|
-
const validatedImpact = validateImpactAssessment(parsed.impact || 'low', originalCommit)
|
|
1330
|
+
const validatedCategory = validateCommitCategory(parsed.category || 'chore', originalCommit)
|
|
1331
|
+
const validatedImpact = validateImpactAssessment(parsed.impact || 'low', originalCommit)
|
|
1274
1332
|
|
|
1275
1333
|
return {
|
|
1276
1334
|
summary: parsed.summary || originalCommit.subject || 'Unknown change',
|
|
@@ -1282,20 +1340,24 @@ export function parseAIResponse(content, originalCommit = {}) {
|
|
|
1282
1340
|
riskFactors: Array.isArray(parsed.riskFactors) ? parsed.riskFactors : [],
|
|
1283
1341
|
recommendations: Array.isArray(parsed.recommendations) ? parsed.recommendations : [],
|
|
1284
1342
|
breakingChanges: Boolean(parsed.breakingChanges),
|
|
1285
|
-
migrationRequired: Boolean(parsed.migrationRequired)
|
|
1286
|
-
}
|
|
1343
|
+
migrationRequired: Boolean(parsed.migrationRequired),
|
|
1344
|
+
}
|
|
1287
1345
|
}
|
|
1288
1346
|
}
|
|
1289
1347
|
|
|
1290
1348
|
// Fallback to text parsing if JSON extraction fails
|
|
1291
|
-
const safeContent = content && typeof content === 'string' ? content : ''
|
|
1292
|
-
const lowerContent = safeContent.toLowerCase()
|
|
1349
|
+
const safeContent = content && typeof content === 'string' ? content : ''
|
|
1350
|
+
const lowerContent = safeContent.toLowerCase()
|
|
1293
1351
|
|
|
1294
1352
|
return {
|
|
1295
1353
|
summary: originalCommit.subject || 'Unknown change',
|
|
1296
|
-
impact: lowerContent.includes('critical')
|
|
1297
|
-
|
|
1298
|
-
|
|
1354
|
+
impact: lowerContent.includes('critical')
|
|
1355
|
+
? 'critical'
|
|
1356
|
+
: lowerContent.includes('high')
|
|
1357
|
+
? 'high'
|
|
1358
|
+
: lowerContent.includes('medium')
|
|
1359
|
+
? 'medium'
|
|
1360
|
+
: 'low',
|
|
1299
1361
|
category: extractCategoryFromText(safeContent),
|
|
1300
1362
|
description: safeContent.substring(0, 200) + (safeContent.length > 200 ? '...' : ''),
|
|
1301
1363
|
technicalDetails: safeContent,
|
|
@@ -1303,10 +1365,10 @@ export function parseAIResponse(content, originalCommit = {}) {
|
|
|
1303
1365
|
riskFactors: [],
|
|
1304
1366
|
recommendations: [],
|
|
1305
1367
|
breakingChanges: lowerContent.includes('breaking'),
|
|
1306
|
-
migrationRequired: lowerContent.includes('migration')
|
|
1307
|
-
}
|
|
1368
|
+
migrationRequired: lowerContent.includes('migration'),
|
|
1369
|
+
}
|
|
1308
1370
|
} catch (error) {
|
|
1309
|
-
console.warn(colors.warningMessage(`AI response parsing error: ${error.message}`))
|
|
1371
|
+
console.warn(colors.warningMessage(`AI response parsing error: ${error.message}`))
|
|
1310
1372
|
return {
|
|
1311
1373
|
summary: originalCommit.subject || 'Unknown change',
|
|
1312
1374
|
impact: 'low',
|
|
@@ -1317,8 +1379,8 @@ export function parseAIResponse(content, originalCommit = {}) {
|
|
|
1317
1379
|
riskFactors: [],
|
|
1318
1380
|
recommendations: [],
|
|
1319
1381
|
breakingChanges: false,
|
|
1320
|
-
migrationRequired: false
|
|
1321
|
-
}
|
|
1382
|
+
migrationRequired: false,
|
|
1383
|
+
}
|
|
1322
1384
|
}
|
|
1323
1385
|
}
|
|
1324
1386
|
|
|
@@ -1328,21 +1390,39 @@ export function parseAIResponse(content, originalCommit = {}) {
|
|
|
1328
1390
|
*/
|
|
1329
1391
|
function extractCategoryFromText(content) {
|
|
1330
1392
|
if (!content || typeof content !== 'string') {
|
|
1331
|
-
return 'chore'
|
|
1393
|
+
return 'chore'
|
|
1332
1394
|
}
|
|
1333
|
-
const text = content.toLowerCase()
|
|
1395
|
+
const text = content.toLowerCase()
|
|
1334
1396
|
|
|
1335
|
-
if (text.includes('feature') || text.includes('feat'))
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
if (text.includes('
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
if (text.includes('
|
|
1342
|
-
|
|
1343
|
-
|
|
1397
|
+
if (text.includes('feature') || text.includes('feat')) {
|
|
1398
|
+
return 'feature'
|
|
1399
|
+
}
|
|
1400
|
+
if (text.includes('fix') || text.includes('bug')) {
|
|
1401
|
+
return 'fix'
|
|
1402
|
+
}
|
|
1403
|
+
if (text.includes('security')) {
|
|
1404
|
+
return 'security'
|
|
1405
|
+
}
|
|
1406
|
+
if (text.includes('breaking')) {
|
|
1407
|
+
return 'breaking'
|
|
1408
|
+
}
|
|
1409
|
+
if (text.includes('doc')) {
|
|
1410
|
+
return 'docs'
|
|
1411
|
+
}
|
|
1412
|
+
if (text.includes('style')) {
|
|
1413
|
+
return 'style'
|
|
1414
|
+
}
|
|
1415
|
+
if (text.includes('refactor')) {
|
|
1416
|
+
return 'refactor'
|
|
1417
|
+
}
|
|
1418
|
+
if (text.includes('perf') || text.includes('performance')) {
|
|
1419
|
+
return 'perf'
|
|
1420
|
+
}
|
|
1421
|
+
if (text.includes('test')) {
|
|
1422
|
+
return 'test'
|
|
1423
|
+
}
|
|
1344
1424
|
|
|
1345
|
-
return 'chore'
|
|
1425
|
+
return 'chore'
|
|
1346
1426
|
}
|
|
1347
1427
|
|
|
1348
1428
|
// ========================================
|
|
@@ -1357,43 +1437,43 @@ function extractCategoryFromText(content) {
|
|
|
1357
1437
|
*/
|
|
1358
1438
|
export function getWorkingDirectoryChanges() {
|
|
1359
1439
|
try {
|
|
1360
|
-
const result = execSync('git status --porcelain', { encoding: 'utf8' })
|
|
1440
|
+
const result = execSync('git status --porcelain', { encoding: 'utf8' })
|
|
1361
1441
|
|
|
1362
1442
|
if (!result.trim()) {
|
|
1363
|
-
return []
|
|
1443
|
+
return []
|
|
1364
1444
|
}
|
|
1365
1445
|
|
|
1366
|
-
const lines = result.split('\n').filter(line => line.trim())
|
|
1446
|
+
const lines = result.split('\n').filter((line) => line.trim())
|
|
1367
1447
|
|
|
1368
|
-
const changes = lines.map(line => {
|
|
1369
|
-
const status = line.substring(0, 2).trim() || '??'
|
|
1370
|
-
const filePath = line.substring(3)
|
|
1448
|
+
const changes = lines.map((line) => {
|
|
1449
|
+
const status = line.substring(0, 2).trim() || '??'
|
|
1450
|
+
const filePath = line.substring(3)
|
|
1371
1451
|
|
|
1372
1452
|
return {
|
|
1373
|
-
status
|
|
1374
|
-
filePath
|
|
1453
|
+
status,
|
|
1454
|
+
filePath,
|
|
1375
1455
|
path: filePath, // alias for compatibility
|
|
1376
1456
|
diff: '', // Will be populated later if needed
|
|
1377
1457
|
additions: 0,
|
|
1378
|
-
deletions: 0
|
|
1379
|
-
}
|
|
1380
|
-
})
|
|
1458
|
+
deletions: 0,
|
|
1459
|
+
}
|
|
1460
|
+
})
|
|
1381
1461
|
|
|
1382
|
-
return changes
|
|
1462
|
+
return changes
|
|
1383
1463
|
} catch (error) {
|
|
1384
|
-
console.warn('Could not get git status:', error.message)
|
|
1385
|
-
return []
|
|
1464
|
+
console.warn('Could not get git status:', error.message)
|
|
1465
|
+
return []
|
|
1386
1466
|
}
|
|
1387
1467
|
}
|
|
1388
1468
|
|
|
1389
|
-
export function processWorkingDirectoryChanges(workingDirChanges,
|
|
1469
|
+
export function processWorkingDirectoryChanges(workingDirChanges, _gitManager = null) {
|
|
1390
1470
|
// If no changes provided, get them from git status
|
|
1391
1471
|
if (!workingDirChanges) {
|
|
1392
|
-
workingDirChanges = getWorkingDirectoryChanges()
|
|
1472
|
+
workingDirChanges = getWorkingDirectoryChanges()
|
|
1393
1473
|
}
|
|
1394
1474
|
|
|
1395
1475
|
if (!workingDirChanges || workingDirChanges.length === 0) {
|
|
1396
|
-
return []
|
|
1476
|
+
return []
|
|
1397
1477
|
}
|
|
1398
1478
|
|
|
1399
1479
|
const categorizedChanges = {
|
|
@@ -1401,15 +1481,15 @@ export function processWorkingDirectoryChanges(workingDirChanges, gitManager = n
|
|
|
1401
1481
|
modified: [],
|
|
1402
1482
|
deleted: [],
|
|
1403
1483
|
renamed: [],
|
|
1404
|
-
unknown: []
|
|
1405
|
-
}
|
|
1484
|
+
unknown: [],
|
|
1485
|
+
}
|
|
1406
1486
|
|
|
1407
|
-
let totalFiles = 0
|
|
1487
|
+
let totalFiles = 0
|
|
1408
1488
|
|
|
1409
|
-
workingDirChanges.forEach(change => {
|
|
1410
|
-
const category = categorizeFile(change.filePath)
|
|
1411
|
-
const language = detectLanguage(change.filePath)
|
|
1412
|
-
const importance = assessFileImportance(change.filePath, change.status)
|
|
1489
|
+
workingDirChanges.forEach((change) => {
|
|
1490
|
+
const category = categorizeFile(change.filePath)
|
|
1491
|
+
const language = detectLanguage(change.filePath)
|
|
1492
|
+
const importance = assessFileImportance(change.filePath, change.status)
|
|
1413
1493
|
|
|
1414
1494
|
const processedChange = {
|
|
1415
1495
|
filePath: change.filePath,
|
|
@@ -1419,41 +1499,41 @@ export function processWorkingDirectoryChanges(workingDirChanges, gitManager = n
|
|
|
1419
1499
|
importance,
|
|
1420
1500
|
diff: change.diff || '',
|
|
1421
1501
|
additions: change.additions || 0,
|
|
1422
|
-
deletions: change.deletions || 0
|
|
1423
|
-
}
|
|
1502
|
+
deletions: change.deletions || 0,
|
|
1503
|
+
}
|
|
1424
1504
|
|
|
1425
1505
|
switch (change.status) {
|
|
1426
1506
|
case 'A':
|
|
1427
|
-
categorizedChanges.added.push(processedChange)
|
|
1428
|
-
break
|
|
1507
|
+
categorizedChanges.added.push(processedChange)
|
|
1508
|
+
break
|
|
1429
1509
|
case 'M':
|
|
1430
|
-
categorizedChanges.modified.push(processedChange)
|
|
1431
|
-
break
|
|
1510
|
+
categorizedChanges.modified.push(processedChange)
|
|
1511
|
+
break
|
|
1432
1512
|
case 'D':
|
|
1433
|
-
categorizedChanges.deleted.push(processedChange)
|
|
1434
|
-
break
|
|
1513
|
+
categorizedChanges.deleted.push(processedChange)
|
|
1514
|
+
break
|
|
1435
1515
|
case 'R':
|
|
1436
|
-
categorizedChanges.renamed.push(processedChange)
|
|
1437
|
-
break
|
|
1516
|
+
categorizedChanges.renamed.push(processedChange)
|
|
1517
|
+
break
|
|
1438
1518
|
default:
|
|
1439
|
-
categorizedChanges.unknown.push(processedChange)
|
|
1519
|
+
categorizedChanges.unknown.push(processedChange)
|
|
1440
1520
|
}
|
|
1441
1521
|
|
|
1442
|
-
totalFiles
|
|
1443
|
-
})
|
|
1522
|
+
totalFiles++
|
|
1523
|
+
})
|
|
1444
1524
|
|
|
1445
1525
|
// Generate summary
|
|
1446
|
-
const summary = generateWorkspaceChangesSummary(categorizedChanges)
|
|
1526
|
+
const summary = generateWorkspaceChangesSummary(categorizedChanges)
|
|
1447
1527
|
|
|
1448
1528
|
// Assess complexity
|
|
1449
|
-
const complexity = assessWorkspaceComplexity(categorizedChanges, totalFiles)
|
|
1529
|
+
const complexity = assessWorkspaceComplexity(categorizedChanges, totalFiles)
|
|
1450
1530
|
|
|
1451
1531
|
return {
|
|
1452
1532
|
categorizedChanges,
|
|
1453
1533
|
summary,
|
|
1454
1534
|
totalFiles,
|
|
1455
|
-
complexity
|
|
1456
|
-
}
|
|
1535
|
+
complexity,
|
|
1536
|
+
}
|
|
1457
1537
|
}
|
|
1458
1538
|
|
|
1459
1539
|
/**
|
|
@@ -1465,195 +1545,232 @@ export function summarizeFileChanges(changes) {
|
|
|
1465
1545
|
summary: 'No file changes detected',
|
|
1466
1546
|
categories: {},
|
|
1467
1547
|
stats: { added: 0, modified: 0, deleted: 0, renamed: 0 },
|
|
1468
|
-
languages: {}
|
|
1469
|
-
}
|
|
1548
|
+
languages: {},
|
|
1549
|
+
}
|
|
1470
1550
|
}
|
|
1471
1551
|
|
|
1472
1552
|
const stats = {
|
|
1473
1553
|
added: 0,
|
|
1474
1554
|
modified: 0,
|
|
1475
1555
|
deleted: 0,
|
|
1476
|
-
renamed: 0
|
|
1477
|
-
}
|
|
1556
|
+
renamed: 0,
|
|
1557
|
+
}
|
|
1478
1558
|
|
|
1479
|
-
const categories = {}
|
|
1480
|
-
const languages = new Set()
|
|
1559
|
+
const categories = {}
|
|
1560
|
+
const languages = new Set()
|
|
1481
1561
|
|
|
1482
|
-
changes.forEach(change => {
|
|
1562
|
+
changes.forEach((change) => {
|
|
1483
1563
|
// Handle status mapping for git status format
|
|
1484
|
-
let status = change.status
|
|
1485
|
-
if (status === '??')
|
|
1486
|
-
|
|
1564
|
+
let status = change.status
|
|
1565
|
+
if (status === '??') {
|
|
1566
|
+
status = 'A' // Untracked files are "added"
|
|
1567
|
+
}
|
|
1568
|
+
if (status.length > 1) {
|
|
1569
|
+
status = status[0] // Take first character for multiple status codes
|
|
1570
|
+
}
|
|
1487
1571
|
|
|
1488
1572
|
switch (status) {
|
|
1489
|
-
case 'A':
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
case '
|
|
1573
|
+
case 'A':
|
|
1574
|
+
stats.added++
|
|
1575
|
+
break
|
|
1576
|
+
case 'M':
|
|
1577
|
+
stats.modified++
|
|
1578
|
+
break
|
|
1579
|
+
case 'D':
|
|
1580
|
+
stats.deleted++
|
|
1581
|
+
break
|
|
1582
|
+
case 'R':
|
|
1583
|
+
stats.renamed++
|
|
1584
|
+
break
|
|
1493
1585
|
}
|
|
1494
1586
|
|
|
1495
|
-
const filePath = change.filePath || change.path
|
|
1496
|
-
const category = categorizeFile(filePath)
|
|
1497
|
-
const language = detectLanguage(filePath)
|
|
1587
|
+
const filePath = change.filePath || change.path
|
|
1588
|
+
const category = categorizeFile(filePath)
|
|
1589
|
+
const language = detectLanguage(filePath)
|
|
1498
1590
|
|
|
1499
1591
|
// Group files by category
|
|
1500
1592
|
if (!categories[category]) {
|
|
1501
|
-
categories[category] = []
|
|
1593
|
+
categories[category] = []
|
|
1502
1594
|
}
|
|
1503
1595
|
categories[category].push({
|
|
1504
|
-
status
|
|
1596
|
+
status,
|
|
1505
1597
|
path: filePath,
|
|
1506
|
-
language
|
|
1507
|
-
})
|
|
1598
|
+
language,
|
|
1599
|
+
})
|
|
1508
1600
|
|
|
1509
|
-
languages.add(language)
|
|
1510
|
-
})
|
|
1601
|
+
languages.add(language)
|
|
1602
|
+
})
|
|
1511
1603
|
|
|
1512
|
-
const parts = []
|
|
1513
|
-
if (stats.added > 0)
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (stats.
|
|
1604
|
+
const parts = []
|
|
1605
|
+
if (stats.added > 0) {
|
|
1606
|
+
parts.push(`${stats.added} added`)
|
|
1607
|
+
}
|
|
1608
|
+
if (stats.modified > 0) {
|
|
1609
|
+
parts.push(`${stats.modified} modified`)
|
|
1610
|
+
}
|
|
1611
|
+
if (stats.deleted > 0) {
|
|
1612
|
+
parts.push(`${stats.deleted} deleted`)
|
|
1613
|
+
}
|
|
1614
|
+
if (stats.renamed > 0) {
|
|
1615
|
+
parts.push(`${stats.renamed} renamed`)
|
|
1616
|
+
}
|
|
1517
1617
|
|
|
1518
|
-
let summary = `${changes.length} files changed: ${parts.join(', ')}
|
|
1618
|
+
let summary = `${changes.length} files changed: ${parts.join(', ')}`
|
|
1519
1619
|
|
|
1520
1620
|
if (Object.keys(categories).length > 0) {
|
|
1521
|
-
summary += `. Affected areas: ${Object.keys(categories).join(', ')}
|
|
1621
|
+
summary += `. Affected areas: ${Object.keys(categories).join(', ')}`
|
|
1522
1622
|
}
|
|
1523
1623
|
|
|
1524
1624
|
return {
|
|
1525
1625
|
summary,
|
|
1526
1626
|
categories,
|
|
1527
1627
|
stats,
|
|
1528
|
-
languages: Array.from(languages)
|
|
1529
|
-
}
|
|
1628
|
+
languages: Array.from(languages),
|
|
1629
|
+
}
|
|
1530
1630
|
}
|
|
1531
1631
|
|
|
1532
1632
|
/**
|
|
1533
1633
|
* Build commit changelog from analyzed commits
|
|
1534
1634
|
*/
|
|
1535
1635
|
export function buildCommitChangelog(analyzedCommits, releaseInsights, version, options = {}) {
|
|
1536
|
-
const { metrics, includeAttribution = true } = options
|
|
1537
|
-
const currentDate = new Date().toISOString().split('T')[0]
|
|
1538
|
-
const versionHeader = version || 'Unreleased'
|
|
1636
|
+
const { metrics, includeAttribution = true } = options
|
|
1637
|
+
const currentDate = new Date().toISOString().split('T')[0]
|
|
1638
|
+
const versionHeader = version || 'Unreleased'
|
|
1539
1639
|
|
|
1540
|
-
let changelog = `# Changelog\n\n## [${versionHeader}] - ${currentDate}\n\n
|
|
1640
|
+
let changelog = `# Changelog\n\n## [${versionHeader}] - ${currentDate}\n\n`
|
|
1541
1641
|
|
|
1542
1642
|
// Add release summary with business impact
|
|
1543
|
-
if (releaseInsights
|
|
1544
|
-
changelog += `### 📋 Release Summary\n${releaseInsights.summary}\n\n
|
|
1545
|
-
changelog += `**Business Impact**: ${releaseInsights.businessImpact}\n
|
|
1546
|
-
changelog += `**Complexity**: ${releaseInsights.complexity}\n
|
|
1547
|
-
if (
|
|
1548
|
-
|
|
1643
|
+
if (releaseInsights?.summary) {
|
|
1644
|
+
changelog += `### 📋 Release Summary\n${releaseInsights.summary}\n\n`
|
|
1645
|
+
changelog += `**Business Impact**: ${releaseInsights.businessImpact}\n`
|
|
1646
|
+
changelog += `**Complexity**: ${releaseInsights.complexity}\n`
|
|
1647
|
+
if (
|
|
1648
|
+
releaseInsights.deploymentRequirements &&
|
|
1649
|
+
releaseInsights.deploymentRequirements.length > 0
|
|
1650
|
+
) {
|
|
1651
|
+
changelog += `**Deployment Requirements**: ${releaseInsights.deploymentRequirements.join(', ')}\n`
|
|
1549
1652
|
}
|
|
1550
|
-
changelog += '\n'
|
|
1653
|
+
changelog += '\n'
|
|
1551
1654
|
}
|
|
1552
1655
|
|
|
1553
1656
|
// Use unified format with impact-based ordering instead of categorized sections
|
|
1554
|
-
changelog +=
|
|
1657
|
+
changelog += '## Changes\n\n'
|
|
1555
1658
|
|
|
1556
1659
|
// Sort commits by business impact (breaking/critical first, then by impact level)
|
|
1557
1660
|
const sortedCommits = analyzedCommits.sort((a, b) => {
|
|
1558
1661
|
// Breaking changes always come first
|
|
1559
|
-
if (a.breaking && !b.breaking)
|
|
1560
|
-
|
|
1662
|
+
if (a.breaking && !b.breaking) {
|
|
1663
|
+
return -1
|
|
1664
|
+
}
|
|
1665
|
+
if (!a.breaking && b.breaking) {
|
|
1666
|
+
return 1
|
|
1667
|
+
}
|
|
1561
1668
|
|
|
1562
1669
|
// Then sort by impact level
|
|
1563
|
-
const impactOrder = { critical: 0, high: 1, medium: 2, low: 3, minimal: 4 }
|
|
1564
|
-
const aImpact = impactOrder[a.aiSummary?.impact] ?? 5
|
|
1565
|
-
const bImpact = impactOrder[b.aiSummary?.impact] ?? 5
|
|
1670
|
+
const impactOrder = { critical: 0, high: 1, medium: 2, low: 3, minimal: 4 }
|
|
1671
|
+
const aImpact = impactOrder[a.aiSummary?.impact] ?? 5
|
|
1672
|
+
const bImpact = impactOrder[b.aiSummary?.impact] ?? 5
|
|
1566
1673
|
|
|
1567
|
-
return aImpact - bImpact
|
|
1568
|
-
})
|
|
1674
|
+
return aImpact - bImpact
|
|
1675
|
+
})
|
|
1569
1676
|
|
|
1570
|
-
sortedCommits.forEach(commit => {
|
|
1571
|
-
const type = commit.breaking ? 'breaking' :
|
|
1572
|
-
const summary = commit.aiSummary?.summary || commit.subject
|
|
1573
|
-
const confidence = commit.aiSummary?.confidence
|
|
1677
|
+
sortedCommits.forEach((commit) => {
|
|
1678
|
+
const type = commit.breaking ? 'breaking' : commit.type || 'chore'
|
|
1679
|
+
const summary = commit.aiSummary?.summary || commit.subject
|
|
1680
|
+
const confidence = commit.aiSummary?.confidence
|
|
1681
|
+
? `${Math.round(commit.aiSummary.confidence * 100)}%`
|
|
1682
|
+
: '85%'
|
|
1574
1683
|
|
|
1575
1684
|
// Format: - (tag) Brief description - Detailed explanation (hash) (confidence%)
|
|
1576
|
-
let line = `- (${type}) ${summary}
|
|
1685
|
+
let line = `- (${type}) ${summary}`
|
|
1577
1686
|
|
|
1578
1687
|
// Add breaking change indicator
|
|
1579
1688
|
if (commit.breaking || commit.aiSummary?.breaking) {
|
|
1580
|
-
line += ' ⚠️ BREAKING CHANGE'
|
|
1689
|
+
line += ' ⚠️ BREAKING CHANGE'
|
|
1581
1690
|
}
|
|
1582
1691
|
|
|
1583
1692
|
// Add high/critical impact indicator
|
|
1584
1693
|
if (commit.aiSummary?.impact === 'critical' || commit.aiSummary?.impact === 'high') {
|
|
1585
|
-
line += ' 🔥'
|
|
1694
|
+
line += ' 🔥'
|
|
1586
1695
|
}
|
|
1587
1696
|
|
|
1588
1697
|
// Add detailed technical explanation
|
|
1589
1698
|
if (commit.aiSummary?.technicalDetails) {
|
|
1590
|
-
line += ` - ${commit.aiSummary.technicalDetails}
|
|
1591
|
-
} else if (
|
|
1592
|
-
|
|
1699
|
+
line += ` - ${commit.aiSummary.technicalDetails}`
|
|
1700
|
+
} else if (
|
|
1701
|
+
commit.aiSummary?.description &&
|
|
1702
|
+
commit.aiSummary.description !== commit.aiSummary.summary
|
|
1703
|
+
) {
|
|
1704
|
+
line += ` - ${commit.aiSummary.description}`
|
|
1593
1705
|
}
|
|
1594
1706
|
|
|
1595
1707
|
// Add commit hash and confidence
|
|
1596
|
-
line += ` (${commit.hash}) (${confidence})
|
|
1708
|
+
line += ` (${commit.hash}) (${confidence})`
|
|
1597
1709
|
|
|
1598
|
-
changelog += `${line}\n
|
|
1710
|
+
changelog += `${line}\n`
|
|
1599
1711
|
|
|
1600
1712
|
// Add sub-bullets for additional details
|
|
1601
1713
|
if (commit.aiSummary?.highlights?.length > 1) {
|
|
1602
|
-
commit.aiSummary.highlights.slice(1).forEach(highlight => {
|
|
1603
|
-
changelog += ` - ${highlight}\n
|
|
1604
|
-
})
|
|
1714
|
+
commit.aiSummary.highlights.slice(1).forEach((highlight) => {
|
|
1715
|
+
changelog += ` - ${highlight}\n`
|
|
1716
|
+
})
|
|
1605
1717
|
}
|
|
1606
1718
|
|
|
1607
1719
|
// Add migration notes
|
|
1608
1720
|
if (commit.aiSummary?.migrationNotes) {
|
|
1609
|
-
changelog += ` - **Migration**: ${commit.aiSummary.migrationNotes}\n
|
|
1721
|
+
changelog += ` - **Migration**: ${commit.aiSummary.migrationNotes}\n`
|
|
1610
1722
|
}
|
|
1611
1723
|
|
|
1612
|
-
changelog += '\n'
|
|
1613
|
-
})
|
|
1724
|
+
changelog += '\n'
|
|
1725
|
+
})
|
|
1614
1726
|
|
|
1615
1727
|
// Add detailed risk assessment
|
|
1616
1728
|
if (releaseInsights && (releaseInsights.riskLevel !== 'low' || releaseInsights.breaking)) {
|
|
1617
|
-
changelog +=
|
|
1618
|
-
changelog += `**Risk Level:** ${releaseInsights.riskLevel.toUpperCase()}\n\n
|
|
1729
|
+
changelog += '### ⚠️ Risk Assessment\n'
|
|
1730
|
+
changelog += `**Risk Level:** ${releaseInsights.riskLevel.toUpperCase()}\n\n`
|
|
1619
1731
|
|
|
1620
1732
|
if (releaseInsights.breaking) {
|
|
1621
|
-
changelog +=
|
|
1733
|
+
changelog +=
|
|
1734
|
+
'🚨 **Breaking Changes**: This release contains breaking changes. Please review migration notes above.\n\n'
|
|
1622
1735
|
}
|
|
1623
1736
|
|
|
1624
|
-
if (
|
|
1625
|
-
|
|
1626
|
-
releaseInsights.deploymentRequirements.
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1737
|
+
if (
|
|
1738
|
+
releaseInsights.deploymentRequirements &&
|
|
1739
|
+
releaseInsights.deploymentRequirements.length > 0
|
|
1740
|
+
) {
|
|
1741
|
+
changelog += '📋 **Deployment Requirements**:\n'
|
|
1742
|
+
releaseInsights.deploymentRequirements.forEach((req) => {
|
|
1743
|
+
changelog += `- ${req}\n`
|
|
1744
|
+
})
|
|
1745
|
+
changelog += '\n'
|
|
1630
1746
|
}
|
|
1631
1747
|
}
|
|
1632
1748
|
|
|
1633
1749
|
// Add performance metrics
|
|
1634
1750
|
if (metrics) {
|
|
1635
|
-
changelog +=
|
|
1636
|
-
changelog += `- **Total Commits**: ${analyzedCommits.length}\n
|
|
1751
|
+
changelog += '### 📊 Generation Metrics\n'
|
|
1752
|
+
changelog += `- **Total Commits**: ${analyzedCommits.length}\n`
|
|
1637
1753
|
if (metrics.startTime) {
|
|
1638
|
-
changelog += `- **Processing Time**: ${formatDuration(Date.now() - metrics.startTime)}\n
|
|
1754
|
+
changelog += `- **Processing Time**: ${formatDuration(Date.now() - metrics.startTime)}\n`
|
|
1639
1755
|
}
|
|
1640
|
-
changelog += `- **AI Calls**: ${metrics.apiCalls || 0}\n
|
|
1756
|
+
changelog += `- **AI Calls**: ${metrics.apiCalls || 0}\n`
|
|
1641
1757
|
if (metrics.totalTokens > 0) {
|
|
1642
|
-
changelog += `- **Tokens Used**: ${metrics.totalTokens.toLocaleString()}\n
|
|
1758
|
+
changelog += `- **Tokens Used**: ${metrics.totalTokens.toLocaleString()}\n`
|
|
1643
1759
|
}
|
|
1644
|
-
changelog += `- **Batches Processed**: ${metrics.batchesProcessed || 0}\n
|
|
1760
|
+
changelog += `- **Batches Processed**: ${metrics.batchesProcessed || 0}\n`
|
|
1645
1761
|
if (metrics.errors > 0) {
|
|
1646
|
-
changelog += `- **Errors**: ${metrics.errors}\n
|
|
1762
|
+
changelog += `- **Errors**: ${metrics.errors}\n`
|
|
1647
1763
|
}
|
|
1648
|
-
changelog += '\n'
|
|
1764
|
+
changelog += '\n'
|
|
1649
1765
|
}
|
|
1650
1766
|
|
|
1651
1767
|
// Add attribution footer
|
|
1652
1768
|
if (includeAttribution !== false) {
|
|
1653
|
-
changelog +=
|
|
1769
|
+
changelog +=
|
|
1770
|
+
'---\n\n*Generated using [ai-changelog-generator](https://github.com/entro314-labs/AI-changelog-generator) - AI-powered changelog generation for Git repositories*\n'
|
|
1654
1771
|
}
|
|
1655
1772
|
|
|
1656
|
-
return changelog
|
|
1773
|
+
return changelog
|
|
1657
1774
|
}
|
|
1658
1775
|
|
|
1659
1776
|
/**
|
|
@@ -1661,22 +1778,22 @@ export function buildCommitChangelog(analyzedCommits, releaseInsights, version,
|
|
|
1661
1778
|
* Helper for processWorkingDirectoryChanges
|
|
1662
1779
|
*/
|
|
1663
1780
|
function generateWorkspaceChangesSummary(categorizedChanges) {
|
|
1664
|
-
const parts = []
|
|
1781
|
+
const parts = []
|
|
1665
1782
|
|
|
1666
1783
|
if (categorizedChanges.added.length > 0) {
|
|
1667
|
-
parts.push(`${categorizedChanges.added.length} files added`)
|
|
1784
|
+
parts.push(`${categorizedChanges.added.length} files added`)
|
|
1668
1785
|
}
|
|
1669
1786
|
if (categorizedChanges.modified.length > 0) {
|
|
1670
|
-
parts.push(`${categorizedChanges.modified.length} files modified`)
|
|
1787
|
+
parts.push(`${categorizedChanges.modified.length} files modified`)
|
|
1671
1788
|
}
|
|
1672
1789
|
if (categorizedChanges.deleted.length > 0) {
|
|
1673
|
-
parts.push(`${categorizedChanges.deleted.length} files deleted`)
|
|
1790
|
+
parts.push(`${categorizedChanges.deleted.length} files deleted`)
|
|
1674
1791
|
}
|
|
1675
1792
|
if (categorizedChanges.renamed.length > 0) {
|
|
1676
|
-
parts.push(`${categorizedChanges.renamed.length} files renamed`)
|
|
1793
|
+
parts.push(`${categorizedChanges.renamed.length} files renamed`)
|
|
1677
1794
|
}
|
|
1678
1795
|
|
|
1679
|
-
return parts.length > 0 ? parts.join(', ') : 'No changes detected'
|
|
1796
|
+
return parts.length > 0 ? parts.join(', ') : 'No changes detected'
|
|
1680
1797
|
}
|
|
1681
1798
|
|
|
1682
1799
|
/**
|
|
@@ -1684,10 +1801,16 @@ function generateWorkspaceChangesSummary(categorizedChanges) {
|
|
|
1684
1801
|
* Helper for processWorkingDirectoryChanges
|
|
1685
1802
|
*/
|
|
1686
1803
|
function assessWorkspaceComplexity(categorizedChanges, totalFiles) {
|
|
1687
|
-
if (totalFiles > 20)
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1804
|
+
if (totalFiles > 20) {
|
|
1805
|
+
return 'high'
|
|
1806
|
+
}
|
|
1807
|
+
if (totalFiles > 5) {
|
|
1808
|
+
return 'medium'
|
|
1809
|
+
}
|
|
1810
|
+
if (categorizedChanges.deleted.length > 0) {
|
|
1811
|
+
return 'medium'
|
|
1812
|
+
}
|
|
1813
|
+
return 'low'
|
|
1691
1814
|
}
|
|
1692
1815
|
|
|
1693
1816
|
// ========================================
|
|
@@ -1698,48 +1821,48 @@ function assessWorkspaceComplexity(categorizedChanges, totalFiles) {
|
|
|
1698
1821
|
* Handle unified output for analysis commands
|
|
1699
1822
|
*/
|
|
1700
1823
|
export function handleUnifiedOutput(data, config) {
|
|
1701
|
-
const { format = 'markdown', outputFile, silent } = config
|
|
1824
|
+
const { format = 'markdown', outputFile, silent } = config
|
|
1702
1825
|
if (format === 'json') {
|
|
1703
|
-
const jsonOutput = safeJsonStringify(data)
|
|
1826
|
+
const jsonOutput = safeJsonStringify(data)
|
|
1704
1827
|
|
|
1705
1828
|
if (outputFile) {
|
|
1706
1829
|
// Write to file
|
|
1707
1830
|
try {
|
|
1708
|
-
fs.writeFileSync(outputFile, jsonOutput, 'utf8')
|
|
1831
|
+
fs.writeFileSync(outputFile, jsonOutput, 'utf8')
|
|
1709
1832
|
if (!silent) {
|
|
1710
|
-
console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
|
|
1833
|
+
console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
|
|
1711
1834
|
}
|
|
1712
1835
|
} catch (error) {
|
|
1713
|
-
console.error(`❌ Error writing to file: ${error.message}`)
|
|
1836
|
+
console.error(`❌ Error writing to file: ${error.message}`)
|
|
1714
1837
|
if (!silent) {
|
|
1715
|
-
console.log(jsonOutput)
|
|
1838
|
+
console.log(jsonOutput)
|
|
1716
1839
|
}
|
|
1717
1840
|
}
|
|
1718
1841
|
} else if (!silent) {
|
|
1719
|
-
console.log(jsonOutput)
|
|
1842
|
+
console.log(jsonOutput)
|
|
1720
1843
|
}
|
|
1721
1844
|
} else {
|
|
1722
1845
|
// Markdown format (default)
|
|
1723
|
-
const markdownOutput = typeof data === 'string' ? data : safeJsonStringify(data)
|
|
1846
|
+
const markdownOutput = typeof data === 'string' ? data : safeJsonStringify(data)
|
|
1724
1847
|
|
|
1725
1848
|
if (outputFile) {
|
|
1726
1849
|
try {
|
|
1727
|
-
fs.writeFileSync(outputFile, markdownOutput, 'utf8')
|
|
1850
|
+
fs.writeFileSync(outputFile, markdownOutput, 'utf8')
|
|
1728
1851
|
if (!silent) {
|
|
1729
|
-
console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
|
|
1852
|
+
console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
|
|
1730
1853
|
}
|
|
1731
1854
|
} catch (error) {
|
|
1732
|
-
console.error(`❌ Error writing to file: ${error.message}`)
|
|
1855
|
+
console.error(`❌ Error writing to file: ${error.message}`)
|
|
1733
1856
|
if (!silent) {
|
|
1734
|
-
console.log(markdownOutput)
|
|
1857
|
+
console.log(markdownOutput)
|
|
1735
1858
|
}
|
|
1736
1859
|
}
|
|
1737
1860
|
} else if (!silent) {
|
|
1738
|
-
console.log(markdownOutput)
|
|
1861
|
+
console.log(markdownOutput)
|
|
1739
1862
|
}
|
|
1740
1863
|
}
|
|
1741
1864
|
|
|
1742
|
-
return data
|
|
1865
|
+
return data
|
|
1743
1866
|
}
|
|
1744
1867
|
|
|
1745
1868
|
/**
|
|
@@ -1747,7 +1870,7 @@ export function handleUnifiedOutput(data, config) {
|
|
|
1747
1870
|
* Wrapper around handleUnifiedOutput for backwards compatibility
|
|
1748
1871
|
*/
|
|
1749
1872
|
export function outputData(data, format = 'markdown') {
|
|
1750
|
-
return handleUnifiedOutput(data, { format, silent: false })
|
|
1873
|
+
return handleUnifiedOutput(data, { format, silent: false })
|
|
1751
1874
|
}
|
|
1752
1875
|
|
|
1753
1876
|
// ========================================
|
|
@@ -1758,9 +1881,9 @@ export function outputData(data, format = 'markdown') {
|
|
|
1758
1881
|
* Run interactive mode with full menu system
|
|
1759
1882
|
*/
|
|
1760
1883
|
export async function runInteractiveMode() {
|
|
1761
|
-
const { select, intro } = await import('@clack/prompts')
|
|
1884
|
+
const { select, intro } = await import('@clack/prompts')
|
|
1762
1885
|
|
|
1763
|
-
intro(colors.header('🎮 AI Changelog Generator - Interactive Mode'))
|
|
1886
|
+
intro(colors.header('🎮 AI Changelog Generator - Interactive Mode'))
|
|
1764
1887
|
|
|
1765
1888
|
const choices = [
|
|
1766
1889
|
{ value: 'changelog-recent', label: '📝 Generate changelog from recent commits' },
|
|
@@ -1770,114 +1893,131 @@ export async function runInteractiveMode() {
|
|
|
1770
1893
|
{ value: 'commit-message', label: '💬 Generate commit message for current changes' },
|
|
1771
1894
|
{ value: 'configure-providers', label: '⚙️ Configure AI providers' },
|
|
1772
1895
|
{ value: 'validate-config', label: '🔍 Validate configuration' },
|
|
1773
|
-
{ value: 'exit', label: '❌ Exit' }
|
|
1774
|
-
]
|
|
1896
|
+
{ value: 'exit', label: '❌ Exit' },
|
|
1897
|
+
]
|
|
1775
1898
|
|
|
1776
1899
|
const action = await select({
|
|
1777
1900
|
message: 'What would you like to do?',
|
|
1778
|
-
options: choices
|
|
1779
|
-
})
|
|
1901
|
+
options: choices,
|
|
1902
|
+
})
|
|
1780
1903
|
|
|
1781
|
-
return { action, timestamp: new Date().toISOString() }
|
|
1904
|
+
return { action, timestamp: new Date().toISOString() }
|
|
1782
1905
|
}
|
|
1783
1906
|
|
|
1784
1907
|
/**
|
|
1785
1908
|
* Analyze changes for commit message with detailed analysis
|
|
1786
1909
|
*/
|
|
1787
1910
|
export function analyzeChangesForCommitMessage(changes, includeScope = false) {
|
|
1788
|
-
if (!changes
|
|
1911
|
+
if (!(changes && Array.isArray(changes)) || changes.length === 0) {
|
|
1789
1912
|
return {
|
|
1790
1913
|
summary: 'No changes detected',
|
|
1791
1914
|
scope: null,
|
|
1792
1915
|
changes: 0,
|
|
1793
1916
|
recommendations: ['Make some changes first'],
|
|
1794
|
-
type: 'chore'
|
|
1795
|
-
}
|
|
1917
|
+
type: 'chore',
|
|
1918
|
+
}
|
|
1796
1919
|
}
|
|
1797
1920
|
|
|
1798
1921
|
// Categorize changes
|
|
1799
|
-
const categories = {}
|
|
1800
|
-
const scopes = new Set()
|
|
1801
|
-
let hasTests = false
|
|
1802
|
-
let hasDocs = false
|
|
1803
|
-
let hasConfig = false
|
|
1804
|
-
let hasSource = false
|
|
1922
|
+
const categories = {}
|
|
1923
|
+
const scopes = new Set()
|
|
1924
|
+
let hasTests = false
|
|
1925
|
+
let hasDocs = false
|
|
1926
|
+
let hasConfig = false
|
|
1927
|
+
let hasSource = false
|
|
1805
1928
|
|
|
1806
|
-
changes.forEach(change => {
|
|
1807
|
-
const filePath = change.path || change.filePath || ''
|
|
1808
|
-
const category = categorizeFile(filePath)
|
|
1929
|
+
changes.forEach((change) => {
|
|
1930
|
+
const filePath = change.path || change.filePath || ''
|
|
1931
|
+
const category = categorizeFile(filePath)
|
|
1809
1932
|
|
|
1810
|
-
if (!categories[category])
|
|
1811
|
-
|
|
1933
|
+
if (!categories[category]) {
|
|
1934
|
+
categories[category] = []
|
|
1935
|
+
}
|
|
1936
|
+
categories[category].push(change)
|
|
1812
1937
|
|
|
1813
1938
|
// Extract scope from path
|
|
1814
1939
|
if (includeScope) {
|
|
1815
|
-
const pathParts = filePath.split('/')
|
|
1940
|
+
const pathParts = filePath.split('/')
|
|
1816
1941
|
if (pathParts.length > 1) {
|
|
1817
|
-
const scope = pathParts
|
|
1942
|
+
const scope = pathParts.at(-2)
|
|
1818
1943
|
if (scope && scope !== '.' && scope !== '..') {
|
|
1819
|
-
scopes.add(scope)
|
|
1944
|
+
scopes.add(scope)
|
|
1820
1945
|
}
|
|
1821
1946
|
}
|
|
1822
1947
|
}
|
|
1823
1948
|
|
|
1824
1949
|
// Track content types
|
|
1825
|
-
if (category === 'tests')
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
if (category === '
|
|
1829
|
-
|
|
1950
|
+
if (category === 'tests') {
|
|
1951
|
+
hasTests = true
|
|
1952
|
+
}
|
|
1953
|
+
if (category === 'documentation') {
|
|
1954
|
+
hasDocs = true
|
|
1955
|
+
}
|
|
1956
|
+
if (category === 'configuration') {
|
|
1957
|
+
hasConfig = true
|
|
1958
|
+
}
|
|
1959
|
+
if (category === 'source') {
|
|
1960
|
+
hasSource = true
|
|
1961
|
+
}
|
|
1962
|
+
})
|
|
1830
1963
|
|
|
1831
1964
|
// Determine primary category and type
|
|
1832
|
-
const primaryCategory = Object.keys(categories)
|
|
1833
|
-
|
|
1965
|
+
const primaryCategory = Object.keys(categories).sort(
|
|
1966
|
+
(a, b) => categories[b].length - categories[a].length
|
|
1967
|
+
)[0]
|
|
1834
1968
|
|
|
1835
1969
|
const typeMapping = {
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
}
|
|
1970
|
+
source: 'feat',
|
|
1971
|
+
tests: 'test',
|
|
1972
|
+
documentation: 'docs',
|
|
1973
|
+
configuration: 'chore',
|
|
1974
|
+
build: 'build',
|
|
1975
|
+
frontend: 'feat',
|
|
1976
|
+
assets: 'chore',
|
|
1977
|
+
}
|
|
1844
1978
|
|
|
1845
|
-
const commitType = typeMapping[primaryCategory] || 'chore'
|
|
1979
|
+
const commitType = typeMapping[primaryCategory] || 'chore'
|
|
1846
1980
|
|
|
1847
1981
|
// Generate summary
|
|
1848
|
-
const fileCount = changes.length
|
|
1849
|
-
const addedFiles = changes.filter(c => c.status === 'A').length
|
|
1850
|
-
const modifiedFiles = changes.filter(c => c.status === 'M').length
|
|
1851
|
-
const deletedFiles = changes.filter(c => c.status === 'D').length
|
|
1982
|
+
const fileCount = changes.length
|
|
1983
|
+
const addedFiles = changes.filter((c) => c.status === 'A').length
|
|
1984
|
+
const modifiedFiles = changes.filter((c) => c.status === 'M').length
|
|
1985
|
+
const deletedFiles = changes.filter((c) => c.status === 'D').length
|
|
1852
1986
|
|
|
1853
|
-
let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed
|
|
1987
|
+
let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`
|
|
1854
1988
|
|
|
1855
|
-
const changeDetails = []
|
|
1856
|
-
if (addedFiles > 0)
|
|
1857
|
-
|
|
1858
|
-
|
|
1989
|
+
const changeDetails = []
|
|
1990
|
+
if (addedFiles > 0) {
|
|
1991
|
+
changeDetails.push(`${addedFiles} added`)
|
|
1992
|
+
}
|
|
1993
|
+
if (modifiedFiles > 0) {
|
|
1994
|
+
changeDetails.push(`${modifiedFiles} modified`)
|
|
1995
|
+
}
|
|
1996
|
+
if (deletedFiles > 0) {
|
|
1997
|
+
changeDetails.push(`${deletedFiles} deleted`)
|
|
1998
|
+
}
|
|
1859
1999
|
|
|
1860
2000
|
if (changeDetails.length > 0) {
|
|
1861
|
-
summary += ` (${changeDetails.join(', ')})
|
|
2001
|
+
summary += ` (${changeDetails.join(', ')})`
|
|
1862
2002
|
}
|
|
1863
2003
|
|
|
1864
2004
|
// Generate recommendations
|
|
1865
|
-
const recommendations = []
|
|
2005
|
+
const recommendations = []
|
|
1866
2006
|
|
|
1867
2007
|
if (hasSource && !hasTests) {
|
|
1868
|
-
recommendations.push('Consider adding tests for source changes')
|
|
2008
|
+
recommendations.push('Consider adding tests for source changes')
|
|
1869
2009
|
}
|
|
1870
2010
|
|
|
1871
2011
|
if (hasSource && !hasDocs) {
|
|
1872
|
-
recommendations.push('Consider updating documentation')
|
|
2012
|
+
recommendations.push('Consider updating documentation')
|
|
1873
2013
|
}
|
|
1874
2014
|
|
|
1875
2015
|
if (fileCount > 10) {
|
|
1876
|
-
recommendations.push('Consider breaking this into smaller commits')
|
|
2016
|
+
recommendations.push('Consider breaking this into smaller commits')
|
|
1877
2017
|
}
|
|
1878
2018
|
|
|
1879
2019
|
if (hasConfig) {
|
|
1880
|
-
recommendations.push('Review configuration changes carefully')
|
|
2020
|
+
recommendations.push('Review configuration changes carefully')
|
|
1881
2021
|
}
|
|
1882
2022
|
|
|
1883
2023
|
return {
|
|
@@ -1895,68 +2035,70 @@ export function analyzeChangesForCommitMessage(changes, includeScope = false) {
|
|
|
1895
2035
|
hasTests,
|
|
1896
2036
|
hasDocs,
|
|
1897
2037
|
hasConfig,
|
|
1898
|
-
hasSource
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
2038
|
+
hasSource,
|
|
2039
|
+
},
|
|
2040
|
+
}
|
|
1901
2041
|
}
|
|
1902
2042
|
|
|
1903
2043
|
/**
|
|
1904
2044
|
* Select specific commits with interactive interface
|
|
1905
2045
|
*/
|
|
1906
2046
|
export async function selectSpecificCommits(maxCommits = 20) {
|
|
1907
|
-
const { multiselect, note } = await import('@clack/prompts')
|
|
1908
|
-
const { execSync } = await import('child_process')
|
|
2047
|
+
const { multiselect, note } = await import('@clack/prompts')
|
|
2048
|
+
const { execSync } = await import('node:child_process')
|
|
1909
2049
|
|
|
1910
2050
|
try {
|
|
1911
|
-
note('🔍 Fetching recent commits...', 'Please wait')
|
|
2051
|
+
note('🔍 Fetching recent commits...', 'Please wait')
|
|
1912
2052
|
|
|
1913
2053
|
// Get recent commits with formatted output
|
|
1914
|
-
const gitCommand = `git log --oneline --no-merges -${maxCommits} --pretty=format:"%h|%s|%an|%ar"
|
|
1915
|
-
const output = execSync(gitCommand, { encoding: 'utf8' })
|
|
2054
|
+
const gitCommand = `git log --oneline --no-merges -${maxCommits} --pretty=format:"%h|%s|%an|%ar"`
|
|
2055
|
+
const output = execSync(gitCommand, { encoding: 'utf8' })
|
|
1916
2056
|
|
|
1917
2057
|
if (!output.trim()) {
|
|
1918
|
-
note('No commits found', 'Warning')
|
|
1919
|
-
return []
|
|
2058
|
+
note('No commits found', 'Warning')
|
|
2059
|
+
return []
|
|
1920
2060
|
}
|
|
1921
2061
|
|
|
1922
2062
|
// Parse commits
|
|
1923
|
-
const commits = output
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2063
|
+
const commits = output
|
|
2064
|
+
.trim()
|
|
2065
|
+
.split('\n')
|
|
2066
|
+
.map((line) => {
|
|
2067
|
+
const [hash, subject, author, date] = line.split('|')
|
|
2068
|
+
return {
|
|
2069
|
+
hash,
|
|
2070
|
+
subject: subject || 'No subject',
|
|
2071
|
+
author: author || 'Unknown',
|
|
2072
|
+
date: date || 'Unknown',
|
|
2073
|
+
display: `${hash} ${subject} (${author}, ${date})`,
|
|
2074
|
+
}
|
|
2075
|
+
})
|
|
1933
2076
|
|
|
1934
2077
|
if (commits.length === 0) {
|
|
1935
|
-
note('No commits available', 'Warning')
|
|
1936
|
-
return []
|
|
2078
|
+
note('No commits available', 'Warning')
|
|
2079
|
+
return []
|
|
1937
2080
|
}
|
|
1938
2081
|
|
|
1939
|
-
note(`Found ${commits.length} recent commits`, 'Info')
|
|
2082
|
+
note(`Found ${commits.length} recent commits`, 'Info')
|
|
1940
2083
|
|
|
1941
2084
|
// Create choices for selection
|
|
1942
|
-
const choices = commits.map(commit => ({
|
|
2085
|
+
const choices = commits.map((commit) => ({
|
|
1943
2086
|
value: commit.hash,
|
|
1944
2087
|
label: commit.display,
|
|
1945
|
-
hint: commit.hash
|
|
1946
|
-
}))
|
|
2088
|
+
hint: commit.hash,
|
|
2089
|
+
}))
|
|
1947
2090
|
|
|
1948
2091
|
const selectedCommits = await multiselect({
|
|
1949
2092
|
message: 'Select commits to include in changelog:',
|
|
1950
2093
|
options: choices,
|
|
1951
|
-
required: true
|
|
1952
|
-
})
|
|
1953
|
-
|
|
1954
|
-
note(`✅ Selected ${selectedCommits.length} commits`, 'Success');
|
|
1955
|
-
return selectedCommits;
|
|
2094
|
+
required: true,
|
|
2095
|
+
})
|
|
1956
2096
|
|
|
2097
|
+
note(`✅ Selected ${selectedCommits.length} commits`, 'Success')
|
|
2098
|
+
return selectedCommits
|
|
1957
2099
|
} catch (error) {
|
|
1958
|
-
note(`Error fetching commits: ${error.message}`, 'Error')
|
|
1959
|
-
return []
|
|
2100
|
+
note(`Error fetching commits: ${error.message}`, 'Error')
|
|
2101
|
+
return []
|
|
1960
2102
|
}
|
|
1961
2103
|
}
|
|
1962
2104
|
|
|
@@ -1968,26 +2110,33 @@ export async function selectSpecificCommits(maxCommits = 20) {
|
|
|
1968
2110
|
* Assess change complexity based on diff output
|
|
1969
2111
|
*/
|
|
1970
2112
|
export function assessChangeComplexity(diff) {
|
|
1971
|
-
if (!diff || diff === 'Binary file or diff unavailable')
|
|
2113
|
+
if (!diff || diff === 'Binary file or diff unavailable') {
|
|
2114
|
+
return { score: 1 }
|
|
2115
|
+
}
|
|
1972
2116
|
|
|
1973
|
-
const lines = diff.split('\n')
|
|
1974
|
-
const additions = lines.filter(line => line.startsWith('+')).length
|
|
1975
|
-
const deletions = lines.filter(line => line.startsWith('-')).length
|
|
1976
|
-
const total = additions + deletions
|
|
2117
|
+
const lines = diff.split('\n')
|
|
2118
|
+
const additions = lines.filter((line) => line.startsWith('+')).length
|
|
2119
|
+
const deletions = lines.filter((line) => line.startsWith('-')).length
|
|
2120
|
+
const total = additions + deletions
|
|
1977
2121
|
|
|
1978
|
-
let score = 1
|
|
1979
|
-
if (total >= 200)
|
|
1980
|
-
|
|
1981
|
-
else if (total >=
|
|
1982
|
-
|
|
2122
|
+
let score = 1
|
|
2123
|
+
if (total >= 200) {
|
|
2124
|
+
score = 5
|
|
2125
|
+
} else if (total >= 100) {
|
|
2126
|
+
score = 4
|
|
2127
|
+
} else if (total >= 50) {
|
|
2128
|
+
score = 3
|
|
2129
|
+
} else if (total >= 10) {
|
|
2130
|
+
score = 2
|
|
2131
|
+
}
|
|
1983
2132
|
|
|
1984
2133
|
return {
|
|
1985
2134
|
score,
|
|
1986
2135
|
additions,
|
|
1987
2136
|
deletions,
|
|
1988
2137
|
total,
|
|
1989
|
-
level: score <= 2 ? 'low' : score <= 3 ? 'medium' : 'high'
|
|
1990
|
-
}
|
|
2138
|
+
level: score <= 2 ? 'low' : score <= 3 ? 'medium' : 'high',
|
|
2139
|
+
}
|
|
1991
2140
|
}
|
|
1992
2141
|
|
|
1993
2142
|
// ========================================
|
|
@@ -1999,11 +2148,11 @@ export function assessChangeComplexity(diff) {
|
|
|
1999
2148
|
*/
|
|
2000
2149
|
export function getCurrentBranch() {
|
|
2001
2150
|
try {
|
|
2002
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim()
|
|
2003
|
-
return branch !== 'HEAD' ? branch : null
|
|
2004
|
-
} catch (
|
|
2005
|
-
console.warn(colors.warning('Could not determine current branch'))
|
|
2006
|
-
return null
|
|
2151
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim()
|
|
2152
|
+
return branch !== 'HEAD' ? branch : null
|
|
2153
|
+
} catch (_error) {
|
|
2154
|
+
console.warn(colors.warning('Could not determine current branch'))
|
|
2155
|
+
return null
|
|
2007
2156
|
}
|
|
2008
2157
|
}
|
|
2009
2158
|
|
|
@@ -2012,8 +2161,8 @@ export function getCurrentBranch() {
|
|
|
2012
2161
|
* Based on better-commits patterns: type/ticket-description, feat/ABC-123-add-feature
|
|
2013
2162
|
*/
|
|
2014
2163
|
export function analyzeBranchIntelligence(branchName = null) {
|
|
2015
|
-
const branch = branchName || getCurrentBranch()
|
|
2016
|
-
|
|
2164
|
+
const branch = branchName || getCurrentBranch()
|
|
2165
|
+
|
|
2017
2166
|
if (!branch) {
|
|
2018
2167
|
return {
|
|
2019
2168
|
branch: null,
|
|
@@ -2021,8 +2170,8 @@ export function analyzeBranchIntelligence(branchName = null) {
|
|
|
2021
2170
|
ticket: null,
|
|
2022
2171
|
description: null,
|
|
2023
2172
|
confidence: 0,
|
|
2024
|
-
patterns: []
|
|
2025
|
-
}
|
|
2173
|
+
patterns: [],
|
|
2174
|
+
}
|
|
2026
2175
|
}
|
|
2027
2176
|
|
|
2028
2177
|
const analysis = {
|
|
@@ -2031,193 +2180,214 @@ export function analyzeBranchIntelligence(branchName = null) {
|
|
|
2031
2180
|
ticket: null,
|
|
2032
2181
|
description: null,
|
|
2033
2182
|
confidence: 0,
|
|
2034
|
-
patterns: []
|
|
2035
|
-
}
|
|
2183
|
+
patterns: [],
|
|
2184
|
+
}
|
|
2036
2185
|
|
|
2037
2186
|
// Pattern 1: type/ticket-description (e.g., feat/ABC-123-add-feature)
|
|
2038
|
-
const typeTicketDescPattern =
|
|
2039
|
-
|
|
2187
|
+
const typeTicketDescPattern =
|
|
2188
|
+
/^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/([A-Z]{2,}-\d+)-(.+)$/i
|
|
2189
|
+
let match = branch.match(typeTicketDescPattern)
|
|
2040
2190
|
if (match) {
|
|
2041
|
-
analysis.type = match[1].toLowerCase()
|
|
2042
|
-
analysis.ticket = match[2].toUpperCase()
|
|
2043
|
-
analysis.description = match[3].replace(/[-_]/g, ' ')
|
|
2044
|
-
analysis.confidence = 95
|
|
2045
|
-
analysis.patterns.push('type/ticket-description')
|
|
2046
|
-
return analysis
|
|
2191
|
+
analysis.type = match[1].toLowerCase()
|
|
2192
|
+
analysis.ticket = match[2].toUpperCase()
|
|
2193
|
+
analysis.description = match[3].replace(/[-_]/g, ' ')
|
|
2194
|
+
analysis.confidence = 95
|
|
2195
|
+
analysis.patterns.push('type/ticket-description')
|
|
2196
|
+
return analysis
|
|
2047
2197
|
}
|
|
2048
2198
|
|
|
2049
2199
|
// Pattern 2: type/description (e.g., feat/add-new-feature)
|
|
2050
|
-
const typeDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/(.+)$/i
|
|
2051
|
-
match = branch.match(typeDescPattern)
|
|
2200
|
+
const typeDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/(.+)$/i
|
|
2201
|
+
match = branch.match(typeDescPattern)
|
|
2052
2202
|
if (match) {
|
|
2053
|
-
analysis.type = match[1].toLowerCase()
|
|
2054
|
-
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2055
|
-
analysis.confidence = 85
|
|
2056
|
-
analysis.patterns.push('type/description')
|
|
2057
|
-
|
|
2203
|
+
analysis.type = match[1].toLowerCase()
|
|
2204
|
+
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2205
|
+
analysis.confidence = 85
|
|
2206
|
+
analysis.patterns.push('type/description')
|
|
2207
|
+
|
|
2058
2208
|
// Look for ticket in description
|
|
2059
|
-
const ticketInDesc = match[2].match(/([A-Z]{2,}-\d+)/)
|
|
2209
|
+
const ticketInDesc = match[2].match(/([A-Z]{2,}-\d+)/)
|
|
2060
2210
|
if (ticketInDesc) {
|
|
2061
|
-
analysis.ticket = ticketInDesc[1]
|
|
2062
|
-
analysis.confidence = 90
|
|
2063
|
-
analysis.patterns.push('ticket-in-description')
|
|
2211
|
+
analysis.ticket = ticketInDesc[1]
|
|
2212
|
+
analysis.confidence = 90
|
|
2213
|
+
analysis.patterns.push('ticket-in-description')
|
|
2064
2214
|
}
|
|
2065
|
-
return analysis
|
|
2215
|
+
return analysis
|
|
2066
2216
|
}
|
|
2067
2217
|
|
|
2068
2218
|
// Pattern 3: ticket-description (e.g., ABC-123-fix-bug)
|
|
2069
|
-
const ticketDescPattern = /^([A-Z]{2,}-\d+)-(.+)
|
|
2070
|
-
match = branch.match(ticketDescPattern)
|
|
2219
|
+
const ticketDescPattern = /^([A-Z]{2,}-\d+)-(.+)$/
|
|
2220
|
+
match = branch.match(ticketDescPattern)
|
|
2071
2221
|
if (match) {
|
|
2072
|
-
analysis.ticket = match[1]
|
|
2073
|
-
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2074
|
-
analysis.confidence = 70
|
|
2075
|
-
analysis.patterns.push('ticket-description')
|
|
2076
|
-
|
|
2222
|
+
analysis.ticket = match[1]
|
|
2223
|
+
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2224
|
+
analysis.confidence = 70
|
|
2225
|
+
analysis.patterns.push('ticket-description')
|
|
2226
|
+
|
|
2077
2227
|
// Infer type from description
|
|
2078
|
-
const inferredType = inferTypeFromDescription(match[2])
|
|
2228
|
+
const inferredType = inferTypeFromDescription(match[2])
|
|
2079
2229
|
if (inferredType) {
|
|
2080
|
-
analysis.type = inferredType
|
|
2081
|
-
analysis.confidence = 75
|
|
2082
|
-
analysis.patterns.push('inferred-type')
|
|
2230
|
+
analysis.type = inferredType
|
|
2231
|
+
analysis.confidence = 75
|
|
2232
|
+
analysis.patterns.push('inferred-type')
|
|
2083
2233
|
}
|
|
2084
|
-
return analysis
|
|
2234
|
+
return analysis
|
|
2085
2235
|
}
|
|
2086
2236
|
|
|
2087
2237
|
// Pattern 4: just type at start (e.g., feat-add-feature)
|
|
2088
|
-
const typeStartPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)[-_](.+)$/i
|
|
2089
|
-
match = branch.match(typeStartPattern)
|
|
2238
|
+
const typeStartPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)[-_](.+)$/i
|
|
2239
|
+
match = branch.match(typeStartPattern)
|
|
2090
2240
|
if (match) {
|
|
2091
|
-
analysis.type = match[1].toLowerCase()
|
|
2092
|
-
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2093
|
-
analysis.confidence = 60
|
|
2094
|
-
analysis.patterns.push('type-start')
|
|
2095
|
-
return analysis
|
|
2241
|
+
analysis.type = match[1].toLowerCase()
|
|
2242
|
+
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2243
|
+
analysis.confidence = 60
|
|
2244
|
+
analysis.patterns.push('type-start')
|
|
2245
|
+
return analysis
|
|
2096
2246
|
}
|
|
2097
2247
|
|
|
2098
2248
|
// Pattern 5: ticket at start (e.g., ABC-123_add_feature)
|
|
2099
|
-
const ticketStartPattern = /^([A-Z]{2,}-\d+)[-_](.+)
|
|
2100
|
-
match = branch.match(ticketStartPattern)
|
|
2249
|
+
const ticketStartPattern = /^([A-Z]{2,}-\d+)[-_](.+)$/
|
|
2250
|
+
match = branch.match(ticketStartPattern)
|
|
2101
2251
|
if (match) {
|
|
2102
|
-
analysis.ticket = match[1]
|
|
2103
|
-
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2104
|
-
analysis.confidence = 60
|
|
2105
|
-
analysis.patterns.push('ticket-start')
|
|
2106
|
-
|
|
2252
|
+
analysis.ticket = match[1]
|
|
2253
|
+
analysis.description = match[2].replace(/[-_]/g, ' ')
|
|
2254
|
+
analysis.confidence = 60
|
|
2255
|
+
analysis.patterns.push('ticket-start')
|
|
2256
|
+
|
|
2107
2257
|
// Infer type from description
|
|
2108
|
-
const inferredType = inferTypeFromDescription(match[2])
|
|
2258
|
+
const inferredType = inferTypeFromDescription(match[2])
|
|
2109
2259
|
if (inferredType) {
|
|
2110
|
-
analysis.type = inferredType
|
|
2111
|
-
analysis.confidence = 65
|
|
2112
|
-
analysis.patterns.push('inferred-type')
|
|
2260
|
+
analysis.type = inferredType
|
|
2261
|
+
analysis.confidence = 65
|
|
2262
|
+
analysis.patterns.push('inferred-type')
|
|
2113
2263
|
}
|
|
2114
|
-
return analysis
|
|
2264
|
+
return analysis
|
|
2115
2265
|
}
|
|
2116
2266
|
|
|
2117
2267
|
// Pattern 6: just ticket anywhere (e.g., add-feature-ABC-123)
|
|
2118
|
-
const ticketAnywherePattern = /([A-Z]{2,}-\d+)
|
|
2119
|
-
match = branch.match(ticketAnywherePattern)
|
|
2268
|
+
const ticketAnywherePattern = /([A-Z]{2,}-\d+)/
|
|
2269
|
+
match = branch.match(ticketAnywherePattern)
|
|
2120
2270
|
if (match) {
|
|
2121
|
-
analysis.ticket = match[1]
|
|
2122
|
-
analysis.confidence = 40
|
|
2123
|
-
analysis.patterns.push('ticket-anywhere')
|
|
2124
|
-
|
|
2271
|
+
analysis.ticket = match[1]
|
|
2272
|
+
analysis.confidence = 40
|
|
2273
|
+
analysis.patterns.push('ticket-anywhere')
|
|
2274
|
+
|
|
2125
2275
|
// Use the whole branch as description, removing ticket
|
|
2126
|
-
analysis.description = branch.replace(match[1], '').replace(/[-_]/g, ' ').trim()
|
|
2127
|
-
return analysis
|
|
2276
|
+
analysis.description = branch.replace(match[1], '').replace(/[-_]/g, ' ').trim()
|
|
2277
|
+
return analysis
|
|
2128
2278
|
}
|
|
2129
2279
|
|
|
2130
2280
|
// Pattern 7: conventional type anywhere in branch
|
|
2131
|
-
const typeAnywherePattern = /(feat|fix|docs|style|refactor|perf|test|build|ci|chore)/i
|
|
2132
|
-
match = branch.match(typeAnywherePattern)
|
|
2281
|
+
const typeAnywherePattern = /(feat|fix|docs|style|refactor|perf|test|build|ci|chore)/i
|
|
2282
|
+
match = branch.match(typeAnywherePattern)
|
|
2133
2283
|
if (match) {
|
|
2134
|
-
analysis.type = match[1].toLowerCase()
|
|
2135
|
-
analysis.confidence = 30
|
|
2136
|
-
analysis.patterns.push('type-anywhere')
|
|
2137
|
-
analysis.description = branch.replace(/[-_]/g, ' ')
|
|
2138
|
-
return analysis
|
|
2284
|
+
analysis.type = match[1].toLowerCase()
|
|
2285
|
+
analysis.confidence = 30
|
|
2286
|
+
analysis.patterns.push('type-anywhere')
|
|
2287
|
+
analysis.description = branch.replace(/[-_]/g, ' ')
|
|
2288
|
+
return analysis
|
|
2139
2289
|
}
|
|
2140
2290
|
|
|
2141
2291
|
// Fallback: use branch as description and try to infer type
|
|
2142
|
-
analysis.description = branch.replace(/[-_]/g, ' ')
|
|
2143
|
-
const inferredType = inferTypeFromDescription(branch)
|
|
2292
|
+
analysis.description = branch.replace(/[-_]/g, ' ')
|
|
2293
|
+
const inferredType = inferTypeFromDescription(branch)
|
|
2144
2294
|
if (inferredType) {
|
|
2145
|
-
analysis.type = inferredType
|
|
2146
|
-
analysis.confidence = 25
|
|
2147
|
-
analysis.patterns.push('inferred-type')
|
|
2295
|
+
analysis.type = inferredType
|
|
2296
|
+
analysis.confidence = 25
|
|
2297
|
+
analysis.patterns.push('inferred-type')
|
|
2148
2298
|
} else {
|
|
2149
|
-
analysis.confidence = 10
|
|
2150
|
-
analysis.patterns.push('no-pattern')
|
|
2299
|
+
analysis.confidence = 10
|
|
2300
|
+
analysis.patterns.push('no-pattern')
|
|
2151
2301
|
}
|
|
2152
2302
|
|
|
2153
|
-
return analysis
|
|
2303
|
+
return analysis
|
|
2154
2304
|
}
|
|
2155
2305
|
|
|
2156
2306
|
/**
|
|
2157
2307
|
* Infer commit type from description text
|
|
2158
2308
|
*/
|
|
2159
2309
|
function inferTypeFromDescription(description) {
|
|
2160
|
-
const text = description.toLowerCase()
|
|
2161
|
-
|
|
2310
|
+
const text = description.toLowerCase()
|
|
2311
|
+
|
|
2162
2312
|
// Feature indicators
|
|
2163
|
-
if (text.match(/add|new|create|implement|introduce|feature/))
|
|
2164
|
-
|
|
2313
|
+
if (text.match(/add|new|create|implement|introduce|feature/)) {
|
|
2314
|
+
return 'feat'
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2165
2317
|
// Fix indicators
|
|
2166
|
-
if (text.match(/fix|bug|error|issue|resolve|correct|patch/))
|
|
2167
|
-
|
|
2318
|
+
if (text.match(/fix|bug|error|issue|resolve|correct|patch/)) {
|
|
2319
|
+
return 'fix'
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2168
2322
|
// Documentation indicators
|
|
2169
|
-
if (text.match(/doc|readme|comment|guide|tutorial/))
|
|
2170
|
-
|
|
2323
|
+
if (text.match(/doc|readme|comment|guide|tutorial/)) {
|
|
2324
|
+
return 'docs'
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2171
2327
|
// Style indicators
|
|
2172
|
-
if (text.match(/style|format|lint|prettier|eslint/))
|
|
2173
|
-
|
|
2328
|
+
if (text.match(/style|format|lint|prettier|eslint/)) {
|
|
2329
|
+
return 'style'
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2174
2332
|
// Refactor indicators
|
|
2175
|
-
if (text.match(/refactor|restructure|reorganize|cleanup|clean/))
|
|
2176
|
-
|
|
2333
|
+
if (text.match(/refactor|restructure|reorganize|cleanup|clean/)) {
|
|
2334
|
+
return 'refactor'
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2177
2337
|
// Performance indicators
|
|
2178
|
-
if (text.match(/perf|performance|optimize|speed|fast/))
|
|
2179
|
-
|
|
2338
|
+
if (text.match(/perf|performance|optimize|speed|fast/)) {
|
|
2339
|
+
return 'perf'
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2180
2342
|
// Test indicators
|
|
2181
|
-
if (text.match(/test|spec|unit|integration/))
|
|
2182
|
-
|
|
2343
|
+
if (text.match(/test|spec|unit|integration/)) {
|
|
2344
|
+
return 'test'
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2183
2347
|
// Build indicators
|
|
2184
|
-
if (text.match(/build|deploy|package|bundle|webpack|vite/))
|
|
2185
|
-
|
|
2348
|
+
if (text.match(/build|deploy|package|bundle|webpack|vite/)) {
|
|
2349
|
+
return 'build'
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2186
2352
|
// CI indicators
|
|
2187
|
-
if (text.match(/ci|pipeline|action|workflow|github/))
|
|
2188
|
-
|
|
2353
|
+
if (text.match(/ci|pipeline|action|workflow|github/)) {
|
|
2354
|
+
return 'ci'
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2189
2357
|
// Chore indicators
|
|
2190
|
-
if (text.match(/chore|update|upgrade|dependency|deps|config/))
|
|
2191
|
-
|
|
2192
|
-
|
|
2358
|
+
if (text.match(/chore|update|upgrade|dependency|deps|config/)) {
|
|
2359
|
+
return 'chore'
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
return null
|
|
2193
2363
|
}
|
|
2194
2364
|
|
|
2195
2365
|
/**
|
|
2196
2366
|
* Generate enhanced commit message context from branch intelligence
|
|
2197
2367
|
*/
|
|
2198
|
-
export function generateCommitContextFromBranch(branchAnalysis,
|
|
2368
|
+
export function generateCommitContextFromBranch(branchAnalysis, _changes = []) {
|
|
2199
2369
|
if (!branchAnalysis || branchAnalysis.confidence < 20) {
|
|
2200
|
-
return ''
|
|
2370
|
+
return ''
|
|
2201
2371
|
}
|
|
2202
2372
|
|
|
2203
|
-
let context = '\n**Branch Context:**'
|
|
2204
|
-
|
|
2373
|
+
let context = '\n**Branch Context:**'
|
|
2374
|
+
|
|
2205
2375
|
if (branchAnalysis.type) {
|
|
2206
|
-
context += `\n- Inferred type: ${branchAnalysis.type}
|
|
2376
|
+
context += `\n- Inferred type: ${branchAnalysis.type}`
|
|
2207
2377
|
}
|
|
2208
|
-
|
|
2378
|
+
|
|
2209
2379
|
if (branchAnalysis.ticket) {
|
|
2210
|
-
context += `\n- Related ticket: ${branchAnalysis.ticket}
|
|
2380
|
+
context += `\n- Related ticket: ${branchAnalysis.ticket}`
|
|
2211
2381
|
}
|
|
2212
|
-
|
|
2382
|
+
|
|
2213
2383
|
if (branchAnalysis.description) {
|
|
2214
|
-
context += `\n- Branch description: ${branchAnalysis.description}
|
|
2384
|
+
context += `\n- Branch description: ${branchAnalysis.description}`
|
|
2215
2385
|
}
|
|
2216
|
-
|
|
2217
|
-
context += `\n- Confidence: ${branchAnalysis.confidence}
|
|
2218
|
-
context += `\n- Detection patterns: ${branchAnalysis.patterns.join(', ')}
|
|
2219
|
-
|
|
2220
|
-
return context
|
|
2386
|
+
|
|
2387
|
+
context += `\n- Confidence: ${branchAnalysis.confidence}%`
|
|
2388
|
+
context += `\n- Detection patterns: ${branchAnalysis.patterns.join(', ')}`
|
|
2389
|
+
|
|
2390
|
+
return context
|
|
2221
2391
|
}
|
|
2222
2392
|
|
|
2223
2393
|
/**
|
|
@@ -2225,48 +2395,50 @@ export function generateCommitContextFromBranch(branchAnalysis, changes = []) {
|
|
|
2225
2395
|
*/
|
|
2226
2396
|
export function getSuggestedCommitType(branchAnalysis, changes = []) {
|
|
2227
2397
|
// High confidence branch type takes precedence
|
|
2228
|
-
if (branchAnalysis
|
|
2398
|
+
if (branchAnalysis?.type && branchAnalysis.confidence >= 70) {
|
|
2229
2399
|
return {
|
|
2230
2400
|
type: branchAnalysis.type,
|
|
2231
2401
|
source: 'branch',
|
|
2232
|
-
confidence: branchAnalysis.confidence
|
|
2233
|
-
}
|
|
2402
|
+
confidence: branchAnalysis.confidence,
|
|
2403
|
+
}
|
|
2234
2404
|
}
|
|
2235
2405
|
|
|
2236
2406
|
// Analyze file changes to infer type
|
|
2237
|
-
const changeAnalysis = analyzeChangesForType(changes)
|
|
2238
|
-
|
|
2407
|
+
const changeAnalysis = analyzeChangesForType(changes)
|
|
2408
|
+
|
|
2239
2409
|
// Medium confidence branch type combined with file analysis
|
|
2240
|
-
if (
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2410
|
+
if (
|
|
2411
|
+
branchAnalysis?.type &&
|
|
2412
|
+
branchAnalysis.confidence >= 40 &&
|
|
2413
|
+
changeAnalysis.type === branchAnalysis.type
|
|
2414
|
+
) {
|
|
2415
|
+
return {
|
|
2416
|
+
type: branchAnalysis.type,
|
|
2417
|
+
source: 'branch+files',
|
|
2418
|
+
confidence: Math.min(95, branchAnalysis.confidence + 20),
|
|
2247
2419
|
}
|
|
2248
2420
|
}
|
|
2249
2421
|
|
|
2250
2422
|
// Use file-based analysis
|
|
2251
2423
|
if (changeAnalysis.confidence >= 60) {
|
|
2252
|
-
return changeAnalysis
|
|
2424
|
+
return changeAnalysis
|
|
2253
2425
|
}
|
|
2254
2426
|
|
|
2255
2427
|
// Low confidence branch type as fallback
|
|
2256
|
-
if (branchAnalysis
|
|
2428
|
+
if (branchAnalysis?.type) {
|
|
2257
2429
|
return {
|
|
2258
2430
|
type: branchAnalysis.type,
|
|
2259
2431
|
source: 'branch-fallback',
|
|
2260
|
-
confidence: branchAnalysis.confidence
|
|
2261
|
-
}
|
|
2432
|
+
confidence: branchAnalysis.confidence,
|
|
2433
|
+
}
|
|
2262
2434
|
}
|
|
2263
2435
|
|
|
2264
2436
|
// Default fallback
|
|
2265
2437
|
return {
|
|
2266
2438
|
type: 'feat',
|
|
2267
2439
|
source: 'default',
|
|
2268
|
-
confidence: 20
|
|
2269
|
-
}
|
|
2440
|
+
confidence: 20,
|
|
2441
|
+
}
|
|
2270
2442
|
}
|
|
2271
2443
|
|
|
2272
2444
|
/**
|
|
@@ -2274,7 +2446,7 @@ export function getSuggestedCommitType(branchAnalysis, changes = []) {
|
|
|
2274
2446
|
*/
|
|
2275
2447
|
function analyzeChangesForType(changes) {
|
|
2276
2448
|
if (!changes || changes.length === 0) {
|
|
2277
|
-
return { type: 'feat', source: 'default', confidence: 20 }
|
|
2449
|
+
return { type: 'feat', source: 'default', confidence: 20 }
|
|
2278
2450
|
}
|
|
2279
2451
|
|
|
2280
2452
|
const categories = {
|
|
@@ -2282,68 +2454,68 @@ function analyzeChangesForType(changes) {
|
|
|
2282
2454
|
test: 0,
|
|
2283
2455
|
config: 0,
|
|
2284
2456
|
source: 0,
|
|
2285
|
-
style: 0
|
|
2286
|
-
}
|
|
2457
|
+
style: 0,
|
|
2458
|
+
}
|
|
2287
2459
|
|
|
2288
2460
|
// Categorize files
|
|
2289
|
-
changes.forEach(change => {
|
|
2290
|
-
const path = change.path || change.filePath || ''
|
|
2291
|
-
const ext = path.split('.').pop()?.toLowerCase() || ''
|
|
2292
|
-
|
|
2461
|
+
changes.forEach((change) => {
|
|
2462
|
+
const path = change.path || change.filePath || ''
|
|
2463
|
+
const ext = path.split('.').pop()?.toLowerCase() || ''
|
|
2464
|
+
|
|
2293
2465
|
if (path.match(/readme|doc|\.md$|guide|tutorial/i)) {
|
|
2294
|
-
categories.docs
|
|
2466
|
+
categories.docs++
|
|
2295
2467
|
} else if (path.match(/test|spec|\.test\.|\.spec\./i)) {
|
|
2296
|
-
categories.test
|
|
2468
|
+
categories.test++
|
|
2297
2469
|
} else if (path.match(/config|\.json$|\.yaml$|\.yml$|\.toml$|\.ini$/i)) {
|
|
2298
|
-
categories.config
|
|
2470
|
+
categories.config++
|
|
2299
2471
|
} else if (path.match(/\.css$|\.scss$|\.less$|\.styl$/i)) {
|
|
2300
|
-
categories.style
|
|
2472
|
+
categories.style++
|
|
2301
2473
|
} else if (['js', 'ts', 'jsx', 'tsx', 'py', 'go', 'rs', 'java', 'c', 'cpp'].includes(ext)) {
|
|
2302
|
-
categories.source
|
|
2474
|
+
categories.source++
|
|
2303
2475
|
}
|
|
2304
|
-
})
|
|
2476
|
+
})
|
|
2477
|
+
|
|
2478
|
+
const total = Object.values(categories).reduce((a, b) => a + b, 0)
|
|
2305
2479
|
|
|
2306
|
-
const total = Object.values(categories).reduce((a, b) => a + b, 0);
|
|
2307
|
-
|
|
2308
2480
|
if (total === 0) {
|
|
2309
|
-
return { type: 'feat', source: 'files-default', confidence: 30 }
|
|
2481
|
+
return { type: 'feat', source: 'files-default', confidence: 30 }
|
|
2310
2482
|
}
|
|
2311
2483
|
|
|
2312
2484
|
// Determine primary type
|
|
2313
2485
|
if (categories.docs / total > 0.6) {
|
|
2314
|
-
return { type: 'docs', source: 'files', confidence: 80 }
|
|
2486
|
+
return { type: 'docs', source: 'files', confidence: 80 }
|
|
2315
2487
|
}
|
|
2316
|
-
|
|
2488
|
+
|
|
2317
2489
|
if (categories.test / total > 0.6) {
|
|
2318
|
-
return { type: 'test', source: 'files', confidence: 80 }
|
|
2490
|
+
return { type: 'test', source: 'files', confidence: 80 }
|
|
2319
2491
|
}
|
|
2320
|
-
|
|
2492
|
+
|
|
2321
2493
|
if (categories.config / total > 0.6) {
|
|
2322
|
-
return { type: 'chore', source: 'files', confidence: 70 }
|
|
2494
|
+
return { type: 'chore', source: 'files', confidence: 70 }
|
|
2323
2495
|
}
|
|
2324
|
-
|
|
2496
|
+
|
|
2325
2497
|
if (categories.style / total > 0.6) {
|
|
2326
|
-
return { type: 'style', source: 'files', confidence: 75 }
|
|
2498
|
+
return { type: 'style', source: 'files', confidence: 75 }
|
|
2327
2499
|
}
|
|
2328
2500
|
|
|
2329
2501
|
// Mixed or source-heavy changes - look at modification patterns
|
|
2330
|
-
const hasNewFiles = changes.some(c => c.status === 'A' || c.status === '??')
|
|
2331
|
-
const hasDeletedFiles = changes.some(c => c.status === 'D')
|
|
2332
|
-
|
|
2502
|
+
const hasNewFiles = changes.some((c) => c.status === 'A' || c.status === '??')
|
|
2503
|
+
const hasDeletedFiles = changes.some((c) => c.status === 'D')
|
|
2504
|
+
|
|
2333
2505
|
if (hasNewFiles && !hasDeletedFiles) {
|
|
2334
|
-
return { type: 'feat', source: 'files-new', confidence: 65 }
|
|
2506
|
+
return { type: 'feat', source: 'files-new', confidence: 65 }
|
|
2335
2507
|
}
|
|
2336
|
-
|
|
2508
|
+
|
|
2337
2509
|
if (hasDeletedFiles) {
|
|
2338
|
-
return { type: 'refactor', source: 'files-deleted', confidence: 60 }
|
|
2510
|
+
return { type: 'refactor', source: 'files-deleted', confidence: 60 }
|
|
2339
2511
|
}
|
|
2340
2512
|
|
|
2341
2513
|
// Default to feat for source changes
|
|
2342
|
-
return { type: 'feat', source: 'files-mixed', confidence: 50 }
|
|
2514
|
+
return { type: 'feat', source: 'files-mixed', confidence: 50 }
|
|
2343
2515
|
}
|
|
2344
2516
|
|
|
2345
2517
|
// ========================================
|
|
2346
2518
|
// CHANGELOG UTILITIES (EXTENDED)
|
|
2347
2519
|
// ========================================
|
|
2348
2520
|
|
|
2349
|
-
// Additional changelog utility functions
|
|
2521
|
+
// Additional changelog utility functions
|