@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,18 +1,20 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { InteractiveStagingService } from '../../infrastructure/interactive/interactive-staging.service.js'
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
|
|
3
|
+
import { AIAnalysisService } from '../../domains/ai/ai-analysis.service.js'
|
|
4
|
+
import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
|
|
5
|
+
import { ChangelogService } from '../../domains/changelog/changelog.service.js'
|
|
6
|
+
import { GitService } from '../../domains/git/git.service.js'
|
|
7
|
+
import { InteractiveStagingService } from '../../infrastructure/interactive/interactive-staging.service.js'
|
|
8
|
+
import { InteractiveWorkflowService } from '../../infrastructure/interactive/interactive-workflow.service.js'
|
|
9
|
+
import { ProviderManagerService } from '../../infrastructure/providers/provider-manager.service.js'
|
|
10
|
+
import { CommitMessageValidationService } from '../../infrastructure/validation/commit-message-validation.service.js'
|
|
11
|
+
import colors from '../../shared/constants/colors.js'
|
|
10
12
|
|
|
11
13
|
export class ChangelogOrchestrator {
|
|
12
14
|
constructor(configManager, options = {}) {
|
|
13
|
-
this.configManager = configManager
|
|
14
|
-
this.options = options
|
|
15
|
-
this.analysisMode = options.analysisMode || 'standard'
|
|
15
|
+
this.configManager = configManager
|
|
16
|
+
this.options = options
|
|
17
|
+
this.analysisMode = options.analysisMode || 'standard'
|
|
16
18
|
this.metrics = {
|
|
17
19
|
startTime: Date.now(),
|
|
18
20
|
commitsProcessed: 0,
|
|
@@ -21,19 +23,30 @@ export class ChangelogOrchestrator {
|
|
|
21
23
|
batchesProcessed: 0,
|
|
22
24
|
totalTokens: 0,
|
|
23
25
|
ruleBasedFallbacks: 0,
|
|
24
|
-
cacheHits: 0
|
|
25
|
-
}
|
|
26
|
+
cacheHits: 0,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
this.initialized = false
|
|
30
|
+
this.initializationPromise = null
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
this.
|
|
32
|
+
// Cache frequently used imports for performance
|
|
33
|
+
this._importCache = new Map()
|
|
29
34
|
|
|
30
35
|
// Start initialization
|
|
31
|
-
this.initializationPromise = this.initializeServices()
|
|
36
|
+
this.initializationPromise = this.initializeServices()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Cached import helper to avoid repeated dynamic imports
|
|
40
|
+
async _getCachedImport(moduleName) {
|
|
41
|
+
if (!this._importCache.has(moduleName)) {
|
|
42
|
+
this._importCache.set(moduleName, await import(moduleName))
|
|
43
|
+
}
|
|
44
|
+
return this._importCache.get(moduleName)
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
async ensureInitialized() {
|
|
35
48
|
if (!this.initialized) {
|
|
36
|
-
await this.initializationPromise
|
|
49
|
+
await this.initializationPromise
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
|
|
@@ -41,126 +54,179 @@ export class ChangelogOrchestrator {
|
|
|
41
54
|
try {
|
|
42
55
|
// Initialize AI provider
|
|
43
56
|
this.providerManager = new ProviderManagerService(this.configManager.getAll(), {
|
|
44
|
-
errorHandler: { logToConsole: true }
|
|
45
|
-
})
|
|
46
|
-
this.aiProvider = this.providerManager.getActiveProvider()
|
|
57
|
+
errorHandler: { logToConsole: true },
|
|
58
|
+
})
|
|
59
|
+
this.aiProvider = this.providerManager.getActiveProvider()
|
|
47
60
|
|
|
48
61
|
// Create lightweight implementations for missing dependencies
|
|
49
|
-
this.gitManager = await this.createGitManager()
|
|
50
|
-
this.tagger = await this.createTagger()
|
|
51
|
-
this.promptEngine = await this.createPromptEngine()
|
|
62
|
+
this.gitManager = await this.createGitManager()
|
|
63
|
+
this.tagger = await this.createTagger()
|
|
64
|
+
this.promptEngine = await this.createPromptEngine()
|
|
52
65
|
|
|
53
66
|
// Initialize domain services with proper dependencies
|
|
54
|
-
this.gitService = new GitService(this.gitManager, this.tagger)
|
|
55
|
-
this.aiAnalysisService = new AIAnalysisService(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
this.gitService = new GitService(this.gitManager, this.tagger)
|
|
68
|
+
this.aiAnalysisService = new AIAnalysisService(
|
|
69
|
+
this.aiProvider,
|
|
70
|
+
this.promptEngine,
|
|
71
|
+
this.tagger,
|
|
72
|
+
this.analysisMode
|
|
73
|
+
)
|
|
74
|
+
this.analysisEngine = new AnalysisEngine(this.gitService, this.aiAnalysisService)
|
|
75
|
+
this.changelogService = new ChangelogService(
|
|
76
|
+
this.gitService,
|
|
77
|
+
this.aiAnalysisService,
|
|
78
|
+
this.analysisEngine,
|
|
79
|
+
this.configManager
|
|
80
|
+
)
|
|
81
|
+
this.interactiveService = new InteractiveWorkflowService(
|
|
82
|
+
this.gitService,
|
|
83
|
+
this.aiAnalysisService,
|
|
84
|
+
this.changelogService
|
|
85
|
+
)
|
|
86
|
+
this.stagingService = new InteractiveStagingService(this.gitManager)
|
|
87
|
+
this.validationService = new CommitMessageValidationService(this.configManager)
|
|
61
88
|
|
|
62
89
|
// Only log if not in MCP server mode
|
|
63
90
|
if (!process.env.MCP_SERVER_MODE) {
|
|
64
|
-
console.log(colors.successMessage('⚙️ Services initialized'))
|
|
91
|
+
console.log(colors.successMessage('⚙️ Services initialized'))
|
|
65
92
|
}
|
|
66
|
-
this.initialized = true
|
|
67
|
-
|
|
93
|
+
this.initialized = true
|
|
68
94
|
} catch (error) {
|
|
69
|
-
|
|
70
|
-
|
|
95
|
+
// Enhanced error handling with recovery suggestions
|
|
96
|
+
let errorMessage = 'Failed to initialize services: '
|
|
97
|
+
const suggestions = []
|
|
98
|
+
|
|
99
|
+
if (error.message.includes('not a git repository')) {
|
|
100
|
+
errorMessage += 'Not in a git repository'
|
|
101
|
+
suggestions.push('Run this command from within a git repository')
|
|
102
|
+
suggestions.push('Initialize a git repository with: git init')
|
|
103
|
+
} else if (error.message.includes('API key') || error.message.includes('provider')) {
|
|
104
|
+
errorMessage += 'AI provider configuration issue'
|
|
105
|
+
suggestions.push('Check your .env.local file for API keys')
|
|
106
|
+
suggestions.push('Run: ai-changelog providers list')
|
|
107
|
+
} else {
|
|
108
|
+
errorMessage += error.message
|
|
109
|
+
suggestions.push('Try running with --debug for more information')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.error(colors.errorMessage(errorMessage))
|
|
113
|
+
if (suggestions.length > 0) {
|
|
114
|
+
console.error(colors.infoMessage('Suggestions:'))
|
|
115
|
+
suggestions.forEach((suggestion) => {
|
|
116
|
+
console.error(colors.dim(` • ${suggestion}`))
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw error
|
|
71
121
|
}
|
|
72
122
|
}
|
|
73
123
|
|
|
74
124
|
async createGitManager() {
|
|
75
|
-
const { execSync } = await
|
|
125
|
+
const { execSync } = await this._getCachedImport('child_process')
|
|
76
126
|
|
|
77
127
|
return {
|
|
78
128
|
isGitRepo: (() => {
|
|
79
129
|
try {
|
|
80
|
-
execSync('git rev-parse --git-dir', { stdio: 'ignore' })
|
|
81
|
-
return true
|
|
130
|
+
execSync('git rev-parse --git-dir', { stdio: 'ignore' })
|
|
131
|
+
return true
|
|
82
132
|
} catch {
|
|
83
|
-
return false
|
|
133
|
+
return false
|
|
84
134
|
}
|
|
85
135
|
})(),
|
|
86
136
|
|
|
87
137
|
execGit(command) {
|
|
88
138
|
try {
|
|
89
|
-
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
139
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
90
140
|
} catch (error) {
|
|
91
|
-
|
|
141
|
+
// Enhanced error handling with more specific messages
|
|
142
|
+
if (error.code === 128) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Git repository error: ${error.message.includes('not a git repository') ? 'Not in a git repository' : error.message}`
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
if (error.code === 129) {
|
|
148
|
+
throw new Error(`Git command syntax error: ${command}`)
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`Git command failed (${command}): ${error.message}`)
|
|
92
151
|
}
|
|
93
152
|
},
|
|
94
153
|
|
|
95
154
|
execGitSafe(command) {
|
|
96
155
|
try {
|
|
97
|
-
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
156
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
98
157
|
} catch {
|
|
99
|
-
return ''
|
|
158
|
+
return ''
|
|
100
159
|
}
|
|
101
160
|
},
|
|
102
161
|
|
|
103
162
|
execGitShow(command) {
|
|
104
163
|
try {
|
|
105
|
-
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
106
|
-
} catch (
|
|
164
|
+
return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
|
|
165
|
+
} catch (_error) {
|
|
107
166
|
// console.warn(`Git command failed: ${command}`);
|
|
108
167
|
// console.warn(`Error: ${error.message}`);
|
|
109
|
-
return null
|
|
168
|
+
return null
|
|
110
169
|
}
|
|
111
170
|
},
|
|
112
171
|
|
|
113
172
|
validateCommitHash(hash) {
|
|
114
173
|
try {
|
|
115
|
-
execSync(`git cat-file -e ${hash}`, { stdio: 'ignore' })
|
|
116
|
-
return true
|
|
174
|
+
execSync(`git cat-file -e ${hash}`, { stdio: 'ignore' })
|
|
175
|
+
return true
|
|
117
176
|
} catch {
|
|
118
|
-
return false
|
|
177
|
+
return false
|
|
119
178
|
}
|
|
120
179
|
},
|
|
121
180
|
|
|
122
181
|
getAllBranches() {
|
|
123
182
|
try {
|
|
124
|
-
const output = execSync('git branch -a', { encoding: 'utf8' })
|
|
125
|
-
return output
|
|
183
|
+
const output = execSync('git branch -a', { encoding: 'utf8' })
|
|
184
|
+
return output
|
|
185
|
+
.split('\n')
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.map((branch) => branch.trim().replace(/^\*\s*/, ''))
|
|
126
188
|
} catch {
|
|
127
|
-
return []
|
|
189
|
+
return []
|
|
128
190
|
}
|
|
129
191
|
},
|
|
130
192
|
|
|
131
193
|
getUnmergedCommits() {
|
|
132
194
|
try {
|
|
133
|
-
const output = execSync('git log --oneline --no-merges HEAD ^origin/main', {
|
|
134
|
-
|
|
195
|
+
const output = execSync('git log --oneline --no-merges HEAD ^origin/main', {
|
|
196
|
+
encoding: 'utf8',
|
|
197
|
+
})
|
|
198
|
+
return output.split('\n').filter(Boolean)
|
|
135
199
|
} catch {
|
|
136
|
-
return []
|
|
200
|
+
return []
|
|
137
201
|
}
|
|
138
202
|
},
|
|
139
203
|
|
|
140
204
|
getDanglingCommits() {
|
|
141
205
|
try {
|
|
142
|
-
const output = execSync('git fsck --no-reflog | grep "dangling commit"', {
|
|
143
|
-
|
|
206
|
+
const output = execSync('git fsck --no-reflog | grep "dangling commit"', {
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
})
|
|
209
|
+
return output.split('\n').filter(Boolean)
|
|
144
210
|
} catch {
|
|
145
|
-
return []
|
|
211
|
+
return []
|
|
146
212
|
}
|
|
147
213
|
},
|
|
148
214
|
|
|
149
215
|
getUntrackedFiles() {
|
|
150
216
|
try {
|
|
151
|
-
const output = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' })
|
|
152
|
-
return output.split('\n').filter(Boolean)
|
|
217
|
+
const output = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' })
|
|
218
|
+
return output.split('\n').filter(Boolean)
|
|
153
219
|
} catch {
|
|
154
|
-
return []
|
|
220
|
+
return []
|
|
155
221
|
}
|
|
156
222
|
},
|
|
157
223
|
|
|
158
224
|
getRecentCommits(limit = 10) {
|
|
159
225
|
try {
|
|
160
|
-
const output = execSync(`git log --oneline -${limit}`, { encoding: 'utf8' })
|
|
161
|
-
return output.split('\n').filter(Boolean)
|
|
226
|
+
const output = execSync(`git log --oneline -${limit}`, { encoding: 'utf8' })
|
|
227
|
+
return output.split('\n').filter(Boolean)
|
|
162
228
|
} catch {
|
|
163
|
-
return []
|
|
229
|
+
return []
|
|
164
230
|
}
|
|
165
231
|
},
|
|
166
232
|
|
|
@@ -168,67 +234,69 @@ export class ChangelogOrchestrator {
|
|
|
168
234
|
return {
|
|
169
235
|
totalCommits: this.getRecentCommits(1000).length,
|
|
170
236
|
branches: this.getAllBranches(),
|
|
171
|
-
untrackedFiles: this.getUntrackedFiles()
|
|
172
|
-
}
|
|
237
|
+
untrackedFiles: this.getUntrackedFiles(),
|
|
238
|
+
}
|
|
173
239
|
},
|
|
174
240
|
|
|
175
241
|
hasFile(filename) {
|
|
176
242
|
try {
|
|
177
|
-
execSync(`test -f ${filename}`, { stdio: 'ignore' })
|
|
178
|
-
return true
|
|
243
|
+
execSync(`test -f ${filename}`, { stdio: 'ignore' })
|
|
244
|
+
return true
|
|
179
245
|
} catch {
|
|
180
|
-
return false
|
|
246
|
+
return false
|
|
181
247
|
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
248
|
+
},
|
|
249
|
+
}
|
|
184
250
|
}
|
|
185
251
|
|
|
186
252
|
async createTagger() {
|
|
187
|
-
const { analyzeSemanticChanges, analyzeFunctionalImpact } = await import(
|
|
253
|
+
const { analyzeSemanticChanges, analyzeFunctionalImpact } = await import(
|
|
254
|
+
'../../shared/utils/utils.js'
|
|
255
|
+
)
|
|
188
256
|
|
|
189
257
|
return {
|
|
190
258
|
analyzeCommit(commit) {
|
|
191
|
-
const semanticChanges = []
|
|
192
|
-
const breakingChanges = []
|
|
193
|
-
const categories = []
|
|
194
|
-
const tags = []
|
|
259
|
+
const semanticChanges = []
|
|
260
|
+
const breakingChanges = []
|
|
261
|
+
const categories = []
|
|
262
|
+
const tags = []
|
|
195
263
|
|
|
196
264
|
// Basic analysis based on commit message
|
|
197
|
-
const message = commit.message.toLowerCase()
|
|
265
|
+
const message = commit.message.toLowerCase()
|
|
198
266
|
|
|
199
267
|
if (message.includes('breaking') || message.includes('!:')) {
|
|
200
|
-
breakingChanges.push('Breaking change detected in commit message')
|
|
201
|
-
categories.push('breaking')
|
|
202
|
-
tags.push('breaking')
|
|
268
|
+
breakingChanges.push('Breaking change detected in commit message')
|
|
269
|
+
categories.push('breaking')
|
|
270
|
+
tags.push('breaking')
|
|
203
271
|
}
|
|
204
272
|
|
|
205
273
|
if (message.startsWith('feat')) {
|
|
206
|
-
categories.push('feature')
|
|
207
|
-
tags.push('feature')
|
|
274
|
+
categories.push('feature')
|
|
275
|
+
tags.push('feature')
|
|
208
276
|
} else if (message.startsWith('fix')) {
|
|
209
|
-
categories.push('fix')
|
|
210
|
-
tags.push('bugfix')
|
|
277
|
+
categories.push('fix')
|
|
278
|
+
tags.push('bugfix')
|
|
211
279
|
} else if (message.startsWith('docs')) {
|
|
212
|
-
categories.push('docs')
|
|
213
|
-
tags.push('documentation')
|
|
280
|
+
categories.push('docs')
|
|
281
|
+
tags.push('documentation')
|
|
214
282
|
}
|
|
215
283
|
|
|
216
284
|
// Analyze files if available
|
|
217
285
|
if (commit.files && commit.files.length > 0) {
|
|
218
|
-
commit.files.forEach(file => {
|
|
219
|
-
const semantic = analyzeSemanticChanges('', file.path)
|
|
286
|
+
commit.files.forEach((file) => {
|
|
287
|
+
const semantic = analyzeSemanticChanges('', file.path)
|
|
220
288
|
if (semantic.frameworks) {
|
|
221
|
-
semanticChanges.push(...semantic.frameworks)
|
|
289
|
+
semanticChanges.push(...semantic.frameworks)
|
|
222
290
|
}
|
|
223
|
-
})
|
|
291
|
+
})
|
|
224
292
|
}
|
|
225
293
|
|
|
226
294
|
// Determine importance
|
|
227
|
-
let importance = 'medium'
|
|
295
|
+
let importance = 'medium'
|
|
228
296
|
if (breakingChanges.length > 0 || commit.files?.length > 10) {
|
|
229
|
-
importance = 'high'
|
|
297
|
+
importance = 'high'
|
|
230
298
|
} else if (categories.includes('docs') || commit.files?.length < 3) {
|
|
231
|
-
importance = 'low'
|
|
299
|
+
importance = 'low'
|
|
232
300
|
}
|
|
233
301
|
|
|
234
302
|
return {
|
|
@@ -236,474 +304,509 @@ export class ChangelogOrchestrator {
|
|
|
236
304
|
breakingChanges,
|
|
237
305
|
categories,
|
|
238
306
|
importance,
|
|
239
|
-
tags
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
307
|
+
tags,
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
}
|
|
243
311
|
}
|
|
244
312
|
|
|
245
313
|
async createPromptEngine() {
|
|
246
|
-
const { buildEnhancedPrompt } = await import('../../shared/utils/utils.js')
|
|
314
|
+
const { buildEnhancedPrompt } = await import('../../shared/utils/utils.js')
|
|
247
315
|
|
|
248
316
|
return {
|
|
249
317
|
systemPrompts: {
|
|
250
|
-
master:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
318
|
+
master:
|
|
319
|
+
'You are an expert software analyst specializing in code change analysis and changelog generation.',
|
|
320
|
+
standard: 'Provide clear, concise analysis focusing on the practical impact of changes.',
|
|
321
|
+
detailed:
|
|
322
|
+
'Provide comprehensive technical analysis with detailed explanations and implications.',
|
|
323
|
+
enterprise:
|
|
324
|
+
'Provide enterprise-grade analysis suitable for stakeholder communication and decision-making.',
|
|
325
|
+
changesAnalysis: 'You are an expert at analyzing code changes and their business impact.',
|
|
255
326
|
},
|
|
256
327
|
|
|
257
|
-
optimizeForProvider(prompt, providerName,
|
|
328
|
+
optimizeForProvider(prompt, providerName, _capabilities = {}) {
|
|
258
329
|
// Simple optimization - could be enhanced based on provider capabilities
|
|
259
330
|
if (providerName?.toLowerCase().includes('claude')) {
|
|
260
|
-
return `Please analyze this carefully and provide structured output:\n\n${prompt}
|
|
261
|
-
} else if (providerName?.toLowerCase().includes('gpt')) {
|
|
262
|
-
return `${prompt}\n\nPlease respond in JSON format as requested.`;
|
|
331
|
+
return `Please analyze this carefully and provide structured output:\n\n${prompt}`
|
|
263
332
|
}
|
|
264
|
-
|
|
333
|
+
if (providerName?.toLowerCase().includes('gpt')) {
|
|
334
|
+
return `${prompt}\n\nPlease respond in JSON format as requested.`
|
|
335
|
+
}
|
|
336
|
+
return prompt
|
|
265
337
|
},
|
|
266
338
|
|
|
267
|
-
buildRepositoryHealthPrompt(healthData,
|
|
268
|
-
return `Analyze the health of this repository based on the following data:\n\n${JSON.stringify(healthData, null, 2)}\n\nProvide assessment and recommendations
|
|
269
|
-
}
|
|
270
|
-
}
|
|
339
|
+
buildRepositoryHealthPrompt(healthData, _analysisMode) {
|
|
340
|
+
return `Analyze the health of this repository based on the following data:\n\n${JSON.stringify(healthData, null, 2)}\n\nProvide assessment and recommendations.`
|
|
341
|
+
},
|
|
342
|
+
}
|
|
271
343
|
}
|
|
272
344
|
|
|
273
345
|
async generateChangelog(version, since) {
|
|
274
346
|
try {
|
|
275
|
-
await this.ensureInitialized()
|
|
347
|
+
await this.ensureInitialized()
|
|
276
348
|
|
|
277
|
-
this.metrics.startTime = Date.now()
|
|
349
|
+
this.metrics.startTime = Date.now()
|
|
278
350
|
|
|
279
|
-
console.log(
|
|
351
|
+
console.log(`\n${colors.processingMessage('🚀 Starting changelog generation...')}`)
|
|
280
352
|
|
|
281
353
|
// Validate git repository
|
|
282
354
|
if (!this.gitManager.isGitRepo) {
|
|
283
|
-
throw new Error('Not a git repository')
|
|
355
|
+
throw new Error('Not a git repository')
|
|
284
356
|
}
|
|
285
357
|
|
|
286
358
|
// Generate changelog using the service
|
|
287
|
-
const result = await this.changelogService.generateChangelog(version, since)
|
|
359
|
+
const result = await this.changelogService.generateChangelog(version, since)
|
|
288
360
|
|
|
289
361
|
if (!result) {
|
|
290
|
-
console.log(colors.warningMessage('No changelog generated'))
|
|
291
|
-
return null
|
|
362
|
+
console.log(colors.warningMessage('No changelog generated'))
|
|
363
|
+
return null
|
|
292
364
|
}
|
|
293
365
|
|
|
294
366
|
// Update metrics
|
|
295
|
-
this.updateMetrics(result)
|
|
367
|
+
this.updateMetrics(result)
|
|
296
368
|
|
|
297
369
|
// Display results
|
|
298
|
-
this.displayResults(result, version)
|
|
299
|
-
|
|
300
|
-
return result;
|
|
370
|
+
this.displayResults(result, version)
|
|
301
371
|
|
|
372
|
+
return result
|
|
302
373
|
} catch (error) {
|
|
303
|
-
this.metrics.errors
|
|
304
|
-
console.error(colors.errorMessage('Changelog generation failed:'), error.message)
|
|
305
|
-
throw error
|
|
374
|
+
this.metrics.errors++
|
|
375
|
+
console.error(colors.errorMessage('Changelog generation failed:'), error.message)
|
|
376
|
+
throw error
|
|
306
377
|
}
|
|
307
378
|
}
|
|
308
379
|
|
|
309
380
|
async analyzeRepository(options = {}) {
|
|
310
381
|
try {
|
|
311
|
-
await this.ensureInitialized()
|
|
382
|
+
await this.ensureInitialized()
|
|
312
383
|
|
|
313
|
-
console.log(colors.processingMessage('🔍 Starting repository analysis...'))
|
|
384
|
+
console.log(colors.processingMessage('🔍 Starting repository analysis...'))
|
|
314
385
|
|
|
315
|
-
const analysisType = options.type || 'changes'
|
|
316
|
-
const result = await this.analysisEngine.analyze(analysisType, options)
|
|
386
|
+
const analysisType = options.type || 'changes'
|
|
387
|
+
const result = await this.analysisEngine.analyze(analysisType, options)
|
|
317
388
|
|
|
318
|
-
this.displayAnalysisResults(result, analysisType)
|
|
319
|
-
|
|
320
|
-
return result;
|
|
389
|
+
this.displayAnalysisResults(result, analysisType)
|
|
321
390
|
|
|
391
|
+
return result
|
|
322
392
|
} catch (error) {
|
|
323
|
-
this.metrics.errors
|
|
324
|
-
console.error(colors.errorMessage('Repository analysis failed:'), error.message)
|
|
325
|
-
throw error
|
|
393
|
+
this.metrics.errors++
|
|
394
|
+
console.error(colors.errorMessage('Repository analysis failed:'), error.message)
|
|
395
|
+
throw error
|
|
326
396
|
}
|
|
327
397
|
}
|
|
328
398
|
|
|
329
399
|
async runInteractive() {
|
|
330
|
-
await this.ensureInitialized()
|
|
400
|
+
await this.ensureInitialized()
|
|
331
401
|
|
|
332
|
-
const { runInteractiveMode, selectSpecificCommits } = await import(
|
|
333
|
-
|
|
402
|
+
const { runInteractiveMode, selectSpecificCommits } = await import(
|
|
403
|
+
'../../shared/utils/utils.js'
|
|
404
|
+
)
|
|
405
|
+
const { confirm } = await this._getCachedImport('@clack/prompts')
|
|
334
406
|
|
|
335
|
-
console.log(colors.processingMessage('🎮 Starting interactive mode...'))
|
|
407
|
+
console.log(colors.processingMessage('🎮 Starting interactive mode...'))
|
|
336
408
|
|
|
337
|
-
let continueSession = true
|
|
409
|
+
let continueSession = true
|
|
338
410
|
|
|
339
411
|
while (continueSession) {
|
|
340
412
|
try {
|
|
341
|
-
const result = await runInteractiveMode()
|
|
413
|
+
const result = await runInteractiveMode()
|
|
342
414
|
|
|
343
415
|
if (result.action === 'exit') {
|
|
344
|
-
console.log(colors.successMessage('👋 Goodbye!'))
|
|
345
|
-
break
|
|
416
|
+
console.log(colors.successMessage('👋 Goodbye!'))
|
|
417
|
+
break
|
|
346
418
|
}
|
|
347
419
|
|
|
348
|
-
await this.handleInteractiveAction(result.action)
|
|
420
|
+
await this.handleInteractiveAction(result.action)
|
|
349
421
|
|
|
350
422
|
// Ask if user wants to continue
|
|
351
423
|
const continueChoice = await confirm({
|
|
352
424
|
message: 'Would you like to perform another action?',
|
|
353
|
-
initialValue: true
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
continueSession = continueChoice;
|
|
425
|
+
initialValue: true,
|
|
426
|
+
})
|
|
357
427
|
|
|
428
|
+
continueSession = continueChoice
|
|
358
429
|
} catch (error) {
|
|
359
|
-
console.error(colors.errorMessage(`Interactive mode error: ${error.message}`))
|
|
430
|
+
console.error(colors.errorMessage(`Interactive mode error: ${error.message}`))
|
|
360
431
|
|
|
361
432
|
const retryChoice = await confirm({
|
|
362
433
|
message: 'Would you like to try again?',
|
|
363
|
-
initialValue: true
|
|
364
|
-
})
|
|
434
|
+
initialValue: true,
|
|
435
|
+
})
|
|
365
436
|
|
|
366
|
-
continueSession = retryChoice
|
|
437
|
+
continueSession = retryChoice
|
|
367
438
|
}
|
|
368
439
|
}
|
|
369
440
|
|
|
370
|
-
return { interactive: true, status: 'completed' }
|
|
441
|
+
return { interactive: true, status: 'completed' }
|
|
371
442
|
}
|
|
372
443
|
|
|
373
444
|
async handleInteractiveAction(action) {
|
|
374
|
-
|
|
375
445
|
switch (action) {
|
|
376
446
|
case 'changelog-recent':
|
|
377
|
-
await this.handleRecentChangelogGeneration()
|
|
378
|
-
break
|
|
447
|
+
await this.handleRecentChangelogGeneration()
|
|
448
|
+
break
|
|
379
449
|
|
|
380
450
|
case 'changelog-specific':
|
|
381
|
-
await this.handleSpecificChangelogGeneration()
|
|
382
|
-
break
|
|
451
|
+
await this.handleSpecificChangelogGeneration()
|
|
452
|
+
break
|
|
383
453
|
|
|
384
454
|
case 'analyze-workdir':
|
|
385
|
-
await this.generateChangelogFromChanges()
|
|
386
|
-
break
|
|
455
|
+
await this.generateChangelogFromChanges()
|
|
456
|
+
break
|
|
387
457
|
|
|
388
458
|
case 'analyze-repo':
|
|
389
|
-
await this.analyzeRepository({ type: 'comprehensive' })
|
|
390
|
-
break
|
|
459
|
+
await this.analyzeRepository({ type: 'comprehensive' })
|
|
460
|
+
break
|
|
391
461
|
|
|
392
462
|
case 'commit-message':
|
|
393
|
-
await this.handleCommitMessageGeneration()
|
|
394
|
-
break
|
|
463
|
+
await this.handleCommitMessageGeneration()
|
|
464
|
+
break
|
|
395
465
|
|
|
396
466
|
case 'configure-providers':
|
|
397
|
-
await this.handleProviderConfiguration()
|
|
398
|
-
break
|
|
467
|
+
await this.handleProviderConfiguration()
|
|
468
|
+
break
|
|
399
469
|
|
|
400
470
|
case 'validate-config':
|
|
401
|
-
await this.validateConfiguration()
|
|
402
|
-
break
|
|
471
|
+
await this.validateConfiguration()
|
|
472
|
+
break
|
|
403
473
|
|
|
404
474
|
default:
|
|
405
|
-
console.log(colors.warningMessage(`Unknown action: ${action}`))
|
|
475
|
+
console.log(colors.warningMessage(`Unknown action: ${action}`))
|
|
406
476
|
}
|
|
407
477
|
}
|
|
408
478
|
|
|
409
479
|
async handleRecentChangelogGeneration() {
|
|
410
|
-
const { text } = await import('@clack/prompts')
|
|
480
|
+
const { text } = await import('@clack/prompts')
|
|
411
481
|
|
|
412
482
|
const commitCountInput = await text({
|
|
413
483
|
message: 'How many recent commits to include?',
|
|
414
484
|
placeholder: '10',
|
|
415
485
|
validate: (value) => {
|
|
416
|
-
const num = parseInt(value)
|
|
417
|
-
if (isNaN(num) || num <= 0 || num > 100) {
|
|
418
|
-
return 'Please enter a number between 1 and 100'
|
|
486
|
+
const num = Number.parseInt(value, 10)
|
|
487
|
+
if (Number.isNaN(num) || num <= 0 || num > 100) {
|
|
488
|
+
return 'Please enter a number between 1 and 100'
|
|
419
489
|
}
|
|
420
|
-
}
|
|
421
|
-
})
|
|
490
|
+
},
|
|
491
|
+
})
|
|
422
492
|
|
|
423
|
-
const commitCount = parseInt(commitCountInput) || 10
|
|
493
|
+
const commitCount = Number.parseInt(commitCountInput, 10) || 10
|
|
424
494
|
|
|
425
|
-
console.log(
|
|
495
|
+
console.log(
|
|
496
|
+
colors.processingMessage(`📝 Generating changelog for ${commitCount} recent commits...`)
|
|
497
|
+
)
|
|
426
498
|
|
|
427
499
|
const result = await this.generateChangelog({
|
|
428
500
|
version: `Recent-${commitCount}-commits`,
|
|
429
|
-
maxCommits: commitCount
|
|
430
|
-
})
|
|
501
|
+
maxCommits: commitCount,
|
|
502
|
+
})
|
|
431
503
|
|
|
432
504
|
if (result?.changelog) {
|
|
433
|
-
console.log(colors.successMessage('✅ Changelog generated successfully!'))
|
|
505
|
+
console.log(colors.successMessage('✅ Changelog generated successfully!'))
|
|
434
506
|
}
|
|
435
507
|
}
|
|
436
508
|
|
|
437
509
|
async handleSpecificChangelogGeneration() {
|
|
438
|
-
const { selectSpecificCommits } = await import('../../shared/utils/utils.js')
|
|
510
|
+
const { selectSpecificCommits } = await import('../../shared/utils/utils.js')
|
|
439
511
|
|
|
440
|
-
console.log(colors.infoMessage('📋 Select specific commits for changelog generation:'))
|
|
512
|
+
console.log(colors.infoMessage('📋 Select specific commits for changelog generation:'))
|
|
441
513
|
|
|
442
|
-
const selectedCommits = await selectSpecificCommits(30)
|
|
514
|
+
const selectedCommits = await selectSpecificCommits(30)
|
|
443
515
|
|
|
444
516
|
if (selectedCommits.length === 0) {
|
|
445
|
-
console.log(colors.warningMessage('No commits selected.'))
|
|
446
|
-
return
|
|
517
|
+
console.log(colors.warningMessage('No commits selected.'))
|
|
518
|
+
return
|
|
447
519
|
}
|
|
448
520
|
|
|
449
|
-
console.log(
|
|
521
|
+
console.log(
|
|
522
|
+
colors.processingMessage(
|
|
523
|
+
`📝 Generating changelog for ${selectedCommits.length} selected commits...`
|
|
524
|
+
)
|
|
525
|
+
)
|
|
450
526
|
|
|
451
|
-
const result = await this.generateChangelogFromCommits(selectedCommits)
|
|
527
|
+
const result = await this.generateChangelogFromCommits(selectedCommits)
|
|
452
528
|
|
|
453
529
|
if (result?.changelog) {
|
|
454
|
-
console.log(colors.successMessage('✅ Changelog generated successfully!'))
|
|
530
|
+
console.log(colors.successMessage('✅ Changelog generated successfully!'))
|
|
455
531
|
}
|
|
456
532
|
}
|
|
457
533
|
|
|
458
534
|
async handleCommitMessageGeneration() {
|
|
459
|
-
console.log(
|
|
535
|
+
console.log(
|
|
536
|
+
colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...')
|
|
537
|
+
)
|
|
460
538
|
|
|
461
539
|
// Use shared utility for getting working directory changes
|
|
462
|
-
const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js')
|
|
463
|
-
const changes = getWorkingDirectoryChanges()
|
|
540
|
+
const { getWorkingDirectoryChanges } = await import('../../shared/utils/utils.js')
|
|
541
|
+
const changes = getWorkingDirectoryChanges()
|
|
464
542
|
|
|
465
543
|
if (!changes || changes.length === 0) {
|
|
466
|
-
console.log(colors.warningMessage('No uncommitted changes found.'))
|
|
467
|
-
return
|
|
544
|
+
console.log(colors.warningMessage('No uncommitted changes found.'))
|
|
545
|
+
return
|
|
468
546
|
}
|
|
469
547
|
|
|
470
|
-
const analysis = await this.interactiveService.generateCommitSuggestion()
|
|
548
|
+
const analysis = await this.interactiveService.generateCommitSuggestion()
|
|
471
549
|
|
|
472
550
|
if (analysis.success && analysis.suggestions.length > 0) {
|
|
473
|
-
const { select } = await
|
|
551
|
+
const { select } = await this._getCachedImport('@clack/prompts')
|
|
474
552
|
|
|
475
553
|
const choices = [
|
|
476
554
|
...analysis.suggestions.map((msg, index) => ({
|
|
477
555
|
value: msg,
|
|
478
|
-
label: `${index + 1}. ${msg}
|
|
556
|
+
label: `${index + 1}. ${msg}`,
|
|
479
557
|
})),
|
|
480
558
|
{
|
|
481
559
|
value: 'CUSTOM',
|
|
482
|
-
label: '✏️ Write custom message'
|
|
483
|
-
}
|
|
484
|
-
]
|
|
560
|
+
label: '✏️ Write custom message',
|
|
561
|
+
},
|
|
562
|
+
]
|
|
485
563
|
|
|
486
564
|
const selectedMessage = await select({
|
|
487
565
|
message: 'Choose a commit message:',
|
|
488
|
-
options: choices
|
|
489
|
-
})
|
|
566
|
+
options: choices,
|
|
567
|
+
})
|
|
490
568
|
|
|
491
569
|
if (selectedMessage === 'CUSTOM') {
|
|
492
|
-
const { text } = await
|
|
570
|
+
const { text } = await this._getCachedImport('@clack/prompts')
|
|
493
571
|
|
|
494
572
|
const customMessage = await text({
|
|
495
573
|
message: 'Enter your commit message:',
|
|
496
574
|
validate: (input) => {
|
|
497
575
|
if (!input || input.trim().length === 0) {
|
|
498
|
-
return 'Commit message cannot be empty'
|
|
576
|
+
return 'Commit message cannot be empty'
|
|
499
577
|
}
|
|
500
|
-
}
|
|
501
|
-
})
|
|
578
|
+
},
|
|
579
|
+
})
|
|
502
580
|
|
|
503
|
-
console.log(colors.successMessage(`📝 Custom message: ${customMessage}`))
|
|
581
|
+
console.log(colors.successMessage(`📝 Custom message: ${customMessage}`))
|
|
504
582
|
} else {
|
|
505
|
-
console.log(colors.successMessage(`📝 Selected: ${selectedMessage}`))
|
|
583
|
+
console.log(colors.successMessage(`📝 Selected: ${selectedMessage}`))
|
|
506
584
|
}
|
|
507
585
|
} else {
|
|
508
|
-
console.log(colors.warningMessage('Could not generate commit message suggestions.'))
|
|
586
|
+
console.log(colors.warningMessage('Could not generate commit message suggestions.'))
|
|
509
587
|
}
|
|
510
588
|
}
|
|
511
589
|
|
|
512
590
|
async handleProviderConfiguration() {
|
|
513
|
-
const { select } = await
|
|
591
|
+
const { select } = await this._getCachedImport('@clack/prompts')
|
|
514
592
|
|
|
515
|
-
const availableProviders = this.providerManager.getAllProviders()
|
|
593
|
+
const availableProviders = this.providerManager.getAllProviders()
|
|
516
594
|
|
|
517
|
-
const choices = availableProviders.map(p => ({
|
|
595
|
+
const choices = availableProviders.map((p) => ({
|
|
518
596
|
value: p.name,
|
|
519
|
-
label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}
|
|
520
|
-
}))
|
|
597
|
+
label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`,
|
|
598
|
+
}))
|
|
521
599
|
|
|
522
600
|
const selectedProvider = await select({
|
|
523
601
|
message: 'Select provider to configure:',
|
|
524
|
-
options: choices
|
|
525
|
-
})
|
|
602
|
+
options: choices,
|
|
603
|
+
})
|
|
526
604
|
|
|
527
|
-
console.log(colors.infoMessage(`🔧 Configuring ${selectedProvider}...`))
|
|
528
|
-
console.log(
|
|
529
|
-
|
|
605
|
+
console.log(colors.infoMessage(`🔧 Configuring ${selectedProvider}...`))
|
|
606
|
+
console.log(
|
|
607
|
+
colors.infoMessage('Please edit your .env.local file to add the required API keys.')
|
|
608
|
+
)
|
|
609
|
+
console.log(colors.highlight(`Example for ${selectedProvider.toUpperCase()}:`))
|
|
530
610
|
|
|
531
611
|
switch (selectedProvider) {
|
|
532
612
|
case 'openai':
|
|
533
|
-
console.log(colors.code('OPENAI_API_KEY=your_api_key_here'))
|
|
534
|
-
break
|
|
613
|
+
console.log(colors.code('OPENAI_API_KEY=your_api_key_here'))
|
|
614
|
+
break
|
|
535
615
|
case 'anthropic':
|
|
536
|
-
console.log(colors.code('ANTHROPIC_API_KEY=your_api_key_here'))
|
|
537
|
-
break
|
|
616
|
+
console.log(colors.code('ANTHROPIC_API_KEY=your_api_key_here'))
|
|
617
|
+
break
|
|
538
618
|
case 'azure':
|
|
539
|
-
console.log(colors.code('AZURE_OPENAI_API_KEY=your_api_key_here'))
|
|
540
|
-
console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_endpoint_here'))
|
|
541
|
-
break
|
|
619
|
+
console.log(colors.code('AZURE_OPENAI_API_KEY=your_api_key_here'))
|
|
620
|
+
console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_endpoint_here'))
|
|
621
|
+
break
|
|
542
622
|
case 'google':
|
|
543
|
-
console.log(colors.code('GOOGLE_API_KEY=your_api_key_here'))
|
|
544
|
-
break
|
|
623
|
+
console.log(colors.code('GOOGLE_API_KEY=your_api_key_here'))
|
|
624
|
+
break
|
|
545
625
|
default:
|
|
546
|
-
console.log(colors.code(`${selectedProvider.toUpperCase()}_API_KEY=your_api_key_here`))
|
|
626
|
+
console.log(colors.code(`${selectedProvider.toUpperCase()}_API_KEY=your_api_key_here`))
|
|
547
627
|
}
|
|
548
628
|
}
|
|
549
629
|
|
|
550
630
|
async generateChangelogFromChanges(version) {
|
|
551
631
|
try {
|
|
552
|
-
await this.ensureInitialized()
|
|
632
|
+
await this.ensureInitialized()
|
|
553
633
|
|
|
554
|
-
console.log(
|
|
634
|
+
console.log(
|
|
635
|
+
colors.processingMessage('📝 Generating changelog from working directory changes...')
|
|
636
|
+
)
|
|
555
637
|
|
|
556
|
-
const result = await this.changelogService.generateChangelogFromChanges(version)
|
|
638
|
+
const result = await this.changelogService.generateChangelogFromChanges(version)
|
|
557
639
|
|
|
558
640
|
if (result) {
|
|
559
|
-
console.log(colors.successMessage('✅ Working directory changelog generated'))
|
|
560
|
-
console.log(result.changelog)
|
|
641
|
+
console.log(colors.successMessage('✅ Working directory changelog generated'))
|
|
642
|
+
console.log(result.changelog)
|
|
561
643
|
}
|
|
562
644
|
|
|
563
|
-
return result
|
|
564
|
-
|
|
645
|
+
return result
|
|
565
646
|
} catch (error) {
|
|
566
|
-
this.metrics.errors
|
|
567
|
-
console.error(
|
|
568
|
-
|
|
647
|
+
this.metrics.errors++
|
|
648
|
+
console.error(
|
|
649
|
+
colors.errorMessage('Working directory changelog generation failed:'),
|
|
650
|
+
error.message
|
|
651
|
+
)
|
|
652
|
+
throw error
|
|
569
653
|
}
|
|
570
654
|
}
|
|
571
655
|
|
|
572
656
|
updateMetrics(result) {
|
|
573
657
|
if (result.analyzedCommits) {
|
|
574
|
-
this.metrics.commitsProcessed += result.analyzedCommits.length
|
|
658
|
+
this.metrics.commitsProcessed += result.analyzedCommits.length
|
|
575
659
|
}
|
|
576
660
|
|
|
577
661
|
// Get metrics from AI service
|
|
578
|
-
const aiMetrics = this.aiAnalysisService.getMetrics()
|
|
579
|
-
this.metrics.apiCalls += aiMetrics.apiCalls
|
|
580
|
-
this.metrics.totalTokens += aiMetrics.totalTokens
|
|
581
|
-
this.metrics.ruleBasedFallbacks += aiMetrics.ruleBasedFallbacks
|
|
662
|
+
const aiMetrics = this.aiAnalysisService.getMetrics()
|
|
663
|
+
this.metrics.apiCalls += aiMetrics.apiCalls
|
|
664
|
+
this.metrics.totalTokens += aiMetrics.totalTokens
|
|
665
|
+
this.metrics.ruleBasedFallbacks += aiMetrics.ruleBasedFallbacks
|
|
582
666
|
}
|
|
583
667
|
|
|
584
|
-
displayResults(result,
|
|
585
|
-
const { changelog, insights, analyzedCommits } = result
|
|
668
|
+
displayResults(result, _version) {
|
|
669
|
+
const { changelog, insights, analyzedCommits } = result
|
|
586
670
|
|
|
587
|
-
console.log(
|
|
671
|
+
console.log(`\n${colors.successMessage('✅ Changelog Generation Complete')}`)
|
|
588
672
|
|
|
589
673
|
if (insights) {
|
|
590
674
|
// Create a clean insights summary
|
|
591
675
|
const insightLines = [
|
|
592
676
|
`${colors.label('Total commits')}: ${colors.number(insights.totalCommits)}`,
|
|
593
677
|
`${colors.label('Complexity')}: ${this.getComplexityColor(insights.complexity)(insights.complexity)}`,
|
|
594
|
-
`${colors.label('Risk level')}: ${this.getRiskColor(insights.riskLevel)(insights.riskLevel)}
|
|
595
|
-
]
|
|
678
|
+
`${colors.label('Risk level')}: ${this.getRiskColor(insights.riskLevel)(insights.riskLevel)}`,
|
|
679
|
+
]
|
|
596
680
|
|
|
597
681
|
if (insights.breaking) {
|
|
598
|
-
insightLines.push('')
|
|
599
|
-
insightLines.push(colors.warningMessage('⚠️ Contains breaking changes'))
|
|
682
|
+
insightLines.push('')
|
|
683
|
+
insightLines.push(colors.warningMessage('⚠️ Contains breaking changes'))
|
|
600
684
|
}
|
|
601
685
|
|
|
602
686
|
if (Object.keys(insights.commitTypes).length > 0) {
|
|
603
|
-
insightLines.push('')
|
|
604
|
-
insightLines.push(colors.dim('Commit types:'))
|
|
687
|
+
insightLines.push('')
|
|
688
|
+
insightLines.push(colors.dim('Commit types:'))
|
|
605
689
|
Object.entries(insights.commitTypes).forEach(([type, count]) => {
|
|
606
|
-
insightLines.push(` ${colors.commitType(type)}: ${colors.number(count)}`)
|
|
607
|
-
})
|
|
690
|
+
insightLines.push(` ${colors.commitType(type)}: ${colors.number(count)}`)
|
|
691
|
+
})
|
|
608
692
|
}
|
|
609
693
|
|
|
610
|
-
console.log(colors.box('📊 Release Insights', insightLines.join('\n')))
|
|
694
|
+
console.log(colors.box('📊 Release Insights', insightLines.join('\n')))
|
|
611
695
|
}
|
|
612
696
|
|
|
613
697
|
// Don't show changelog content in terminal - it's saved to file
|
|
614
698
|
|
|
615
|
-
this.displayMetrics()
|
|
699
|
+
this.displayMetrics()
|
|
616
700
|
}
|
|
617
701
|
|
|
618
702
|
getComplexityColor(complexity) {
|
|
619
|
-
const level = complexity?.toLowerCase()
|
|
703
|
+
const level = complexity?.toLowerCase()
|
|
620
704
|
switch (level) {
|
|
621
|
-
case 'low':
|
|
622
|
-
|
|
623
|
-
case '
|
|
624
|
-
|
|
705
|
+
case 'low':
|
|
706
|
+
return colors.success
|
|
707
|
+
case 'medium':
|
|
708
|
+
return colors.warning
|
|
709
|
+
case 'high':
|
|
710
|
+
return colors.error
|
|
711
|
+
default:
|
|
712
|
+
return colors.highlight
|
|
625
713
|
}
|
|
626
714
|
}
|
|
627
715
|
|
|
628
716
|
getRiskColor(risk) {
|
|
629
|
-
const level = risk?.toLowerCase()
|
|
717
|
+
const level = risk?.toLowerCase()
|
|
630
718
|
switch (level) {
|
|
631
|
-
case 'low':
|
|
632
|
-
|
|
633
|
-
case '
|
|
634
|
-
|
|
635
|
-
|
|
719
|
+
case 'low':
|
|
720
|
+
return colors.riskLow
|
|
721
|
+
case 'medium':
|
|
722
|
+
return colors.riskMedium
|
|
723
|
+
case 'high':
|
|
724
|
+
return colors.riskHigh
|
|
725
|
+
case 'critical':
|
|
726
|
+
return colors.riskCritical
|
|
727
|
+
default:
|
|
728
|
+
return colors.highlight
|
|
636
729
|
}
|
|
637
730
|
}
|
|
638
731
|
|
|
639
732
|
displayAnalysisResults(result, type) {
|
|
640
|
-
console.log(
|
|
641
|
-
|
|
733
|
+
console.log(
|
|
734
|
+
colors.successMessage(
|
|
735
|
+
`\n✅ ${type.charAt(0).toUpperCase() + type.slice(1)} Analysis Complete`
|
|
736
|
+
)
|
|
737
|
+
)
|
|
738
|
+
console.log(colors.separator())
|
|
642
739
|
|
|
643
740
|
if (result.summary) {
|
|
644
|
-
console.log(colors.sectionHeader('📋 Summary'))
|
|
645
|
-
console.log(result.summary)
|
|
646
|
-
console.log('')
|
|
741
|
+
console.log(colors.sectionHeader('📋 Summary'))
|
|
742
|
+
console.log(result.summary)
|
|
743
|
+
console.log('')
|
|
647
744
|
}
|
|
648
745
|
|
|
649
746
|
if (result.analysis) {
|
|
650
|
-
console.log(colors.sectionHeader('🔍 Analysis Details'))
|
|
747
|
+
console.log(colors.sectionHeader('🔍 Analysis Details'))
|
|
651
748
|
if (typeof result.analysis === 'object') {
|
|
652
749
|
Object.entries(result.analysis).forEach(([key, value]) => {
|
|
653
750
|
if (typeof value === 'object') {
|
|
654
|
-
console.log(`${key}: ${JSON.stringify(value, null, 2)}`)
|
|
751
|
+
console.log(`${key}: ${JSON.stringify(value, null, 2)}`)
|
|
655
752
|
} else {
|
|
656
|
-
console.log(`${key}: ${colors.highlight(value)}`)
|
|
753
|
+
console.log(`${key}: ${colors.highlight(value)}`)
|
|
657
754
|
}
|
|
658
|
-
})
|
|
755
|
+
})
|
|
659
756
|
} else {
|
|
660
|
-
console.log(result.analysis)
|
|
757
|
+
console.log(result.analysis)
|
|
661
758
|
}
|
|
662
759
|
}
|
|
663
760
|
|
|
664
|
-
this.displayMetrics()
|
|
761
|
+
this.displayMetrics()
|
|
665
762
|
}
|
|
666
763
|
|
|
667
764
|
displayMetrics() {
|
|
668
|
-
const duration = Date.now() - this.metrics.startTime
|
|
765
|
+
const duration = Date.now() - this.metrics.startTime
|
|
669
766
|
|
|
670
767
|
const metricLines = [
|
|
671
768
|
`${colors.label('Duration')}: ${colors.number(this.formatDuration(duration))}`,
|
|
672
769
|
`${colors.label('Commits processed')}: ${colors.number(this.metrics.commitsProcessed)}`,
|
|
673
770
|
`${colors.label('API calls')}: ${colors.number(this.metrics.apiCalls)}`,
|
|
674
|
-
`${colors.label('Total tokens')}: ${colors.number(this.metrics.totalTokens.toLocaleString())}
|
|
675
|
-
]
|
|
771
|
+
`${colors.label('Total tokens')}: ${colors.number(this.metrics.totalTokens.toLocaleString())}`,
|
|
772
|
+
]
|
|
676
773
|
|
|
677
774
|
if (this.metrics.ruleBasedFallbacks > 0) {
|
|
678
|
-
metricLines.push('')
|
|
679
|
-
metricLines.push(
|
|
775
|
+
metricLines.push('')
|
|
776
|
+
metricLines.push(
|
|
777
|
+
colors.warning(`⚠️ Rule-based fallbacks: ${this.metrics.ruleBasedFallbacks}`)
|
|
778
|
+
)
|
|
680
779
|
}
|
|
681
780
|
|
|
682
781
|
if (this.metrics.errors > 0) {
|
|
683
|
-
metricLines.push('')
|
|
684
|
-
metricLines.push(colors.error(`❌ Errors: ${this.metrics.errors}`))
|
|
782
|
+
metricLines.push('')
|
|
783
|
+
metricLines.push(colors.error(`❌ Errors: ${this.metrics.errors}`))
|
|
685
784
|
}
|
|
686
785
|
|
|
687
|
-
console.log(colors.box('📈 Performance Metrics', metricLines.join('\n')))
|
|
786
|
+
console.log(colors.box('📈 Performance Metrics', metricLines.join('\n')))
|
|
688
787
|
}
|
|
689
788
|
|
|
690
789
|
formatDuration(ms) {
|
|
691
|
-
if (ms < 1000)
|
|
692
|
-
|
|
693
|
-
|
|
790
|
+
if (ms < 1000) {
|
|
791
|
+
return `${ms}ms`
|
|
792
|
+
}
|
|
793
|
+
if (ms < 60000) {
|
|
794
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
795
|
+
}
|
|
796
|
+
return `${Math.floor(ms / 60000)}m ${Math.floor((ms % 60000) / 1000)}s`
|
|
694
797
|
}
|
|
695
798
|
|
|
696
799
|
// Configuration methods
|
|
697
800
|
setAnalysisMode(mode) {
|
|
698
|
-
this.analysisMode = mode
|
|
801
|
+
this.analysisMode = mode
|
|
699
802
|
if (this.aiAnalysisService) {
|
|
700
|
-
this.aiAnalysisService.analysisMode = mode
|
|
803
|
+
this.aiAnalysisService.analysisMode = mode
|
|
701
804
|
}
|
|
702
805
|
}
|
|
703
806
|
|
|
704
807
|
setModelOverride(model) {
|
|
705
808
|
if (this.aiAnalysisService) {
|
|
706
|
-
this.aiAnalysisService.setModelOverride(model)
|
|
809
|
+
this.aiAnalysisService.setModelOverride(model)
|
|
707
810
|
}
|
|
708
811
|
}
|
|
709
812
|
|
|
@@ -711,8 +814,8 @@ export class ChangelogOrchestrator {
|
|
|
711
814
|
getMetrics() {
|
|
712
815
|
return {
|
|
713
816
|
...this.metrics,
|
|
714
|
-
aiMetrics: this.aiAnalysisService?.getMetrics() || {}
|
|
715
|
-
}
|
|
817
|
+
aiMetrics: this.aiAnalysisService?.getMetrics() || {},
|
|
818
|
+
}
|
|
716
819
|
}
|
|
717
820
|
|
|
718
821
|
resetMetrics() {
|
|
@@ -724,285 +827,445 @@ export class ChangelogOrchestrator {
|
|
|
724
827
|
batchesProcessed: 0,
|
|
725
828
|
totalTokens: 0,
|
|
726
829
|
ruleBasedFallbacks: 0,
|
|
727
|
-
cacheHits: 0
|
|
728
|
-
}
|
|
830
|
+
cacheHits: 0,
|
|
831
|
+
}
|
|
729
832
|
|
|
730
833
|
if (this.aiAnalysisService) {
|
|
731
|
-
this.aiAnalysisService.resetMetrics()
|
|
834
|
+
this.aiAnalysisService.resetMetrics()
|
|
732
835
|
}
|
|
733
836
|
}
|
|
734
837
|
|
|
735
838
|
// Interactive commit workflow
|
|
736
839
|
async executeCommitWorkflow(options = {}) {
|
|
737
|
-
await this.ensureInitialized()
|
|
840
|
+
await this.ensureInitialized()
|
|
738
841
|
|
|
739
|
-
console.log(colors.header('🚀 Interactive Commit Workflow'))
|
|
842
|
+
console.log(colors.header('🚀 Interactive Commit Workflow'))
|
|
740
843
|
|
|
741
844
|
try {
|
|
742
845
|
// Step 1: Show current git status
|
|
743
|
-
const statusResult = await this.stagingService.showGitStatus()
|
|
744
|
-
|
|
846
|
+
const statusResult = await this.stagingService.showGitStatus()
|
|
847
|
+
|
|
745
848
|
// Check if we have any changes at all
|
|
746
|
-
const totalChanges =
|
|
849
|
+
const totalChanges =
|
|
850
|
+
statusResult.staged.length + statusResult.unstaged.length + statusResult.untracked.length
|
|
747
851
|
if (totalChanges === 0) {
|
|
748
|
-
console.log(colors.infoMessage('✨ Working directory clean - no changes to commit.'))
|
|
749
|
-
return { success: false, message: 'No changes to commit' }
|
|
852
|
+
console.log(colors.infoMessage('✨ Working directory clean - no changes to commit.'))
|
|
853
|
+
return { success: false, message: 'No changes to commit' }
|
|
750
854
|
}
|
|
751
855
|
|
|
752
856
|
// Step 2: Handle staging based on options
|
|
753
|
-
let stagedFiles = []
|
|
754
|
-
|
|
857
|
+
let stagedFiles = []
|
|
858
|
+
|
|
755
859
|
if (options.stageAll) {
|
|
756
860
|
// Auto-stage all changes
|
|
757
|
-
console.log(colors.processingMessage('📦 Staging all changes...'))
|
|
758
|
-
await this.stagingService.stageAllChanges()
|
|
759
|
-
stagedFiles = [...statusResult.unstaged, ...statusResult.untracked]
|
|
760
|
-
} else if (
|
|
861
|
+
console.log(colors.processingMessage('📦 Staging all changes...'))
|
|
862
|
+
await this.stagingService.stageAllChanges()
|
|
863
|
+
stagedFiles = [...statusResult.unstaged, ...statusResult.untracked]
|
|
864
|
+
} else if (
|
|
865
|
+
options.interactive &&
|
|
866
|
+
(statusResult.unstaged.length > 0 || statusResult.untracked.length > 0)
|
|
867
|
+
) {
|
|
761
868
|
// Interactive staging
|
|
762
|
-
console.log(colors.infoMessage('\n🎯 Interactive staging mode'))
|
|
763
|
-
stagedFiles = await this.stagingService.selectFilesToStage()
|
|
764
|
-
|
|
869
|
+
console.log(colors.infoMessage('\n🎯 Interactive staging mode'))
|
|
870
|
+
stagedFiles = await this.stagingService.selectFilesToStage()
|
|
871
|
+
|
|
765
872
|
if (stagedFiles.length === 0 && statusResult.staged.length === 0) {
|
|
766
|
-
console.log(colors.warningMessage('No files staged for commit.'))
|
|
767
|
-
return { success: false, message: 'No files staged' }
|
|
873
|
+
console.log(colors.warningMessage('No files staged for commit.'))
|
|
874
|
+
return { success: false, message: 'No files staged' }
|
|
768
875
|
}
|
|
769
876
|
}
|
|
770
877
|
|
|
771
878
|
// Step 3: Verify we have staged changes
|
|
772
879
|
if (!this.stagingService.hasStagedChanges()) {
|
|
773
|
-
console.log(colors.warningMessage('No staged changes found for commit.'))
|
|
774
|
-
|
|
880
|
+
console.log(colors.warningMessage('No staged changes found for commit.'))
|
|
881
|
+
|
|
775
882
|
if (statusResult.unstaged.length > 0 || statusResult.untracked.length > 0) {
|
|
776
|
-
console.log(
|
|
883
|
+
console.log(
|
|
884
|
+
colors.infoMessage(
|
|
885
|
+
'💡 Use --all flag to stage all changes, or run interactively to select files.'
|
|
886
|
+
)
|
|
887
|
+
)
|
|
777
888
|
}
|
|
778
|
-
|
|
779
|
-
return { success: false, message: 'No staged changes' }
|
|
889
|
+
|
|
890
|
+
return { success: false, message: 'No staged changes' }
|
|
780
891
|
}
|
|
781
892
|
|
|
782
893
|
// Step 4: Get final staged changes for analysis
|
|
783
|
-
const finalStatus = this.stagingService.getDetailedStatus()
|
|
784
|
-
console.log(
|
|
894
|
+
const finalStatus = this.stagingService.getDetailedStatus()
|
|
895
|
+
console.log(
|
|
896
|
+
colors.successMessage(`\n✅ Ready to commit ${finalStatus.staged.length} staged file(s)`)
|
|
897
|
+
)
|
|
785
898
|
|
|
786
899
|
// Step 5: Branch Intelligence Analysis
|
|
787
|
-
const { analyzeBranchIntelligence, getSuggestedCommitType, generateCommitContextFromBranch } =
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const
|
|
791
|
-
const
|
|
900
|
+
const { analyzeBranchIntelligence, getSuggestedCommitType, generateCommitContextFromBranch } =
|
|
901
|
+
await import('../../shared/utils/utils.js')
|
|
902
|
+
|
|
903
|
+
const branchAnalysis = analyzeBranchIntelligence()
|
|
904
|
+
const suggestedType = getSuggestedCommitType(branchAnalysis, finalStatus.staged)
|
|
905
|
+
const _branchContext = generateCommitContextFromBranch(branchAnalysis, finalStatus.staged)
|
|
792
906
|
|
|
793
907
|
// Display branch intelligence findings
|
|
794
908
|
if (branchAnalysis.confidence > 20) {
|
|
795
|
-
console.log(colors.infoMessage('\n🧠 Branch Intelligence:'))
|
|
796
|
-
console.log(colors.secondary(` Branch: ${branchAnalysis.branch}`))
|
|
797
|
-
|
|
909
|
+
console.log(colors.infoMessage('\n🧠 Branch Intelligence:'))
|
|
910
|
+
console.log(colors.secondary(` Branch: ${branchAnalysis.branch}`))
|
|
911
|
+
|
|
798
912
|
if (branchAnalysis.type) {
|
|
799
|
-
console.log(
|
|
913
|
+
console.log(
|
|
914
|
+
colors.success(
|
|
915
|
+
` 🏷️ Detected type: ${branchAnalysis.type} (${branchAnalysis.confidence}% confidence)`
|
|
916
|
+
)
|
|
917
|
+
)
|
|
800
918
|
}
|
|
801
|
-
|
|
919
|
+
|
|
802
920
|
if (branchAnalysis.ticket) {
|
|
803
|
-
console.log(colors.highlight(` 🎫 Related ticket: ${branchAnalysis.ticket}`))
|
|
921
|
+
console.log(colors.highlight(` 🎫 Related ticket: ${branchAnalysis.ticket}`))
|
|
804
922
|
}
|
|
805
|
-
|
|
923
|
+
|
|
806
924
|
if (branchAnalysis.description) {
|
|
807
|
-
console.log(colors.dim(` 📝 Description: ${branchAnalysis.description}`))
|
|
925
|
+
console.log(colors.dim(` 📝 Description: ${branchAnalysis.description}`))
|
|
808
926
|
}
|
|
809
|
-
|
|
810
|
-
console.log(colors.dim(` 🔍 Patterns: ${branchAnalysis.patterns.join(', ')}`))
|
|
927
|
+
|
|
928
|
+
console.log(colors.dim(` 🔍 Patterns: ${branchAnalysis.patterns.join(', ')}`))
|
|
811
929
|
}
|
|
812
930
|
|
|
813
931
|
// Display suggested commit type
|
|
814
|
-
console.log(
|
|
932
|
+
console.log(
|
|
933
|
+
colors.infoMessage(
|
|
934
|
+
`\n💡 Suggested commit type: ${colors.highlight(suggestedType.type)} (from ${suggestedType.source}, ${suggestedType.confidence}% confidence)`
|
|
935
|
+
)
|
|
936
|
+
)
|
|
815
937
|
|
|
816
938
|
// Step 6: Generate enhanced commit message
|
|
817
|
-
let commitMessage
|
|
818
|
-
|
|
939
|
+
let commitMessage
|
|
940
|
+
|
|
819
941
|
if (options.customMessage) {
|
|
820
|
-
commitMessage = options.customMessage
|
|
942
|
+
commitMessage = options.customMessage
|
|
821
943
|
} else {
|
|
822
944
|
// Generate AI-enhanced commit message with branch context
|
|
823
|
-
|
|
945
|
+
console.log(colors.processingMessage('🤖 Generating AI-powered commit message...'))
|
|
946
|
+
|
|
947
|
+
try {
|
|
948
|
+
commitMessage = await this.generateAICommitMessage(
|
|
949
|
+
branchAnalysis,
|
|
950
|
+
suggestedType,
|
|
951
|
+
finalStatus.staged
|
|
952
|
+
)
|
|
953
|
+
} catch (_error) {
|
|
954
|
+
console.log(colors.warningMessage('⚠️ AI generation failed, using rule-based fallback'))
|
|
955
|
+
commitMessage = this.generateBranchAwareCommitMessage(
|
|
956
|
+
branchAnalysis,
|
|
957
|
+
suggestedType,
|
|
958
|
+
finalStatus.staged
|
|
959
|
+
)
|
|
960
|
+
}
|
|
824
961
|
}
|
|
825
962
|
|
|
826
963
|
// Step 7: Validate commit message
|
|
827
|
-
console.log(colors.processingMessage('\n🔍 Validating commit message...'))
|
|
828
|
-
|
|
964
|
+
console.log(colors.processingMessage('\n🔍 Validating commit message...'))
|
|
965
|
+
|
|
829
966
|
const validationContext = {
|
|
830
967
|
branchAnalysis,
|
|
831
968
|
stagedFiles: finalStatus.staged,
|
|
832
|
-
suggestedType
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const validationResult = await this.validationService.validateCommitMessage(
|
|
836
|
-
|
|
969
|
+
suggestedType,
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
const validationResult = await this.validationService.validateCommitMessage(
|
|
973
|
+
commitMessage,
|
|
974
|
+
validationContext
|
|
975
|
+
)
|
|
976
|
+
|
|
837
977
|
// Display validation results
|
|
838
|
-
const isValid = this.validationService.displayValidationResults(validationResult)
|
|
839
|
-
|
|
978
|
+
const isValid = this.validationService.displayValidationResults(validationResult)
|
|
979
|
+
|
|
840
980
|
// Step 8: Interactive improvement if needed
|
|
841
981
|
if (!isValid || validationResult.warnings.length > 0) {
|
|
842
|
-
const { confirm } = await
|
|
843
|
-
|
|
982
|
+
const { confirm } = await this._getCachedImport('@clack/prompts')
|
|
983
|
+
|
|
844
984
|
const shouldImprove = await confirm({
|
|
845
985
|
message: 'Would you like to improve the commit message?',
|
|
846
|
-
initialValue: !isValid
|
|
847
|
-
})
|
|
848
|
-
|
|
986
|
+
initialValue: !isValid,
|
|
987
|
+
})
|
|
988
|
+
|
|
849
989
|
if (shouldImprove) {
|
|
850
|
-
commitMessage = await this.handleCommitMessageImprovement(
|
|
990
|
+
commitMessage = await this.handleCommitMessageImprovement(
|
|
991
|
+
commitMessage,
|
|
992
|
+
validationResult,
|
|
993
|
+
validationContext
|
|
994
|
+
)
|
|
851
995
|
}
|
|
852
996
|
}
|
|
853
997
|
|
|
854
998
|
if (options.dryRun) {
|
|
855
|
-
console.log(colors.infoMessage('\n📋 Dry-run mode - showing what would be committed:'))
|
|
856
|
-
console.log(colors.highlight(`Commit message:\n${commitMessage}`))
|
|
999
|
+
console.log(colors.infoMessage('\n📋 Dry-run mode - showing what would be committed:'))
|
|
1000
|
+
console.log(colors.highlight(`Commit message:\n${commitMessage}`))
|
|
857
1001
|
return {
|
|
858
1002
|
success: true,
|
|
859
1003
|
commitMessage,
|
|
860
1004
|
stagedFiles: finalStatus.staged.length,
|
|
861
|
-
dryRun: true
|
|
862
|
-
}
|
|
1005
|
+
dryRun: true,
|
|
1006
|
+
}
|
|
863
1007
|
}
|
|
864
1008
|
|
|
865
|
-
// Step 6:
|
|
866
|
-
console.log(colors.
|
|
867
|
-
console.log(colors.infoMessage('Current capabilities:'));
|
|
868
|
-
console.log(colors.success(' • Git status analysis ✅'));
|
|
869
|
-
console.log(colors.success(' • Interactive staging ✅'));
|
|
870
|
-
console.log(colors.dim(' • AI commit message generation (coming soon) ⏳'));
|
|
871
|
-
console.log(colors.dim(' • Git commit execution (coming soon) ⏳'));
|
|
1009
|
+
// Step 6: Execute the actual commit
|
|
1010
|
+
console.log(colors.processingMessage('\n💾 Executing commit...'))
|
|
872
1011
|
|
|
873
|
-
|
|
874
|
-
|
|
1012
|
+
try {
|
|
1013
|
+
// Secure commit execution using git commit with stdin to avoid command injection
|
|
1014
|
+
const { execSync } = await this._getCachedImport('child_process')
|
|
1015
|
+
const commitHash = execSync('git commit --file=-', {
|
|
1016
|
+
input: commitMessage,
|
|
1017
|
+
encoding: 'utf8',
|
|
1018
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1019
|
+
}).trim()
|
|
1020
|
+
|
|
1021
|
+
console.log(colors.successMessage('✅ Commit executed successfully!'))
|
|
1022
|
+
console.log(colors.highlight(`📝 Commit hash: ${commitHash.trim()}`))
|
|
1023
|
+
console.log(colors.dim(`📝 Message: ${commitMessage.split('\n')[0]}`))
|
|
1024
|
+
|
|
1025
|
+
return {
|
|
1026
|
+
success: true,
|
|
1027
|
+
commitHash: commitHash.trim(),
|
|
1028
|
+
commitMessage,
|
|
1029
|
+
stagedFiles: finalStatus.staged.length,
|
|
1030
|
+
}
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error(colors.errorMessage(`❌ Failed to execute commit: ${error.message}`))
|
|
1033
|
+
console.log(colors.infoMessage('\n💡 Files remain staged. You can manually commit with:'))
|
|
1034
|
+
console.log(colors.code('git commit --file=- # (and paste your commit message)'))
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
success: false,
|
|
1038
|
+
error: error.message,
|
|
1039
|
+
commitMessage,
|
|
1040
|
+
stagedFiles: finalStatus.staged.length,
|
|
1041
|
+
filesStaged: true,
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
875
1044
|
|
|
876
1045
|
return {
|
|
877
1046
|
success: true,
|
|
878
1047
|
commitMessage,
|
|
879
1048
|
stagedFiles: finalStatus.staged.length,
|
|
880
|
-
phase: 'staging-complete'
|
|
881
|
-
}
|
|
1049
|
+
phase: 'staging-complete',
|
|
1050
|
+
}
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
console.error(colors.errorMessage(`Commit workflow error: ${error.message}`))
|
|
1053
|
+
throw error
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Generate AI-powered commit message using branch intelligence and file changes
|
|
1059
|
+
*/
|
|
1060
|
+
async generateAICommitMessage(branchAnalysis, suggestedType, stagedFiles) {
|
|
1061
|
+
if (!this.aiAnalysisService?.aiProvider?.isAvailable()) {
|
|
1062
|
+
throw new Error('AI provider not available')
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// Build context for AI
|
|
1066
|
+
const branchInfo =
|
|
1067
|
+
branchAnalysis?.confidence > 30
|
|
1068
|
+
? `Branch: ${branchAnalysis.branch} (${branchAnalysis.type || 'unknown'} type, ${branchAnalysis.confidence}% confidence)`
|
|
1069
|
+
: `Branch: ${branchAnalysis?.branch || 'unknown'}`
|
|
1070
|
+
|
|
1071
|
+
const fileChanges = stagedFiles.map((f) => `${f.status} ${f.path}`).join('\n')
|
|
1072
|
+
|
|
1073
|
+
// Build detailed context about the changes
|
|
1074
|
+
const changesSummary = {
|
|
1075
|
+
added: stagedFiles.filter((f) => f.status === 'A').length,
|
|
1076
|
+
modified: stagedFiles.filter((f) => f.status === 'M').length,
|
|
1077
|
+
deleted: stagedFiles.filter((f) => f.status === 'D').length,
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const prompt = `Generate a high-quality conventional commit message for the following staged changes:
|
|
1081
|
+
|
|
1082
|
+
${branchInfo}
|
|
1083
|
+
${branchAnalysis?.ticket ? `Related ticket: ${branchAnalysis.ticket}` : ''}
|
|
1084
|
+
|
|
1085
|
+
File changes (${stagedFiles.length} files):
|
|
1086
|
+
${fileChanges}
|
|
882
1087
|
|
|
1088
|
+
Change summary: ${changesSummary.added} added, ${changesSummary.modified} modified, ${changesSummary.deleted} deleted
|
|
1089
|
+
|
|
1090
|
+
Suggested commit type: ${suggestedType.type} (${suggestedType.confidence}% confidence from ${suggestedType.source})
|
|
1091
|
+
|
|
1092
|
+
Requirements:
|
|
1093
|
+
- Use conventional commit format: type(scope): description
|
|
1094
|
+
- Keep subject line under 72 characters
|
|
1095
|
+
- Use imperative mood ("add", not "added")
|
|
1096
|
+
- Be specific and descriptive about what changed
|
|
1097
|
+
- Include a body if the changes are complex
|
|
1098
|
+
- Use the suggested type unless the changes clearly indicate otherwise
|
|
1099
|
+
|
|
1100
|
+
Generate only the commit message, no explanation.`
|
|
1101
|
+
|
|
1102
|
+
try {
|
|
1103
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
1104
|
+
[
|
|
1105
|
+
{
|
|
1106
|
+
role: 'user',
|
|
1107
|
+
content: prompt,
|
|
1108
|
+
},
|
|
1109
|
+
],
|
|
1110
|
+
{
|
|
1111
|
+
max_tokens: 200,
|
|
1112
|
+
temperature: 0.3,
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
const aiCommitMessage = response.content.trim()
|
|
1117
|
+
|
|
1118
|
+
// Validate the AI response has the basic structure
|
|
1119
|
+
if (!aiCommitMessage.includes(':') || aiCommitMessage.length < 10) {
|
|
1120
|
+
throw new Error('AI generated invalid commit message format')
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
return aiCommitMessage
|
|
883
1124
|
} catch (error) {
|
|
884
|
-
|
|
885
|
-
throw error;
|
|
1125
|
+
throw new Error(`AI commit generation failed: ${error.message}`)
|
|
886
1126
|
}
|
|
887
1127
|
}
|
|
888
1128
|
|
|
889
1129
|
/**
|
|
890
|
-
* Generate branch-aware commit message using
|
|
1130
|
+
* Generate branch-aware commit message using rules and branch intelligence (fallback)
|
|
891
1131
|
*/
|
|
892
1132
|
generateBranchAwareCommitMessage(branchAnalysis, suggestedType, stagedFiles) {
|
|
893
|
-
const type = suggestedType.type
|
|
894
|
-
|
|
1133
|
+
const type = suggestedType.type
|
|
1134
|
+
|
|
895
1135
|
// Build description based on branch intelligence
|
|
896
|
-
let description = 'implement changes'
|
|
897
|
-
|
|
1136
|
+
let description = 'implement changes'
|
|
1137
|
+
|
|
898
1138
|
if (branchAnalysis.description && branchAnalysis.confidence > 40) {
|
|
899
|
-
description = branchAnalysis.description
|
|
1139
|
+
description = branchAnalysis.description
|
|
900
1140
|
} else {
|
|
901
1141
|
// Generate description from file changes
|
|
902
|
-
const fileTypes = new Set()
|
|
903
|
-
stagedFiles.forEach(file => {
|
|
904
|
-
const path = file.path
|
|
905
|
-
if (path.includes('service'))
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
if (path.includes('
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1142
|
+
const fileTypes = new Set()
|
|
1143
|
+
stagedFiles.forEach((file) => {
|
|
1144
|
+
const path = file.path
|
|
1145
|
+
if (path.includes('service')) {
|
|
1146
|
+
fileTypes.add('services')
|
|
1147
|
+
}
|
|
1148
|
+
if (path.includes('component')) {
|
|
1149
|
+
fileTypes.add('components')
|
|
1150
|
+
}
|
|
1151
|
+
if (path.includes('utils')) {
|
|
1152
|
+
fileTypes.add('utilities')
|
|
1153
|
+
}
|
|
1154
|
+
if (path.includes('config')) {
|
|
1155
|
+
fileTypes.add('configuration')
|
|
1156
|
+
}
|
|
1157
|
+
if (path.includes('test')) {
|
|
1158
|
+
fileTypes.add('tests')
|
|
1159
|
+
}
|
|
1160
|
+
if (path.includes('doc')) {
|
|
1161
|
+
fileTypes.add('documentation')
|
|
1162
|
+
}
|
|
1163
|
+
})
|
|
1164
|
+
|
|
913
1165
|
if (fileTypes.size > 0) {
|
|
914
|
-
description = `update ${Array.from(fileTypes).join(', ')}
|
|
1166
|
+
description = `update ${Array.from(fileTypes).join(', ')}`
|
|
915
1167
|
}
|
|
916
1168
|
}
|
|
917
1169
|
|
|
918
1170
|
// Build commit message
|
|
919
|
-
let commitMessage = `${type}: ${description}
|
|
920
|
-
|
|
1171
|
+
let commitMessage = `${type}: ${description}`
|
|
1172
|
+
|
|
921
1173
|
// Add body with details
|
|
922
|
-
const bodyLines = []
|
|
923
|
-
|
|
1174
|
+
const bodyLines = []
|
|
1175
|
+
|
|
924
1176
|
if (branchAnalysis.ticket) {
|
|
925
|
-
bodyLines.push(`Related to: ${branchAnalysis.ticket}`)
|
|
1177
|
+
bodyLines.push(`Related to: ${branchAnalysis.ticket}`)
|
|
926
1178
|
}
|
|
927
|
-
|
|
1179
|
+
|
|
928
1180
|
// Add file summary
|
|
929
|
-
const addedFiles = stagedFiles.filter(f => f.status === 'A').length
|
|
930
|
-
const modifiedFiles = stagedFiles.filter(f => f.status === 'M').length
|
|
931
|
-
const deletedFiles = stagedFiles.filter(f => f.status === 'D').length
|
|
932
|
-
|
|
933
|
-
const changes = []
|
|
934
|
-
if (addedFiles > 0)
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1181
|
+
const addedFiles = stagedFiles.filter((f) => f.status === 'A').length
|
|
1182
|
+
const modifiedFiles = stagedFiles.filter((f) => f.status === 'M').length
|
|
1183
|
+
const deletedFiles = stagedFiles.filter((f) => f.status === 'D').length
|
|
1184
|
+
|
|
1185
|
+
const changes = []
|
|
1186
|
+
if (addedFiles > 0) {
|
|
1187
|
+
changes.push(`${addedFiles} added`)
|
|
1188
|
+
}
|
|
1189
|
+
if (modifiedFiles > 0) {
|
|
1190
|
+
changes.push(`${modifiedFiles} modified`)
|
|
1191
|
+
}
|
|
1192
|
+
if (deletedFiles > 0) {
|
|
1193
|
+
changes.push(`${deletedFiles} deleted`)
|
|
1194
|
+
}
|
|
1195
|
+
|
|
938
1196
|
if (changes.length > 0) {
|
|
939
|
-
bodyLines.push(`Files: ${changes.join(', ')}`)
|
|
1197
|
+
bodyLines.push(`Files: ${changes.join(', ')}`)
|
|
940
1198
|
}
|
|
941
|
-
|
|
1199
|
+
|
|
942
1200
|
// Add branch context
|
|
943
1201
|
if (branchAnalysis.confidence > 60) {
|
|
944
|
-
bodyLines.push(`Branch: ${branchAnalysis.branch} (${branchAnalysis.confidence}% confidence)`)
|
|
1202
|
+
bodyLines.push(`Branch: ${branchAnalysis.branch} (${branchAnalysis.confidence}% confidence)`)
|
|
945
1203
|
}
|
|
946
1204
|
|
|
947
1205
|
if (bodyLines.length > 0) {
|
|
948
|
-
commitMessage +=
|
|
1206
|
+
commitMessage += `\n\n${bodyLines.join('\n')}`
|
|
949
1207
|
}
|
|
950
1208
|
|
|
951
|
-
return commitMessage
|
|
1209
|
+
return commitMessage
|
|
952
1210
|
}
|
|
953
1211
|
|
|
954
1212
|
/**
|
|
955
1213
|
* Handle interactive commit message improvement
|
|
956
1214
|
*/
|
|
957
1215
|
async handleCommitMessageImprovement(originalMessage, validationResult, context) {
|
|
958
|
-
const { select, text, confirm } = await import('@clack/prompts')
|
|
1216
|
+
const { select, text, confirm } = await import('@clack/prompts')
|
|
959
1217
|
|
|
960
|
-
console.log(colors.infoMessage('\n🔧 Commit Message Improvement'))
|
|
1218
|
+
console.log(colors.infoMessage('\n🔧 Commit Message Improvement'))
|
|
961
1219
|
|
|
962
1220
|
// Try automatic improvement first
|
|
963
|
-
const improvementResult = await this.validationService.improveCommitMessage(
|
|
964
|
-
|
|
1221
|
+
const improvementResult = await this.validationService.improveCommitMessage(
|
|
1222
|
+
originalMessage,
|
|
1223
|
+
context
|
|
1224
|
+
)
|
|
1225
|
+
|
|
965
1226
|
const options = [
|
|
966
1227
|
{
|
|
967
1228
|
value: 'auto',
|
|
968
1229
|
label: '🤖 Use automatically improved version',
|
|
969
|
-
hint: improvementResult.improved
|
|
1230
|
+
hint: improvementResult.improved
|
|
1231
|
+
? 'AI-suggested improvements applied'
|
|
1232
|
+
: 'Minor fixes applied',
|
|
970
1233
|
},
|
|
971
1234
|
{
|
|
972
1235
|
value: 'manual',
|
|
973
1236
|
label: '✏️ Edit manually',
|
|
974
|
-
hint: 'Customize the commit message yourself'
|
|
975
|
-
}
|
|
976
|
-
]
|
|
1237
|
+
hint: 'Customize the commit message yourself',
|
|
1238
|
+
},
|
|
1239
|
+
]
|
|
977
1240
|
|
|
978
1241
|
// Add AI suggestions if available
|
|
979
1242
|
if (this.aiAnalysisService.hasAI) {
|
|
980
1243
|
options.unshift({
|
|
981
1244
|
value: 'ai',
|
|
982
1245
|
label: '🧠 Generate AI suggestions',
|
|
983
|
-
hint: 'Get AI-powered commit message alternatives'
|
|
984
|
-
})
|
|
1246
|
+
hint: 'Get AI-powered commit message alternatives',
|
|
1247
|
+
})
|
|
985
1248
|
}
|
|
986
1249
|
|
|
987
1250
|
const choice = await select({
|
|
988
1251
|
message: 'How would you like to improve the commit message?',
|
|
989
|
-
options
|
|
990
|
-
})
|
|
1252
|
+
options,
|
|
1253
|
+
})
|
|
991
1254
|
|
|
992
1255
|
switch (choice) {
|
|
993
1256
|
case 'ai':
|
|
994
|
-
return await this.generateAICommitSuggestions(originalMessage, context, validationResult)
|
|
995
|
-
|
|
1257
|
+
return await this.generateAICommitSuggestions(originalMessage, context, validationResult)
|
|
1258
|
+
|
|
996
1259
|
case 'auto':
|
|
997
|
-
console.log(colors.successMessage(
|
|
998
|
-
console.log(colors.highlight(improvementResult.message))
|
|
999
|
-
return improvementResult.message
|
|
1000
|
-
|
|
1260
|
+
console.log(colors.successMessage('\n✅ Using improved message:'))
|
|
1261
|
+
console.log(colors.highlight(improvementResult.message))
|
|
1262
|
+
return improvementResult.message
|
|
1263
|
+
|
|
1001
1264
|
case 'manual':
|
|
1002
|
-
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1003
|
-
|
|
1265
|
+
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1266
|
+
|
|
1004
1267
|
default:
|
|
1005
|
-
return originalMessage
|
|
1268
|
+
return originalMessage
|
|
1006
1269
|
}
|
|
1007
1270
|
}
|
|
1008
1271
|
|
|
@@ -1010,22 +1273,24 @@ export class ChangelogOrchestrator {
|
|
|
1010
1273
|
* Generate AI-powered commit message suggestions
|
|
1011
1274
|
*/
|
|
1012
1275
|
async generateAICommitSuggestions(originalMessage, context, validationResult) {
|
|
1013
|
-
|
|
1276
|
+
if (!this.aiAnalysisService?.aiProvider?.isAvailable()) {
|
|
1277
|
+
throw new Error('AI provider not available for suggestions')
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
const { select } = await this._getCachedImport('@clack/prompts')
|
|
1014
1281
|
|
|
1015
|
-
console.log(colors.processingMessage('🤖 Generating AI suggestions...'))
|
|
1282
|
+
console.log(colors.processingMessage('🤖 Generating AI suggestions...'))
|
|
1016
1283
|
|
|
1017
1284
|
try {
|
|
1018
1285
|
// Build comprehensive context for AI
|
|
1019
|
-
const branchInfo =
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
...validationResult.warnings
|
|
1028
|
-
].join('\n');
|
|
1286
|
+
const branchInfo =
|
|
1287
|
+
context.branchAnalysis?.confidence > 30
|
|
1288
|
+
? `Branch: ${context.branchAnalysis.branch} (${context.branchAnalysis.type || 'unknown'} type)`
|
|
1289
|
+
: ''
|
|
1290
|
+
|
|
1291
|
+
const fileChanges = context.stagedFiles.map((f) => `${f.status} ${f.path}`).join('\n')
|
|
1292
|
+
|
|
1293
|
+
const validationIssues = [...validationResult.errors, ...validationResult.warnings].join('\n')
|
|
1029
1294
|
|
|
1030
1295
|
const prompt = `Improve this commit message based on the validation feedback and context:
|
|
1031
1296
|
|
|
@@ -1046,49 +1311,55 @@ Requirements:
|
|
|
1046
1311
|
- Use imperative mood
|
|
1047
1312
|
- Be specific and descriptive
|
|
1048
1313
|
|
|
1049
|
-
Provide 3 improved alternatives
|
|
1314
|
+
Provide 3 improved alternatives.`
|
|
1315
|
+
|
|
1316
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
1317
|
+
[
|
|
1318
|
+
{
|
|
1319
|
+
role: 'user',
|
|
1320
|
+
content: prompt,
|
|
1321
|
+
},
|
|
1322
|
+
],
|
|
1323
|
+
{ max_tokens: 300 }
|
|
1324
|
+
)
|
|
1050
1325
|
|
|
1051
|
-
const
|
|
1052
|
-
role: 'user',
|
|
1053
|
-
content: prompt
|
|
1054
|
-
}], { max_tokens: 300 });
|
|
1326
|
+
const suggestions = this.parseAICommitSuggestions(response.content)
|
|
1055
1327
|
|
|
1056
|
-
const suggestions = this.parseAICommitSuggestions(response.content);
|
|
1057
|
-
|
|
1058
1328
|
if (suggestions.length === 0) {
|
|
1059
|
-
console.log(
|
|
1060
|
-
|
|
1329
|
+
console.log(
|
|
1330
|
+
colors.warningMessage('No AI suggestions generated, falling back to manual edit.')
|
|
1331
|
+
)
|
|
1332
|
+
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1061
1333
|
}
|
|
1062
1334
|
|
|
1063
1335
|
// Present suggestions to user
|
|
1064
1336
|
const choices = suggestions.map((suggestion, index) => ({
|
|
1065
1337
|
value: suggestion,
|
|
1066
1338
|
label: `${index + 1}. ${suggestion.split('\n')[0]}`, // First line only
|
|
1067
|
-
hint: suggestion.includes('\n') ? 'Has body content' : 'Subject only'
|
|
1068
|
-
}))
|
|
1339
|
+
hint: suggestion.includes('\n') ? 'Has body content' : 'Subject only',
|
|
1340
|
+
}))
|
|
1069
1341
|
|
|
1070
1342
|
choices.push({
|
|
1071
1343
|
value: 'MANUAL',
|
|
1072
1344
|
label: '✏️ Edit manually instead',
|
|
1073
|
-
hint: 'Write your own commit message'
|
|
1074
|
-
})
|
|
1345
|
+
hint: 'Write your own commit message',
|
|
1346
|
+
})
|
|
1075
1347
|
|
|
1076
1348
|
const selectedMessage = await select({
|
|
1077
1349
|
message: 'Choose an AI-generated commit message:',
|
|
1078
|
-
options: choices
|
|
1079
|
-
})
|
|
1350
|
+
options: choices,
|
|
1351
|
+
})
|
|
1080
1352
|
|
|
1081
1353
|
if (selectedMessage === 'MANUAL') {
|
|
1082
|
-
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1354
|
+
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1083
1355
|
}
|
|
1084
1356
|
|
|
1085
|
-
console.log(colors.successMessage('\n✅ Selected AI suggestion:'))
|
|
1086
|
-
console.log(colors.highlight(selectedMessage))
|
|
1087
|
-
return selectedMessage
|
|
1088
|
-
|
|
1357
|
+
console.log(colors.successMessage('\n✅ Selected AI suggestion:'))
|
|
1358
|
+
console.log(colors.highlight(selectedMessage))
|
|
1359
|
+
return selectedMessage
|
|
1089
1360
|
} catch (error) {
|
|
1090
|
-
console.error(colors.errorMessage(`AI suggestion failed: ${error.message}`))
|
|
1091
|
-
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1361
|
+
console.error(colors.errorMessage(`AI suggestion failed: ${error.message}`))
|
|
1362
|
+
return await this.handleManualCommitEdit(originalMessage, validationResult)
|
|
1092
1363
|
}
|
|
1093
1364
|
}
|
|
1094
1365
|
|
|
@@ -1096,30 +1367,30 @@ Provide 3 improved alternatives.`;
|
|
|
1096
1367
|
* Handle manual commit message editing
|
|
1097
1368
|
*/
|
|
1098
1369
|
async handleManualCommitEdit(originalMessage, validationResult) {
|
|
1099
|
-
const { text, confirm } = await import('@clack/prompts')
|
|
1370
|
+
const { text, confirm } = await import('@clack/prompts')
|
|
1100
1371
|
|
|
1101
|
-
console.log(colors.infoMessage('\n✏️ Manual Edit Mode'))
|
|
1102
|
-
console.log(colors.dim('Validation issues to address:'))
|
|
1103
|
-
|
|
1104
|
-
validationResult.errors.forEach(error => {
|
|
1105
|
-
console.log(colors.error(` • ${error}`))
|
|
1106
|
-
})
|
|
1107
|
-
|
|
1108
|
-
validationResult.warnings.forEach(warning => {
|
|
1109
|
-
console.log(colors.warning(` • ${warning}`))
|
|
1110
|
-
})
|
|
1372
|
+
console.log(colors.infoMessage('\n✏️ Manual Edit Mode'))
|
|
1373
|
+
console.log(colors.dim('Validation issues to address:'))
|
|
1374
|
+
|
|
1375
|
+
validationResult.errors.forEach((error) => {
|
|
1376
|
+
console.log(colors.error(` • ${error}`))
|
|
1377
|
+
})
|
|
1378
|
+
|
|
1379
|
+
validationResult.warnings.forEach((warning) => {
|
|
1380
|
+
console.log(colors.warning(` • ${warning}`))
|
|
1381
|
+
})
|
|
1111
1382
|
|
|
1112
1383
|
if (validationResult.suggestions.length > 0) {
|
|
1113
|
-
console.log(colors.dim('\nSuggestions:'))
|
|
1114
|
-
validationResult.suggestions.forEach(suggestion => {
|
|
1115
|
-
console.log(colors.dim(` • ${suggestion}`))
|
|
1116
|
-
})
|
|
1384
|
+
console.log(colors.dim('\nSuggestions:'))
|
|
1385
|
+
validationResult.suggestions.forEach((suggestion) => {
|
|
1386
|
+
console.log(colors.dim(` • ${suggestion}`))
|
|
1387
|
+
})
|
|
1117
1388
|
}
|
|
1118
1389
|
|
|
1119
|
-
let improvedMessage
|
|
1120
|
-
let isValid = false
|
|
1121
|
-
let attempts = 0
|
|
1122
|
-
const maxAttempts = 3
|
|
1390
|
+
let improvedMessage
|
|
1391
|
+
let isValid = false
|
|
1392
|
+
let attempts = 0
|
|
1393
|
+
const maxAttempts = 3
|
|
1123
1394
|
|
|
1124
1395
|
while (!isValid && attempts < maxAttempts) {
|
|
1125
1396
|
improvedMessage = await text({
|
|
@@ -1128,72 +1399,76 @@ Provide 3 improved alternatives.`;
|
|
|
1128
1399
|
defaultValue: attempts === 0 ? originalMessage : undefined,
|
|
1129
1400
|
validate: (input) => {
|
|
1130
1401
|
if (!input || input.trim().length === 0) {
|
|
1131
|
-
return 'Commit message cannot be empty'
|
|
1402
|
+
return 'Commit message cannot be empty'
|
|
1132
1403
|
}
|
|
1133
|
-
}
|
|
1134
|
-
})
|
|
1404
|
+
},
|
|
1405
|
+
})
|
|
1135
1406
|
|
|
1136
1407
|
// Validate the improved message
|
|
1137
1408
|
const newValidation = await this.validationService.validateCommitMessage(improvedMessage, {
|
|
1138
|
-
branchAnalysis: validationResult.parsed?.branchAnalysis
|
|
1139
|
-
})
|
|
1409
|
+
branchAnalysis: validationResult.parsed?.branchAnalysis,
|
|
1410
|
+
})
|
|
1140
1411
|
|
|
1141
1412
|
if (newValidation.valid) {
|
|
1142
|
-
isValid = true
|
|
1143
|
-
console.log(colors.successMessage('✅ Validation passed!'))
|
|
1413
|
+
isValid = true
|
|
1414
|
+
console.log(colors.successMessage('✅ Validation passed!'))
|
|
1144
1415
|
} else {
|
|
1145
|
-
attempts
|
|
1146
|
-
console.log(
|
|
1147
|
-
|
|
1416
|
+
attempts++
|
|
1417
|
+
console.log(
|
|
1418
|
+
colors.warningMessage(`\n⚠️ Validation failed (attempt ${attempts}/${maxAttempts}):`)
|
|
1419
|
+
)
|
|
1420
|
+
this.validationService.displayValidationResults(newValidation)
|
|
1148
1421
|
|
|
1149
1422
|
if (attempts < maxAttempts) {
|
|
1150
1423
|
const tryAgain = await confirm({
|
|
1151
1424
|
message: 'Try again with improvements?',
|
|
1152
|
-
initialValue: true
|
|
1153
|
-
})
|
|
1425
|
+
initialValue: true,
|
|
1426
|
+
})
|
|
1154
1427
|
|
|
1155
1428
|
if (!tryAgain) {
|
|
1156
|
-
break
|
|
1429
|
+
break
|
|
1157
1430
|
}
|
|
1158
1431
|
}
|
|
1159
1432
|
}
|
|
1160
1433
|
}
|
|
1161
1434
|
|
|
1162
|
-
return improvedMessage || originalMessage
|
|
1435
|
+
return improvedMessage || originalMessage
|
|
1163
1436
|
}
|
|
1164
1437
|
|
|
1165
1438
|
/**
|
|
1166
1439
|
* Parse AI-generated commit suggestions
|
|
1167
1440
|
*/
|
|
1168
1441
|
parseAICommitSuggestions(content) {
|
|
1169
|
-
const suggestions = []
|
|
1170
|
-
const lines = content.split('\n').filter(line => line.trim())
|
|
1442
|
+
const suggestions = []
|
|
1443
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
1171
1444
|
|
|
1172
|
-
let currentSuggestion = ''
|
|
1445
|
+
let currentSuggestion = ''
|
|
1173
1446
|
for (const line of lines) {
|
|
1174
|
-
const trimmed = line.trim()
|
|
1175
|
-
|
|
1447
|
+
const trimmed = line.trim()
|
|
1448
|
+
|
|
1176
1449
|
// Check if it's a new suggestion (starts with number, bullet, or looks like commit format)
|
|
1177
|
-
if (
|
|
1450
|
+
if (
|
|
1451
|
+
trimmed.match(
|
|
1452
|
+
/^(\d+[.)]|\*|-|•)|^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.*?\))?:/
|
|
1453
|
+
)
|
|
1454
|
+
) {
|
|
1178
1455
|
if (currentSuggestion) {
|
|
1179
|
-
suggestions.push(currentSuggestion.trim())
|
|
1456
|
+
suggestions.push(currentSuggestion.trim())
|
|
1180
1457
|
}
|
|
1181
1458
|
// Clean up the line (remove numbering/bullets)
|
|
1182
|
-
currentSuggestion = trimmed.replace(/^(\d+[
|
|
1459
|
+
currentSuggestion = trimmed.replace(/^(\d+[.)]|\*|-|•)\s*/, '')
|
|
1183
1460
|
} else if (currentSuggestion && trimmed.length > 0) {
|
|
1184
1461
|
// Add to current suggestion (body content)
|
|
1185
|
-
currentSuggestion +=
|
|
1462
|
+
currentSuggestion += `\n${trimmed}`
|
|
1186
1463
|
}
|
|
1187
1464
|
}
|
|
1188
1465
|
|
|
1189
1466
|
// Add the last suggestion
|
|
1190
1467
|
if (currentSuggestion) {
|
|
1191
|
-
suggestions.push(currentSuggestion.trim())
|
|
1468
|
+
suggestions.push(currentSuggestion.trim())
|
|
1192
1469
|
}
|
|
1193
1470
|
|
|
1194
1471
|
// Filter valid suggestions
|
|
1195
|
-
return suggestions
|
|
1196
|
-
.filter(s => s.length > 10 && s.includes(':'))
|
|
1197
|
-
.slice(0, 3); // Limit to 3 suggestions
|
|
1472
|
+
return suggestions.filter((s) => s.length > 10 && s.includes(':')).slice(0, 3) // Limit to 3 suggestions
|
|
1198
1473
|
}
|
|
1199
|
-
}
|
|
1474
|
+
}
|