@entro314labs/ai-changelog-generator 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +383 -877
- package/README.md +8 -3
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +9 -9
- package/bin/ai-changelog-mcp.js +19 -17
- package/bin/ai-changelog.js +6 -6
- package/package.json +80 -48
- package/src/ai-changelog-generator.js +83 -81
- package/src/application/orchestrators/changelog.orchestrator.js +791 -516
- package/src/application/services/application.service.js +137 -128
- package/src/cli.js +76 -57
- package/src/domains/ai/ai-analysis.service.js +289 -209
- package/src/domains/analysis/analysis.engine.js +253 -193
- package/src/domains/changelog/changelog.service.js +1062 -784
- package/src/domains/changelog/workspace-changelog.service.js +420 -249
- package/src/domains/git/git-repository.analyzer.js +348 -258
- package/src/domains/git/git.service.js +132 -112
- package/src/infrastructure/cli/cli.controller.js +390 -274
- package/src/infrastructure/config/configuration.manager.js +220 -190
- package/src/infrastructure/interactive/interactive-staging.service.js +154 -135
- package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
- package/src/infrastructure/mcp/mcp-server.service.js +208 -207
- package/src/infrastructure/metrics/metrics.collector.js +140 -123
- package/src/infrastructure/providers/core/base-provider.js +87 -40
- package/src/infrastructure/providers/implementations/anthropic.js +101 -99
- package/src/infrastructure/providers/implementations/azure.js +124 -101
- package/src/infrastructure/providers/implementations/bedrock.js +136 -126
- package/src/infrastructure/providers/implementations/dummy.js +23 -23
- package/src/infrastructure/providers/implementations/google.js +123 -114
- package/src/infrastructure/providers/implementations/huggingface.js +94 -87
- package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
- package/src/infrastructure/providers/implementations/mock.js +69 -73
- package/src/infrastructure/providers/implementations/ollama.js +89 -66
- package/src/infrastructure/providers/implementations/openai.js +88 -89
- package/src/infrastructure/providers/implementations/vertex.js +227 -197
- package/src/infrastructure/providers/provider-management.service.js +245 -207
- package/src/infrastructure/providers/provider-manager.service.js +145 -125
- package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
- package/src/infrastructure/providers/utils/model-config.js +220 -195
- package/src/infrastructure/providers/utils/provider-utils.js +105 -100
- package/src/infrastructure/validation/commit-message-validation.service.js +259 -161
- package/src/shared/constants/colors.js +453 -180
- package/src/shared/utils/cli-demo.js +285 -0
- package/src/shared/utils/cli-entry-utils.js +257 -249
- package/src/shared/utils/cli-ui.js +447 -0
- package/src/shared/utils/diff-processor.js +513 -0
- package/src/shared/utils/error-classes.js +125 -156
- package/src/shared/utils/json-utils.js +93 -89
- package/src/shared/utils/utils.js +1117 -945
- package/types/index.d.ts +353 -344
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import process from 'node:process'
|
|
3
|
+
|
|
4
|
+
import colors from '../../shared/constants/colors.js'
|
|
5
|
+
import {
|
|
6
|
+
handleUnifiedOutput,
|
|
7
|
+
markdownCommitLink,
|
|
8
|
+
markdownCommitRangeLink,
|
|
9
|
+
parseConventionalCommit,
|
|
10
|
+
processIssueReferences,
|
|
11
|
+
sleep,
|
|
12
|
+
} from '../../shared/utils/utils.js'
|
|
13
|
+
import { WorkspaceChangelogService } from './workspace-changelog.service.js'
|
|
5
14
|
|
|
6
15
|
export class ChangelogService {
|
|
7
16
|
constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
|
|
8
|
-
this.gitService = gitService
|
|
9
|
-
this.aiAnalysisService = aiAnalysisService
|
|
10
|
-
this.analysisEngine = analysisEngine
|
|
11
|
-
this.configManager = configManager
|
|
12
|
-
this.workspaceChangelogService = new WorkspaceChangelogService(aiAnalysisService, gitService)
|
|
17
|
+
this.gitService = gitService
|
|
18
|
+
this.aiAnalysisService = aiAnalysisService
|
|
19
|
+
this.analysisEngine = analysisEngine
|
|
20
|
+
this.configManager = configManager
|
|
21
|
+
this.workspaceChangelogService = new WorkspaceChangelogService(aiAnalysisService, gitService)
|
|
13
22
|
}
|
|
14
23
|
|
|
15
24
|
/**
|
|
@@ -19,117 +28,178 @@ export class ChangelogService {
|
|
|
19
28
|
* @returns {Promise<string>} Complete changelog content
|
|
20
29
|
*/
|
|
21
30
|
async generateChangelog(version = null, since = null) {
|
|
22
|
-
console.log(colors.processingMessage('🤖 Analyzing changes with AI...'))
|
|
31
|
+
console.log(colors.processingMessage('🤖 Analyzing changes with AI...'))
|
|
23
32
|
|
|
24
33
|
// Get committed changes
|
|
25
|
-
const commits = await this.gitService.getCommitsSince(since)
|
|
34
|
+
const commits = await this.gitService.getCommitsSince(since)
|
|
26
35
|
|
|
27
36
|
// Get working directory changes using analysis engine
|
|
28
|
-
let workingDirAnalysis = null
|
|
37
|
+
let workingDirAnalysis = null
|
|
29
38
|
if (this.analysisEngine) {
|
|
30
|
-
workingDirAnalysis = await this.analysisEngine.analyzeCurrentChanges()
|
|
39
|
+
workingDirAnalysis = await this.analysisEngine.analyzeCurrentChanges()
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
if (commits.length === 0 && (!workingDirAnalysis || workingDirAnalysis.changes.length === 0)) {
|
|
34
|
-
console.log(colors.infoMessage('No commits or working directory changes found.'))
|
|
35
|
-
return
|
|
43
|
+
console.log(colors.infoMessage('No commits or working directory changes found.'))
|
|
44
|
+
return
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
const workingChangesCount = workingDirAnalysis ? workingDirAnalysis.changes.length : 0
|
|
39
|
-
console.log(
|
|
47
|
+
const workingChangesCount = workingDirAnalysis ? workingDirAnalysis.changes.length : 0
|
|
48
|
+
console.log(
|
|
49
|
+
colors.processingMessage(
|
|
50
|
+
`Found ${colors.number(commits.length)} commits and ${colors.number(workingChangesCount)} working directory changes...`
|
|
51
|
+
)
|
|
52
|
+
)
|
|
40
53
|
|
|
41
54
|
// Extract hash strings from commit objects
|
|
42
|
-
const commitHashes = commits.map(c => c.hash)
|
|
43
|
-
console.log(
|
|
55
|
+
const commitHashes = commits.map((c) => c.hash)
|
|
56
|
+
console.log(
|
|
57
|
+
colors.processingMessage(`Analyzing ${colors.number(commitHashes.length)} commits...`)
|
|
58
|
+
)
|
|
44
59
|
|
|
45
60
|
// Use batch processing for large commit sets
|
|
46
|
-
let analyzedCommits
|
|
61
|
+
let analyzedCommits
|
|
47
62
|
if (commitHashes.length > 20) {
|
|
48
|
-
console.log(colors.infoMessage('Using batch processing for large commit set...'))
|
|
49
|
-
analyzedCommits = await this.generateChangelogBatch(commitHashes)
|
|
63
|
+
console.log(colors.infoMessage('Using batch processing for large commit set...'))
|
|
64
|
+
analyzedCommits = await this.generateChangelogBatch(commitHashes)
|
|
50
65
|
} else {
|
|
51
|
-
analyzedCommits = await this.processCommitsSequentially(commitHashes)
|
|
66
|
+
analyzedCommits = await this.processCommitsSequentially(commitHashes)
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
if (analyzedCommits.length === 0) {
|
|
55
|
-
console.log(colors.warningMessage('No valid commits to analyze.'))
|
|
56
|
-
return
|
|
70
|
+
console.log(colors.warningMessage('No valid commits to analyze.'))
|
|
71
|
+
return
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
// Generate release insights including working directory changes
|
|
60
|
-
const insights = await this.generateReleaseInsights(
|
|
75
|
+
const insights = await this.generateReleaseInsights(
|
|
76
|
+
analyzedCommits,
|
|
77
|
+
version,
|
|
78
|
+
workingDirAnalysis
|
|
79
|
+
)
|
|
61
80
|
|
|
62
81
|
// Build final changelog including both committed and working changes
|
|
63
|
-
const changelog = await this.buildChangelog(
|
|
82
|
+
const changelog = await this.buildChangelog(
|
|
83
|
+
analyzedCommits,
|
|
84
|
+
insights,
|
|
85
|
+
version,
|
|
86
|
+
workingDirAnalysis
|
|
87
|
+
)
|
|
64
88
|
|
|
65
89
|
// Write changelog to file using existing utility
|
|
66
|
-
const filename = version && version !== 'latest' ? `CHANGELOG-${version}.md` : 'AI_CHANGELOG.md'
|
|
67
|
-
const outputFile = path.join(process.cwd(), filename)
|
|
68
|
-
handleUnifiedOutput(changelog, { format: 'markdown', outputFile, silent: false })
|
|
90
|
+
const filename = version && version !== 'latest' ? `CHANGELOG-${version}.md` : 'AI_CHANGELOG.md'
|
|
91
|
+
const outputFile = path.join(process.cwd(), filename)
|
|
92
|
+
handleUnifiedOutput(changelog, { format: 'markdown', outputFile, silent: false })
|
|
69
93
|
|
|
70
94
|
return {
|
|
71
95
|
changelog,
|
|
72
96
|
insights,
|
|
73
97
|
analyzedCommits,
|
|
74
|
-
workingDirAnalysis
|
|
75
|
-
}
|
|
98
|
+
workingDirAnalysis,
|
|
99
|
+
}
|
|
76
100
|
}
|
|
77
101
|
|
|
78
102
|
async processCommitsSequentially(commitHashes) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
103
|
+
// Optimize by using concurrent processing with controlled concurrency
|
|
104
|
+
const concurrency = 3 // Limit concurrent API calls to avoid rate limits
|
|
105
|
+
const analyzedCommits = []
|
|
106
|
+
|
|
107
|
+
console.log(
|
|
108
|
+
colors.processingMessage(
|
|
109
|
+
`Processing ${commitHashes.length} commits with ${concurrency} concurrent operations...`
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// Process in chunks to control concurrency
|
|
114
|
+
for (let i = 0; i < commitHashes.length; i += concurrency) {
|
|
115
|
+
const chunk = commitHashes.slice(i, i + concurrency)
|
|
116
|
+
|
|
117
|
+
// Process chunk concurrently
|
|
118
|
+
const chunkPromises = chunk.map(async (commitHash, index) => {
|
|
119
|
+
const globalIndex = i + index
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const commitAnalysis = await this.gitService.getCommitAnalysis(commitHash)
|
|
123
|
+
if (!commitAnalysis) {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
83
126
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
127
|
+
const selectedModel = await this.aiAnalysisService.selectOptimalModel(commitAnalysis)
|
|
128
|
+
console.log(
|
|
129
|
+
colors.processingMessage(
|
|
130
|
+
`Processing commit ${colors.highlight(`${globalIndex + 1}/${commitHashes.length}`)}: ${colors.hash(commitHash.substring(0, 7))}`
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const aiSummary = await this.aiAnalysisService.generateAISummary(
|
|
135
|
+
commitAnalysis,
|
|
136
|
+
selectedModel
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (aiSummary) {
|
|
140
|
+
return {
|
|
141
|
+
...commitAnalysis,
|
|
142
|
+
aiSummary,
|
|
143
|
+
type: aiSummary.category || 'other',
|
|
144
|
+
breaking: aiSummary.breaking,
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.warn(
|
|
150
|
+
colors.warningMessage(
|
|
151
|
+
`Failed to process commit ${commitHash.substring(0, 7)}: ${error.message}`
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
})
|
|
88
157
|
|
|
89
|
-
|
|
158
|
+
// Wait for chunk to complete and add successful results
|
|
159
|
+
const chunkResults = await Promise.all(chunkPromises)
|
|
160
|
+
analyzedCommits.push(...chunkResults.filter((result) => result !== null))
|
|
90
161
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
aiSummary,
|
|
95
|
-
type: aiSummary.category || 'other',
|
|
96
|
-
breaking: aiSummary.breaking || false
|
|
97
|
-
});
|
|
98
|
-
}
|
|
162
|
+
// Brief delay between chunks to be respectful to APIs
|
|
163
|
+
if (i + concurrency < commitHashes.length) {
|
|
164
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
99
165
|
}
|
|
100
166
|
}
|
|
101
167
|
|
|
102
|
-
return analyzedCommits
|
|
168
|
+
return analyzedCommits
|
|
103
169
|
}
|
|
104
170
|
|
|
105
171
|
async generateChangelogBatch(commitHashes) {
|
|
106
|
-
const batchSize = 10
|
|
107
|
-
const results = []
|
|
172
|
+
const batchSize = 10
|
|
173
|
+
const results = []
|
|
108
174
|
|
|
109
175
|
for (let i = 0; i < commitHashes.length; i += batchSize) {
|
|
110
|
-
const batch = commitHashes.slice(i, i + batchSize)
|
|
111
|
-
const batchNum = Math.floor(i/batchSize) + 1
|
|
112
|
-
const totalBatches = Math.ceil(commitHashes.length/batchSize)
|
|
176
|
+
const batch = commitHashes.slice(i, i + batchSize)
|
|
177
|
+
const batchNum = Math.floor(i / batchSize) + 1
|
|
178
|
+
const totalBatches = Math.ceil(commitHashes.length / batchSize)
|
|
113
179
|
|
|
114
|
-
console.log(
|
|
115
|
-
|
|
180
|
+
console.log(
|
|
181
|
+
colors.processingMessage(
|
|
182
|
+
`Processing batch ${colors.highlight(`${batchNum}/${totalBatches}`)} (${colors.number(batch.length)} commits)`
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
console.log(colors.progress(batchNum, totalBatches, 'batches processed'))
|
|
116
186
|
|
|
117
|
-
const batchPromises = batch.map(hash => this.gitService.getCommitAnalysis(hash))
|
|
118
|
-
const batchResults = await Promise.allSettled(batchPromises)
|
|
187
|
+
const batchPromises = batch.map((hash) => this.gitService.getCommitAnalysis(hash))
|
|
188
|
+
const batchResults = await Promise.allSettled(batchPromises)
|
|
119
189
|
|
|
120
190
|
const successfulResults = batchResults
|
|
121
|
-
.filter(r => r.status === 'fulfilled' && r.value)
|
|
122
|
-
.map(r => r.value)
|
|
191
|
+
.filter((r) => r.status === 'fulfilled' && r.value)
|
|
192
|
+
.map((r) => r.value)
|
|
123
193
|
|
|
124
|
-
results.push(...successfulResults)
|
|
194
|
+
results.push(...successfulResults)
|
|
125
195
|
|
|
126
196
|
// Rate limiting between batches
|
|
127
197
|
if (i + batchSize < commitHashes.length) {
|
|
128
|
-
await sleep(1000)
|
|
198
|
+
await sleep(1000)
|
|
129
199
|
}
|
|
130
200
|
}
|
|
131
201
|
|
|
132
|
-
return results.filter(Boolean)
|
|
202
|
+
return results.filter(Boolean)
|
|
133
203
|
}
|
|
134
204
|
|
|
135
205
|
async generateReleaseInsights(analyzedCommits, version, workingDirAnalysis = null) {
|
|
@@ -142,323 +212,382 @@ export class ChangelogService {
|
|
|
142
212
|
breaking: false,
|
|
143
213
|
complexity: 'low',
|
|
144
214
|
businessImpact: 'minor',
|
|
145
|
-
deploymentRequirements: []
|
|
146
|
-
}
|
|
215
|
+
deploymentRequirements: [],
|
|
216
|
+
}
|
|
147
217
|
|
|
148
218
|
// Count commit types and assess risk
|
|
149
|
-
analyzedCommits.forEach(commit => {
|
|
150
|
-
insights.commitTypes[commit.type] = (insights.commitTypes[commit.type] || 0) + 1
|
|
151
|
-
if (commit.breaking)
|
|
152
|
-
|
|
219
|
+
analyzedCommits.forEach((commit) => {
|
|
220
|
+
insights.commitTypes[commit.type] = (insights.commitTypes[commit.type] || 0) + 1
|
|
221
|
+
if (commit.breaking) {
|
|
222
|
+
insights.breaking = true
|
|
223
|
+
}
|
|
224
|
+
if (commit.semanticAnalysis?.riskLevel === 'high') {
|
|
225
|
+
insights.riskLevel = 'high'
|
|
226
|
+
}
|
|
153
227
|
|
|
154
|
-
commit.files.forEach(file => {
|
|
155
|
-
insights.affectedAreas.add(file.category)
|
|
156
|
-
})
|
|
228
|
+
commit.files.forEach((file) => {
|
|
229
|
+
insights.affectedAreas.add(file.category)
|
|
230
|
+
})
|
|
157
231
|
|
|
158
232
|
// Check for deployment requirements
|
|
159
233
|
if (commit.semanticAnalysis?.hasDbChanges) {
|
|
160
|
-
insights.deploymentRequirements.push('Database migration required')
|
|
234
|
+
insights.deploymentRequirements.push('Database migration required')
|
|
161
235
|
}
|
|
162
236
|
if (commit.breaking) {
|
|
163
|
-
insights.deploymentRequirements.push('Breaking changes - review migration notes above.')
|
|
237
|
+
insights.deploymentRequirements.push('Breaking changes - review migration notes above.')
|
|
164
238
|
}
|
|
165
|
-
})
|
|
239
|
+
})
|
|
166
240
|
|
|
167
|
-
insights.affectedAreas = Array.from(insights.affectedAreas)
|
|
241
|
+
insights.affectedAreas = Array.from(insights.affectedAreas)
|
|
168
242
|
|
|
169
243
|
// Include working directory changes in insights
|
|
170
244
|
if (workingDirAnalysis && workingDirAnalysis.changes.length > 0) {
|
|
171
245
|
insights.workingDirectoryChanges = {
|
|
172
246
|
count: workingDirAnalysis.changes.length,
|
|
173
|
-
summary: workingDirAnalysis.analysis
|
|
174
|
-
|
|
247
|
+
summary: workingDirAnalysis.analysis
|
|
248
|
+
? workingDirAnalysis.analysis.summary
|
|
249
|
+
: 'Working directory changes detected',
|
|
250
|
+
}
|
|
175
251
|
|
|
176
252
|
// Add working directory files to affected areas
|
|
177
|
-
workingDirAnalysis.changes.forEach(change => {
|
|
253
|
+
workingDirAnalysis.changes.forEach((change) => {
|
|
178
254
|
if (change.category) {
|
|
179
|
-
insights.affectedAreas.push(change.category)
|
|
255
|
+
insights.affectedAreas.push(change.category)
|
|
180
256
|
}
|
|
181
|
-
})
|
|
257
|
+
})
|
|
182
258
|
|
|
183
259
|
// Remove duplicates
|
|
184
|
-
insights.affectedAreas = Array.from(new Set(insights.affectedAreas))
|
|
260
|
+
insights.affectedAreas = Array.from(new Set(insights.affectedAreas))
|
|
185
261
|
}
|
|
186
262
|
|
|
187
263
|
// Assess overall complexity
|
|
188
|
-
const totalFiles = analyzedCommits.reduce((sum, commit) => sum + commit.files.length, 0)
|
|
189
|
-
const avgFilesPerCommit = totalFiles / analyzedCommits.length
|
|
264
|
+
const totalFiles = analyzedCommits.reduce((sum, commit) => sum + commit.files.length, 0)
|
|
265
|
+
const avgFilesPerCommit = totalFiles / analyzedCommits.length
|
|
190
266
|
|
|
191
267
|
if (avgFilesPerCommit > 20 || insights.breaking) {
|
|
192
|
-
insights.complexity = 'high'
|
|
268
|
+
insights.complexity = 'high'
|
|
193
269
|
} else if (avgFilesPerCommit > 10) {
|
|
194
|
-
insights.complexity = 'medium'
|
|
270
|
+
insights.complexity = 'medium'
|
|
195
271
|
}
|
|
196
272
|
|
|
197
273
|
// Generate summary based on insights
|
|
198
|
-
insights.summary = this.generateInsightsSummary(insights, version)
|
|
274
|
+
insights.summary = this.generateInsightsSummary(insights, version)
|
|
199
275
|
|
|
200
|
-
return insights
|
|
276
|
+
return insights
|
|
201
277
|
}
|
|
202
278
|
|
|
203
279
|
generateInsightsSummary(insights, version) {
|
|
204
|
-
const { totalCommits, commitTypes, breaking, complexity, affectedAreas } = insights
|
|
280
|
+
const { totalCommits, commitTypes, breaking, complexity, affectedAreas } = insights
|
|
205
281
|
|
|
206
|
-
let summary = `Release ${version || 'latest'} includes ${totalCommits} commits
|
|
282
|
+
let summary = `Release ${version || 'latest'} includes ${totalCommits} commits`
|
|
207
283
|
|
|
208
284
|
if (breaking) {
|
|
209
|
-
summary += ' with breaking changes'
|
|
285
|
+
summary += ' with breaking changes'
|
|
210
286
|
}
|
|
211
287
|
|
|
212
288
|
if (Object.keys(commitTypes).length > 0) {
|
|
213
289
|
const types = Object.entries(commitTypes)
|
|
214
|
-
.sort(([,a], [,b]) => b - a)
|
|
290
|
+
.sort(([, a], [, b]) => b - a)
|
|
215
291
|
.map(([type, count]) => `${count} ${type}`)
|
|
216
|
-
.join(', ')
|
|
217
|
-
summary += ` (${types})
|
|
292
|
+
.join(', ')
|
|
293
|
+
summary += ` (${types})`
|
|
218
294
|
}
|
|
219
295
|
|
|
220
|
-
summary += `. Complexity: ${complexity}. Affected areas: ${affectedAreas.join(', ')}
|
|
296
|
+
summary += `. Complexity: ${complexity}. Affected areas: ${affectedAreas.join(', ')}.`
|
|
221
297
|
|
|
222
|
-
return summary
|
|
298
|
+
return summary
|
|
223
299
|
}
|
|
224
300
|
|
|
225
|
-
async buildChangelog(analyzedCommits,
|
|
226
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
227
|
-
const commitUrl = this.configManager?.getCommitUrl()
|
|
228
|
-
const commitRangeUrl = this.configManager?.getCommitRangeUrl()
|
|
229
|
-
const issueUrl = this.configManager?.getIssueUrl()
|
|
230
|
-
const issueRegex = this.configManager?.getIssueRegexPattern()
|
|
231
|
-
|
|
232
|
-
|
|
301
|
+
async buildChangelog(analyzedCommits, _insights, version, workingDirAnalysis = null) {
|
|
302
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
303
|
+
const commitUrl = this.configManager?.getCommitUrl()
|
|
304
|
+
const commitRangeUrl = this.configManager?.getCommitRangeUrl()
|
|
305
|
+
const issueUrl = this.configManager?.getIssueUrl()
|
|
306
|
+
const issueRegex = this.configManager?.getIssueRegexPattern()
|
|
307
|
+
|
|
308
|
+
// Keep a Changelog standard header
|
|
309
|
+
let changelog = '# Changelog\n\n'
|
|
310
|
+
changelog += 'All notable changes to this project will be documented in this file.\n\n'
|
|
311
|
+
changelog +=
|
|
312
|
+
'The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\n'
|
|
313
|
+
changelog +=
|
|
314
|
+
'and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n'
|
|
233
315
|
|
|
234
316
|
// Version header with commit range link
|
|
235
|
-
const firstCommit = analyzedCommits[0]
|
|
236
|
-
const lastCommit = analyzedCommits
|
|
237
|
-
let versionHeader = `## [${version || 'Unreleased'}] - ${timestamp}
|
|
317
|
+
const firstCommit = analyzedCommits[0]
|
|
318
|
+
const lastCommit = analyzedCommits.at(-1)
|
|
319
|
+
let versionHeader = `## [${version || 'Unreleased'}] - ${timestamp}`
|
|
238
320
|
|
|
239
321
|
if (firstCommit && lastCommit && commitRangeUrl) {
|
|
240
|
-
const rangeLink = markdownCommitRangeLink(
|
|
241
|
-
|
|
322
|
+
const rangeLink = markdownCommitRangeLink(
|
|
323
|
+
lastCommit.fullHash,
|
|
324
|
+
firstCommit.fullHash,
|
|
325
|
+
commitRangeUrl
|
|
326
|
+
)
|
|
327
|
+
versionHeader += ` (${rangeLink})`
|
|
242
328
|
}
|
|
243
329
|
|
|
244
|
-
changelog += `${versionHeader}\n\n
|
|
245
|
-
|
|
246
|
-
// Release summary
|
|
247
|
-
changelog += `### 📋 Release Summary\n`;
|
|
248
|
-
changelog += `${insights.summary}\n\n`;
|
|
249
|
-
changelog += `**Business Impact**: ${insights.businessImpact}\n`;
|
|
250
|
-
changelog += `**Complexity**: ${insights.complexity}\n\n`;
|
|
330
|
+
changelog += `${versionHeader}\n\n`
|
|
251
331
|
|
|
252
332
|
// Group commits by conventional type
|
|
253
|
-
const groupedCommits = this.groupCommitsByType(analyzedCommits)
|
|
254
|
-
|
|
333
|
+
const groupedCommits = this.groupCommitsByType(analyzedCommits)
|
|
334
|
+
|
|
335
|
+
// Define preferred order for changelog sections
|
|
336
|
+
const preferredOrder = [
|
|
337
|
+
'feat',
|
|
338
|
+
'fix',
|
|
339
|
+
'perf',
|
|
340
|
+
'refactor',
|
|
341
|
+
'docs',
|
|
342
|
+
'test',
|
|
343
|
+
'build',
|
|
344
|
+
'ci',
|
|
345
|
+
'chore',
|
|
346
|
+
'style',
|
|
347
|
+
'revert',
|
|
348
|
+
'other',
|
|
349
|
+
]
|
|
350
|
+
const sortedTypes = preferredOrder
|
|
351
|
+
.filter((type) => groupedCommits[type] && groupedCommits[type].length > 0)
|
|
352
|
+
.concat(
|
|
353
|
+
Object.keys(groupedCommits).filter(
|
|
354
|
+
(type) => !preferredOrder.includes(type) && groupedCommits[type].length > 0
|
|
355
|
+
)
|
|
356
|
+
)
|
|
255
357
|
|
|
256
358
|
// Generate sections for each commit type
|
|
257
|
-
changelog += `## Changes\n\n`;
|
|
258
|
-
|
|
259
359
|
for (const type of sortedTypes) {
|
|
260
|
-
const commits = groupedCommits[type]
|
|
261
|
-
if (commits.length === 0)
|
|
360
|
+
const commits = groupedCommits[type]
|
|
361
|
+
if (commits.length === 0) {
|
|
362
|
+
continue
|
|
363
|
+
}
|
|
262
364
|
|
|
263
|
-
changelog += `### ${this.getTypeHeader(type)}\n\n
|
|
365
|
+
changelog += `### ${this.getTypeHeader(type)}\n\n`
|
|
264
366
|
|
|
265
367
|
for (const commit of commits) {
|
|
266
|
-
changelog += this.buildCommitEntry(commit, commitUrl, issueUrl, issueRegex)
|
|
368
|
+
changelog += this.buildCommitEntry(commit, commitUrl, issueUrl, issueRegex)
|
|
267
369
|
}
|
|
268
370
|
|
|
269
|
-
changelog += '\n'
|
|
371
|
+
changelog += '\n'
|
|
270
372
|
}
|
|
271
373
|
|
|
272
|
-
// Breaking changes section
|
|
273
|
-
const breakingCommits = analyzedCommits.filter(
|
|
274
|
-
commit.breaking || (commit.breakingChanges && commit.breakingChanges.length > 0)
|
|
275
|
-
)
|
|
374
|
+
// Breaking changes section (if any breaking changes exist)
|
|
375
|
+
const breakingCommits = analyzedCommits.filter(
|
|
376
|
+
(commit) => commit.breaking || (commit.breakingChanges && commit.breakingChanges.length > 0)
|
|
377
|
+
)
|
|
276
378
|
|
|
277
379
|
if (breakingCommits.length > 0) {
|
|
278
|
-
|
|
279
|
-
changelog += `### ${breakingHeader}\n\n`;
|
|
380
|
+
changelog += '### 🚨 BREAKING CHANGES\n\n'
|
|
280
381
|
|
|
281
382
|
for (const commit of breakingCommits) {
|
|
282
|
-
changelog += this.buildBreakingChangeEntry(commit, commitUrl, issueUrl, issueRegex)
|
|
383
|
+
changelog += this.buildBreakingChangeEntry(commit, commitUrl, issueUrl, issueRegex)
|
|
283
384
|
}
|
|
284
385
|
|
|
285
|
-
changelog += '\n'
|
|
386
|
+
changelog += '\n'
|
|
286
387
|
}
|
|
287
388
|
|
|
288
389
|
// Append working directory changes if present
|
|
289
|
-
if (workingDirAnalysis
|
|
290
|
-
const workingDirSection = await this.buildWorkingDirectorySection(workingDirAnalysis)
|
|
291
|
-
changelog += workingDirSection
|
|
390
|
+
if (workingDirAnalysis?.changes && workingDirAnalysis.changes.length > 0) {
|
|
391
|
+
const workingDirSection = await this.buildWorkingDirectorySection(workingDirAnalysis)
|
|
392
|
+
changelog += workingDirSection
|
|
292
393
|
}
|
|
293
394
|
|
|
294
395
|
// Attribution footer
|
|
295
|
-
changelog +=
|
|
296
|
-
changelog +=
|
|
396
|
+
changelog += '---\n\n'
|
|
397
|
+
changelog +=
|
|
398
|
+
'*Generated using [ai-changelog-generator](https://github.com/entro314-labs/AI-changelog-generator) - AI-powered changelog generation for Git repositories*\n'
|
|
297
399
|
|
|
298
|
-
return changelog
|
|
400
|
+
return changelog
|
|
299
401
|
}
|
|
300
402
|
|
|
301
403
|
buildCommitEntry(commit, commitUrl, issueUrl, issueRegex) {
|
|
302
|
-
|
|
404
|
+
// Use AI-generated summary if available, fallback to commit message
|
|
405
|
+
let title =
|
|
406
|
+
commit.aiSummary?.summary || commit.description || commit.subject || 'No description'
|
|
303
407
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
let description = commit.aiSummary?.technicalDetails || commit.aiSummary?.description;
|
|
307
|
-
|
|
308
|
-
// If AI description is same as title, don't repeat it
|
|
309
|
-
if (description === title) {
|
|
310
|
-
description = null;
|
|
311
|
-
}
|
|
408
|
+
// Clean up title - remove conventional commit prefix if it exists
|
|
409
|
+
title = title.replace(/^(feat|fix|refactor|docs|chore|test|style|perf|build|ci):\s*/i, '')
|
|
312
410
|
|
|
313
411
|
// Process issue references in title
|
|
314
412
|
if (issueUrl && issueRegex) {
|
|
315
|
-
title = processIssueReferences(title, issueUrl, issueRegex)
|
|
413
|
+
title = processIssueReferences(title, issueUrl, issueRegex)
|
|
316
414
|
}
|
|
317
415
|
|
|
318
|
-
// Build entry
|
|
319
|
-
let entry = '- '
|
|
416
|
+
// Build clean entry
|
|
417
|
+
let entry = '- '
|
|
418
|
+
|
|
419
|
+
// Add scope if available
|
|
320
420
|
if (commit.scope) {
|
|
321
|
-
entry += `**${commit.scope}**:
|
|
421
|
+
entry += `**${commit.scope}**: `
|
|
322
422
|
}
|
|
323
423
|
|
|
324
|
-
// Add
|
|
325
|
-
|
|
326
|
-
entry += '🔥 ';
|
|
327
|
-
}
|
|
424
|
+
// Add the main title/description
|
|
425
|
+
entry += title
|
|
328
426
|
|
|
329
|
-
|
|
427
|
+
// Add commit link if available
|
|
428
|
+
if (commitUrl && (commit.fullHash || commit.hash)) {
|
|
429
|
+
const commitLink = markdownCommitLink(commit.fullHash || commit.hash, commitUrl)
|
|
430
|
+
entry += ` (${commitLink})`
|
|
431
|
+
}
|
|
330
432
|
|
|
331
|
-
// Add
|
|
332
|
-
|
|
333
|
-
|
|
433
|
+
// Add additional technical details if available and different from title
|
|
434
|
+
const techDetails = commit.aiSummary?.technicalDetails
|
|
435
|
+
if (techDetails && techDetails !== title && techDetails.length > title.length) {
|
|
436
|
+
entry += `\n - ${techDetails}`
|
|
334
437
|
}
|
|
335
438
|
|
|
336
|
-
|
|
439
|
+
// Add migration notes if available
|
|
440
|
+
if (commit.aiSummary?.migrationNotes) {
|
|
441
|
+
entry += `\n - **Migration**: ${commit.aiSummary.migrationNotes}`
|
|
442
|
+
}
|
|
337
443
|
|
|
338
444
|
// Add issue references from commit body
|
|
339
445
|
if (commit.issueReferences && commit.issueReferences.length > 0) {
|
|
340
|
-
const issueLinks = commit.issueReferences
|
|
341
|
-
issueUrl ? markdownIssueLink(ref, issueUrl) : ref
|
|
342
|
-
|
|
343
|
-
entry +=
|
|
446
|
+
const issueLinks = commit.issueReferences
|
|
447
|
+
.map((ref) => (issueUrl ? markdownIssueLink(ref, issueUrl) : ref))
|
|
448
|
+
.join(', ')
|
|
449
|
+
entry += `\n - References: ${issueLinks}`
|
|
344
450
|
}
|
|
345
451
|
|
|
346
452
|
// Add closes references
|
|
347
453
|
if (commit.closesReferences && commit.closesReferences.length > 0) {
|
|
348
|
-
const closesLinks = commit.closesReferences
|
|
349
|
-
issueUrl ? markdownIssueLink(ref, issueUrl) : ref
|
|
350
|
-
|
|
351
|
-
entry +=
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Add migration notes if available
|
|
355
|
-
if (commit.aiSummary?.migrationNotes) {
|
|
356
|
-
entry += `\n - **Migration**: ${commit.aiSummary.migrationNotes}`;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Add highlights as sub-bullets
|
|
360
|
-
if (commit.aiSummary?.highlights && commit.aiSummary.highlights.length > 1) {
|
|
361
|
-
commit.aiSummary.highlights.slice(1).forEach(highlight => {
|
|
362
|
-
entry += `\n - ${highlight}`;
|
|
363
|
-
});
|
|
454
|
+
const closesLinks = commit.closesReferences
|
|
455
|
+
.map((ref) => (issueUrl ? markdownIssueLink(ref, issueUrl) : ref))
|
|
456
|
+
.join(', ')
|
|
457
|
+
entry += `\n - Closes: ${closesLinks}`
|
|
364
458
|
}
|
|
365
459
|
|
|
366
|
-
return `${entry}\n
|
|
460
|
+
return `${entry}\n`
|
|
367
461
|
}
|
|
368
462
|
|
|
369
|
-
buildBreakingChangeEntry(commit, commitUrl,
|
|
370
|
-
|
|
371
|
-
let entry = '- ';
|
|
463
|
+
buildBreakingChangeEntry(commit, commitUrl, _issueUrl, _issueRegex) {
|
|
464
|
+
let entry = '- '
|
|
372
465
|
|
|
466
|
+
// Add scope if available
|
|
373
467
|
if (commit.scope) {
|
|
374
|
-
entry += `**${commit.scope}**:
|
|
468
|
+
entry += `**${commit.scope}**: `
|
|
375
469
|
}
|
|
376
470
|
|
|
377
471
|
// Use AI-generated breaking change description if available
|
|
378
|
-
let breakingDescription = null
|
|
472
|
+
let breakingDescription = null
|
|
379
473
|
if (commit.aiSummary?.breakingChangeDescription) {
|
|
380
|
-
breakingDescription = commit.aiSummary.breakingChangeDescription
|
|
474
|
+
breakingDescription = commit.aiSummary.breakingChangeDescription
|
|
381
475
|
} else if (commit.breakingChanges && commit.breakingChanges.length > 0) {
|
|
382
|
-
breakingDescription = commit.breakingChanges[0]
|
|
476
|
+
breakingDescription = commit.breakingChanges[0]
|
|
383
477
|
} else {
|
|
384
|
-
breakingDescription =
|
|
478
|
+
breakingDescription =
|
|
479
|
+
commit.aiSummary?.summary || commit.description || commit.subject || 'Breaking change'
|
|
385
480
|
}
|
|
386
481
|
|
|
387
|
-
|
|
482
|
+
// Clean up breaking description
|
|
483
|
+
breakingDescription = breakingDescription.replace(
|
|
484
|
+
/^(feat|fix|refactor|docs|chore|test|style|perf|build|ci):\s*/i,
|
|
485
|
+
''
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
entry += breakingDescription
|
|
489
|
+
|
|
490
|
+
// Add commit link if available
|
|
491
|
+
if (commitUrl && (commit.fullHash || commit.hash)) {
|
|
492
|
+
const commitLink = markdownCommitLink(commit.fullHash || commit.hash, commitUrl)
|
|
493
|
+
entry += ` (${commitLink})`
|
|
494
|
+
}
|
|
388
495
|
|
|
389
496
|
// Add migration notes for breaking changes
|
|
390
497
|
if (commit.aiSummary?.migrationNotes) {
|
|
391
|
-
entry += `\n - **Migration**: ${commit.aiSummary.migrationNotes}
|
|
498
|
+
entry += `\n - **Migration**: ${commit.aiSummary.migrationNotes}`
|
|
392
499
|
}
|
|
393
500
|
|
|
394
501
|
// Add additional breaking change details
|
|
395
502
|
if (commit.breakingChanges && commit.breakingChanges.length > 1) {
|
|
396
|
-
commit.breakingChanges.slice(1).forEach(change => {
|
|
397
|
-
entry += `\n - ${change}
|
|
398
|
-
})
|
|
503
|
+
commit.breakingChanges.slice(1).forEach((change) => {
|
|
504
|
+
entry += `\n - ${change}`
|
|
505
|
+
})
|
|
399
506
|
}
|
|
400
507
|
|
|
401
|
-
// Add technical details for breaking changes
|
|
402
|
-
|
|
403
|
-
|
|
508
|
+
// Add technical details for breaking changes if different from description
|
|
509
|
+
const techDetails = commit.aiSummary?.technicalDetails
|
|
510
|
+
if (
|
|
511
|
+
techDetails &&
|
|
512
|
+
techDetails !== breakingDescription &&
|
|
513
|
+
techDetails.length > breakingDescription.length
|
|
514
|
+
) {
|
|
515
|
+
entry += `\n - ${techDetails}`
|
|
404
516
|
}
|
|
405
517
|
|
|
406
|
-
return `${entry}\n
|
|
518
|
+
return `${entry}\n`
|
|
407
519
|
}
|
|
408
520
|
|
|
409
521
|
groupCommitsByType(commits) {
|
|
410
|
-
const configuredTypes = this.configManager?.getChangelogCommitTypes() || [
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
522
|
+
const configuredTypes = this.configManager?.getChangelogCommitTypes() || [
|
|
523
|
+
'feat',
|
|
524
|
+
'fix',
|
|
525
|
+
'refactor',
|
|
526
|
+
'perf',
|
|
527
|
+
'docs',
|
|
528
|
+
'build',
|
|
529
|
+
'chore',
|
|
530
|
+
]
|
|
531
|
+
const includeInvalid = this.configManager?.shouldIncludeInvalidCommits() ?? true
|
|
532
|
+
|
|
533
|
+
const types = {}
|
|
534
|
+
|
|
535
|
+
commits.forEach((commit) => {
|
|
416
536
|
// ALWAYS parse conventional commit first - this is the primary source of truth for types
|
|
417
|
-
const conventionalCommit = parseConventionalCommit(
|
|
418
|
-
|
|
537
|
+
const conventionalCommit = parseConventionalCommit(
|
|
538
|
+
commit.subject || commit.message,
|
|
539
|
+
commit.body
|
|
540
|
+
)
|
|
541
|
+
let type = conventionalCommit.type
|
|
419
542
|
|
|
420
543
|
// Only use AI analysis type as fallback if no conventional commit type found
|
|
421
544
|
if (!type || type === 'other') {
|
|
422
|
-
type = commit.aiSummary?.category || commit.type || (includeInvalid ? 'other' : null)
|
|
545
|
+
type = commit.aiSummary?.category || commit.type || (includeInvalid ? 'other' : null)
|
|
423
546
|
}
|
|
424
547
|
|
|
425
548
|
// Only include configured types or invalid commits if allowed
|
|
426
549
|
if (type && (configuredTypes.includes(type) || (type === 'other' && includeInvalid))) {
|
|
427
|
-
if (!types[type])
|
|
550
|
+
if (!types[type]) {
|
|
551
|
+
types[type] = []
|
|
552
|
+
}
|
|
428
553
|
|
|
429
554
|
// Preserve AI analysis and enhance with conventional commit data
|
|
430
555
|
types[type].push({
|
|
431
556
|
...commit, // Preserves aiSummary and other AI analysis
|
|
432
557
|
...conventionalCommit, // Add conventional commit fields - this includes scope, breaking, etc.
|
|
433
|
-
type
|
|
434
|
-
})
|
|
558
|
+
type,
|
|
559
|
+
})
|
|
435
560
|
}
|
|
436
|
-
})
|
|
561
|
+
})
|
|
437
562
|
|
|
438
|
-
return types
|
|
563
|
+
return types
|
|
439
564
|
}
|
|
440
565
|
|
|
441
566
|
getTypeHeader(type) {
|
|
442
|
-
const configuredHeadlines = this.configManager?.getHeadlines() || {}
|
|
567
|
+
const configuredHeadlines = this.configManager?.getHeadlines() || {}
|
|
443
568
|
|
|
444
569
|
// Use configured headlines from YAML config or defaults
|
|
445
570
|
const defaultHeaders = {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
return
|
|
571
|
+
feat: '🚀 Features',
|
|
572
|
+
fix: '🐛 Bug Fixes',
|
|
573
|
+
perf: '⚡ Performance Improvements',
|
|
574
|
+
refactor: '♻️ Refactoring',
|
|
575
|
+
docs: '📚 Documentation',
|
|
576
|
+
test: '🧪 Tests',
|
|
577
|
+
build: '🔧 Build System',
|
|
578
|
+
ci: '⚙️ CI/CD',
|
|
579
|
+
chore: '🔧 Maintenance',
|
|
580
|
+
style: '💄 Code Style',
|
|
581
|
+
revert: '⏪ Reverts',
|
|
582
|
+
merge: '🔀 Merges',
|
|
583
|
+
other: '📦 Other Changes',
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
return (
|
|
587
|
+
configuredHeadlines[type] ||
|
|
588
|
+
defaultHeaders[type] ||
|
|
589
|
+
`📦 ${type.charAt(0).toUpperCase() + type.slice(1)}`
|
|
590
|
+
)
|
|
462
591
|
}
|
|
463
592
|
|
|
464
593
|
/**
|
|
@@ -469,19 +598,19 @@ export class ChangelogService {
|
|
|
469
598
|
async generateChangelogFromChanges(version = null) {
|
|
470
599
|
// Use the proper WorkspaceChangelogService with optimized single AI call approach
|
|
471
600
|
const result = await this.workspaceChangelogService.generateWorkspaceChangelog(version, {
|
|
472
|
-
analysisMode: 'detailed' // Use detailed mode for better analysis
|
|
473
|
-
})
|
|
601
|
+
analysisMode: 'detailed', // Use detailed mode for better analysis
|
|
602
|
+
})
|
|
474
603
|
|
|
475
604
|
if (!result) {
|
|
476
|
-
console.log(colors.infoMessage('No changes detected in working directory.'))
|
|
477
|
-
return null
|
|
605
|
+
console.log(colors.infoMessage('No changes detected in working directory.'))
|
|
606
|
+
return null
|
|
478
607
|
}
|
|
479
608
|
|
|
480
609
|
return {
|
|
481
610
|
changelog: result.changelog,
|
|
482
611
|
analysis: result.context,
|
|
483
|
-
changes: result.changes
|
|
484
|
-
}
|
|
612
|
+
changes: result.changes,
|
|
613
|
+
}
|
|
485
614
|
}
|
|
486
615
|
|
|
487
616
|
/**
|
|
@@ -491,1258 +620,1408 @@ export class ChangelogService {
|
|
|
491
620
|
*/
|
|
492
621
|
async buildWorkingDirectorySection(workingDirAnalysis) {
|
|
493
622
|
// Use the optimized WorkspaceChangelogService for consistent formatting
|
|
494
|
-
const workspaceResult =
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
623
|
+
const workspaceResult =
|
|
624
|
+
await this.workspaceChangelogService.generateCommitStyleWorkingDirectoryEntries({
|
|
625
|
+
analysisMode: 'detailed',
|
|
626
|
+
workingDirAnalysis,
|
|
627
|
+
})
|
|
498
628
|
|
|
499
|
-
if (!workspaceResult
|
|
500
|
-
return
|
|
629
|
+
if (!workspaceResult?.entries || workspaceResult.entries.length === 0) {
|
|
630
|
+
return ''
|
|
501
631
|
}
|
|
502
632
|
|
|
503
|
-
let section =
|
|
633
|
+
let section = '### 🔧 Unreleased Changes\n\n'
|
|
504
634
|
|
|
505
|
-
// Add each entry in
|
|
506
|
-
workspaceResult.entries.forEach(entry => {
|
|
507
|
-
|
|
508
|
-
|
|
635
|
+
// Add each entry in clean format
|
|
636
|
+
workspaceResult.entries.forEach((entry) => {
|
|
637
|
+
// Clean up the AI-generated entries to match our new format
|
|
638
|
+
let cleanEntry = entry.replace(/^- \([\w]+\)\s*/, '- ') // Remove type prefixes like "(feature)"
|
|
639
|
+
cleanEntry = cleanEntry.replace(/^- (.+?) - (.+)$/, '- $2') // Extract just the description
|
|
640
|
+
section += `${cleanEntry}\n`
|
|
641
|
+
})
|
|
509
642
|
|
|
510
|
-
|
|
643
|
+
section += '\n'
|
|
644
|
+
return section
|
|
511
645
|
}
|
|
512
646
|
|
|
513
647
|
async generateAIPoweredChangeEntry(change) {
|
|
514
|
-
const path = change.path || change.filePath || 'unknown'
|
|
515
|
-
const status = change.status || '??'
|
|
516
|
-
const
|
|
648
|
+
const path = change.path || change.filePath || 'unknown'
|
|
649
|
+
const status = change.status || '??'
|
|
650
|
+
const _category = change.category || 'other'
|
|
517
651
|
|
|
518
652
|
// Map status to change type
|
|
519
653
|
const statusTypeMap = {
|
|
520
654
|
'??': 'feature',
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
}
|
|
655
|
+
A: 'feature',
|
|
656
|
+
M: 'update',
|
|
657
|
+
D: 'remove',
|
|
658
|
+
R: 'refactor',
|
|
659
|
+
}
|
|
526
660
|
|
|
527
|
-
const changeType = statusTypeMap[status] || 'other'
|
|
661
|
+
const changeType = statusTypeMap[status] || 'other'
|
|
528
662
|
|
|
529
663
|
// Use AI to analyze the actual diff content for intelligent description
|
|
530
|
-
let description = 'Working directory change'
|
|
531
|
-
let confidence = 85
|
|
664
|
+
let description = 'Working directory change'
|
|
665
|
+
let confidence = 85
|
|
532
666
|
|
|
533
667
|
try {
|
|
534
668
|
// Use AI analysis for working directory files to get better descriptions
|
|
535
669
|
// especially important for architectural changes (deleted files)
|
|
536
|
-
if (this.aiAnalysisService
|
|
670
|
+
if (this.aiAnalysisService?.hasAI) {
|
|
537
671
|
const aiSummary = await this.aiAnalysisService.generateAISummary({
|
|
538
672
|
subject: `Working directory change: ${path}`,
|
|
539
673
|
files: [change],
|
|
540
|
-
diffStats: { files: 1, insertions: 0, deletions: 0 }
|
|
541
|
-
})
|
|
542
|
-
if (aiSummary
|
|
543
|
-
description = aiSummary.technicalDetails
|
|
544
|
-
confidence = 85
|
|
674
|
+
diffStats: { files: 1, insertions: 0, deletions: 0 },
|
|
675
|
+
})
|
|
676
|
+
if (aiSummary?.technicalDetails) {
|
|
677
|
+
description = aiSummary.technicalDetails
|
|
678
|
+
confidence = 85
|
|
545
679
|
} else {
|
|
546
|
-
description = this.analyzeDiffContentForDescription(change)
|
|
547
|
-
confidence = 75
|
|
680
|
+
description = this.analyzeDiffContentForDescription(change)
|
|
681
|
+
confidence = 75
|
|
548
682
|
}
|
|
549
683
|
} else {
|
|
550
|
-
description = this.analyzeDiffContentForDescription(change)
|
|
551
|
-
confidence = 75
|
|
684
|
+
description = this.analyzeDiffContentForDescription(change)
|
|
685
|
+
confidence = 75
|
|
552
686
|
}
|
|
553
|
-
} catch (
|
|
554
|
-
console.warn(`AI analysis failed for ${path}, using enhanced fallback`)
|
|
687
|
+
} catch (_error) {
|
|
688
|
+
console.warn(`AI analysis failed for ${path}, using enhanced fallback`)
|
|
555
689
|
// Use enhanced analysis instead of generic
|
|
556
|
-
description = this.analyzeDiffContentForDescription(change)
|
|
557
|
-
confidence = 75
|
|
690
|
+
description = this.analyzeDiffContentForDescription(change)
|
|
691
|
+
confidence = 75
|
|
558
692
|
}
|
|
559
693
|
|
|
560
694
|
// Format like commit entry
|
|
561
|
-
return `- (${changeType}) ${path} - ${description} (${confidence}%)
|
|
695
|
+
return `- (${changeType}) ${path} - ${description} (${confidence}%)`
|
|
562
696
|
}
|
|
563
697
|
|
|
564
698
|
generateWorkingDirectoryChangeEntry(change) {
|
|
565
|
-
const path = change.path || change.filePath || 'unknown'
|
|
566
|
-
const status = change.status || '??'
|
|
567
|
-
const
|
|
568
|
-
const
|
|
699
|
+
const path = change.path || change.filePath || 'unknown'
|
|
700
|
+
const status = change.status || '??'
|
|
701
|
+
const _category = change.category || 'other'
|
|
702
|
+
const _importance = change.importance || 'medium'
|
|
569
703
|
|
|
570
704
|
// Map status to change type
|
|
571
705
|
const statusTypeMap = {
|
|
572
706
|
'??': 'feature',
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
707
|
+
A: 'feature',
|
|
708
|
+
M: 'update',
|
|
709
|
+
D: 'remove',
|
|
710
|
+
R: 'refactor',
|
|
711
|
+
}
|
|
578
712
|
|
|
579
|
-
const changeType = statusTypeMap[status] || 'other'
|
|
713
|
+
const changeType = statusTypeMap[status] || 'other'
|
|
580
714
|
|
|
581
715
|
// Generate AI-style description
|
|
582
|
-
const description = this.generateChangeDescription(change)
|
|
716
|
+
const description = this.generateChangeDescription(change)
|
|
583
717
|
|
|
584
718
|
// Calculate confidence score based on change analysis
|
|
585
|
-
const confidence = this.calculateChangeConfidence(change)
|
|
719
|
+
const confidence = this.calculateChangeConfidence(change)
|
|
586
720
|
|
|
587
721
|
// Format like commit entry
|
|
588
|
-
return `- (${changeType}) ${path} - ${description} (${confidence}%)
|
|
722
|
+
return `- (${changeType}) ${path} - ${description} (${confidence}%)`
|
|
589
723
|
}
|
|
590
724
|
|
|
591
725
|
generateChangeDescription(change) {
|
|
592
|
-
const path = change.path || change.filePath
|
|
593
|
-
const status = change.status
|
|
594
|
-
const diff = change.diff || ''
|
|
595
|
-
const category = change.category || 'other'
|
|
726
|
+
const path = change.path || change.filePath
|
|
727
|
+
const status = change.status
|
|
728
|
+
const diff = change.diff || ''
|
|
729
|
+
const category = change.category || 'other'
|
|
596
730
|
|
|
597
731
|
if (status === 'D') {
|
|
598
|
-
return `Removed ${category} file from working directory
|
|
732
|
+
return `Removed ${category} file from working directory`
|
|
599
733
|
}
|
|
600
734
|
|
|
601
735
|
if (status === 'A' || status === '??') {
|
|
602
736
|
// Handle directories differently from files
|
|
603
737
|
if (path.endsWith('/')) {
|
|
604
|
-
return this.analyzeDirectoryAddition(path)
|
|
738
|
+
return this.analyzeDirectoryAddition(path)
|
|
605
739
|
}
|
|
606
740
|
|
|
607
741
|
// For new files, analyze the actual content from the diff
|
|
608
|
-
const contentAnalysis = this.analyzeNewFileContent(diff, path)
|
|
742
|
+
const contentAnalysis = this.analyzeNewFileContent(diff, path)
|
|
609
743
|
if (contentAnalysis) {
|
|
610
|
-
return contentAnalysis
|
|
744
|
+
return contentAnalysis
|
|
611
745
|
}
|
|
612
|
-
return `Added new ${category} file to working directory
|
|
746
|
+
return `Added new ${category} file to working directory`
|
|
613
747
|
}
|
|
614
748
|
|
|
615
749
|
if (status === 'M') {
|
|
616
750
|
// Analyze what actually changed in the diff - prioritize functional analysis
|
|
617
|
-
const changeAnalysis = this.analyzeModifiedFileChanges(diff, path)
|
|
751
|
+
const changeAnalysis = this.analyzeModifiedFileChanges(diff, path)
|
|
618
752
|
if (changeAnalysis) {
|
|
619
|
-
return `Modified ${category} file - ${changeAnalysis}
|
|
753
|
+
return `Modified ${category} file - ${changeAnalysis}`
|
|
620
754
|
}
|
|
621
755
|
|
|
622
756
|
// Fallback to generic line count only if no functional analysis available
|
|
623
|
-
const lines = diff.split('\n')
|
|
624
|
-
const additions = lines.filter(
|
|
625
|
-
|
|
626
|
-
|
|
757
|
+
const lines = diff.split('\n')
|
|
758
|
+
const additions = lines.filter(
|
|
759
|
+
(line) => line.startsWith('+') && !line.startsWith('+++')
|
|
760
|
+
).length
|
|
761
|
+
const deletions = lines.filter(
|
|
762
|
+
(line) => line.startsWith('-') && !line.startsWith('---')
|
|
763
|
+
).length
|
|
764
|
+
return `Modified ${category} file with ${additions} additions and ${deletions} deletions`
|
|
627
765
|
}
|
|
628
766
|
|
|
629
767
|
if (status === 'R') {
|
|
630
|
-
return `Renamed ${category} file in working directory
|
|
768
|
+
return `Renamed ${category} file in working directory`
|
|
631
769
|
}
|
|
632
770
|
|
|
633
|
-
return `Updated ${category} file in working directory
|
|
771
|
+
return `Updated ${category} file in working directory`
|
|
634
772
|
}
|
|
635
773
|
|
|
636
774
|
analyzeNewFileContent(diff, path) {
|
|
637
|
-
if (!diff
|
|
638
|
-
return null
|
|
775
|
+
if (!diff?.includes('Content preview:')) {
|
|
776
|
+
return null
|
|
639
777
|
}
|
|
640
778
|
|
|
641
779
|
// Extract the content preview from our diff
|
|
642
|
-
const previewMatch = diff.match(/Content preview:\n([\s\S]*?)(?:\n\.\.\. \(truncated\)|$)/)
|
|
643
|
-
if (!previewMatch
|
|
644
|
-
return null
|
|
780
|
+
const previewMatch = diff.match(/Content preview:\n([\s\S]*?)(?:\n\.\.\. \(truncated\)|$)/)
|
|
781
|
+
if (!previewMatch?.[1]) {
|
|
782
|
+
return null
|
|
645
783
|
}
|
|
646
784
|
|
|
647
|
-
const content = previewMatch[1]
|
|
648
|
-
const filename = path ? path.split('/').pop() : 'file'
|
|
785
|
+
const content = previewMatch[1]
|
|
786
|
+
const filename = path ? path.split('/').pop() : 'file'
|
|
649
787
|
|
|
650
788
|
// Analyze based on file type and content
|
|
651
|
-
if (path
|
|
652
|
-
return this.analyzeMarkdownContent(content, filename)
|
|
789
|
+
if (path?.endsWith('.md')) {
|
|
790
|
+
return this.analyzeMarkdownContent(content, filename)
|
|
653
791
|
}
|
|
654
792
|
|
|
655
793
|
if (path && (path.endsWith('.js') || path.endsWith('.ts'))) {
|
|
656
|
-
return this.analyzeJavaScriptContent(content, filename)
|
|
794
|
+
return this.analyzeJavaScriptContent(content, filename)
|
|
657
795
|
}
|
|
658
796
|
|
|
659
|
-
if (path
|
|
660
|
-
return this.analyzeJsonContent(content, filename)
|
|
797
|
+
if (path?.endsWith('.json')) {
|
|
798
|
+
return this.analyzeJsonContent(content, filename)
|
|
661
799
|
}
|
|
662
800
|
|
|
663
|
-
if (path
|
|
664
|
-
return this.analyzeConfigContent(content, filename)
|
|
801
|
+
if (path?.startsWith('.')) {
|
|
802
|
+
return this.analyzeConfigContent(content, filename)
|
|
665
803
|
}
|
|
666
804
|
|
|
667
805
|
// Generic content analysis
|
|
668
|
-
const lines = content.split('\n').length
|
|
669
|
-
return `Added new file containing ${lines} lines of content
|
|
806
|
+
const lines = content.split('\n').length
|
|
807
|
+
return `Added new file containing ${lines} lines of content`
|
|
670
808
|
}
|
|
671
809
|
|
|
672
810
|
analyzeMarkdownContent(content, filename) {
|
|
673
|
-
const lines = content.split('\n')
|
|
674
|
-
const headings = lines.filter(line => line.startsWith('#')).length
|
|
675
|
-
const codeBlocks = (content.match(/```/g) || []).length / 2
|
|
676
|
-
const links = (content.match(/\[.*?\]\(.*?\)/g) || []).length
|
|
677
|
-
const tables = (content.match(/\|.*\|/g) || []).length
|
|
811
|
+
const lines = content.split('\n')
|
|
812
|
+
const headings = lines.filter((line) => line.startsWith('#')).length
|
|
813
|
+
const codeBlocks = (content.match(/```/g) || []).length / 2
|
|
814
|
+
const links = (content.match(/\[.*?\]\(.*?\)/g) || []).length
|
|
815
|
+
const tables = (content.match(/\|.*\|/g) || []).length
|
|
678
816
|
|
|
679
817
|
// Extract actual content insights
|
|
680
|
-
const contentLower = content.toLowerCase()
|
|
681
|
-
let documentType = ''
|
|
682
|
-
|
|
818
|
+
const contentLower = content.toLowerCase()
|
|
819
|
+
let documentType = ''
|
|
820
|
+
const keyContent = []
|
|
683
821
|
|
|
684
822
|
if (filename.includes('MAPPING') || filename.includes('mapping')) {
|
|
685
|
-
documentType = 'architectural mapping documentation'
|
|
686
|
-
if (contentLower.includes('class'))
|
|
687
|
-
|
|
688
|
-
|
|
823
|
+
documentType = 'architectural mapping documentation'
|
|
824
|
+
if (contentLower.includes('class')) {
|
|
825
|
+
keyContent.push('class documentation')
|
|
826
|
+
}
|
|
827
|
+
if (contentLower.includes('function')) {
|
|
828
|
+
keyContent.push('function mapping')
|
|
829
|
+
}
|
|
830
|
+
if (contentLower.includes('service')) {
|
|
831
|
+
keyContent.push('service architecture')
|
|
832
|
+
}
|
|
689
833
|
} else if (filename.includes('ENVIRONMENT') || filename.includes('env')) {
|
|
690
|
-
documentType = 'environment variable configuration guide'
|
|
691
|
-
const envVars = (content.match(/[A-Z_]+=/g) || []).length
|
|
692
|
-
if (envVars > 0)
|
|
834
|
+
documentType = 'environment variable configuration guide'
|
|
835
|
+
const envVars = (content.match(/[A-Z_]+=/g) || []).length
|
|
836
|
+
if (envVars > 0) {
|
|
837
|
+
keyContent.push(`${envVars} environment variables`)
|
|
838
|
+
}
|
|
693
839
|
} else if (filename.includes('README') || filename.includes('readme')) {
|
|
694
|
-
documentType = 'project documentation'
|
|
695
|
-
if (contentLower.includes('install'))
|
|
696
|
-
|
|
840
|
+
documentType = 'project documentation'
|
|
841
|
+
if (contentLower.includes('install')) {
|
|
842
|
+
keyContent.push('installation guide')
|
|
843
|
+
}
|
|
844
|
+
if (contentLower.includes('setup')) {
|
|
845
|
+
keyContent.push('setup instructions')
|
|
846
|
+
}
|
|
697
847
|
} else if (filename.includes('CHANGELOG') || filename.includes('changelog')) {
|
|
698
|
-
documentType = 'changelog documentation'
|
|
699
|
-
const versions = (content.match(/##?\s+\[?\d+\.\d+/g) || []).length
|
|
700
|
-
if (versions > 0)
|
|
848
|
+
documentType = 'changelog documentation'
|
|
849
|
+
const versions = (content.match(/##?\s+\[?\d+\.\d+/g) || []).length
|
|
850
|
+
if (versions > 0) {
|
|
851
|
+
keyContent.push(`${versions} version entries`)
|
|
852
|
+
}
|
|
701
853
|
} else {
|
|
702
|
-
documentType = 'documentation'
|
|
854
|
+
documentType = 'documentation'
|
|
703
855
|
}
|
|
704
856
|
|
|
705
|
-
let analysis = `Added ${filename} ${documentType}
|
|
857
|
+
let analysis = `Added ${filename} ${documentType}`
|
|
706
858
|
|
|
707
859
|
// Add specific content details
|
|
708
|
-
const details = []
|
|
709
|
-
if (keyContent.length > 0)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (
|
|
713
|
-
|
|
860
|
+
const details = []
|
|
861
|
+
if (keyContent.length > 0) {
|
|
862
|
+
details.push(...keyContent)
|
|
863
|
+
}
|
|
864
|
+
if (headings > 0) {
|
|
865
|
+
details.push(`${headings} sections`)
|
|
866
|
+
}
|
|
867
|
+
if (codeBlocks > 0) {
|
|
868
|
+
details.push(`${codeBlocks} code examples`)
|
|
869
|
+
}
|
|
870
|
+
if (tables > 0) {
|
|
871
|
+
details.push(`${tables} table rows`)
|
|
872
|
+
}
|
|
873
|
+
if (links > 0) {
|
|
874
|
+
details.push(`${links} links`)
|
|
875
|
+
}
|
|
714
876
|
|
|
715
877
|
if (details.length > 0) {
|
|
716
|
-
analysis += ` containing ${details.slice(0, 3).join(', ')}
|
|
878
|
+
analysis += ` containing ${details.slice(0, 3).join(', ')}`
|
|
717
879
|
}
|
|
718
880
|
|
|
719
|
-
return analysis
|
|
881
|
+
return analysis
|
|
720
882
|
}
|
|
721
883
|
|
|
722
884
|
analyzeJavaScriptContent(content, filename) {
|
|
723
|
-
const functions = (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(|=>\s*{/g) || []).length
|
|
724
|
-
const imports = (content.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
725
|
-
const classes = (content.match(/class\s+\w+/g) || []).length
|
|
726
|
-
const exports = (content.match(/export\s+/g) || []).length
|
|
885
|
+
const functions = (content.match(/function\s+\w+|const\s+\w+\s*=\s*\(|=>\s*{/g) || []).length
|
|
886
|
+
const imports = (content.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
887
|
+
const classes = (content.match(/class\s+\w+/g) || []).length
|
|
888
|
+
const exports = (content.match(/export\s+/g) || []).length
|
|
727
889
|
|
|
728
890
|
// Extract actual content insights
|
|
729
|
-
const lines = content.split('\n')
|
|
730
|
-
const
|
|
731
|
-
|
|
891
|
+
const lines = content.split('\n')
|
|
892
|
+
const _comments = lines.filter(
|
|
893
|
+
(line) => line.trim().startsWith('//') || line.trim().startsWith('*')
|
|
894
|
+
).length
|
|
895
|
+
const consoleStatements = (content.match(/console\.(log|error|warn|info)/g) || []).length
|
|
732
896
|
|
|
733
897
|
// Look for specific patterns that indicate purpose
|
|
734
|
-
let purpose = ''
|
|
898
|
+
let purpose = ''
|
|
735
899
|
if (content.includes('verification') || content.includes('check')) {
|
|
736
|
-
purpose = 'verification script'
|
|
900
|
+
purpose = 'verification script'
|
|
737
901
|
} else if (content.includes('test') || content.includes('spec')) {
|
|
738
|
-
purpose = 'test file'
|
|
902
|
+
purpose = 'test file'
|
|
739
903
|
} else if (content.includes('server') || content.includes('mcp')) {
|
|
740
|
-
purpose = 'MCP server entry point'
|
|
904
|
+
purpose = 'MCP server entry point'
|
|
741
905
|
} else if (content.includes('setup') || content.includes('config')) {
|
|
742
|
-
purpose = 'setup/configuration script'
|
|
906
|
+
purpose = 'setup/configuration script'
|
|
743
907
|
} else if (content.includes('import') && content.includes('update')) {
|
|
744
|
-
purpose = 'import update utility'
|
|
908
|
+
purpose = 'import update utility'
|
|
745
909
|
} else if (filename.includes('util')) {
|
|
746
|
-
purpose = 'utility module'
|
|
910
|
+
purpose = 'utility module'
|
|
747
911
|
} else {
|
|
748
|
-
purpose = 'JavaScript module'
|
|
912
|
+
purpose = 'JavaScript module'
|
|
749
913
|
}
|
|
750
914
|
|
|
751
915
|
// Extract meaningful text content for analysis
|
|
752
916
|
const meaningfulContent = content
|
|
753
917
|
.split('\n')
|
|
754
|
-
.filter(
|
|
918
|
+
.filter(
|
|
919
|
+
(line) => line.trim() && !line.trim().startsWith('//') && !line.trim().startsWith('*')
|
|
920
|
+
)
|
|
755
921
|
.join(' ')
|
|
756
|
-
.toLowerCase()
|
|
922
|
+
.toLowerCase()
|
|
757
923
|
|
|
758
924
|
// Look for specific functionality indicators
|
|
759
|
-
|
|
925
|
+
const functionality = []
|
|
760
926
|
if (meaningfulContent.includes('method') && meaningfulContent.includes('analysis')) {
|
|
761
|
-
functionality.push('analyzing methods')
|
|
927
|
+
functionality.push('analyzing methods')
|
|
762
928
|
}
|
|
763
929
|
if (meaningfulContent.includes('legacy') || meaningfulContent.includes('old')) {
|
|
764
|
-
functionality.push('legacy code handling')
|
|
930
|
+
functionality.push('legacy code handling')
|
|
765
931
|
}
|
|
766
932
|
if (meaningfulContent.includes('repository') || meaningfulContent.includes('repo')) {
|
|
767
|
-
functionality.push('repository analysis')
|
|
933
|
+
functionality.push('repository analysis')
|
|
768
934
|
}
|
|
769
935
|
if (meaningfulContent.includes('investigation') || meaningfulContent.includes('check')) {
|
|
770
|
-
functionality.push('investigation checklist')
|
|
936
|
+
functionality.push('investigation checklist')
|
|
771
937
|
}
|
|
772
938
|
if (consoleStatements > 3) {
|
|
773
|
-
functionality.push('detailed logging')
|
|
939
|
+
functionality.push('detailed logging')
|
|
774
940
|
}
|
|
775
941
|
|
|
776
|
-
let analysis = `Added ${filename.replace('.js', '')} ${purpose}
|
|
942
|
+
let analysis = `Added ${filename.replace('.js', '')} ${purpose}`
|
|
777
943
|
|
|
778
944
|
// Add functionality description
|
|
779
945
|
if (functionality.length > 0) {
|
|
780
|
-
analysis += ` for ${functionality.slice(0, 2).join(' and ')}
|
|
946
|
+
analysis += ` for ${functionality.slice(0, 2).join(' and ')}`
|
|
781
947
|
}
|
|
782
948
|
|
|
783
|
-
const features = []
|
|
784
|
-
if (classes > 0)
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
if (
|
|
949
|
+
const features = []
|
|
950
|
+
if (classes > 0) {
|
|
951
|
+
features.push(`${classes} class${classes > 1 ? 'es' : ''}`)
|
|
952
|
+
}
|
|
953
|
+
if (functions > 0) {
|
|
954
|
+
features.push(`${functions} function${functions > 1 ? 's' : ''}`)
|
|
955
|
+
}
|
|
956
|
+
if (imports > 0) {
|
|
957
|
+
features.push(`${imports} import${imports > 1 ? 's' : ''}`)
|
|
958
|
+
}
|
|
959
|
+
if (exports > 0) {
|
|
960
|
+
features.push(`${exports} export${exports > 1 ? 's' : ''}`)
|
|
961
|
+
}
|
|
788
962
|
|
|
789
963
|
if (features.length > 0) {
|
|
790
|
-
analysis += ` with ${features.slice(0, 3).join(', ')}
|
|
964
|
+
analysis += ` with ${features.slice(0, 3).join(', ')}`
|
|
791
965
|
}
|
|
792
966
|
|
|
793
|
-
return analysis
|
|
967
|
+
return analysis
|
|
794
968
|
}
|
|
795
969
|
|
|
796
970
|
analyzeJsonContent(content, filename) {
|
|
797
971
|
try {
|
|
798
|
-
const parsed = JSON.parse(content)
|
|
972
|
+
const parsed = JSON.parse(content)
|
|
799
973
|
|
|
800
974
|
if (filename === 'package.json') {
|
|
801
|
-
const deps = Object.keys(parsed.dependencies || {}).length
|
|
802
|
-
const devDeps = Object.keys(parsed.devDependencies || {}).length
|
|
803
|
-
const scripts = Object.keys(parsed.scripts || {}).length
|
|
975
|
+
const deps = Object.keys(parsed.dependencies || {}).length
|
|
976
|
+
const devDeps = Object.keys(parsed.devDependencies || {}).length
|
|
977
|
+
const scripts = Object.keys(parsed.scripts || {}).length
|
|
804
978
|
|
|
805
|
-
return `Added package.json with ${deps} dependencies, ${devDeps} dev dependencies, and ${scripts} scripts
|
|
979
|
+
return `Added package.json with ${deps} dependencies, ${devDeps} dev dependencies, and ${scripts} scripts`
|
|
806
980
|
}
|
|
807
981
|
|
|
808
982
|
if (filename === 'manifest.json') {
|
|
809
|
-
return `Added manifest.json configuration for ${parsed.name || 'application'} with ${Object.keys(parsed).length} properties
|
|
983
|
+
return `Added manifest.json configuration for ${parsed.name || 'application'} with ${Object.keys(parsed).length} properties`
|
|
810
984
|
}
|
|
811
985
|
|
|
812
|
-
const keys = Object.keys(parsed).length
|
|
813
|
-
return `Added ${filename} configuration with ${keys} settings
|
|
986
|
+
const keys = Object.keys(parsed).length
|
|
987
|
+
return `Added ${filename} configuration with ${keys} settings`
|
|
814
988
|
} catch {
|
|
815
|
-
return `Added ${filename} JSON configuration file
|
|
989
|
+
return `Added ${filename} JSON configuration file`
|
|
816
990
|
}
|
|
817
991
|
}
|
|
818
992
|
|
|
819
993
|
analyzeConfigContent(content, filename) {
|
|
820
|
-
const lines = content.split('\n').filter(line => line.trim()).length
|
|
994
|
+
const lines = content.split('\n').filter((line) => line.trim()).length
|
|
821
995
|
|
|
822
996
|
if (filename.includes('env')) {
|
|
823
|
-
const variables = content.split('\n').filter(line => line.includes('=')).length
|
|
824
|
-
return `Added environment configuration with ${variables} variables
|
|
997
|
+
const variables = content.split('\n').filter((line) => line.includes('=')).length
|
|
998
|
+
return `Added environment configuration with ${variables} variables`
|
|
825
999
|
}
|
|
826
1000
|
|
|
827
1001
|
if (filename.includes('ignore')) {
|
|
828
|
-
const patterns = content
|
|
829
|
-
|
|
1002
|
+
const patterns = content
|
|
1003
|
+
.split('\n')
|
|
1004
|
+
.filter((line) => line.trim() && !line.startsWith('#')).length
|
|
1005
|
+
return `Added ignore file with ${patterns} patterns`
|
|
830
1006
|
}
|
|
831
1007
|
|
|
832
|
-
return `Added ${filename} configuration with ${lines} lines
|
|
1008
|
+
return `Added ${filename} configuration with ${lines} lines`
|
|
833
1009
|
}
|
|
834
1010
|
|
|
835
1011
|
analyzeDirectoryAddition(path) {
|
|
836
|
-
const dirName = path.replace(/\/$/, '')
|
|
1012
|
+
const dirName = path.replace(/\/$/, '')
|
|
837
1013
|
|
|
838
1014
|
// Analyze based on directory name and purpose
|
|
839
1015
|
if (dirName === '.claude') {
|
|
840
|
-
return 'Added Claude Code configuration directory for AI coding assistant integration'
|
|
1016
|
+
return 'Added Claude Code configuration directory for AI coding assistant integration'
|
|
841
1017
|
}
|
|
842
1018
|
|
|
843
1019
|
if (dirName === '.context') {
|
|
844
|
-
return 'Added context directory for project context and documentation'
|
|
1020
|
+
return 'Added context directory for project context and documentation'
|
|
845
1021
|
}
|
|
846
1022
|
|
|
847
1023
|
if (dirName === '.github') {
|
|
848
|
-
return 'Added GitHub configuration directory for workflows, templates, and repository settings'
|
|
1024
|
+
return 'Added GitHub configuration directory for workflows, templates, and repository settings'
|
|
849
1025
|
}
|
|
850
1026
|
|
|
851
1027
|
if (dirName === 'docs') {
|
|
852
|
-
return 'Added documentation directory for project documentation'
|
|
1028
|
+
return 'Added documentation directory for project documentation'
|
|
853
1029
|
}
|
|
854
1030
|
|
|
855
1031
|
if (dirName === 'test' || dirName === 'tests') {
|
|
856
|
-
return 'Added test directory for unit tests and test files'
|
|
1032
|
+
return 'Added test directory for unit tests and test files'
|
|
857
1033
|
}
|
|
858
1034
|
|
|
859
1035
|
if (dirName === 'src') {
|
|
860
|
-
return 'Added source code directory for main application code'
|
|
1036
|
+
return 'Added source code directory for main application code'
|
|
861
1037
|
}
|
|
862
1038
|
|
|
863
1039
|
if (dirName === 'lib') {
|
|
864
|
-
return 'Added library directory for shared code and utilities'
|
|
1040
|
+
return 'Added library directory for shared code and utilities'
|
|
865
1041
|
}
|
|
866
1042
|
|
|
867
1043
|
if (dirName === 'bin') {
|
|
868
|
-
return 'Added binary directory for executable scripts'
|
|
1044
|
+
return 'Added binary directory for executable scripts'
|
|
869
1045
|
}
|
|
870
1046
|
|
|
871
1047
|
if (dirName === 'config') {
|
|
872
|
-
return 'Added configuration directory for application settings'
|
|
1048
|
+
return 'Added configuration directory for application settings'
|
|
873
1049
|
}
|
|
874
1050
|
|
|
875
1051
|
if (dirName === 'server') {
|
|
876
|
-
return 'Added server directory for server-side code and configuration'
|
|
1052
|
+
return 'Added server directory for server-side code and configuration'
|
|
877
1053
|
}
|
|
878
1054
|
|
|
879
1055
|
if (dirName === 'dxt') {
|
|
880
|
-
return 'Added DXT directory for development tooling and utilities'
|
|
1056
|
+
return 'Added DXT directory for development tooling and utilities'
|
|
881
1057
|
}
|
|
882
1058
|
|
|
883
1059
|
if (dirName.includes('test')) {
|
|
884
|
-
return `Added ${dirName} directory for testing and test results
|
|
1060
|
+
return `Added ${dirName} directory for testing and test results`
|
|
885
1061
|
}
|
|
886
1062
|
|
|
887
1063
|
if (dirName.startsWith('.')) {
|
|
888
|
-
return `Added ${dirName} configuration directory
|
|
1064
|
+
return `Added ${dirName} configuration directory`
|
|
889
1065
|
}
|
|
890
1066
|
|
|
891
|
-
return `Added ${dirName} directory
|
|
1067
|
+
return `Added ${dirName} directory`
|
|
892
1068
|
}
|
|
893
1069
|
|
|
894
1070
|
groupWorkingDirectoryChangesByType(changes) {
|
|
895
1071
|
const grouped = {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
}
|
|
1072
|
+
architectural: [],
|
|
1073
|
+
feature: [],
|
|
1074
|
+
refactor: [],
|
|
1075
|
+
update: [],
|
|
1076
|
+
remove: [],
|
|
1077
|
+
config: [],
|
|
1078
|
+
docs: [],
|
|
1079
|
+
test: [],
|
|
1080
|
+
}
|
|
905
1081
|
|
|
906
1082
|
for (const change of changes) {
|
|
907
|
-
const path = change.path || change.filePath || ''
|
|
908
|
-
const status = change.status || '??'
|
|
909
|
-
const
|
|
1083
|
+
const path = change.path || change.filePath || ''
|
|
1084
|
+
const status = change.status || '??'
|
|
1085
|
+
const _category = change.category || 'other'
|
|
910
1086
|
|
|
911
1087
|
// Determine the change type based on content and context
|
|
912
1088
|
if (this.isArchitecturalChange(path, change)) {
|
|
913
|
-
grouped.architectural.push(change)
|
|
1089
|
+
grouped.architectural.push(change)
|
|
914
1090
|
} else if (status === 'D') {
|
|
915
|
-
grouped.remove.push(change)
|
|
1091
|
+
grouped.remove.push(change)
|
|
916
1092
|
} else if (status === 'M') {
|
|
917
1093
|
if (this.isConfigurationFile(path)) {
|
|
918
|
-
grouped.config.push(change)
|
|
1094
|
+
grouped.config.push(change)
|
|
919
1095
|
} else {
|
|
920
|
-
grouped.update.push(change)
|
|
1096
|
+
grouped.update.push(change)
|
|
921
1097
|
}
|
|
922
1098
|
} else if (status === 'A' || status === '??') {
|
|
923
1099
|
if (this.isTestFile(path)) {
|
|
924
|
-
grouped.test.push(change)
|
|
1100
|
+
grouped.test.push(change)
|
|
925
1101
|
} else if (this.isDocumentationFile(path)) {
|
|
926
|
-
grouped.docs.push(change)
|
|
1102
|
+
grouped.docs.push(change)
|
|
927
1103
|
} else if (this.isConfigurationFile(path)) {
|
|
928
|
-
grouped.config.push(change)
|
|
1104
|
+
grouped.config.push(change)
|
|
929
1105
|
} else {
|
|
930
|
-
grouped.feature.push(change)
|
|
1106
|
+
grouped.feature.push(change)
|
|
931
1107
|
}
|
|
932
1108
|
}
|
|
933
1109
|
}
|
|
934
1110
|
|
|
935
1111
|
// Remove empty groups
|
|
936
|
-
return Object.fromEntries(
|
|
937
|
-
Object.entries(grouped).filter(([key, value]) => value.length > 0)
|
|
938
|
-
);
|
|
1112
|
+
return Object.fromEntries(Object.entries(grouped).filter(([_key, value]) => value.length > 0))
|
|
939
1113
|
}
|
|
940
1114
|
|
|
941
1115
|
async generateGroupedChangeEntry(changeType, files) {
|
|
942
|
-
const fileCount = files.length
|
|
1116
|
+
const fileCount = files.length
|
|
943
1117
|
|
|
944
1118
|
// Generate individual file descriptions using our existing perfect analysis
|
|
945
|
-
const fileDescriptions = []
|
|
1119
|
+
const fileDescriptions = []
|
|
946
1120
|
for (const file of files) {
|
|
947
|
-
const description = await this.generateAIPoweredChangeEntry(file)
|
|
1121
|
+
const description = await this.generateAIPoweredChangeEntry(file)
|
|
948
1122
|
if (description) {
|
|
949
1123
|
// Extract just the description part (remove the "- (type) path - " prefix)
|
|
950
|
-
const match = description.match(/- \(\w+\) .+ - (.+) \(\d+%\)/)
|
|
1124
|
+
const match = description.match(/- \(\w+\) .+ - (.+) \(\d+%\)/)
|
|
951
1125
|
if (match) {
|
|
952
|
-
fileDescriptions.push(match[1])
|
|
1126
|
+
fileDescriptions.push(match[1])
|
|
953
1127
|
}
|
|
954
1128
|
}
|
|
955
1129
|
}
|
|
956
1130
|
|
|
957
1131
|
// Intelligently summarize the individual descriptions
|
|
958
|
-
const summary = this.summarizeFileDescriptions(fileDescriptions, changeType, fileCount)
|
|
1132
|
+
const summary = this.summarizeFileDescriptions(fileDescriptions, changeType, fileCount)
|
|
959
1133
|
|
|
960
|
-
return `- (${changeType}) ${summary}
|
|
1134
|
+
return `- (${changeType}) ${summary}`
|
|
961
1135
|
}
|
|
962
1136
|
|
|
963
1137
|
summarizeFileDescriptions(descriptions, changeType, fileCount) {
|
|
964
1138
|
if (descriptions.length === 0) {
|
|
965
|
-
return `${changeType} changes affecting ${fileCount} files
|
|
1139
|
+
return `${changeType} changes affecting ${fileCount} files`
|
|
966
1140
|
}
|
|
967
1141
|
|
|
968
1142
|
// Extract specific, detailed functional changes and contexts
|
|
969
|
-
const specificChanges = []
|
|
970
|
-
const
|
|
971
|
-
const technologies = new Set()
|
|
972
|
-
const purposes = new Set()
|
|
1143
|
+
const specificChanges = []
|
|
1144
|
+
const _keyFiles = []
|
|
1145
|
+
const technologies = new Set()
|
|
1146
|
+
const purposes = new Set()
|
|
973
1147
|
|
|
974
1148
|
for (const desc of descriptions) {
|
|
975
1149
|
// Use the existing individual file analysis instead of hardcoded patterns
|
|
976
|
-
if (desc
|
|
977
|
-
specificChanges.push(desc)
|
|
1150
|
+
if (desc?.trim() && desc !== 'No description available') {
|
|
1151
|
+
specificChanges.push(desc)
|
|
978
1152
|
}
|
|
979
1153
|
}
|
|
980
1154
|
|
|
981
1155
|
// If we have specific changes, create detailed description
|
|
982
1156
|
if (specificChanges.length > 0) {
|
|
983
|
-
const uniqueChanges = [...new Set(specificChanges)]
|
|
1157
|
+
const uniqueChanges = [...new Set(specificChanges)]
|
|
984
1158
|
|
|
985
1159
|
// Create a detailed, specific description
|
|
986
1160
|
if (uniqueChanges.length === 1) {
|
|
987
|
-
const change = uniqueChanges[0]
|
|
988
|
-
const techList = [...technologies]
|
|
1161
|
+
const change = uniqueChanges[0]
|
|
1162
|
+
const techList = [...technologies]
|
|
989
1163
|
if (techList.length > 0) {
|
|
990
|
-
return `${change} affecting ${techList.join(', ')} (${fileCount} files)
|
|
1164
|
+
return `${change} affecting ${techList.join(', ')} (${fileCount} files)`
|
|
991
1165
|
}
|
|
992
|
-
return `${change} (${fileCount} files)
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
}
|
|
1004
|
-
return `${primary} (${fileCount} files)`;
|
|
1005
|
-
}
|
|
1006
|
-
} else {
|
|
1007
|
-
// For smaller changes, show more details instead of hiding them
|
|
1008
|
-
if (uniqueChanges.length <= 5) {
|
|
1009
|
-
// Show all changes if 5 or fewer - user wants to see exactly what each feature is
|
|
1010
|
-
return `${uniqueChanges.join(', ')} (${fileCount} files)`;
|
|
1011
|
-
} else {
|
|
1012
|
-
// Show first 4 and remaining count for 6+ changes
|
|
1013
|
-
const primary = uniqueChanges.slice(0, 4).join(', ');
|
|
1014
|
-
const remaining = uniqueChanges.length - 4;
|
|
1015
|
-
return `${primary} and ${remaining} additional enhancement${remaining > 1 ? 's' : ''} (${fileCount} files)`;
|
|
1016
|
-
}
|
|
1166
|
+
return `${change} (${fileCount} files)`
|
|
1167
|
+
}
|
|
1168
|
+
if (uniqueChanges.length === 2) {
|
|
1169
|
+
return `${uniqueChanges.join(' and ')} (${fileCount} files)`
|
|
1170
|
+
}
|
|
1171
|
+
// For architectural changes affecting many files, show more detail
|
|
1172
|
+
if (changeType === 'architectural' && fileCount >= 10) {
|
|
1173
|
+
// Show first 5 changes for major architectural changes
|
|
1174
|
+
const primary = uniqueChanges.slice(0, 5).join(', ')
|
|
1175
|
+
const remaining = uniqueChanges.length - 5
|
|
1176
|
+
if (remaining > 0) {
|
|
1177
|
+
return `${primary} and ${remaining} additional change${remaining > 1 ? 's' : ''} (${fileCount} files)`
|
|
1017
1178
|
}
|
|
1179
|
+
return `${primary} (${fileCount} files)`
|
|
1180
|
+
}
|
|
1181
|
+
// For smaller changes, show more details instead of hiding them
|
|
1182
|
+
if (uniqueChanges.length <= 5) {
|
|
1183
|
+
// Show all changes if 5 or fewer - user wants to see exactly what each feature is
|
|
1184
|
+
return `${uniqueChanges.join(', ')} (${fileCount} files)`
|
|
1018
1185
|
}
|
|
1186
|
+
// Show first 4 and remaining count for 6+ changes
|
|
1187
|
+
const primary = uniqueChanges.slice(0, 4).join(', ')
|
|
1188
|
+
const remaining = uniqueChanges.length - 4
|
|
1189
|
+
return `${primary} and ${remaining} additional enhancement${remaining > 1 ? 's' : ''} (${fileCount} files)`
|
|
1019
1190
|
}
|
|
1020
1191
|
|
|
1021
1192
|
// Enhanced fallback with more context
|
|
1022
|
-
const techList = [...technologies]
|
|
1023
|
-
const purposeList = [...purposes]
|
|
1193
|
+
const techList = [...technologies]
|
|
1194
|
+
const purposeList = [...purposes]
|
|
1024
1195
|
|
|
1025
1196
|
if (techList.length > 0 && purposeList.length > 0) {
|
|
1026
|
-
return `${changeType} changes to ${techList.slice(0, 2).join(', ')} for ${purposeList.slice(0, 2).join(', ')} (${fileCount} files)
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1197
|
+
return `${changeType} changes to ${techList.slice(0, 2).join(', ')} for ${purposeList.slice(0, 2).join(', ')} (${fileCount} files)`
|
|
1198
|
+
}
|
|
1199
|
+
if (techList.length > 0) {
|
|
1200
|
+
return `${changeType} changes to ${techList.slice(0, 3).join(', ')} (${fileCount} files)`
|
|
1201
|
+
}
|
|
1202
|
+
if (purposeList.length > 0) {
|
|
1203
|
+
return `${changeType} changes for ${purposeList.slice(0, 3).join(', ')} (${fileCount} files)`
|
|
1031
1204
|
}
|
|
1032
1205
|
|
|
1033
1206
|
// Final fallback
|
|
1034
|
-
return `${changeType} changes affecting ${fileCount} files
|
|
1207
|
+
return `${changeType} changes affecting ${fileCount} files`
|
|
1035
1208
|
}
|
|
1036
1209
|
|
|
1037
|
-
isArchitecturalChange(path,
|
|
1038
|
-
return
|
|
1039
|
-
|
|
1210
|
+
isArchitecturalChange(path, _change) {
|
|
1211
|
+
return (
|
|
1212
|
+
path === 'src/' ||
|
|
1213
|
+
path.includes('lib/') ||
|
|
1214
|
+
path.includes('bin/') ||
|
|
1215
|
+
path.includes('package.json')
|
|
1216
|
+
)
|
|
1040
1217
|
}
|
|
1041
1218
|
|
|
1042
1219
|
isConfigurationFile(path) {
|
|
1043
|
-
return
|
|
1044
|
-
|
|
1045
|
-
|
|
1220
|
+
return (
|
|
1221
|
+
path.startsWith('.') ||
|
|
1222
|
+
path.includes('config') ||
|
|
1223
|
+
path.endsWith('.json') ||
|
|
1224
|
+
path.endsWith('.env') ||
|
|
1225
|
+
path.includes('package.json')
|
|
1226
|
+
)
|
|
1046
1227
|
}
|
|
1047
1228
|
|
|
1048
1229
|
isTestFile(path) {
|
|
1049
|
-
return
|
|
1050
|
-
|
|
1230
|
+
return (
|
|
1231
|
+
path.includes('test') ||
|
|
1232
|
+
path.includes('spec') ||
|
|
1233
|
+
path.startsWith('test-') ||
|
|
1234
|
+
path.includes('/test/')
|
|
1235
|
+
)
|
|
1051
1236
|
}
|
|
1052
1237
|
|
|
1053
1238
|
isDocumentationFile(path) {
|
|
1054
|
-
return
|
|
1055
|
-
|
|
1239
|
+
return (
|
|
1240
|
+
path.endsWith('.md') ||
|
|
1241
|
+
path.includes('docs') ||
|
|
1242
|
+
path.includes('README') ||
|
|
1243
|
+
path.includes('CHANGELOG')
|
|
1244
|
+
)
|
|
1056
1245
|
}
|
|
1057
1246
|
|
|
1058
1247
|
analyzeModifiedFileChanges(diff, path) {
|
|
1059
|
-
const lines = diff.split('\n')
|
|
1060
|
-
const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'))
|
|
1061
|
-
const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'))
|
|
1248
|
+
const lines = diff.split('\n')
|
|
1249
|
+
const addedLines = lines.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
1250
|
+
const removedLines = lines.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
1062
1251
|
|
|
1063
|
-
const addedContent = addedLines.map(line => line.substring(1)).join('\n')
|
|
1064
|
-
const removedContent = removedLines.map(line => line.substring(1)).join('\n')
|
|
1252
|
+
const addedContent = addedLines.map((line) => line.substring(1)).join('\n')
|
|
1253
|
+
const removedContent = removedLines.map((line) => line.substring(1)).join('\n')
|
|
1065
1254
|
|
|
1066
1255
|
// Extract actual functionality changes instead of just counting
|
|
1067
|
-
const functionalChanges = this.extractFunctionalityChanges(addedContent, removedContent, path)
|
|
1256
|
+
const functionalChanges = this.extractFunctionalityChanges(addedContent, removedContent, path)
|
|
1068
1257
|
|
|
1069
1258
|
if (functionalChanges && functionalChanges.length > 0) {
|
|
1070
|
-
return functionalChanges.join(', ')
|
|
1259
|
+
return functionalChanges.join(', ')
|
|
1071
1260
|
}
|
|
1072
1261
|
|
|
1073
1262
|
// Fallback to basic pattern analysis if no functional changes detected
|
|
1074
|
-
const changes = []
|
|
1263
|
+
const changes = []
|
|
1075
1264
|
|
|
1076
1265
|
// Function changes
|
|
1077
|
-
const addedFunctions = (addedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length
|
|
1078
|
-
const removedFunctions = (removedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || [])
|
|
1266
|
+
const addedFunctions = (addedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || []).length
|
|
1267
|
+
const removedFunctions = (removedContent.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g) || [])
|
|
1268
|
+
.length
|
|
1079
1269
|
|
|
1080
|
-
if (addedFunctions > 0)
|
|
1081
|
-
|
|
1270
|
+
if (addedFunctions > 0) {
|
|
1271
|
+
changes.push(`Added ${addedFunctions} function${addedFunctions > 1 ? 's' : ''}`)
|
|
1272
|
+
}
|
|
1273
|
+
if (removedFunctions > 0) {
|
|
1274
|
+
changes.push(`removed ${removedFunctions} function${removedFunctions > 1 ? 's' : ''}`)
|
|
1275
|
+
}
|
|
1082
1276
|
|
|
1083
1277
|
// Import/export changes
|
|
1084
|
-
const addedImports = (addedContent.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
1085
|
-
const removedImports = (removedContent.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
1278
|
+
const addedImports = (addedContent.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
1279
|
+
const removedImports = (removedContent.match(/import\s+.*?from|require\s*\(/g) || []).length
|
|
1086
1280
|
|
|
1087
|
-
if (addedImports > 0)
|
|
1088
|
-
|
|
1281
|
+
if (addedImports > 0) {
|
|
1282
|
+
changes.push(`added ${addedImports} import${addedImports > 1 ? 's' : ''}`)
|
|
1283
|
+
}
|
|
1284
|
+
if (removedImports > 0) {
|
|
1285
|
+
changes.push(`removed ${removedImports} import${removedImports > 1 ? 's' : ''}`)
|
|
1286
|
+
}
|
|
1089
1287
|
|
|
1090
1288
|
// Class changes
|
|
1091
|
-
const addedClasses = (addedContent.match(/class\s+\w+/g) || []).length
|
|
1092
|
-
const removedClasses = (removedContent.match(/class\s+\w+/g) || []).length
|
|
1289
|
+
const addedClasses = (addedContent.match(/class\s+\w+/g) || []).length
|
|
1290
|
+
const removedClasses = (removedContent.match(/class\s+\w+/g) || []).length
|
|
1093
1291
|
|
|
1094
|
-
if (addedClasses > 0)
|
|
1095
|
-
|
|
1292
|
+
if (addedClasses > 0) {
|
|
1293
|
+
changes.push(`added ${addedClasses} class${addedClasses > 1 ? 'es' : ''}`)
|
|
1294
|
+
}
|
|
1295
|
+
if (removedClasses > 0) {
|
|
1296
|
+
changes.push(`removed ${removedClasses} class${removedClasses > 1 ? 'es' : ''}`)
|
|
1297
|
+
}
|
|
1096
1298
|
|
|
1097
1299
|
// Method changes within classes
|
|
1098
|
-
const addedMethods = (addedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length
|
|
1099
|
-
const removedMethods = (removedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length
|
|
1300
|
+
const addedMethods = (addedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length
|
|
1301
|
+
const removedMethods = (removedContent.match(/\s+\w+\s*\([^)]*\)\s*{/g) || []).length
|
|
1100
1302
|
|
|
1101
|
-
if (addedMethods > 0)
|
|
1102
|
-
|
|
1303
|
+
if (addedMethods > 0) {
|
|
1304
|
+
changes.push(`added ${addedMethods} method${addedMethods > 1 ? 's' : ''}`)
|
|
1305
|
+
}
|
|
1306
|
+
if (removedMethods > 0) {
|
|
1307
|
+
changes.push(`removed ${removedMethods} method${removedMethods > 1 ? 's' : ''}`)
|
|
1308
|
+
}
|
|
1103
1309
|
|
|
1104
1310
|
// Error handling changes
|
|
1105
1311
|
if (addedContent.includes('try') || addedContent.includes('catch')) {
|
|
1106
|
-
changes.push('enhanced error handling')
|
|
1312
|
+
changes.push('enhanced error handling')
|
|
1107
1313
|
}
|
|
1108
1314
|
|
|
1109
1315
|
// Configuration-specific changes
|
|
1110
|
-
if (path
|
|
1111
|
-
if (addedContent.includes('"dependencies"'))
|
|
1112
|
-
|
|
1316
|
+
if (path?.includes('package.json')) {
|
|
1317
|
+
if (addedContent.includes('"dependencies"')) {
|
|
1318
|
+
changes.push('updated dependencies')
|
|
1319
|
+
}
|
|
1320
|
+
if (addedContent.includes('"scripts"')) {
|
|
1321
|
+
changes.push('modified scripts')
|
|
1322
|
+
}
|
|
1113
1323
|
}
|
|
1114
1324
|
|
|
1115
|
-
return changes.length > 0 ? changes.slice(0, 3).join(', ') : null
|
|
1325
|
+
return changes.length > 0 ? changes.slice(0, 3).join(', ') : null
|
|
1116
1326
|
}
|
|
1117
1327
|
|
|
1118
1328
|
inferFileContent(path, diff) {
|
|
1119
|
-
if (!path)
|
|
1329
|
+
if (!path) {
|
|
1330
|
+
return 'content'
|
|
1331
|
+
}
|
|
1120
1332
|
|
|
1121
|
-
const ext = path.split('.').pop()?.toLowerCase()
|
|
1333
|
+
const ext = path.split('.').pop()?.toLowerCase()
|
|
1122
1334
|
|
|
1123
1335
|
if (['js', 'ts', 'jsx', 'tsx'].includes(ext)) {
|
|
1124
1336
|
if (diff.includes('function') || diff.includes('const ') || diff.includes('class ')) {
|
|
1125
|
-
return 'JavaScript/TypeScript code including functions and classes'
|
|
1337
|
+
return 'JavaScript/TypeScript code including functions and classes'
|
|
1126
1338
|
}
|
|
1127
|
-
return 'JavaScript/TypeScript code'
|
|
1339
|
+
return 'JavaScript/TypeScript code'
|
|
1128
1340
|
}
|
|
1129
1341
|
|
|
1130
1342
|
if (ext === 'json') {
|
|
1131
1343
|
if (path.includes('package.json')) {
|
|
1132
|
-
return 'package configuration and dependencies'
|
|
1344
|
+
return 'package configuration and dependencies'
|
|
1133
1345
|
}
|
|
1134
|
-
return 'JSON configuration data'
|
|
1346
|
+
return 'JSON configuration data'
|
|
1135
1347
|
}
|
|
1136
1348
|
|
|
1137
1349
|
if (['md', 'txt'].includes(ext)) {
|
|
1138
|
-
return 'documentation and text content'
|
|
1350
|
+
return 'documentation and text content'
|
|
1139
1351
|
}
|
|
1140
1352
|
|
|
1141
1353
|
if (['png', 'jpg', 'jpeg', 'gif', 'svg'].includes(ext)) {
|
|
1142
|
-
return 'image or asset data'
|
|
1354
|
+
return 'image or asset data'
|
|
1143
1355
|
}
|
|
1144
1356
|
|
|
1145
1357
|
if (['css', 'scss', 'less'].includes(ext)) {
|
|
1146
|
-
return 'styling definitions'
|
|
1358
|
+
return 'styling definitions'
|
|
1147
1359
|
}
|
|
1148
1360
|
|
|
1149
|
-
return 'content'
|
|
1361
|
+
return 'content'
|
|
1150
1362
|
}
|
|
1151
1363
|
|
|
1152
1364
|
extractSpecificChanges(diff, path) {
|
|
1153
|
-
if (!diff || diff.length < 50)
|
|
1365
|
+
if (!diff || diff.length < 50) {
|
|
1366
|
+
return null
|
|
1367
|
+
}
|
|
1154
1368
|
|
|
1155
|
-
const addedLines = diff
|
|
1156
|
-
|
|
1369
|
+
const addedLines = diff
|
|
1370
|
+
.split('\n')
|
|
1371
|
+
.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
1372
|
+
const removedLines = diff
|
|
1373
|
+
.split('\n')
|
|
1374
|
+
.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
1157
1375
|
|
|
1158
|
-
const changes = []
|
|
1376
|
+
const changes = []
|
|
1159
1377
|
|
|
1160
1378
|
// Check for function changes
|
|
1161
|
-
const addedFunctions = addedLines.filter(
|
|
1162
|
-
|
|
1163
|
-
|
|
1379
|
+
const addedFunctions = addedLines.filter(
|
|
1380
|
+
(line) =>
|
|
1381
|
+
line.includes('function ') ||
|
|
1382
|
+
(line.includes('const ') && line.includes('= (')) ||
|
|
1383
|
+
line.includes('async ')
|
|
1384
|
+
)
|
|
1164
1385
|
|
|
1165
1386
|
if (addedFunctions.length > 0) {
|
|
1166
|
-
changes.push(
|
|
1387
|
+
changes.push(
|
|
1388
|
+
`Added ${addedFunctions.length} new function${addedFunctions.length > 1 ? 's' : ''}`
|
|
1389
|
+
)
|
|
1167
1390
|
}
|
|
1168
1391
|
|
|
1169
1392
|
// Check for import changes
|
|
1170
|
-
const addedImports = addedLines.filter(
|
|
1171
|
-
|
|
1393
|
+
const addedImports = addedLines.filter(
|
|
1394
|
+
(line) => line.includes('import ') || line.includes('require(')
|
|
1395
|
+
)
|
|
1396
|
+
const removedImports = removedLines.filter(
|
|
1397
|
+
(line) => line.includes('import ') || line.includes('require(')
|
|
1398
|
+
)
|
|
1172
1399
|
|
|
1173
1400
|
if (addedImports.length > 0) {
|
|
1174
|
-
changes.push(`added ${addedImports.length} import${addedImports.length > 1 ? 's' : ''}`)
|
|
1401
|
+
changes.push(`added ${addedImports.length} import${addedImports.length > 1 ? 's' : ''}`)
|
|
1175
1402
|
}
|
|
1176
1403
|
if (removedImports.length > 0) {
|
|
1177
|
-
changes.push(`removed ${removedImports.length} import${removedImports.length > 1 ? 's' : ''}`)
|
|
1404
|
+
changes.push(`removed ${removedImports.length} import${removedImports.length > 1 ? 's' : ''}`)
|
|
1178
1405
|
}
|
|
1179
1406
|
|
|
1180
1407
|
// Check for package.json specific changes
|
|
1181
|
-
if (path
|
|
1408
|
+
if (path?.includes('package.json')) {
|
|
1182
1409
|
if (diff.includes('"dependencies"') || diff.includes('"devDependencies"')) {
|
|
1183
|
-
changes.push('updated project dependencies')
|
|
1410
|
+
changes.push('updated project dependencies')
|
|
1184
1411
|
}
|
|
1185
1412
|
if (diff.includes('"scripts"')) {
|
|
1186
|
-
changes.push('modified build scripts')
|
|
1413
|
+
changes.push('modified build scripts')
|
|
1187
1414
|
}
|
|
1188
1415
|
}
|
|
1189
1416
|
|
|
1190
|
-
return changes.length > 0 ? changes.slice(0, 2).join(' and ') : null
|
|
1417
|
+
return changes.length > 0 ? changes.slice(0, 2).join(' and ') : null
|
|
1191
1418
|
}
|
|
1192
1419
|
|
|
1193
1420
|
extractFunctionalityChanges(addedContent, removedContent, path) {
|
|
1194
|
-
const functionalChanges = []
|
|
1421
|
+
const functionalChanges = []
|
|
1195
1422
|
|
|
1196
1423
|
// Analyze what functionality was actually added or removed
|
|
1197
|
-
if (removedContent.length > 50) {
|
|
1424
|
+
if (removedContent.length > 50) {
|
|
1425
|
+
// Only analyze substantial changes
|
|
1198
1426
|
|
|
1199
1427
|
// Connection/Testing functionality
|
|
1200
1428
|
if (removedContent.includes('testConnection') || removedContent.includes('test.success')) {
|
|
1201
|
-
functionalChanges.push('removed connection testing functionality')
|
|
1429
|
+
functionalChanges.push('removed connection testing functionality')
|
|
1202
1430
|
}
|
|
1203
1431
|
|
|
1204
1432
|
// Model availability checking
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1433
|
+
if (
|
|
1434
|
+
removedContent.includes('getAvailableModels') ||
|
|
1435
|
+
removedContent.includes('getAvailable') ||
|
|
1436
|
+
removedContent.includes('models.length')
|
|
1437
|
+
) {
|
|
1438
|
+
functionalChanges.push('removed model availability checking')
|
|
1207
1439
|
}
|
|
1208
1440
|
|
|
1209
1441
|
// Authentication/Provider initialization
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1442
|
+
if (
|
|
1443
|
+
removedContent.includes('new AIProvider') ||
|
|
1444
|
+
removedContent.includes('provider =') ||
|
|
1445
|
+
removedContent.includes('Provider(')
|
|
1446
|
+
) {
|
|
1447
|
+
functionalChanges.push('removed provider initialization')
|
|
1212
1448
|
}
|
|
1213
1449
|
|
|
1214
1450
|
// Error handling/validation
|
|
1215
|
-
if (
|
|
1216
|
-
|
|
1451
|
+
if (
|
|
1452
|
+
removedContent.includes('if (testResult.success)') ||
|
|
1453
|
+
removedContent.includes('Connection test failed')
|
|
1454
|
+
) {
|
|
1455
|
+
functionalChanges.push('removed error handling and validation')
|
|
1217
1456
|
}
|
|
1218
1457
|
|
|
1219
1458
|
// Configuration/Setup
|
|
1220
1459
|
if (removedContent.includes('require(') && addedContent.includes('skipping test')) {
|
|
1221
|
-
functionalChanges.push('simplified configuration setup')
|
|
1460
|
+
functionalChanges.push('simplified configuration setup')
|
|
1222
1461
|
}
|
|
1223
1462
|
|
|
1224
1463
|
// Database/API operations
|
|
1225
|
-
if (
|
|
1226
|
-
|
|
1464
|
+
if (
|
|
1465
|
+
(removedContent.includes('await ') && removedContent.includes('database')) ||
|
|
1466
|
+
removedContent.includes('api')
|
|
1467
|
+
) {
|
|
1468
|
+
functionalChanges.push('removed database/API operations')
|
|
1227
1469
|
}
|
|
1228
1470
|
|
|
1229
1471
|
// UI/Display functionality
|
|
1230
1472
|
if (removedContent.includes('forEach') && removedContent.includes('console.log')) {
|
|
1231
|
-
functionalChanges.push('removed dynamic model listing display')
|
|
1473
|
+
functionalChanges.push('removed dynamic model listing display')
|
|
1232
1474
|
}
|
|
1233
1475
|
|
|
1234
1476
|
// Authentication changes
|
|
1235
|
-
if (
|
|
1236
|
-
|
|
1477
|
+
if (
|
|
1478
|
+
removedContent.includes('auth') ||
|
|
1479
|
+
removedContent.includes('token') ||
|
|
1480
|
+
removedContent.includes('key')
|
|
1481
|
+
) {
|
|
1482
|
+
functionalChanges.push('modified authentication handling')
|
|
1237
1483
|
}
|
|
1238
1484
|
}
|
|
1239
1485
|
|
|
1240
1486
|
// Analyze what was added
|
|
1241
1487
|
if (addedContent.length > 20) {
|
|
1242
|
-
if (
|
|
1243
|
-
|
|
1488
|
+
if (
|
|
1489
|
+
addedContent.includes('Configuration testing requires') ||
|
|
1490
|
+
addedContent.includes('skipping test')
|
|
1491
|
+
) {
|
|
1492
|
+
functionalChanges.push('added configuration migration notice')
|
|
1244
1493
|
}
|
|
1245
1494
|
|
|
1246
1495
|
if (addedContent.includes('--validate') || addedContent.includes('test your configuration')) {
|
|
1247
|
-
functionalChanges.push('redirected testing to CLI --validate')
|
|
1496
|
+
functionalChanges.push('redirected testing to CLI --validate')
|
|
1248
1497
|
}
|
|
1249
1498
|
|
|
1250
1499
|
// New functionality patterns
|
|
1251
1500
|
if (addedContent.includes('async ') && addedContent.includes('function')) {
|
|
1252
|
-
const newFunctions = (addedContent.match(/async\\s+function\\s+([\\w]+)/g) || []).map(f =>
|
|
1501
|
+
const newFunctions = (addedContent.match(/async\\s+function\\s+([\\w]+)/g) || []).map((f) =>
|
|
1502
|
+
f.replace('async function ', '')
|
|
1503
|
+
)
|
|
1253
1504
|
if (newFunctions.length > 0) {
|
|
1254
|
-
functionalChanges.push(`added ${newFunctions.slice(0, 2).join(', ')} async functionality`)
|
|
1505
|
+
functionalChanges.push(`added ${newFunctions.slice(0, 2).join(', ')} async functionality`)
|
|
1255
1506
|
}
|
|
1256
1507
|
}
|
|
1257
1508
|
}
|
|
1258
1509
|
|
|
1259
1510
|
// File-specific analysis
|
|
1260
1511
|
if (path) {
|
|
1261
|
-
if (
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1512
|
+
if (
|
|
1513
|
+
path.includes('setup') &&
|
|
1514
|
+
functionalChanges.length === 0 &&
|
|
1515
|
+
removedContent.includes('test') &&
|
|
1516
|
+
addedContent.includes('validate')
|
|
1517
|
+
) {
|
|
1518
|
+
functionalChanges.push('replaced test functions with validation system')
|
|
1265
1519
|
}
|
|
1266
1520
|
|
|
1267
|
-
if (
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1521
|
+
if (
|
|
1522
|
+
(path.includes('provider') || path.includes('ai-')) &&
|
|
1523
|
+
removedContent.includes('import') &&
|
|
1524
|
+
addedContent.includes('moved to')
|
|
1525
|
+
) {
|
|
1526
|
+
functionalChanges.push('refactored import paths for new architecture')
|
|
1271
1527
|
}
|
|
1272
1528
|
}
|
|
1273
1529
|
|
|
1274
|
-
return functionalChanges.length > 0 ? functionalChanges : null
|
|
1530
|
+
return functionalChanges.length > 0 ? functionalChanges : null
|
|
1275
1531
|
}
|
|
1276
1532
|
|
|
1277
1533
|
extractFileDescriptionFromAI(aiSummary, change) {
|
|
1278
1534
|
// Extract meaningful description from AI analysis
|
|
1279
1535
|
if (!aiSummary || typeof aiSummary !== 'string') {
|
|
1280
|
-
return this.analyzeDiffContentForDescription(change)
|
|
1536
|
+
return this.analyzeDiffContentForDescription(change)
|
|
1281
1537
|
}
|
|
1282
1538
|
|
|
1283
1539
|
// Clean up AI response to be suitable for changelog entry
|
|
1284
|
-
let description = aiSummary.trim()
|
|
1540
|
+
let description = aiSummary.trim()
|
|
1285
1541
|
|
|
1286
1542
|
// Remove generic prefixes
|
|
1287
|
-
description = description.replace(/^(This change|The change|Change:|Summary:)\s*/i, '')
|
|
1288
|
-
description = description.replace(/^(Analysis|Description):\s*/i, '')
|
|
1543
|
+
description = description.replace(/^(This change|The change|Change:|Summary:)\s*/i, '')
|
|
1544
|
+
description = description.replace(/^(Analysis|Description):\s*/i, '')
|
|
1289
1545
|
|
|
1290
1546
|
// Take first sentence if it's a long response
|
|
1291
|
-
const sentences = description.split(/[.!?]\s+/)
|
|
1547
|
+
const sentences = description.split(/[.!?]\s+/)
|
|
1292
1548
|
if (sentences.length > 1 && sentences[0].length > 20) {
|
|
1293
|
-
description = sentences[0]
|
|
1549
|
+
description = sentences[0]
|
|
1294
1550
|
}
|
|
1295
1551
|
|
|
1296
1552
|
// Capitalize first letter
|
|
1297
1553
|
if (description.length > 0) {
|
|
1298
|
-
description = description.charAt(0).toUpperCase() + description.slice(1)
|
|
1554
|
+
description = description.charAt(0).toUpperCase() + description.slice(1)
|
|
1299
1555
|
}
|
|
1300
1556
|
|
|
1301
1557
|
// Fallback if AI response is too generic
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1558
|
+
if (
|
|
1559
|
+
description.length < 10 ||
|
|
1560
|
+
description.toLowerCase().includes('file changed') ||
|
|
1561
|
+
description.toLowerCase().includes('modified') ||
|
|
1562
|
+
description.toLowerCase().includes('updated')
|
|
1563
|
+
) {
|
|
1564
|
+
return this.analyzeDiffContentForDescription(change)
|
|
1307
1565
|
}
|
|
1308
1566
|
|
|
1309
|
-
return description
|
|
1567
|
+
return description
|
|
1310
1568
|
}
|
|
1311
1569
|
|
|
1312
1570
|
analyzeDiffContentForDescription(change) {
|
|
1313
|
-
const path = change.path || change.filePath
|
|
1314
|
-
const status = change.status
|
|
1315
|
-
const diff = change.diff || ''
|
|
1316
|
-
const category = change.category || 'other'
|
|
1571
|
+
const path = change.path || change.filePath
|
|
1572
|
+
const status = change.status
|
|
1573
|
+
const diff = change.diff || ''
|
|
1574
|
+
const category = change.category || 'other'
|
|
1317
1575
|
|
|
1318
1576
|
if (!diff || diff === 'Analysis failed') {
|
|
1319
|
-
return this.generateChangeDescription(change)
|
|
1577
|
+
return this.generateChangeDescription(change)
|
|
1320
1578
|
}
|
|
1321
1579
|
|
|
1322
1580
|
// For modified files, use the enhanced functional analysis
|
|
1323
1581
|
if (status === 'M' && this.analyzeModifiedFileChanges) {
|
|
1324
|
-
const functionalAnalysis = this.analyzeModifiedFileChanges(diff, path)
|
|
1582
|
+
const functionalAnalysis = this.analyzeModifiedFileChanges(diff, path)
|
|
1325
1583
|
if (functionalAnalysis) {
|
|
1326
|
-
return functionalAnalysis
|
|
1584
|
+
return functionalAnalysis
|
|
1327
1585
|
}
|
|
1328
1586
|
}
|
|
1329
1587
|
|
|
1330
1588
|
// For new files, use content analysis
|
|
1331
1589
|
if (status === 'A' || status === '??') {
|
|
1332
|
-
const newFileAnalysis = this.analyzeNewFileContent(diff, path)
|
|
1590
|
+
const newFileAnalysis = this.analyzeNewFileContent(diff, path)
|
|
1333
1591
|
if (newFileAnalysis) {
|
|
1334
|
-
return newFileAnalysis
|
|
1592
|
+
return newFileAnalysis
|
|
1335
1593
|
}
|
|
1336
1594
|
}
|
|
1337
1595
|
|
|
1338
1596
|
// For deleted files, analyze what was removed
|
|
1339
1597
|
if (status === 'D') {
|
|
1340
|
-
const deletedFileAnalysis = this.analyzeDeletedFileContent(change)
|
|
1598
|
+
const deletedFileAnalysis = this.analyzeDeletedFileContent(change)
|
|
1341
1599
|
if (deletedFileAnalysis) {
|
|
1342
|
-
return deletedFileAnalysis
|
|
1600
|
+
return deletedFileAnalysis
|
|
1343
1601
|
}
|
|
1344
1602
|
}
|
|
1345
1603
|
|
|
1346
1604
|
// Fallback to old pattern-based analysis for other cases
|
|
1347
|
-
const lines = diff.split('\n')
|
|
1348
|
-
const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'))
|
|
1349
|
-
const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'))
|
|
1605
|
+
const lines = diff.split('\n')
|
|
1606
|
+
const addedLines = lines.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
1607
|
+
const removedLines = lines.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
1350
1608
|
|
|
1351
|
-
const addedContent = addedLines.map(line => line.substring(1).trim()).join(' ')
|
|
1352
|
-
const removedContent = removedLines.map(line => line.substring(1).trim()).join(' ')
|
|
1609
|
+
const addedContent = addedLines.map((line) => line.substring(1).trim()).join(' ')
|
|
1610
|
+
const removedContent = removedLines.map((line) => line.substring(1).trim()).join(' ')
|
|
1353
1611
|
|
|
1354
1612
|
// Look for specific patterns in the actual changes
|
|
1355
|
-
const changes = []
|
|
1613
|
+
const changes = []
|
|
1356
1614
|
|
|
1357
1615
|
// Function/method additions
|
|
1358
|
-
const addedFunctions = this.extractFunctions(addedContent)
|
|
1359
|
-
const removedFunctions = this.extractFunctions(removedContent)
|
|
1616
|
+
const addedFunctions = this.extractFunctions(addedContent)
|
|
1617
|
+
const removedFunctions = this.extractFunctions(removedContent)
|
|
1360
1618
|
|
|
1361
1619
|
if (addedFunctions.length > 0) {
|
|
1362
|
-
changes.push(
|
|
1620
|
+
changes.push(
|
|
1621
|
+
`Added ${addedFunctions.slice(0, 2).join(', ')} method${addedFunctions.length > 1 ? 's' : ''}`
|
|
1622
|
+
)
|
|
1363
1623
|
}
|
|
1364
1624
|
if (removedFunctions.length > 0) {
|
|
1365
|
-
changes.push(
|
|
1625
|
+
changes.push(
|
|
1626
|
+
`removed ${removedFunctions.slice(0, 2).join(', ')} method${removedFunctions.length > 1 ? 's' : ''}`
|
|
1627
|
+
)
|
|
1366
1628
|
}
|
|
1367
1629
|
|
|
1368
1630
|
// Import/export changes
|
|
1369
1631
|
if (addedContent.includes('import ') || addedContent.includes('require(')) {
|
|
1370
|
-
const importCount = (addedContent.match(/import\s+/g) || []).length
|
|
1371
|
-
changes.push(`added ${importCount} import${importCount > 1 ? 's' : ''}`)
|
|
1632
|
+
const importCount = (addedContent.match(/import\s+/g) || []).length
|
|
1633
|
+
changes.push(`added ${importCount} import${importCount > 1 ? 's' : ''}`)
|
|
1372
1634
|
}
|
|
1373
1635
|
if (removedContent.includes('import ') || removedContent.includes('require(')) {
|
|
1374
|
-
const importCount = (removedContent.match(/import\s+/g) || []).length
|
|
1375
|
-
changes.push(`removed ${importCount} import${importCount > 1 ? 's' : ''}`)
|
|
1636
|
+
const importCount = (removedContent.match(/import\s+/g) || []).length
|
|
1637
|
+
changes.push(`removed ${importCount} import${importCount > 1 ? 's' : ''}`)
|
|
1376
1638
|
}
|
|
1377
1639
|
|
|
1378
1640
|
// Configuration changes
|
|
1379
|
-
if (path
|
|
1641
|
+
if (path?.includes('package.json')) {
|
|
1380
1642
|
if (addedContent.includes('"dependencies"') || addedContent.includes('"devDependencies"')) {
|
|
1381
|
-
changes.push('updated dependencies')
|
|
1643
|
+
changes.push('updated dependencies')
|
|
1382
1644
|
}
|
|
1383
1645
|
if (addedContent.includes('"scripts"')) {
|
|
1384
|
-
changes.push('modified scripts')
|
|
1646
|
+
changes.push('modified scripts')
|
|
1385
1647
|
}
|
|
1386
1648
|
}
|
|
1387
1649
|
|
|
1388
1650
|
// Error handling
|
|
1389
|
-
if (
|
|
1390
|
-
|
|
1651
|
+
if (
|
|
1652
|
+
addedContent.includes('try') ||
|
|
1653
|
+
addedContent.includes('catch') ||
|
|
1654
|
+
addedContent.includes('throw')
|
|
1655
|
+
) {
|
|
1656
|
+
changes.push('enhanced error handling')
|
|
1391
1657
|
}
|
|
1392
1658
|
|
|
1393
1659
|
// Build meaningful description
|
|
1394
1660
|
if (changes.length > 0) {
|
|
1395
|
-
const changeDesc = changes.slice(0, 3).join(', ')
|
|
1396
|
-
return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions. ${changeDesc}
|
|
1661
|
+
const changeDesc = changes.slice(0, 3).join(', ')
|
|
1662
|
+
return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions. ${changeDesc}`
|
|
1397
1663
|
}
|
|
1398
1664
|
|
|
1399
1665
|
// Generic fallback with line counts
|
|
1400
1666
|
if (status === 'M' && (addedLines.length > 0 || removedLines.length > 0)) {
|
|
1401
|
-
return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions
|
|
1667
|
+
return `Modified ${category} file with ${addedLines.length} additions and ${removedLines.length} deletions`
|
|
1402
1668
|
}
|
|
1403
1669
|
|
|
1404
|
-
return this.generateChangeDescription(change)
|
|
1670
|
+
return this.generateChangeDescription(change)
|
|
1405
1671
|
}
|
|
1406
1672
|
|
|
1407
1673
|
calculateChangeConfidence(change) {
|
|
1408
1674
|
// Simple confidence calculation based on available data
|
|
1409
|
-
let confidence = 70
|
|
1675
|
+
let confidence = 70 // Base confidence
|
|
1410
1676
|
|
|
1411
|
-
if (change.diff && change.diff.length > 50)
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
if (change.
|
|
1677
|
+
if (change.diff && change.diff.length > 50) {
|
|
1678
|
+
confidence += 10
|
|
1679
|
+
}
|
|
1680
|
+
if (change.semanticChanges?.patterns && change.semanticChanges.patterns.length > 0) {
|
|
1681
|
+
confidence += 10
|
|
1682
|
+
}
|
|
1683
|
+
if (change.complexity?.score) {
|
|
1684
|
+
confidence += 5
|
|
1685
|
+
}
|
|
1686
|
+
if (change.functionalImpact) {
|
|
1687
|
+
confidence += 5
|
|
1688
|
+
}
|
|
1415
1689
|
|
|
1416
|
-
return Math.min(confidence, 95)
|
|
1690
|
+
return Math.min(confidence, 95)
|
|
1417
1691
|
}
|
|
1418
1692
|
|
|
1419
1693
|
generateDiffSummary(change) {
|
|
1420
1694
|
if (!change.diff || change.diff === 'Analysis failed') {
|
|
1421
|
-
return null
|
|
1695
|
+
return null
|
|
1422
1696
|
}
|
|
1423
1697
|
|
|
1424
|
-
const diff = change.diff
|
|
1425
|
-
const status = change.status
|
|
1426
|
-
const filePath = change.path || change.filePath
|
|
1698
|
+
const diff = change.diff
|
|
1699
|
+
const status = change.status
|
|
1700
|
+
const filePath = change.path || change.filePath
|
|
1427
1701
|
|
|
1428
1702
|
// Handle different file statuses
|
|
1429
1703
|
if (status === 'A' || status === '??') {
|
|
1430
1704
|
if (diff.includes('New file created with')) {
|
|
1431
|
-
const lineMatch = diff.match(/(\d+) lines/)
|
|
1432
|
-
const lines = lineMatch ? lineMatch[1] : 'unknown'
|
|
1433
|
-
return `New file with ${lines} lines
|
|
1705
|
+
const lineMatch = diff.match(/(\d+) lines/)
|
|
1706
|
+
const lines = lineMatch ? lineMatch[1] : 'unknown'
|
|
1707
|
+
return `New file with ${lines} lines`
|
|
1434
1708
|
}
|
|
1435
|
-
return 'New file created'
|
|
1709
|
+
return 'New file created'
|
|
1436
1710
|
}
|
|
1437
1711
|
|
|
1438
1712
|
if (status === 'D') {
|
|
1439
|
-
return 'File deleted'
|
|
1713
|
+
return 'File deleted'
|
|
1440
1714
|
}
|
|
1441
1715
|
|
|
1442
1716
|
if (status === 'R') {
|
|
1443
|
-
return 'File renamed'
|
|
1717
|
+
return 'File renamed'
|
|
1444
1718
|
}
|
|
1445
1719
|
|
|
1446
1720
|
// For modified files, analyze actual diff content
|
|
1447
1721
|
if (status === 'M' || status.includes('M')) {
|
|
1448
|
-
const analysis = this.analyzeDiffContent(diff, filePath)
|
|
1449
|
-
return analysis
|
|
1722
|
+
const analysis = this.analyzeDiffContent(diff, filePath)
|
|
1723
|
+
return analysis
|
|
1450
1724
|
}
|
|
1451
1725
|
|
|
1452
|
-
return null
|
|
1726
|
+
return null
|
|
1453
1727
|
}
|
|
1454
1728
|
|
|
1455
1729
|
analyzeDiffContent(diff, filePath) {
|
|
1456
|
-
const lines = diff.split('\n')
|
|
1457
|
-
const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'))
|
|
1458
|
-
const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---'))
|
|
1730
|
+
const lines = diff.split('\n')
|
|
1731
|
+
const addedLines = lines.filter((line) => line.startsWith('+') && !line.startsWith('+++'))
|
|
1732
|
+
const removedLines = lines.filter((line) => line.startsWith('-') && !line.startsWith('---'))
|
|
1459
1733
|
|
|
1460
|
-
const changes = []
|
|
1734
|
+
const changes = []
|
|
1461
1735
|
|
|
1462
1736
|
// Analyze what was actually changed
|
|
1463
|
-
const addedContent = addedLines.map(line => line.substring(1).trim()).join(' ')
|
|
1464
|
-
const removedContent = removedLines.map(line => line.substring(1).trim()).join(' ')
|
|
1737
|
+
const addedContent = addedLines.map((line) => line.substring(1).trim()).join(' ')
|
|
1738
|
+
const removedContent = removedLines.map((line) => line.substring(1).trim()).join(' ')
|
|
1465
1739
|
|
|
1466
1740
|
// Function/method changes
|
|
1467
|
-
const addedFunctions = this.extractFunctions(addedContent)
|
|
1468
|
-
const removedFunctions = this.extractFunctions(removedContent)
|
|
1741
|
+
const addedFunctions = this.extractFunctions(addedContent)
|
|
1742
|
+
const removedFunctions = this.extractFunctions(removedContent)
|
|
1469
1743
|
|
|
1470
1744
|
if (addedFunctions.length > 0) {
|
|
1471
|
-
changes.push(`added ${addedFunctions.slice(0, 2).join(', ')}`)
|
|
1745
|
+
changes.push(`added ${addedFunctions.slice(0, 2).join(', ')}`)
|
|
1472
1746
|
}
|
|
1473
1747
|
if (removedFunctions.length > 0) {
|
|
1474
|
-
changes.push(`removed ${removedFunctions.slice(0, 2).join(', ')}`)
|
|
1748
|
+
changes.push(`removed ${removedFunctions.slice(0, 2).join(', ')}`)
|
|
1475
1749
|
}
|
|
1476
1750
|
|
|
1477
1751
|
// Configuration changes
|
|
1478
|
-
if (filePath
|
|
1479
|
-
const configChanges = this.analyzePackageJsonChanges(addedContent, removedContent)
|
|
1752
|
+
if (filePath?.includes('package.json')) {
|
|
1753
|
+
const configChanges = this.analyzePackageJsonChanges(addedContent, removedContent)
|
|
1480
1754
|
if (configChanges.length > 0) {
|
|
1481
|
-
changes.push(...configChanges)
|
|
1755
|
+
changes.push(...configChanges)
|
|
1482
1756
|
}
|
|
1483
1757
|
}
|
|
1484
1758
|
|
|
1485
1759
|
// Import/export changes
|
|
1486
|
-
const importChanges = this.analyzeImportChanges(addedContent, removedContent)
|
|
1760
|
+
const importChanges = this.analyzeImportChanges(addedContent, removedContent)
|
|
1487
1761
|
if (importChanges.length > 0) {
|
|
1488
|
-
changes.push(...importChanges)
|
|
1762
|
+
changes.push(...importChanges)
|
|
1489
1763
|
}
|
|
1490
1764
|
|
|
1491
1765
|
// Variable/constant changes
|
|
1492
|
-
const variableChanges = this.analyzeVariableChanges(addedContent, removedContent)
|
|
1766
|
+
const variableChanges = this.analyzeVariableChanges(addedContent, removedContent)
|
|
1493
1767
|
if (variableChanges.length > 0) {
|
|
1494
|
-
changes.push(...variableChanges)
|
|
1768
|
+
changes.push(...variableChanges)
|
|
1495
1769
|
}
|
|
1496
1770
|
|
|
1497
1771
|
// Error handling changes
|
|
1498
|
-
const errorChanges = this.analyzeErrorHandling(addedContent, removedContent)
|
|
1772
|
+
const errorChanges = this.analyzeErrorHandling(addedContent, removedContent)
|
|
1499
1773
|
if (errorChanges.length > 0) {
|
|
1500
|
-
changes.push(...errorChanges)
|
|
1774
|
+
changes.push(...errorChanges)
|
|
1501
1775
|
}
|
|
1502
1776
|
|
|
1503
1777
|
// Build summary with line counts
|
|
1504
|
-
let summary = `+${addedLines.length}, -${removedLines.length} lines
|
|
1778
|
+
let summary = `+${addedLines.length}, -${removedLines.length} lines`
|
|
1505
1779
|
if (changes.length > 0) {
|
|
1506
|
-
summary += `: ${changes.slice(0, 3).join(', ')}
|
|
1780
|
+
summary += `: ${changes.slice(0, 3).join(', ')}`
|
|
1507
1781
|
}
|
|
1508
1782
|
|
|
1509
|
-
return summary
|
|
1783
|
+
return summary
|
|
1510
1784
|
}
|
|
1511
1785
|
|
|
1512
1786
|
extractFunctions(content) {
|
|
1513
|
-
const functions = []
|
|
1787
|
+
const functions = []
|
|
1514
1788
|
const patterns = [
|
|
1515
1789
|
/function\s+(\w+)/g,
|
|
1516
1790
|
/(\w+)\s*\(/g,
|
|
1517
1791
|
/const\s+(\w+)\s*=\s*\(/g,
|
|
1518
|
-
/async\s+(\w+)/g
|
|
1519
|
-
]
|
|
1792
|
+
/async\s+(\w+)/g,
|
|
1793
|
+
]
|
|
1520
1794
|
|
|
1521
|
-
patterns.forEach(pattern => {
|
|
1522
|
-
let match
|
|
1795
|
+
patterns.forEach((pattern) => {
|
|
1796
|
+
let match
|
|
1523
1797
|
while ((match = pattern.exec(content)) !== null) {
|
|
1524
1798
|
if (match[1] && !functions.includes(match[1])) {
|
|
1525
|
-
functions.push(match[1])
|
|
1799
|
+
functions.push(match[1])
|
|
1526
1800
|
}
|
|
1527
1801
|
}
|
|
1528
|
-
})
|
|
1802
|
+
})
|
|
1529
1803
|
|
|
1530
|
-
return functions.slice(0, 3)
|
|
1804
|
+
return functions.slice(0, 3)
|
|
1531
1805
|
}
|
|
1532
1806
|
|
|
1533
|
-
analyzePackageJsonChanges(added,
|
|
1534
|
-
const changes = []
|
|
1807
|
+
analyzePackageJsonChanges(added, _removed) {
|
|
1808
|
+
const changes = []
|
|
1535
1809
|
|
|
1536
1810
|
if (added.includes('"dependencies"') || added.includes('"devDependencies"')) {
|
|
1537
|
-
changes.push('updated dependencies')
|
|
1811
|
+
changes.push('updated dependencies')
|
|
1538
1812
|
}
|
|
1539
1813
|
if (added.includes('"scripts"')) {
|
|
1540
|
-
changes.push('updated scripts')
|
|
1814
|
+
changes.push('updated scripts')
|
|
1541
1815
|
}
|
|
1542
1816
|
if (added.includes('"name"') || added.includes('"version"')) {
|
|
1543
|
-
changes.push('updated metadata')
|
|
1817
|
+
changes.push('updated metadata')
|
|
1544
1818
|
}
|
|
1545
1819
|
if (added.includes('"repository"') || added.includes('"author"')) {
|
|
1546
|
-
changes.push('updated project info')
|
|
1820
|
+
changes.push('updated project info')
|
|
1547
1821
|
}
|
|
1548
1822
|
|
|
1549
|
-
return changes
|
|
1823
|
+
return changes
|
|
1550
1824
|
}
|
|
1551
1825
|
|
|
1552
1826
|
analyzeImportChanges(added, removed) {
|
|
1553
|
-
const changes = []
|
|
1827
|
+
const changes = []
|
|
1554
1828
|
|
|
1555
1829
|
if (added.includes('import') || added.includes('require(')) {
|
|
1556
|
-
changes.push('added imports')
|
|
1830
|
+
changes.push('added imports')
|
|
1557
1831
|
}
|
|
1558
1832
|
if (removed.includes('import') || removed.includes('require(')) {
|
|
1559
|
-
changes.push('removed imports')
|
|
1833
|
+
changes.push('removed imports')
|
|
1560
1834
|
}
|
|
1561
1835
|
if (added.includes('export')) {
|
|
1562
|
-
changes.push('added exports')
|
|
1836
|
+
changes.push('added exports')
|
|
1563
1837
|
}
|
|
1564
1838
|
if (removed.includes('export')) {
|
|
1565
|
-
changes.push('removed exports')
|
|
1839
|
+
changes.push('removed exports')
|
|
1566
1840
|
}
|
|
1567
1841
|
|
|
1568
|
-
return changes
|
|
1842
|
+
return changes
|
|
1569
1843
|
}
|
|
1570
1844
|
|
|
1571
|
-
analyzeVariableChanges(added,
|
|
1572
|
-
const changes = []
|
|
1845
|
+
analyzeVariableChanges(added, _removed) {
|
|
1846
|
+
const changes = []
|
|
1573
1847
|
|
|
1574
1848
|
if (added.includes('const ') || added.includes('let ') || added.includes('var ')) {
|
|
1575
|
-
changes.push('added variables')
|
|
1849
|
+
changes.push('added variables')
|
|
1576
1850
|
}
|
|
1577
1851
|
if (added.includes('= {') || added.includes('= [')) {
|
|
1578
|
-
changes.push('updated data structures')
|
|
1852
|
+
changes.push('updated data structures')
|
|
1579
1853
|
}
|
|
1580
1854
|
|
|
1581
|
-
return changes
|
|
1855
|
+
return changes
|
|
1582
1856
|
}
|
|
1583
1857
|
|
|
1584
|
-
analyzeErrorHandling(added,
|
|
1585
|
-
const changes = []
|
|
1858
|
+
analyzeErrorHandling(added, _removed) {
|
|
1859
|
+
const changes = []
|
|
1586
1860
|
|
|
1587
1861
|
if (added.includes('try') || added.includes('catch') || added.includes('throw')) {
|
|
1588
|
-
changes.push('enhanced error handling')
|
|
1862
|
+
changes.push('enhanced error handling')
|
|
1589
1863
|
}
|
|
1590
1864
|
if (added.includes('console.error') || added.includes('console.warn')) {
|
|
1591
|
-
changes.push('added logging')
|
|
1865
|
+
changes.push('added logging')
|
|
1592
1866
|
}
|
|
1593
1867
|
if (added.includes('if (') && added.includes('error')) {
|
|
1594
|
-
changes.push('added error checks')
|
|
1868
|
+
changes.push('added error checks')
|
|
1595
1869
|
}
|
|
1596
1870
|
|
|
1597
|
-
return changes
|
|
1871
|
+
return changes
|
|
1598
1872
|
}
|
|
1599
1873
|
|
|
1600
1874
|
buildChangelogFromAnalysis(analysis, changes, version) {
|
|
1601
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
1875
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
1602
1876
|
|
|
1603
|
-
let changelog = `# Working Directory Changes - ${timestamp}\n\n
|
|
1877
|
+
let changelog = `# Working Directory Changes - ${timestamp}\n\n`
|
|
1604
1878
|
|
|
1605
1879
|
if (version) {
|
|
1606
|
-
changelog += `## Version ${version}\n\n
|
|
1880
|
+
changelog += `## Version ${version}\n\n`
|
|
1607
1881
|
}
|
|
1608
1882
|
|
|
1609
|
-
changelog +=
|
|
1610
|
-
changelog += `${analysis.summary || 'Working directory changes detected'}\n\n
|
|
1883
|
+
changelog += '### Summary\n'
|
|
1884
|
+
changelog += `${analysis.summary || 'Working directory changes detected'}\n\n`
|
|
1611
1885
|
|
|
1612
|
-
changelog += `### Changes (${changes.length} files)\n
|
|
1613
|
-
changes.forEach(change => {
|
|
1614
|
-
changelog += `- ${change.status} ${change.path}\n
|
|
1615
|
-
})
|
|
1886
|
+
changelog += `### Changes (${changes.length} files)\n`
|
|
1887
|
+
changes.forEach((change) => {
|
|
1888
|
+
changelog += `- ${change.status} ${change.path}\n`
|
|
1889
|
+
})
|
|
1616
1890
|
|
|
1617
|
-
return changelog
|
|
1891
|
+
return changelog
|
|
1618
1892
|
}
|
|
1619
1893
|
|
|
1620
1894
|
analyzeDeletedFileContent(change) {
|
|
1621
|
-
const filePath = change.path || change.filePath
|
|
1622
|
-
const beforeContent = change.beforeContent || ''
|
|
1895
|
+
const filePath = change.path || change.filePath
|
|
1896
|
+
const beforeContent = change.beforeContent || ''
|
|
1623
1897
|
|
|
1624
1898
|
if (!beforeContent || beforeContent.trim() === '') {
|
|
1625
|
-
return `Removed ${path.basename(filePath)} file from working directory
|
|
1899
|
+
return `Removed ${path.basename(filePath)} file from working directory`
|
|
1626
1900
|
}
|
|
1627
1901
|
|
|
1628
1902
|
// Analyze what functionality was removed based on the file content
|
|
1629
|
-
const language = change.language || this.detectLanguageFromPath(filePath)
|
|
1630
|
-
const category = change.category || 'other'
|
|
1903
|
+
const language = change.language || this.detectLanguageFromPath(filePath)
|
|
1904
|
+
const category = change.category || 'other'
|
|
1631
1905
|
|
|
1632
1906
|
if (language === 'javascript') {
|
|
1633
|
-
return this.analyzeDeletedJavaScriptFile(beforeContent, filePath, category)
|
|
1634
|
-
} else if (language === 'markdown') {
|
|
1635
|
-
return this.analyzeDeletedMarkdownFile(beforeContent, filePath);
|
|
1636
|
-
} else if (language === 'json') {
|
|
1637
|
-
return this.analyzeDeletedJsonFile(beforeContent, filePath);
|
|
1638
|
-
} else {
|
|
1639
|
-
// Generic analysis for other file types
|
|
1640
|
-
const lines = beforeContent.split('\n').length;
|
|
1641
|
-
const fileName = path.basename(filePath);
|
|
1642
|
-
const extension = path.extname(filePath);
|
|
1643
|
-
|
|
1644
|
-
if (category === 'source') {
|
|
1645
|
-
return `Removed ${fileName} source file containing ${lines} lines of ${language || extension} code`;
|
|
1646
|
-
} else if (category === 'documentation') {
|
|
1647
|
-
return `Removed ${fileName} documentation file containing ${lines} lines`;
|
|
1648
|
-
} else if (category === 'configuration') {
|
|
1649
|
-
return `Removed ${fileName} configuration file`;
|
|
1650
|
-
} else {
|
|
1651
|
-
return `Removed ${fileName} file containing ${lines} lines`;
|
|
1652
|
-
}
|
|
1907
|
+
return this.analyzeDeletedJavaScriptFile(beforeContent, filePath, category)
|
|
1653
1908
|
}
|
|
1909
|
+
if (language === 'markdown') {
|
|
1910
|
+
return this.analyzeDeletedMarkdownFile(beforeContent, filePath)
|
|
1911
|
+
}
|
|
1912
|
+
if (language === 'json') {
|
|
1913
|
+
return this.analyzeDeletedJsonFile(beforeContent, filePath)
|
|
1914
|
+
}
|
|
1915
|
+
// Generic analysis for other file types
|
|
1916
|
+
const lines = beforeContent.split('\n').length
|
|
1917
|
+
const fileName = path.basename(filePath)
|
|
1918
|
+
const extension = path.extname(filePath)
|
|
1919
|
+
|
|
1920
|
+
if (category === 'source') {
|
|
1921
|
+
return `Removed ${fileName} source file containing ${lines} lines of ${language || extension} code`
|
|
1922
|
+
}
|
|
1923
|
+
if (category === 'documentation') {
|
|
1924
|
+
return `Removed ${fileName} documentation file containing ${lines} lines`
|
|
1925
|
+
}
|
|
1926
|
+
if (category === 'configuration') {
|
|
1927
|
+
return `Removed ${fileName} configuration file`
|
|
1928
|
+
}
|
|
1929
|
+
return `Removed ${fileName} file containing ${lines} lines`
|
|
1654
1930
|
}
|
|
1655
1931
|
|
|
1656
|
-
analyzeDeletedJavaScriptFile(content, filePath,
|
|
1657
|
-
const fileName = path.basename(filePath)
|
|
1932
|
+
analyzeDeletedJavaScriptFile(content, filePath, _category) {
|
|
1933
|
+
const fileName = path.basename(filePath)
|
|
1658
1934
|
|
|
1659
1935
|
// Extract functions, classes, and main features
|
|
1660
|
-
const functions = this.extractFunctions(content)
|
|
1661
|
-
const classes = this.extractClasses(content)
|
|
1662
|
-
const imports = this.extractImports(content)
|
|
1663
|
-
const exports = this.extractExports(content)
|
|
1936
|
+
const functions = this.extractFunctions(content)
|
|
1937
|
+
const classes = this.extractClasses(content)
|
|
1938
|
+
const imports = this.extractImports(content)
|
|
1939
|
+
const exports = this.extractExports(content)
|
|
1664
1940
|
|
|
1665
|
-
const features = []
|
|
1941
|
+
const features = []
|
|
1666
1942
|
|
|
1667
1943
|
if (classes.length > 0) {
|
|
1668
|
-
features.push(
|
|
1944
|
+
features.push(
|
|
1945
|
+
`${classes.length} class${classes.length > 1 ? 'es' : ''} (${classes.slice(0, 2).join(', ')})`
|
|
1946
|
+
)
|
|
1669
1947
|
}
|
|
1670
1948
|
|
|
1671
1949
|
if (functions.length > 0) {
|
|
1672
|
-
features.push(
|
|
1950
|
+
features.push(
|
|
1951
|
+
`${functions.length} function${functions.length > 1 ? 's' : ''} (${functions.slice(0, 3).join(', ')})`
|
|
1952
|
+
)
|
|
1673
1953
|
}
|
|
1674
1954
|
|
|
1675
1955
|
if (imports.length > 0) {
|
|
1676
|
-
features.push(`${imports.length} import${imports.length > 1 ? 's' : ''}`)
|
|
1956
|
+
features.push(`${imports.length} import${imports.length > 1 ? 's' : ''}`)
|
|
1677
1957
|
}
|
|
1678
1958
|
|
|
1679
1959
|
if (exports.length > 0) {
|
|
1680
|
-
features.push(`${exports.length} export${exports.length > 1 ? 's' : ''}`)
|
|
1960
|
+
features.push(`${exports.length} export${exports.length > 1 ? 's' : ''}`)
|
|
1681
1961
|
}
|
|
1682
1962
|
|
|
1683
1963
|
// Determine the purpose based on file name and content patterns
|
|
1684
|
-
let purpose = ''
|
|
1964
|
+
let purpose = ''
|
|
1685
1965
|
if (fileName.includes('generator')) {
|
|
1686
|
-
purpose = 'generator implementation'
|
|
1966
|
+
purpose = 'generator implementation'
|
|
1687
1967
|
} else if (fileName.includes('provider')) {
|
|
1688
|
-
purpose = 'provider implementation'
|
|
1968
|
+
purpose = 'provider implementation'
|
|
1689
1969
|
} else if (fileName.includes('config')) {
|
|
1690
|
-
purpose = 'configuration management'
|
|
1970
|
+
purpose = 'configuration management'
|
|
1691
1971
|
} else if (fileName.includes('manager') || fileName.includes('service')) {
|
|
1692
|
-
purpose = 'service management'
|
|
1972
|
+
purpose = 'service management'
|
|
1693
1973
|
} else if (fileName.includes('cli') || fileName.includes('bin')) {
|
|
1694
|
-
purpose = 'CLI functionality'
|
|
1974
|
+
purpose = 'CLI functionality'
|
|
1695
1975
|
} else if (content.includes('class ') && content.includes('constructor')) {
|
|
1696
|
-
purpose = 'service class'
|
|
1976
|
+
purpose = 'service class'
|
|
1697
1977
|
} else if (content.includes('module.exports') || content.includes('export')) {
|
|
1698
|
-
purpose = 'module'
|
|
1978
|
+
purpose = 'module'
|
|
1699
1979
|
} else {
|
|
1700
|
-
purpose = 'JavaScript module'
|
|
1980
|
+
purpose = 'JavaScript module'
|
|
1701
1981
|
}
|
|
1702
1982
|
|
|
1703
1983
|
if (features.length > 0) {
|
|
1704
|
-
return `Removed ${fileName} ${purpose} containing ${features.join(', ')}
|
|
1705
|
-
} else {
|
|
1706
|
-
const lines = content.split('\n').length;
|
|
1707
|
-
return `Removed ${fileName} ${purpose} containing ${lines} lines of JavaScript code`;
|
|
1984
|
+
return `Removed ${fileName} ${purpose} containing ${features.join(', ')}`
|
|
1708
1985
|
}
|
|
1986
|
+
const lines = content.split('\n').length
|
|
1987
|
+
return `Removed ${fileName} ${purpose} containing ${lines} lines of JavaScript code`
|
|
1709
1988
|
}
|
|
1710
1989
|
|
|
1711
1990
|
analyzeDeletedMarkdownFile(content, filePath) {
|
|
1712
|
-
const fileName = path.basename(filePath)
|
|
1713
|
-
const lines = content.split('\n')
|
|
1714
|
-
const sections = lines.filter(line => line.startsWith('#')).length
|
|
1991
|
+
const fileName = path.basename(filePath)
|
|
1992
|
+
const lines = content.split('\n')
|
|
1993
|
+
const sections = lines.filter((line) => line.startsWith('#')).length
|
|
1715
1994
|
|
|
1716
1995
|
if (fileName.toLowerCase().includes('readme')) {
|
|
1717
|
-
return `Removed ${fileName} project documentation containing ${sections} sections and ${lines.length} lines
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
return `Removed ${fileName} documentation containing ${
|
|
1996
|
+
return `Removed ${fileName} project documentation containing ${sections} sections and ${lines.length} lines`
|
|
1997
|
+
}
|
|
1998
|
+
if (fileName.toLowerCase().includes('changelog')) {
|
|
1999
|
+
return `Removed ${fileName} changelog documentation`
|
|
2000
|
+
}
|
|
2001
|
+
if (sections > 0) {
|
|
2002
|
+
return `Removed ${fileName} documentation containing ${sections} sections`
|
|
1724
2003
|
}
|
|
2004
|
+
return `Removed ${fileName} documentation containing ${lines.length} lines`
|
|
1725
2005
|
}
|
|
1726
2006
|
|
|
1727
2007
|
analyzeDeletedJsonFile(content, filePath) {
|
|
1728
|
-
const fileName = path.basename(filePath)
|
|
2008
|
+
const fileName = path.basename(filePath)
|
|
1729
2009
|
|
|
1730
2010
|
try {
|
|
1731
|
-
const parsed = JSON.parse(content)
|
|
1732
|
-
const keys = Object.keys(parsed).length
|
|
2011
|
+
const parsed = JSON.parse(content)
|
|
2012
|
+
const keys = Object.keys(parsed).length
|
|
1733
2013
|
|
|
1734
2014
|
if (fileName === 'package.json') {
|
|
1735
|
-
return `Removed package.json configuration with ${keys} properties (dependencies, metadata, scripts)
|
|
1736
|
-
} else {
|
|
1737
|
-
return `Removed ${fileName} configuration file with ${keys} configuration properties`;
|
|
2015
|
+
return `Removed package.json configuration with ${keys} properties (dependencies, metadata, scripts)`
|
|
1738
2016
|
}
|
|
2017
|
+
return `Removed ${fileName} configuration file with ${keys} configuration properties`
|
|
1739
2018
|
} catch {
|
|
1740
|
-
return `Removed ${fileName} JSON configuration file
|
|
2019
|
+
return `Removed ${fileName} JSON configuration file`
|
|
1741
2020
|
}
|
|
1742
2021
|
}
|
|
1743
2022
|
|
|
1744
2023
|
detectLanguageFromPath(filePath) {
|
|
1745
|
-
const ext = path.extname(filePath).toLowerCase()
|
|
2024
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
1746
2025
|
const languageMap = {
|
|
1747
2026
|
'.js': 'javascript',
|
|
1748
2027
|
'.ts': 'typescript',
|
|
@@ -1753,9 +2032,8 @@ export class ChangelogService {
|
|
|
1753
2032
|
'.css': 'css',
|
|
1754
2033
|
'.html': 'html',
|
|
1755
2034
|
'.yml': 'yaml',
|
|
1756
|
-
'.yaml': 'yaml'
|
|
1757
|
-
}
|
|
1758
|
-
return languageMap[ext] || 'text'
|
|
2035
|
+
'.yaml': 'yaml',
|
|
2036
|
+
}
|
|
2037
|
+
return languageMap[ext] || 'text'
|
|
1759
2038
|
}
|
|
1760
|
-
|
|
1761
|
-
}
|
|
2039
|
+
}
|