@entro314labs/ai-changelog-generator 3.0.5 ā 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 -785
- package/README.md +30 -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 +84 -52
- package/src/ai-changelog-generator.js +83 -81
- package/src/application/orchestrators/changelog.orchestrator.js +1040 -296
- package/src/application/services/application.service.js +145 -123
- 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 +415 -247
- package/src/infrastructure/config/configuration.manager.js +220 -190
- package/src/infrastructure/interactive/interactive-staging.service.js +332 -0
- 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 +556 -0
- package/src/shared/constants/colors.js +467 -172
- 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 +1299 -775
- package/types/index.d.ts +353 -344
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import colors from '../../shared/constants/colors.js'
|
|
4
|
+
import { EnhancedConsole, SimpleSpinner } from '../../shared/utils/cli-ui.js'
|
|
5
|
+
import { runInteractiveMode, selectSpecificCommits } from '../../shared/utils/utils.js'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Interactive Workflow Service
|
|
@@ -13,13 +16,13 @@ import colors from '../../shared/constants/colors.js';
|
|
|
13
16
|
*/
|
|
14
17
|
export class InteractiveWorkflowService {
|
|
15
18
|
constructor(gitService, aiAnalysisService, changelogService) {
|
|
16
|
-
this.gitService = gitService
|
|
17
|
-
this.aiAnalysisService = aiAnalysisService
|
|
18
|
-
this.changelogService = changelogService
|
|
19
|
+
this.gitService = gitService
|
|
20
|
+
this.aiAnalysisService = aiAnalysisService
|
|
21
|
+
this.changelogService = changelogService
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
async runInteractiveMode() {
|
|
22
|
-
return await runInteractiveMode()
|
|
25
|
+
return await runInteractiveMode()
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
async validateCommitMessage(message) {
|
|
@@ -27,67 +30,71 @@ export class InteractiveWorkflowService {
|
|
|
27
30
|
return {
|
|
28
31
|
valid: false,
|
|
29
32
|
issues: ['Commit message is empty'],
|
|
30
|
-
suggestions: ['Provide a descriptive commit message']
|
|
31
|
-
}
|
|
33
|
+
suggestions: ['Provide a descriptive commit message'],
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
const issues = []
|
|
35
|
-
const suggestions = []
|
|
37
|
+
const issues = []
|
|
38
|
+
const suggestions = []
|
|
36
39
|
|
|
37
40
|
// Length validation
|
|
38
41
|
if (message.length < 10) {
|
|
39
|
-
issues.push('Commit message is too short (minimum 10 characters)')
|
|
40
|
-
suggestions.push('Add more detail about what was changed')
|
|
42
|
+
issues.push('Commit message is too short (minimum 10 characters)')
|
|
43
|
+
suggestions.push('Add more detail about what was changed')
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
if (message.length > 72) {
|
|
44
|
-
issues.push('Commit message first line is too long (maximum 72 characters)')
|
|
45
|
-
suggestions.push('Keep the first line concise, add details in the body')
|
|
47
|
+
issues.push('Commit message first line is too long (maximum 72 characters)')
|
|
48
|
+
suggestions.push('Keep the first line concise, add details in the body')
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// Format validation
|
|
49
|
-
const lines = message.split('\n')
|
|
50
|
-
const firstLine = lines[0]
|
|
52
|
+
const lines = message.split('\n')
|
|
53
|
+
const firstLine = lines[0]
|
|
51
54
|
|
|
52
55
|
// Check for conventional commit format
|
|
53
|
-
const conventionalPattern =
|
|
56
|
+
const conventionalPattern =
|
|
57
|
+
/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .+/
|
|
54
58
|
if (!conventionalPattern.test(firstLine)) {
|
|
55
|
-
suggestions.push('Consider using conventional commit format: type(scope): description')
|
|
59
|
+
suggestions.push('Consider using conventional commit format: type(scope): description')
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
// Check for imperative mood
|
|
59
|
-
const imperativeWords = ['add', 'fix', 'update', 'remove', 'create', 'implement', 'refactor']
|
|
60
|
-
const firstWord = firstLine
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const imperativeWords = ['add', 'fix', 'update', 'remove', 'create', 'implement', 'refactor']
|
|
64
|
+
const firstWord = firstLine
|
|
65
|
+
.split(' ')[0]
|
|
66
|
+
.toLowerCase()
|
|
67
|
+
.replace(/[^a-z]/g, '')
|
|
68
|
+
|
|
69
|
+
if (!imperativeWords.some((word) => firstWord.startsWith(word))) {
|
|
70
|
+
suggestions.push('Use imperative mood (e.g., "Add feature" not "Added feature")')
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
// Check for body separation
|
|
67
74
|
if (lines.length > 1 && lines[1].trim() !== '') {
|
|
68
|
-
issues.push('Missing blank line between subject and body')
|
|
69
|
-
suggestions.push('Add a blank line after the first line if including a body')
|
|
75
|
+
issues.push('Missing blank line between subject and body')
|
|
76
|
+
suggestions.push('Add a blank line after the first line if including a body')
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
return {
|
|
73
80
|
valid: issues.length === 0,
|
|
74
81
|
issues,
|
|
75
82
|
suggestions,
|
|
76
|
-
score: Math.max(0, 100 -
|
|
77
|
-
}
|
|
83
|
+
score: Math.max(0, 100 - issues.length * 20 - suggestions.length * 10),
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
async generateCommitSuggestion(message = null) {
|
|
81
88
|
try {
|
|
82
89
|
// If no message provided, analyze current changes
|
|
83
|
-
let analysisContext = ''
|
|
90
|
+
let analysisContext = ''
|
|
84
91
|
|
|
85
92
|
if (!message) {
|
|
86
93
|
// Use the shared utility function for getting working directory changes
|
|
87
|
-
const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js')
|
|
88
|
-
const changes = getWorkingDirectoryChanges()
|
|
94
|
+
const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js')
|
|
95
|
+
const changes = getWorkingDirectoryChanges()
|
|
89
96
|
if (changes && changes.length > 0) {
|
|
90
|
-
analysisContext = this.analyzeChangesForCommitMessage(changes, true)
|
|
97
|
+
analysisContext = this.analyzeChangesForCommitMessage(changes, true)
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
|
|
@@ -115,258 +122,288 @@ Requirements:
|
|
|
115
122
|
- Use imperative mood
|
|
116
123
|
- Be specific and descriptive
|
|
117
124
|
|
|
118
|
-
Provide 3 suggestions
|
|
125
|
+
Provide 3 suggestions.`
|
|
119
126
|
|
|
120
|
-
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
128
|
+
[
|
|
129
|
+
{
|
|
130
|
+
role: 'user',
|
|
131
|
+
content: prompt,
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
{ max_tokens: 200 }
|
|
135
|
+
)
|
|
124
136
|
|
|
125
137
|
return {
|
|
126
138
|
success: true,
|
|
127
139
|
original: message,
|
|
128
140
|
suggestions: this.parseCommitSuggestions(response.content),
|
|
129
|
-
context: analysisContext
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
// Rule-based suggestions
|
|
133
|
-
return this.generateRuleBasedCommitSuggestion(message, analysisContext);
|
|
141
|
+
context: analysisContext,
|
|
142
|
+
}
|
|
134
143
|
}
|
|
135
|
-
|
|
144
|
+
// Rule-based suggestions
|
|
145
|
+
return this.generateRuleBasedCommitSuggestion(message, analysisContext)
|
|
136
146
|
} catch (error) {
|
|
137
|
-
|
|
147
|
+
EnhancedConsole.error(`Error generating commit suggestion: ${error.message}`)
|
|
138
148
|
return {
|
|
139
149
|
success: false,
|
|
140
150
|
error: error.message,
|
|
141
|
-
fallback: this.generateRuleBasedCommitSuggestion(message)
|
|
142
|
-
}
|
|
151
|
+
fallback: this.generateRuleBasedCommitSuggestion(message),
|
|
152
|
+
}
|
|
143
153
|
}
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
analyzeChangesForCommitMessage(changes, includeScope = false) {
|
|
147
157
|
if (!changes || changes.length === 0) {
|
|
148
|
-
return 'No changes detected'
|
|
158
|
+
return 'No changes detected'
|
|
149
159
|
}
|
|
150
160
|
|
|
151
|
-
const categories = this.categorizeChanges(changes)
|
|
152
|
-
const primaryCategory = Object.keys(categories)[0]
|
|
153
|
-
const fileCount = changes.length
|
|
161
|
+
const categories = this.categorizeChanges(changes)
|
|
162
|
+
const primaryCategory = Object.keys(categories)[0]
|
|
163
|
+
const fileCount = changes.length
|
|
154
164
|
|
|
155
|
-
let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed
|
|
165
|
+
let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`
|
|
156
166
|
|
|
157
167
|
if (primaryCategory) {
|
|
158
|
-
summary += ` in ${primaryCategory}
|
|
168
|
+
summary += ` in ${primaryCategory}`
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
if (includeScope) {
|
|
162
|
-
const scopes = this.extractScopes(changes)
|
|
172
|
+
const scopes = this.extractScopes(changes)
|
|
163
173
|
if (scopes.length > 0) {
|
|
164
|
-
summary += ` (scope: ${scopes.join(', ')})
|
|
174
|
+
summary += ` (scope: ${scopes.join(', ')})`
|
|
165
175
|
}
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
// Add change details
|
|
169
|
-
const additions = changes.filter(c => c.status === 'A').length
|
|
170
|
-
const modifications = changes.filter(c => c.status === 'M').length
|
|
171
|
-
const deletions = changes.filter(c => c.status === 'D').length
|
|
179
|
+
const additions = changes.filter((c) => c.status === 'A').length
|
|
180
|
+
const modifications = changes.filter((c) => c.status === 'M').length
|
|
181
|
+
const deletions = changes.filter((c) => c.status === 'D').length
|
|
172
182
|
|
|
173
|
-
const details = []
|
|
174
|
-
if (additions > 0)
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
const details = []
|
|
184
|
+
if (additions > 0) {
|
|
185
|
+
details.push(`${additions} added`)
|
|
186
|
+
}
|
|
187
|
+
if (modifications > 0) {
|
|
188
|
+
details.push(`${modifications} modified`)
|
|
189
|
+
}
|
|
190
|
+
if (deletions > 0) {
|
|
191
|
+
details.push(`${deletions} deleted`)
|
|
192
|
+
}
|
|
177
193
|
|
|
178
194
|
if (details.length > 0) {
|
|
179
|
-
summary += ` (${details.join(', ')})
|
|
195
|
+
summary += ` (${details.join(', ')})`
|
|
180
196
|
}
|
|
181
197
|
|
|
182
|
-
return summary
|
|
198
|
+
return summary
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
async selectSpecificCommits() {
|
|
186
|
-
return await selectSpecificCommits()
|
|
202
|
+
return await selectSpecificCommits()
|
|
187
203
|
}
|
|
188
204
|
|
|
189
205
|
async generateChangelogForRecentCommits(count = 10) {
|
|
190
206
|
try {
|
|
191
|
-
|
|
207
|
+
const spinner = new SimpleSpinner(`Generating changelog for recent ${count} commits...`)
|
|
208
|
+
spinner.start()
|
|
192
209
|
|
|
193
|
-
const commits = await this.gitService.getCommitsSince(null)
|
|
194
|
-
const recentCommits = commits.slice(0, count)
|
|
210
|
+
const commits = await this.gitService.getCommitsSince(null)
|
|
211
|
+
const recentCommits = commits.slice(0, count)
|
|
195
212
|
|
|
196
213
|
if (recentCommits.length === 0) {
|
|
197
|
-
|
|
198
|
-
|
|
214
|
+
spinner.stop()
|
|
215
|
+
EnhancedConsole.info('No recent commits found.')
|
|
216
|
+
return null
|
|
199
217
|
}
|
|
200
218
|
|
|
201
219
|
const result = await this.changelogService.generateChangelogBatch(
|
|
202
|
-
recentCommits.map(c => c.hash)
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
console.log(colors.successMessage(`ā
Generated changelog for ${recentCommits.length} commits`));
|
|
206
|
-
return result;
|
|
220
|
+
recentCommits.map((c) => c.hash)
|
|
221
|
+
)
|
|
207
222
|
|
|
223
|
+
spinner.succeed(`Generated changelog for ${recentCommits.length} commits`)
|
|
224
|
+
return result
|
|
208
225
|
} catch (error) {
|
|
209
|
-
|
|
210
|
-
|
|
226
|
+
if (typeof spinner !== 'undefined') {
|
|
227
|
+
spinner.fail('Failed to generate changelog')
|
|
228
|
+
}
|
|
229
|
+
EnhancedConsole.error(`Error generating changelog for recent commits: ${error.message}`)
|
|
230
|
+
throw error
|
|
211
231
|
}
|
|
212
232
|
}
|
|
213
233
|
|
|
214
234
|
async generateChangelogForCommits(commitHashes) {
|
|
215
235
|
if (!commitHashes || commitHashes.length === 0) {
|
|
216
|
-
|
|
217
|
-
return null
|
|
236
|
+
EnhancedConsole.warn('No commit hashes provided')
|
|
237
|
+
return null
|
|
218
238
|
}
|
|
219
239
|
|
|
220
240
|
try {
|
|
221
|
-
|
|
241
|
+
const spinner = new SimpleSpinner(
|
|
242
|
+
`Generating changelog for ${commitHashes.length} specific commits...`
|
|
243
|
+
)
|
|
244
|
+
spinner.start()
|
|
222
245
|
|
|
223
246
|
// Validate commit hashes
|
|
224
|
-
const validCommits = []
|
|
247
|
+
const validCommits = []
|
|
225
248
|
for (const hash of commitHashes) {
|
|
226
|
-
const analysis = await this.gitService.getCommitAnalysis(hash)
|
|
249
|
+
const analysis = await this.gitService.getCommitAnalysis(hash)
|
|
227
250
|
if (analysis) {
|
|
228
|
-
validCommits.push(analysis)
|
|
251
|
+
validCommits.push(analysis)
|
|
229
252
|
} else {
|
|
230
|
-
|
|
253
|
+
EnhancedConsole.warn(`Invalid or inaccessible commit: ${hash}`)
|
|
231
254
|
}
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
if (validCommits.length === 0) {
|
|
235
|
-
|
|
236
|
-
return null
|
|
258
|
+
spinner.fail('No valid commits found')
|
|
259
|
+
return null
|
|
237
260
|
}
|
|
238
261
|
|
|
239
262
|
// Generate AI summaries for each commit
|
|
240
|
-
const analyzedCommits = []
|
|
263
|
+
const analyzedCommits = []
|
|
241
264
|
for (let i = 0; i < validCommits.length; i++) {
|
|
242
|
-
const commit = validCommits[i]
|
|
243
|
-
|
|
265
|
+
const commit = validCommits[i]
|
|
266
|
+
process.stdout.write(
|
|
267
|
+
`\r${colors.statusSymbol('processing', `Processing commit ${i + 1}/${validCommits.length}: ${colors.hash(commit.hash)}`)}`
|
|
268
|
+
)
|
|
244
269
|
|
|
245
|
-
const selectedModel = await this.aiAnalysisService.selectOptimalModel(commit)
|
|
246
|
-
const aiSummary = await this.aiAnalysisService.generateAISummary(commit, selectedModel)
|
|
270
|
+
const selectedModel = await this.aiAnalysisService.selectOptimalModel(commit)
|
|
271
|
+
const aiSummary = await this.aiAnalysisService.generateAISummary(commit, selectedModel)
|
|
247
272
|
|
|
248
273
|
if (aiSummary) {
|
|
249
274
|
analyzedCommits.push({
|
|
250
275
|
...commit,
|
|
251
276
|
aiSummary,
|
|
252
277
|
type: aiSummary.category || 'other',
|
|
253
|
-
breaking: aiSummary.breaking
|
|
254
|
-
})
|
|
278
|
+
breaking: aiSummary.breaking,
|
|
279
|
+
})
|
|
255
280
|
}
|
|
256
281
|
}
|
|
257
282
|
|
|
258
283
|
if (analyzedCommits.length === 0) {
|
|
259
|
-
|
|
260
|
-
return null
|
|
284
|
+
spinner.fail('No commits could be analyzed')
|
|
285
|
+
return null
|
|
261
286
|
}
|
|
262
287
|
|
|
263
288
|
// Generate release insights and build changelog
|
|
264
|
-
const insights = await this.changelogService.generateReleaseInsights(
|
|
265
|
-
|
|
289
|
+
const insights = await this.changelogService.generateReleaseInsights(
|
|
290
|
+
analyzedCommits,
|
|
291
|
+
'Selected Commits'
|
|
292
|
+
)
|
|
293
|
+
const changelog = await this.changelogService.buildChangelog(
|
|
294
|
+
analyzedCommits,
|
|
295
|
+
insights,
|
|
296
|
+
'Selected Commits'
|
|
297
|
+
)
|
|
266
298
|
|
|
267
|
-
|
|
299
|
+
process.stdout.write(`\r${' '.repeat(80)}\r`) // Clear processing line
|
|
300
|
+
spinner.succeed(`Generated changelog for ${analyzedCommits.length} commits`)
|
|
268
301
|
|
|
269
302
|
return {
|
|
270
303
|
changelog,
|
|
271
304
|
insights,
|
|
272
305
|
analyzedCommits,
|
|
273
306
|
requestedCommits: commitHashes.length,
|
|
274
|
-
processedCommits: analyzedCommits.length
|
|
275
|
-
}
|
|
276
|
-
|
|
307
|
+
processedCommits: analyzedCommits.length,
|
|
308
|
+
}
|
|
277
309
|
} catch (error) {
|
|
278
|
-
console.error(colors.errorMessage(`Error generating changelog for commits: ${error.message}`))
|
|
279
|
-
throw error
|
|
310
|
+
console.error(colors.errorMessage(`Error generating changelog for commits: ${error.message}`))
|
|
311
|
+
throw error
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
|
|
283
315
|
// Helper methods
|
|
284
316
|
categorizeChanges(changes) {
|
|
285
|
-
const categories = {}
|
|
286
|
-
changes.forEach(change => {
|
|
287
|
-
const category = this.getFileCategory(change.path)
|
|
288
|
-
if (!categories[category])
|
|
289
|
-
|
|
290
|
-
|
|
317
|
+
const categories = {}
|
|
318
|
+
changes.forEach((change) => {
|
|
319
|
+
const category = this.getFileCategory(change.path)
|
|
320
|
+
if (!categories[category]) {
|
|
321
|
+
categories[category] = []
|
|
322
|
+
}
|
|
323
|
+
categories[category].push(change)
|
|
324
|
+
})
|
|
291
325
|
|
|
292
326
|
// Sort by count
|
|
293
327
|
return Object.fromEntries(
|
|
294
|
-
Object.entries(categories).sort(([,a], [,b]) => b.length - a.length)
|
|
295
|
-
)
|
|
328
|
+
Object.entries(categories).sort(([, a], [, b]) => b.length - a.length)
|
|
329
|
+
)
|
|
296
330
|
}
|
|
297
331
|
|
|
298
332
|
getFileCategory(filePath) {
|
|
299
333
|
if (!filePath || typeof filePath !== 'string') {
|
|
300
|
-
return 'other'
|
|
334
|
+
return 'other'
|
|
301
335
|
}
|
|
302
336
|
|
|
303
|
-
const path = filePath.toLowerCase()
|
|
337
|
+
const path = filePath.toLowerCase()
|
|
304
338
|
|
|
305
339
|
if (path.includes('/test/') || path.includes('.test.') || path.includes('.spec.')) {
|
|
306
|
-
return 'tests'
|
|
340
|
+
return 'tests'
|
|
307
341
|
}
|
|
308
342
|
if (path.includes('/doc/') || path.endsWith('.md') || path.endsWith('.txt')) {
|
|
309
|
-
return 'documentation'
|
|
343
|
+
return 'documentation'
|
|
310
344
|
}
|
|
311
345
|
if (path.includes('/config/') || path.endsWith('.json') || path.endsWith('.yaml')) {
|
|
312
|
-
return 'configuration'
|
|
346
|
+
return 'configuration'
|
|
313
347
|
}
|
|
314
348
|
if (path.includes('/src/') || path.includes('/lib/')) {
|
|
315
|
-
return 'source'
|
|
349
|
+
return 'source'
|
|
316
350
|
}
|
|
317
351
|
if (path.includes('/style/') || path.endsWith('.css') || path.endsWith('.scss')) {
|
|
318
|
-
return 'styles'
|
|
352
|
+
return 'styles'
|
|
319
353
|
}
|
|
320
354
|
|
|
321
|
-
return 'other'
|
|
355
|
+
return 'other'
|
|
322
356
|
}
|
|
323
357
|
|
|
324
358
|
extractScopes(changes) {
|
|
325
|
-
const scopes = new Set()
|
|
359
|
+
const scopes = new Set()
|
|
326
360
|
|
|
327
|
-
changes.forEach(change => {
|
|
328
|
-
const parts = change.path.split('/')
|
|
361
|
+
changes.forEach((change) => {
|
|
362
|
+
const parts = change.path.split('/')
|
|
329
363
|
if (parts.length > 1) {
|
|
330
364
|
// Extract directory name as scope
|
|
331
|
-
const scope = parts
|
|
365
|
+
const scope = parts.at(-2)
|
|
332
366
|
if (scope && scope !== '.' && scope !== '..') {
|
|
333
|
-
scopes.add(scope)
|
|
367
|
+
scopes.add(scope)
|
|
334
368
|
}
|
|
335
369
|
}
|
|
336
|
-
})
|
|
370
|
+
})
|
|
337
371
|
|
|
338
|
-
return Array.from(scopes).slice(0, 3)
|
|
372
|
+
return Array.from(scopes).slice(0, 3) // Limit to 3 scopes
|
|
339
373
|
}
|
|
340
374
|
|
|
341
375
|
parseCommitSuggestions(content) {
|
|
342
376
|
// Parse AI response to extract suggestions
|
|
343
|
-
const lines = content.split('\n').filter(line => line.trim())
|
|
344
|
-
const suggestions = []
|
|
377
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
378
|
+
const suggestions = []
|
|
345
379
|
|
|
346
|
-
lines.forEach(line => {
|
|
380
|
+
lines.forEach((line) => {
|
|
347
381
|
// Remove numbering and clean up
|
|
348
|
-
const cleaned = line
|
|
382
|
+
const cleaned = line
|
|
383
|
+
.replace(/^\d+\.\s*/, '')
|
|
384
|
+
.replace(/^-\s*/, '')
|
|
385
|
+
.trim()
|
|
349
386
|
if (cleaned && cleaned.length > 10 && cleaned.includes(':')) {
|
|
350
|
-
suggestions.push(cleaned)
|
|
387
|
+
suggestions.push(cleaned)
|
|
351
388
|
}
|
|
352
|
-
})
|
|
389
|
+
})
|
|
353
390
|
|
|
354
|
-
return suggestions.length > 0 ? suggestions : [content.trim()]
|
|
391
|
+
return suggestions.length > 0 ? suggestions : [content.trim()]
|
|
355
392
|
}
|
|
356
393
|
|
|
357
394
|
generateRuleBasedCommitSuggestion(message, context) {
|
|
358
|
-
const suggestions = []
|
|
395
|
+
const suggestions = []
|
|
359
396
|
|
|
360
397
|
if (message) {
|
|
361
398
|
// Improve existing message
|
|
362
|
-
const improved = this.improveCommitMessage(message)
|
|
363
|
-
suggestions.push(improved)
|
|
399
|
+
const improved = this.improveCommitMessage(message)
|
|
400
|
+
suggestions.push(improved)
|
|
364
401
|
}
|
|
365
402
|
|
|
366
403
|
if (context) {
|
|
367
404
|
// Generate from context
|
|
368
|
-
const fromContext = this.generateFromContext(context)
|
|
369
|
-
suggestions.push(fromContext)
|
|
405
|
+
const fromContext = this.generateFromContext(context)
|
|
406
|
+
suggestions.push(fromContext)
|
|
370
407
|
}
|
|
371
408
|
|
|
372
409
|
// Add generic suggestions
|
|
@@ -374,71 +411,75 @@ Provide 3 suggestions.`;
|
|
|
374
411
|
'feat: add new functionality',
|
|
375
412
|
'fix: resolve issue with component',
|
|
376
413
|
'docs: update documentation'
|
|
377
|
-
)
|
|
414
|
+
)
|
|
378
415
|
|
|
379
416
|
return {
|
|
380
417
|
success: true,
|
|
381
418
|
suggestions: suggestions.slice(0, 3),
|
|
382
|
-
source: 'rule-based'
|
|
383
|
-
}
|
|
419
|
+
source: 'rule-based',
|
|
420
|
+
}
|
|
384
421
|
}
|
|
385
422
|
|
|
386
423
|
improveCommitMessage(message) {
|
|
387
424
|
// Basic improvements
|
|
388
|
-
let improved = message.trim()
|
|
425
|
+
let improved = message.trim()
|
|
389
426
|
|
|
390
427
|
// Add conventional commit prefix if missing
|
|
391
428
|
if (!/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)/.test(improved)) {
|
|
392
|
-
improved = `feat: ${improved}
|
|
429
|
+
improved = `feat: ${improved}`
|
|
393
430
|
}
|
|
394
431
|
|
|
395
432
|
// Ensure imperative mood
|
|
396
|
-
improved = improved.replace(/^(\w+)ed\s/, '$1 ')
|
|
397
|
-
improved = improved.replace(/^(\w+)s\s/, '$1 ')
|
|
433
|
+
improved = improved.replace(/^(\w+)ed\s/, '$1 ')
|
|
434
|
+
improved = improved.replace(/^(\w+)s\s/, '$1 ')
|
|
398
435
|
|
|
399
|
-
return improved
|
|
436
|
+
return improved
|
|
400
437
|
}
|
|
401
438
|
|
|
402
439
|
generateFromContext(context) {
|
|
403
440
|
if (!context || typeof context !== 'string') {
|
|
404
|
-
return 'feat: implement changes'
|
|
441
|
+
return 'feat: implement changes'
|
|
405
442
|
}
|
|
406
443
|
|
|
407
444
|
if (context.includes('test')) {
|
|
408
|
-
return 'test: add test coverage'
|
|
445
|
+
return 'test: add test coverage'
|
|
409
446
|
}
|
|
410
447
|
if (context.includes('doc')) {
|
|
411
|
-
return 'docs: update documentation'
|
|
448
|
+
return 'docs: update documentation'
|
|
412
449
|
}
|
|
413
450
|
if (context.includes('config')) {
|
|
414
|
-
return 'chore: update configuration'
|
|
451
|
+
return 'chore: update configuration'
|
|
415
452
|
}
|
|
416
453
|
if (context.includes('fix') || context.includes('bug')) {
|
|
417
|
-
return 'fix: resolve issue'
|
|
454
|
+
return 'fix: resolve issue'
|
|
418
455
|
}
|
|
419
456
|
|
|
420
|
-
return 'feat: implement changes'
|
|
457
|
+
return 'feat: implement changes'
|
|
421
458
|
}
|
|
422
459
|
|
|
423
460
|
// Utility method for displaying interactive results
|
|
424
461
|
displayInteractiveResults(results) {
|
|
425
|
-
if (!results)
|
|
462
|
+
if (!results) {
|
|
463
|
+
return
|
|
464
|
+
}
|
|
426
465
|
|
|
427
|
-
console.log(colors.header('\nš Interactive Session Results:'))
|
|
466
|
+
console.log(colors.header('\nš Interactive Session Results:'))
|
|
428
467
|
|
|
429
468
|
if (results.changelog) {
|
|
430
|
-
console.log(colors.subheader('Generated Changelog:'))
|
|
431
|
-
console.log(results.changelog)
|
|
469
|
+
console.log(colors.subheader('Generated Changelog:'))
|
|
470
|
+
console.log(results.changelog)
|
|
432
471
|
}
|
|
433
472
|
|
|
434
473
|
if (results.insights) {
|
|
435
|
-
console.log(colors.subheader('\nš Insights:'))
|
|
436
|
-
console.log(`Total commits: ${colors.number(results.insights.totalCommits)}`)
|
|
437
|
-
console.log(`Risk level: ${colors.highlight(results.insights.riskLevel)}`)
|
|
474
|
+
console.log(colors.subheader('\nš Insights:'))
|
|
475
|
+
console.log(`Total commits: ${colors.number(results.insights.totalCommits)}`)
|
|
476
|
+
console.log(`Risk level: ${colors.highlight(results.insights.riskLevel)}`)
|
|
438
477
|
}
|
|
439
478
|
|
|
440
479
|
if (results.analyzedCommits) {
|
|
441
|
-
console.log(
|
|
480
|
+
console.log(
|
|
481
|
+
colors.subheader(`\nš Processed ${colors.number(results.analyzedCommits.length)} commits`)
|
|
482
|
+
)
|
|
442
483
|
}
|
|
443
484
|
}
|
|
444
|
-
}
|
|
485
|
+
}
|