@entro314labs/ai-changelog-generator 3.2.1 → 3.3.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/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +0 -0
- package/package.json +72 -80
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +12 -202
- package/src/cli.js +4 -5
- package/src/domains/ai/ai-analysis.service.js +2 -0
- package/src/domains/analysis/analysis.engine.js +714 -37
- package/src/domains/changelog/changelog.service.js +615 -30
- package/src/domains/changelog/workspace-changelog.service.js +418 -627
- package/src/domains/git/commit-tagger.js +552 -0
- package/src/domains/git/git-manager.js +357 -0
- package/src/domains/git/git.service.js +865 -16
- package/src/infrastructure/cli/cli.controller.js +14 -9
- package/src/infrastructure/config/configuration.manager.js +24 -2
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +35 -11
- package/src/infrastructure/providers/core/base-provider.js +1 -1
- package/src/infrastructure/providers/implementations/anthropic.js +16 -173
- package/src/infrastructure/providers/implementations/azure.js +16 -63
- package/src/infrastructure/providers/implementations/dummy.js +13 -16
- package/src/infrastructure/providers/implementations/mock.js +13 -26
- package/src/infrastructure/providers/implementations/ollama.js +12 -4
- package/src/infrastructure/providers/implementations/openai.js +13 -165
- package/src/infrastructure/providers/provider-management.service.js +126 -412
- package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
- package/src/shared/utils/cli-ui.js +1 -1
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +65 -60
- package/src/domains/git/git-repository.analyzer.js +0 -678
|
@@ -1,743 +1,534 @@
|
|
|
1
1
|
import colors from '../../shared/constants/colors.js'
|
|
2
|
-
import {
|
|
3
|
-
import { DiffProcessor } from '../../shared/utils/diff-processor.js'
|
|
4
|
-
import {
|
|
5
|
-
assessFileImportance,
|
|
6
|
-
categorizeFile,
|
|
7
|
-
detectLanguage,
|
|
8
|
-
getWorkingDirectoryChanges,
|
|
9
|
-
summarizeFileChanges,
|
|
10
|
-
} from '../../shared/utils/utils.js'
|
|
2
|
+
import { ChangelogService } from './changelog.service.js'
|
|
11
3
|
|
|
12
4
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Handles changelog generation from working directory changes
|
|
5
|
+
* WorkspaceChangelogService extends ChangelogService with workspace-specific functionality
|
|
6
|
+
* for analyzing uncommitted changes and workspace state
|
|
16
7
|
*/
|
|
17
|
-
export class WorkspaceChangelogService {
|
|
18
|
-
constructor(aiAnalysisService,
|
|
19
|
-
|
|
20
|
-
this.
|
|
8
|
+
export class WorkspaceChangelogService extends ChangelogService {
|
|
9
|
+
constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
|
|
10
|
+
super(gitService, aiAnalysisService, analysisEngine, configManager)
|
|
11
|
+
this.workspaceMetrics = {
|
|
12
|
+
unstagedFiles: 0,
|
|
13
|
+
stagedFiles: 0,
|
|
14
|
+
untrackedFiles: 0,
|
|
15
|
+
modifiedLines: 0,
|
|
16
|
+
}
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Analyze workspace changes without committing
|
|
21
|
+
* @returns {Promise<Object>} Workspace analysis results
|
|
22
|
+
*/
|
|
23
|
+
async analyzeWorkspaceChanges() {
|
|
24
|
+
console.log(colors.processingMessage('🔍 Analyzing workspace changes...'))
|
|
25
|
+
|
|
24
26
|
try {
|
|
25
|
-
// Get
|
|
26
|
-
const
|
|
27
|
+
// Get git status information
|
|
28
|
+
const status = await this.gitService.getStatus()
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// Update workspace metrics
|
|
31
|
+
this.workspaceMetrics.unstagedFiles = status.unstaged?.length || 0
|
|
32
|
+
this.workspaceMetrics.stagedFiles = status.staged?.length || 0
|
|
33
|
+
this.workspaceMetrics.untrackedFiles = status.untracked?.length || 0
|
|
32
34
|
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
enableFiltering: true,
|
|
42
|
-
enablePatternDetection: true,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const processedResult = diffProcessor.processFiles(enhancedChanges)
|
|
46
|
-
|
|
47
|
-
// Generate changelog content with processed files
|
|
48
|
-
const changelog = await this.generateChangelogContent(
|
|
49
|
-
processedResult.processedFiles,
|
|
50
|
-
changesSummary,
|
|
51
|
-
processedResult,
|
|
52
|
-
analysisMode
|
|
53
|
-
)
|
|
35
|
+
// Get detailed diff for staged/unstaged changes
|
|
36
|
+
const diff = await this.gitService.getDiff()
|
|
37
|
+
|
|
38
|
+
// Use analysis engine if available
|
|
39
|
+
let analysis = null
|
|
40
|
+
if (this.analysisEngine) {
|
|
41
|
+
analysis = await this.analysisEngine.analyzeCurrentChanges()
|
|
42
|
+
}
|
|
54
43
|
|
|
55
44
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
summary: changesSummary,
|
|
61
|
-
filesProcessed: processedResult.filesProcessed,
|
|
62
|
-
filesSkipped: processedResult.filesSkipped,
|
|
45
|
+
status,
|
|
46
|
+
diff,
|
|
47
|
+
analysis,
|
|
48
|
+
metrics: this.workspaceMetrics,
|
|
63
49
|
}
|
|
64
50
|
} catch (error) {
|
|
65
|
-
console.error(colors.errorMessage(
|
|
51
|
+
console.error(colors.errorMessage(`Failed to analyze workspace: ${error.message}`))
|
|
66
52
|
throw error
|
|
67
53
|
}
|
|
68
54
|
}
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
try {
|
|
77
|
-
// Use DiffProcessor for intelligent diff processing
|
|
78
|
-
const diffProcessor = new DiffProcessor({
|
|
79
|
-
analysisMode,
|
|
80
|
-
enableFiltering: true,
|
|
81
|
-
enablePatternDetection: true,
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const processedResult = diffProcessor.processFiles(changes)
|
|
85
|
-
const { processedFiles, patterns } = processedResult
|
|
86
|
-
|
|
87
|
-
// Build pattern summary if patterns were detected
|
|
88
|
-
const patternSummary =
|
|
89
|
-
Object.keys(patterns).length > 0
|
|
90
|
-
? `\n\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
|
|
91
|
-
.map((p) => `- ${p.description}`)
|
|
92
|
-
.join('\n')}`
|
|
93
|
-
: ''
|
|
94
|
-
|
|
95
|
-
// Build files section with processed diffs
|
|
96
|
-
const filesSection = processedFiles
|
|
97
|
-
.map((file) => {
|
|
98
|
-
if (file.isSummary) {
|
|
99
|
-
return `\n**[REMAINING FILES]:** ${file.diff}`
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const compressionInfo = file.compressionApplied
|
|
103
|
-
? ` [compressed from ${file.originalSize || 'unknown'} chars]`
|
|
104
|
-
: ''
|
|
105
|
-
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
106
|
-
|
|
107
|
-
return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}`
|
|
108
|
-
})
|
|
109
|
-
.join('\n')
|
|
110
|
-
|
|
111
|
-
// Build comprehensive prompt with processed changes
|
|
112
|
-
const prompt = `Generate a comprehensive AI changelog for the following working directory changes:
|
|
113
|
-
|
|
114
|
-
**Analysis Mode**: ${analysisMode}
|
|
115
|
-
**Total Files**: ${changesSummary.totalFiles} (${processedResult.filesProcessed} analyzed, ${processedResult.filesSkipped} summarized)
|
|
116
|
-
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}${patternSummary}
|
|
117
|
-
|
|
118
|
-
**PROCESSED FILES:**${filesSection}
|
|
119
|
-
|
|
120
|
-
CRITICAL INSTRUCTIONS FOR ANALYSIS:
|
|
121
|
-
1. **ONLY DESCRIBE CHANGES VISIBLE IN THE DIFF CONTENT** - Do not invent or assume changes
|
|
122
|
-
2. **BE FACTUAL AND PRECISE** - Only mention specific lines, functions, imports that you can see
|
|
123
|
-
3. **NO ASSUMPTIONS OR SPECULATION** - If you can't see it in the diff, don't mention it
|
|
124
|
-
4. **STICK TO OBSERVABLE FACTS** - Describe what was added, removed, or modified line by line
|
|
125
|
-
5. **DO NOT MAKE UP INTEGRATION DETAILS** - Don't assume files work together unless explicitly shown
|
|
126
|
-
|
|
127
|
-
STRICT FORMATTING REQUIREMENTS:
|
|
128
|
-
Generate working directory change entries based ONLY on visible diff content:
|
|
129
|
-
- (type) Detailed but focused description - Include key functional changes, method/function names, and important technical details without overwhelming verbosity
|
|
130
|
-
|
|
131
|
-
EXAMPLES of CORRECT DETAILED FORMAT:
|
|
132
|
-
✅ (feature) Created new bedrock.js file - Added BedrockProvider class with generateCompletion(), initializeClient(), and getAvailableModels() methods. Imported AWS SDK BedrockRuntimeClient and added support for Claude-3-5-sonnet and Llama-3.1 models with streaming capabilities.
|
|
133
|
-
|
|
134
|
-
✅ (refactor) Updated model list in anthropic.js - Changed getDefaultModel() return value from 'claude-3-5-sonnet-20241022' to 'claude-sonnet-4-20250514'. Added claude-sonnet-4 model entry with 200k context and updated pricing tier.
|
|
135
|
-
|
|
136
|
-
✅ (fix) Updated configuration.manager.js - Added null check in getProviderConfig() method to prevent crashes when .env.local file is missing. Modified loadConfig() to gracefully handle missing environment files.
|
|
137
|
-
|
|
138
|
-
EXAMPLES of FORBIDDEN ASSUMPTIONS:
|
|
139
|
-
❌ "Updated other providers to recognize bedrock" (not visible in diff)
|
|
140
|
-
❌ "Refactored provider selection logic" (not shown in actual changes)
|
|
141
|
-
❌ "Improved integration across the system" (speculation)
|
|
142
|
-
❌ "Enhanced error handling throughout" (assumption)
|
|
143
|
-
|
|
144
|
-
ONLY describe what you can literally see in the diff content. Do not invent connections or integrations.`
|
|
145
|
-
|
|
146
|
-
// Make AI call with all the context
|
|
147
|
-
const messages = [
|
|
148
|
-
{
|
|
149
|
-
role: 'system',
|
|
150
|
-
content:
|
|
151
|
-
'You are an expert at analyzing code changes and generating detailed but focused changelog entries. You MUST only describe changes that are visible in the provided diff content. Include specific function/method names, key technical details, and the functional purpose of changes. Be precise and factual - only describe what you can literally see in the diffs. Provide enough detail to understand what changed technically, but avoid overwhelming verbosity.',
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
role: 'user',
|
|
155
|
-
content: prompt,
|
|
156
|
-
},
|
|
157
|
-
]
|
|
158
|
-
|
|
159
|
-
const options = {
|
|
160
|
-
max_tokens:
|
|
161
|
-
analysisMode === 'enterprise' ? 2500 : analysisMode === 'detailed' ? 2000 : 1200,
|
|
162
|
-
temperature: 0.2,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const response = await this.aiAnalysisService.aiProvider.generateCompletion(messages, options)
|
|
166
|
-
|
|
167
|
-
let changelog = response.content || response.text
|
|
168
|
-
|
|
169
|
-
// Check if changelog content is valid
|
|
170
|
-
if (!changelog || typeof changelog !== 'string') {
|
|
171
|
-
console.warn(colors.warningMessage('⚠️ AI response was empty or invalid'))
|
|
172
|
-
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
173
|
-
|
|
174
|
-
// Generate basic changelog from file changes
|
|
175
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
176
|
-
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
177
|
-
const basicEntries = fallbackChanges.map((change) => {
|
|
178
|
-
const filePath = change.filePath || change.path || 'unknown file'
|
|
179
|
-
const status = change.status || 'M'
|
|
180
|
-
const changeType =
|
|
181
|
-
status === 'M'
|
|
182
|
-
? 'Modified'
|
|
183
|
-
: status === 'A'
|
|
184
|
-
? 'Added'
|
|
185
|
-
: status === 'D'
|
|
186
|
-
? 'Deleted'
|
|
187
|
-
: 'Changed'
|
|
188
|
-
return `- ${changeType} ${filePath}`
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
changelog = `# Working Directory Changelog - ${timestamp}\n\n## Changes\n\n${basicEntries.join('\n')}`
|
|
192
|
-
}
|
|
56
|
+
/**
|
|
57
|
+
* Generate changelog preview for workspace changes
|
|
58
|
+
* @returns {Promise<string>} Preview changelog content
|
|
59
|
+
*/
|
|
60
|
+
async generateWorkspacePreview() {
|
|
61
|
+
console.log(colors.processingMessage('📝 Generating workspace preview...'))
|
|
193
62
|
|
|
194
|
-
|
|
195
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
63
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
196
64
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
65
|
+
if (!workspaceData.analysis || workspaceData.analysis.changes.length === 0) {
|
|
66
|
+
return colors.infoMessage('No significant workspace changes detected.')
|
|
67
|
+
}
|
|
201
68
|
|
|
202
|
-
|
|
203
|
-
|
|
69
|
+
// Generate preview using parent class methods
|
|
70
|
+
const previewContent = await this.generateChangelogFromAnalysis(workspaceData.analysis)
|
|
204
71
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Specific error guidance for AI failures
|
|
208
|
-
if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
|
|
209
|
-
console.warn(colors.warningMessage('⚠️ Cannot connect to AI provider'))
|
|
210
|
-
console.warn(colors.infoMessage('💡 Check internet connection and provider service status'))
|
|
211
|
-
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
212
|
-
console.warn(colors.warningMessage('⚠️ API authentication failed'))
|
|
213
|
-
console.warn(colors.infoMessage('💡 Run: ai-changelog init'))
|
|
214
|
-
} else if (error.message.includes('rate limit')) {
|
|
215
|
-
console.warn(colors.warningMessage('⚠️ Rate limit exceeded'))
|
|
216
|
-
console.warn(colors.infoMessage('💡 Wait a moment before retrying'))
|
|
217
|
-
} else {
|
|
218
|
-
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
219
|
-
}
|
|
72
|
+
return previewContent
|
|
73
|
+
}
|
|
220
74
|
|
|
221
|
-
|
|
222
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Get workspace statistics
|
|
77
|
+
* @returns {Object} Workspace statistics
|
|
78
|
+
*/
|
|
79
|
+
getWorkspaceStats() {
|
|
80
|
+
return {
|
|
81
|
+
...this.workspaceMetrics,
|
|
82
|
+
hasChanges: this.hasWorkspaceChanges(),
|
|
83
|
+
summary: this.getWorkspaceSummary(),
|
|
223
84
|
}
|
|
224
85
|
}
|
|
225
86
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Check if workspace has any changes
|
|
89
|
+
* @returns {boolean} True if workspace has changes
|
|
90
|
+
*/
|
|
91
|
+
hasWorkspaceChanges() {
|
|
92
|
+
return (
|
|
93
|
+
this.workspaceMetrics.unstagedFiles > 0 ||
|
|
94
|
+
this.workspaceMetrics.stagedFiles > 0 ||
|
|
95
|
+
this.workspaceMetrics.untrackedFiles > 0
|
|
96
|
+
)
|
|
97
|
+
}
|
|
234
98
|
|
|
235
|
-
|
|
236
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Get workspace summary string
|
|
101
|
+
* @returns {string} Human-readable workspace summary
|
|
102
|
+
*/
|
|
103
|
+
getWorkspaceSummary() {
|
|
104
|
+
const { unstagedFiles, stagedFiles, untrackedFiles } = this.workspaceMetrics
|
|
105
|
+
const parts = []
|
|
237
106
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
107
|
+
if (stagedFiles > 0) {
|
|
108
|
+
parts.push(`${stagedFiles} staged`)
|
|
109
|
+
}
|
|
110
|
+
if (unstagedFiles > 0) {
|
|
111
|
+
parts.push(`${unstagedFiles} unstaged`)
|
|
112
|
+
}
|
|
113
|
+
if (untrackedFiles > 0) {
|
|
114
|
+
parts.push(`${untrackedFiles} untracked`)
|
|
115
|
+
}
|
|
243
116
|
|
|
244
|
-
return
|
|
117
|
+
return parts.length > 0 ? parts.join(', ') : 'No changes'
|
|
245
118
|
}
|
|
246
119
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Validate workspace state before operations
|
|
122
|
+
* @returns {Promise<boolean>} True if workspace is valid
|
|
123
|
+
*/
|
|
124
|
+
async validateWorkspace() {
|
|
125
|
+
try {
|
|
126
|
+
// Check if we're in a git repository
|
|
127
|
+
const isGitRepo = await this.gitService.isGitRepository()
|
|
128
|
+
if (!isGitRepo) {
|
|
129
|
+
console.error(colors.errorMessage('Not in a git repository'))
|
|
130
|
+
return false
|
|
257
131
|
}
|
|
258
132
|
|
|
259
|
-
//
|
|
260
|
-
if (this.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
change.status,
|
|
264
|
-
change.path || change.filePath
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
if (diffAnalysis) {
|
|
268
|
-
enhancedChange.diff = diffAnalysis.diff
|
|
269
|
-
enhancedChange.beforeContent = diffAnalysis.beforeContent
|
|
270
|
-
enhancedChange.afterContent = diffAnalysis.afterContent
|
|
271
|
-
enhancedChange.semanticChanges = diffAnalysis.semanticChanges
|
|
272
|
-
enhancedChange.functionalImpact = diffAnalysis.functionalImpact
|
|
273
|
-
enhancedChange.complexity = diffAnalysis.complexity
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
console.warn(`Failed to get diff for ${change.path || change.filePath}:`, error.message)
|
|
277
|
-
}
|
|
133
|
+
// Check if workspace has changes
|
|
134
|
+
if (!this.hasWorkspaceChanges()) {
|
|
135
|
+
console.log(colors.infoMessage('No workspace changes detected'))
|
|
136
|
+
return false
|
|
278
137
|
}
|
|
279
138
|
|
|
280
|
-
|
|
139
|
+
return true
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(colors.errorMessage(`Workspace validation failed: ${error.message}`))
|
|
142
|
+
return false
|
|
281
143
|
}
|
|
282
|
-
|
|
283
|
-
return enhancedChanges
|
|
284
144
|
}
|
|
285
145
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
complexity: this.assessWorkspaceComplexity(changes),
|
|
293
|
-
recommendations: this.generateRecommendations(changes),
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return context
|
|
297
|
-
}
|
|
146
|
+
/**
|
|
147
|
+
* Generate comprehensive workspace changelog
|
|
148
|
+
* @returns {Promise<string>} Comprehensive changelog content
|
|
149
|
+
*/
|
|
150
|
+
async generateComprehensiveWorkspaceChangelog() {
|
|
151
|
+
console.log(colors.processingMessage('📋 Generating comprehensive workspace changelog...'))
|
|
298
152
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
153
|
+
try {
|
|
154
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
302
155
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
change.importance === 'critical' ||
|
|
307
|
-
change.category === 'configuration' ||
|
|
308
|
-
change.status === 'D'
|
|
309
|
-
)
|
|
156
|
+
if (!workspaceData.analysis) {
|
|
157
|
+
return this.generateBasicWorkspaceChangelog(workspaceData)
|
|
158
|
+
}
|
|
310
159
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
160
|
+
return await this.generateAIChangelogContentFromChanges(workspaceData.analysis.changes)
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(
|
|
163
|
+
colors.errorMessage(`Failed to generate comprehensive changelog: ${error.message}`)
|
|
164
|
+
)
|
|
165
|
+
throw error
|
|
316
166
|
}
|
|
317
|
-
return 'low'
|
|
318
167
|
}
|
|
319
168
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
169
|
+
/**
|
|
170
|
+
* Generate AI-powered changelog content from changes
|
|
171
|
+
* @param {Array} changes - Array of change objects
|
|
172
|
+
* @returns {Promise<string>} Generated changelog content
|
|
173
|
+
*/
|
|
174
|
+
async generateAIChangelogContentFromChanges(changes) {
|
|
175
|
+
if (!changes || changes.length === 0) {
|
|
176
|
+
return colors.infoMessage('No changes to process for changelog.')
|
|
326
177
|
}
|
|
327
|
-
return 'low'
|
|
328
|
-
}
|
|
329
178
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const hasDocs = changes.some((change) => change.category === 'documentation')
|
|
179
|
+
try {
|
|
180
|
+
// Use AI analysis service if available
|
|
181
|
+
if (this.aiAnalysisService && this.aiAnalysisService.hasAI) {
|
|
182
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(changes)
|
|
183
|
+
return await this.generateChangelogContent(enhancedChanges)
|
|
184
|
+
}
|
|
337
185
|
|
|
338
|
-
|
|
339
|
-
|
|
186
|
+
// Fallback to rule-based generation
|
|
187
|
+
return this.generateRuleBasedChangelog(changes)
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.error(colors.errorMessage(`AI changelog generation failed: ${error.message}`))
|
|
190
|
+
return this.generateRuleBasedChangelog(changes)
|
|
340
191
|
}
|
|
192
|
+
}
|
|
341
193
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Enhance changes with diff information
|
|
196
|
+
* @param {Array} changes - Array of change objects
|
|
197
|
+
* @returns {Promise<Array>} Enhanced changes with diff data
|
|
198
|
+
*/
|
|
199
|
+
async enhanceChangesWithDiff(changes) {
|
|
200
|
+
const enhancedChanges = []
|
|
345
201
|
|
|
346
|
-
|
|
347
|
-
|
|
202
|
+
for (const change of changes) {
|
|
203
|
+
try {
|
|
204
|
+
// Get detailed diff for the file
|
|
205
|
+
const diff = await this.gitService.getFileDiff(change.file)
|
|
206
|
+
|
|
207
|
+
enhancedChanges.push({
|
|
208
|
+
...change,
|
|
209
|
+
diff,
|
|
210
|
+
complexity: this.assessChangeComplexity(diff),
|
|
211
|
+
impact: this.assessChangeImpact(change.file, diff),
|
|
212
|
+
})
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.warn(
|
|
215
|
+
colors.warningMessage(`Failed to enhance change for ${change.file}: ${error.message}`)
|
|
216
|
+
)
|
|
217
|
+
enhancedChanges.push(change)
|
|
218
|
+
}
|
|
348
219
|
}
|
|
349
220
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
221
|
+
return enhancedChanges
|
|
222
|
+
}
|
|
353
223
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
224
|
+
/**
|
|
225
|
+
* Generate changelog content from enhanced changes
|
|
226
|
+
* @param {Array} enhancedChanges - Enhanced change objects
|
|
227
|
+
* @returns {Promise<string>} Generated changelog content
|
|
228
|
+
*/
|
|
229
|
+
async generateChangelogContent(enhancedChanges) {
|
|
230
|
+
const sections = {
|
|
231
|
+
features: [],
|
|
232
|
+
fixes: [],
|
|
233
|
+
improvements: [],
|
|
234
|
+
docs: [],
|
|
235
|
+
tests: [],
|
|
236
|
+
chores: [],
|
|
357
237
|
}
|
|
358
238
|
|
|
359
|
-
|
|
360
|
-
|
|
239
|
+
// Categorize changes
|
|
240
|
+
for (const change of enhancedChanges) {
|
|
241
|
+
const category = this.categorizeChange(change)
|
|
242
|
+
if (sections[category]) {
|
|
243
|
+
sections[category].push(change)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
361
246
|
|
|
362
|
-
|
|
363
|
-
let content = '
|
|
247
|
+
// Generate markdown content
|
|
248
|
+
let content = '# Workspace Changes\n\n'
|
|
364
249
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
250
|
+
for (const [section, changes] of Object.entries(sections)) {
|
|
251
|
+
if (changes.length > 0) {
|
|
252
|
+
content += `## ${this.formatSectionTitle(section)}\n\n`
|
|
368
253
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
})
|
|
254
|
+
for (const change of changes) {
|
|
255
|
+
content += `- ${this.formatChangeEntry(change)}\n`
|
|
256
|
+
}
|
|
373
257
|
|
|
374
|
-
|
|
375
|
-
|
|
258
|
+
content += '\n'
|
|
259
|
+
}
|
|
260
|
+
}
|
|
376
261
|
|
|
377
262
|
return content
|
|
378
263
|
}
|
|
379
264
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Generate commit-style working directory entries
|
|
267
|
+
* @returns {Promise<Array>} Array of commit-style entries
|
|
268
|
+
*/
|
|
269
|
+
async generateCommitStyleWorkingDirectoryEntries() {
|
|
270
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
271
|
+
const entries = []
|
|
272
|
+
|
|
273
|
+
if (workspaceData.status) {
|
|
274
|
+
// Process staged files
|
|
275
|
+
if (workspaceData.status.staged) {
|
|
276
|
+
for (const file of workspaceData.status.staged) {
|
|
277
|
+
entries.push({
|
|
278
|
+
type: 'staged',
|
|
279
|
+
file,
|
|
280
|
+
message: `Add: ${file}`,
|
|
281
|
+
timestamp: new Date().toISOString(),
|
|
282
|
+
})
|
|
283
|
+
}
|
|
284
|
+
}
|
|
393
285
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
286
|
+
// Process unstaged files
|
|
287
|
+
if (workspaceData.status.unstaged) {
|
|
288
|
+
for (const file of workspaceData.status.unstaged) {
|
|
289
|
+
entries.push({
|
|
290
|
+
type: 'unstaged',
|
|
291
|
+
file,
|
|
292
|
+
message: `Modify: ${file}`,
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
}
|
|
404
297
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
298
|
+
// Process untracked files
|
|
299
|
+
if (workspaceData.status.untracked) {
|
|
300
|
+
for (const file of workspaceData.status.untracked) {
|
|
301
|
+
entries.push({
|
|
302
|
+
type: 'untracked',
|
|
303
|
+
file,
|
|
304
|
+
message: `Create: ${file}`,
|
|
305
|
+
timestamp: new Date().toISOString(),
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
}
|
|
408
309
|
}
|
|
409
|
-
|
|
310
|
+
|
|
311
|
+
return entries
|
|
410
312
|
}
|
|
411
313
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
} else {
|
|
419
|
-
rawChanges = getWorkingDirectoryChanges()
|
|
420
|
-
}
|
|
314
|
+
/**
|
|
315
|
+
* Generate workspace changelog
|
|
316
|
+
* @returns {Promise<string>} Workspace changelog content
|
|
317
|
+
*/
|
|
318
|
+
async generateWorkspaceChangelog() {
|
|
319
|
+
console.log(colors.processingMessage('📝 Generating workspace changelog...'))
|
|
421
320
|
|
|
422
321
|
try {
|
|
423
|
-
|
|
424
|
-
return { entries: [] }
|
|
425
|
-
}
|
|
322
|
+
const entries = await this.generateCommitStyleWorkingDirectoryEntries()
|
|
426
323
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
430
|
-
|
|
431
|
-
// Use DiffProcessor for intelligent diff processing
|
|
432
|
-
const analysisMode =
|
|
433
|
-
this.aiAnalysisService?.analysisMode || options.analysisMode || 'standard'
|
|
434
|
-
const diffProcessor = new DiffProcessor({
|
|
435
|
-
analysisMode,
|
|
436
|
-
enableFiltering: true,
|
|
437
|
-
enablePatternDetection: true,
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
const processedResult = diffProcessor.processFiles(enhancedChanges)
|
|
441
|
-
const { processedFiles, patterns } = processedResult
|
|
442
|
-
|
|
443
|
-
// Build pattern summary if patterns were detected
|
|
444
|
-
const patternSummary =
|
|
445
|
-
Object.keys(patterns).length > 0
|
|
446
|
-
? `\n\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
|
|
447
|
-
.map((p) => `- ${p.description}`)
|
|
448
|
-
.join('\n')}`
|
|
449
|
-
: ''
|
|
450
|
-
|
|
451
|
-
// Build files section with processed diffs
|
|
452
|
-
const filesSection = processedFiles
|
|
453
|
-
.map((file) => {
|
|
454
|
-
if (file.isSummary) {
|
|
455
|
-
return `\n**[REMAINING FILES]:** ${file.diff}`
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const compressionInfo = file.compressionApplied
|
|
459
|
-
? ` [compressed from ${file.originalSize || 'unknown'} chars]`
|
|
460
|
-
: ''
|
|
461
|
-
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
462
|
-
|
|
463
|
-
return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}`
|
|
464
|
-
})
|
|
465
|
-
.join('\n')
|
|
466
|
-
|
|
467
|
-
// Build prompt for commit-style entries
|
|
468
|
-
const prompt = `Generate working directory change entries in the SAME FORMAT as git commits:
|
|
469
|
-
|
|
470
|
-
**Analysis Mode**: ${analysisMode}
|
|
471
|
-
**Total Files**: ${changesSummary.totalFiles} (${processedResult.filesProcessed} analyzed, ${processedResult.filesSkipped} summarized)
|
|
472
|
-
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}${patternSummary}
|
|
473
|
-
|
|
474
|
-
**PROCESSED FILES:**${filesSection}
|
|
475
|
-
|
|
476
|
-
STRICT FORMATTING REQUIREMENTS:
|
|
477
|
-
Generate working directory change entries based ONLY on visible diff content:
|
|
478
|
-
- (type) Detailed but focused description - Include key functional changes, method/function names, and important technical details without overwhelming verbosity
|
|
479
|
-
|
|
480
|
-
Where:
|
|
481
|
-
- type = feature, fix, refactor, docs, chore, etc. based on the actual changes
|
|
482
|
-
- Detailed description = specific functions/methods affected, key technical changes, and functional purpose
|
|
483
|
-
- Include exact method names, variable names, and technical specifics from the diffs
|
|
484
|
-
|
|
485
|
-
EXAMPLES of CORRECT DETAILED FORMAT:
|
|
486
|
-
- (feature) Created new bedrock.js file - Added BedrockProvider class with generateCompletion(), initializeClient(), and getAvailableModels() methods. Imported AWS SDK BedrockRuntimeClient and added support for Claude-3-5-sonnet and Llama-3.1 models with streaming capabilities.
|
|
487
|
-
|
|
488
|
-
- (refactor) Updated model list in anthropic.js - Changed getDefaultModel() return value from 'claude-3-5-sonnet-20241022' to 'claude-sonnet-4-20250514'. Added claude-sonnet-4 model entry with 200k context window and updated pricing tier.
|
|
489
|
-
|
|
490
|
-
- (fix) Updated configuration.manager.js - Added null check in getProviderConfig() method to prevent crashes when .env.local file is missing. Modified loadConfig() to gracefully handle missing environment files.
|
|
491
|
-
|
|
492
|
-
FORBIDDEN - DO NOT MAKE ASSUMPTIONS:
|
|
493
|
-
❌ Do not mention "integration" unless you see actual integration code
|
|
494
|
-
❌ Do not mention "provider selection logic" unless you see that specific code
|
|
495
|
-
❌ Do not assume files work together unless explicitly shown in diffs
|
|
496
|
-
|
|
497
|
-
Generate one entry per file or logical change group. Only describe what you can literally see.`
|
|
498
|
-
|
|
499
|
-
// Make AI call
|
|
500
|
-
const messages = [
|
|
501
|
-
{
|
|
502
|
-
role: 'system',
|
|
503
|
-
content:
|
|
504
|
-
'You are an expert at analyzing code changes and generating detailed but focused commit-style changelog entries. You MUST only describe changes that are visible in the provided diff content. Include specific function/method names, key technical details, and the functional purpose of changes. Be precise and factual - only describe what you can literally see in the diffs. Provide enough detail to understand what changed technically, but avoid overwhelming verbosity.',
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
role: 'user',
|
|
508
|
-
content: prompt,
|
|
509
|
-
},
|
|
510
|
-
]
|
|
511
|
-
|
|
512
|
-
// Set token limits based on analysis mode and number of changes
|
|
513
|
-
let maxTokens = 1200 // Default
|
|
514
|
-
if (analysisMode === 'enterprise') {
|
|
515
|
-
maxTokens = 3000
|
|
516
|
-
} else if (analysisMode === 'detailed') {
|
|
517
|
-
maxTokens = 2500
|
|
324
|
+
if (entries.length === 0) {
|
|
325
|
+
return colors.infoMessage('No workspace changes to include in changelog.')
|
|
518
326
|
}
|
|
519
327
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
maxTokens = Math.min(maxTokens + 1500, 6000)
|
|
523
|
-
}
|
|
328
|
+
let content = '# Workspace Changes\n\n'
|
|
329
|
+
content += `Generated on: ${new Date().toLocaleString()}\n\n`
|
|
524
330
|
|
|
525
|
-
const
|
|
526
|
-
max_tokens: maxTokens,
|
|
527
|
-
temperature: 0.3,
|
|
528
|
-
}
|
|
331
|
+
const groupedEntries = this.groupEntriesByType(entries)
|
|
529
332
|
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
333
|
+
for (const [type, typeEntries] of Object.entries(groupedEntries)) {
|
|
334
|
+
if (typeEntries.length > 0) {
|
|
335
|
+
content += `## ${this.formatEntryType(type)} (${typeEntries.length})\n\n`
|
|
534
336
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
if (!content || typeof content !== 'string') {
|
|
539
|
-
console.warn(colors.warningMessage('⚠️ AI response was empty or invalid'))
|
|
540
|
-
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
541
|
-
|
|
542
|
-
// Fallback to basic entries from the changes we were given
|
|
543
|
-
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
544
|
-
const basicEntries = fallbackChanges.map((change) => {
|
|
545
|
-
const filePath = change.filePath || change.path || 'unknown file'
|
|
546
|
-
const status = change.status || 'M'
|
|
547
|
-
const changeType =
|
|
548
|
-
status === 'M'
|
|
549
|
-
? 'update'
|
|
550
|
-
: status === 'A'
|
|
551
|
-
? 'feature'
|
|
552
|
-
: status === 'D'
|
|
553
|
-
? 'remove'
|
|
554
|
-
: 'chore'
|
|
555
|
-
const changeDesc =
|
|
556
|
-
status === 'M'
|
|
557
|
-
? 'updated'
|
|
558
|
-
: status === 'A'
|
|
559
|
-
? 'added'
|
|
560
|
-
: status === 'D'
|
|
561
|
-
? 'deleted'
|
|
562
|
-
: 'changed'
|
|
563
|
-
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`
|
|
564
|
-
})
|
|
337
|
+
for (const entry of typeEntries) {
|
|
338
|
+
content += `- ${entry.message}\n`
|
|
339
|
+
}
|
|
565
340
|
|
|
566
|
-
|
|
341
|
+
content += '\n'
|
|
342
|
+
}
|
|
567
343
|
}
|
|
568
344
|
|
|
569
|
-
|
|
570
|
-
const entries = content
|
|
571
|
-
.split('\n')
|
|
572
|
-
.filter((line) => {
|
|
573
|
-
const trimmed = line.trim()
|
|
574
|
-
// Accept lines starting with '- (' or directly with '(' for changelog entries
|
|
575
|
-
return trimmed.startsWith('- (') || trimmed.startsWith('(')
|
|
576
|
-
})
|
|
577
|
-
.map((line) => {
|
|
578
|
-
const trimmed = line.trim()
|
|
579
|
-
// Ensure all entries start with '- ' for consistent formatting
|
|
580
|
-
return trimmed.startsWith('- ') ? trimmed : `- ${trimmed}`
|
|
581
|
-
})
|
|
582
|
-
return {
|
|
583
|
-
entries,
|
|
584
|
-
changes: enhancedChanges,
|
|
585
|
-
summary: changesSummary,
|
|
586
|
-
}
|
|
345
|
+
return content
|
|
587
346
|
} catch (error) {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
console.warn(colors.warningMessage('⚠️ AI provider connection failed'))
|
|
591
|
-
console.warn(
|
|
592
|
-
colors.infoMessage('💡 Check your internet connection and provider configuration')
|
|
593
|
-
)
|
|
594
|
-
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
595
|
-
console.warn(colors.warningMessage('⚠️ Authentication failed'))
|
|
596
|
-
console.warn(colors.infoMessage('💡 Run `ai-changelog init` to configure your API key'))
|
|
597
|
-
} else {
|
|
598
|
-
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
599
|
-
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// Return basic entries from the changes we were given instead of getting fresh ones
|
|
603
|
-
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
604
|
-
const basicEntries = fallbackChanges.map((change) => {
|
|
605
|
-
const filePath = change.filePath || change.path || 'unknown file'
|
|
606
|
-
const status = change.status || 'M'
|
|
607
|
-
const changeType =
|
|
608
|
-
status === 'M'
|
|
609
|
-
? 'update'
|
|
610
|
-
: status === 'A'
|
|
611
|
-
? 'feature'
|
|
612
|
-
: status === 'D'
|
|
613
|
-
? 'remove'
|
|
614
|
-
: 'chore'
|
|
615
|
-
const changeDesc =
|
|
616
|
-
status === 'M'
|
|
617
|
-
? 'updated'
|
|
618
|
-
: status === 'A'
|
|
619
|
-
? 'added'
|
|
620
|
-
: status === 'D'
|
|
621
|
-
? 'deleted'
|
|
622
|
-
: 'changed'
|
|
623
|
-
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
return { entries: basicEntries }
|
|
347
|
+
console.error(colors.errorMessage(`Failed to generate workspace changelog: ${error.message}`))
|
|
348
|
+
throw error
|
|
627
349
|
}
|
|
628
350
|
}
|
|
629
351
|
|
|
630
|
-
|
|
631
|
-
const result = await this.generateComprehensiveWorkspaceChangelog(options)
|
|
352
|
+
// Helper methods
|
|
632
353
|
|
|
633
|
-
|
|
634
|
-
|
|
354
|
+
/**
|
|
355
|
+
* Generate basic workspace changelog without AI
|
|
356
|
+
*/
|
|
357
|
+
generateBasicWorkspaceChangelog(workspaceData) {
|
|
358
|
+
let content = '# Workspace Changes\n\n'
|
|
359
|
+
|
|
360
|
+
if (workspaceData.metrics) {
|
|
361
|
+
content += '## Summary\n\n'
|
|
362
|
+
content += `- Staged files: ${workspaceData.metrics.stagedFiles}\n`
|
|
363
|
+
content += `- Unstaged files: ${workspaceData.metrics.unstagedFiles}\n`
|
|
364
|
+
content += `- Untracked files: ${workspaceData.metrics.untrackedFiles}\n\n`
|
|
635
365
|
}
|
|
636
366
|
|
|
637
|
-
|
|
367
|
+
return content
|
|
368
|
+
}
|
|
638
369
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
}
|
|
370
|
+
/**
|
|
371
|
+
* Generate rule-based changelog fallback
|
|
372
|
+
*/
|
|
373
|
+
generateRuleBasedChangelog(changes) {
|
|
374
|
+
let content = '# Changes Summary\n\n'
|
|
646
375
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
changelog += this.generateContextSection(result.context)
|
|
376
|
+
for (const change of changes) {
|
|
377
|
+
content += `- ${change.type || 'Modified'}: ${change.file}\n`
|
|
650
378
|
}
|
|
651
379
|
|
|
652
|
-
return
|
|
653
|
-
...result,
|
|
654
|
-
changelog,
|
|
655
|
-
version,
|
|
656
|
-
}
|
|
380
|
+
return content
|
|
657
381
|
}
|
|
658
382
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
section += `- **Complexity:** ${context.complexity}\n\n`
|
|
665
|
-
|
|
666
|
-
if (context.recommendations.length > 0) {
|
|
667
|
-
section += '### Recommendations\n\n'
|
|
668
|
-
context.recommendations.forEach((rec) => {
|
|
669
|
-
section += `- ${rec}\n`
|
|
670
|
-
})
|
|
671
|
-
section += '\n'
|
|
672
|
-
}
|
|
383
|
+
/**
|
|
384
|
+
* Assess change complexity
|
|
385
|
+
*/
|
|
386
|
+
assessChangeComplexity(diff) {
|
|
387
|
+
if (!diff) return 'low'
|
|
673
388
|
|
|
674
|
-
|
|
389
|
+
const lines = diff.split('\n').length
|
|
390
|
+
if (lines > 100) return 'high'
|
|
391
|
+
if (lines > 20) return 'medium'
|
|
392
|
+
return 'low'
|
|
675
393
|
}
|
|
676
394
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Assess change impact
|
|
397
|
+
*/
|
|
398
|
+
assessChangeImpact(file, diff) {
|
|
399
|
+
const criticalFiles = ['package.json', 'README.md', 'Dockerfile', '.env']
|
|
400
|
+
const isCritical = criticalFiles.some((critical) => file.includes(critical))
|
|
401
|
+
|
|
402
|
+
if (isCritical) return 'high'
|
|
403
|
+
if (file.includes('test') || file.includes('spec')) return 'low'
|
|
404
|
+
return 'medium'
|
|
684
405
|
}
|
|
685
406
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Categorize a change
|
|
409
|
+
*/
|
|
410
|
+
categorizeChange(change) {
|
|
411
|
+
const file = change.file.toLowerCase()
|
|
412
|
+
|
|
413
|
+
if (file.includes('test') || file.includes('spec')) return 'tests'
|
|
414
|
+
if (file.includes('readme') || file.includes('doc')) return 'docs'
|
|
415
|
+
if (file.includes('fix') || change.type === 'fix') return 'fixes'
|
|
416
|
+
if (file.includes('feat') || change.type === 'feature') return 'features'
|
|
417
|
+
if (file.includes('config') || file.includes('package')) return 'chores'
|
|
418
|
+
|
|
419
|
+
return 'improvements'
|
|
691
420
|
}
|
|
692
421
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
422
|
+
/**
|
|
423
|
+
* Format section title
|
|
424
|
+
*/
|
|
425
|
+
formatSectionTitle(section) {
|
|
426
|
+
const titles = {
|
|
427
|
+
features: 'Features',
|
|
428
|
+
fixes: 'Bug Fixes',
|
|
429
|
+
improvements: 'Improvements',
|
|
430
|
+
docs: 'Documentation',
|
|
431
|
+
tests: 'Tests',
|
|
432
|
+
chores: 'Maintenance',
|
|
433
|
+
}
|
|
434
|
+
return titles[section] || section
|
|
699
435
|
}
|
|
700
436
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
437
|
+
/**
|
|
438
|
+
* Format change entry
|
|
439
|
+
*/
|
|
440
|
+
formatChangeEntry(change) {
|
|
441
|
+
const impact = change.impact ? `[${change.impact}]` : ''
|
|
442
|
+
const complexity = change.complexity ? `{${change.complexity}}` : ''
|
|
443
|
+
return `${change.file} ${impact} ${complexity}`.trim()
|
|
706
444
|
}
|
|
707
445
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
446
|
+
/**
|
|
447
|
+
* Group entries by type
|
|
448
|
+
*/
|
|
449
|
+
groupEntriesByType(entries) {
|
|
450
|
+
return entries.reduce((groups, entry) => {
|
|
451
|
+
const type = entry.type || 'unknown'
|
|
452
|
+
if (!groups[type]) groups[type] = []
|
|
453
|
+
groups[type].push(entry)
|
|
454
|
+
return groups
|
|
455
|
+
}, {})
|
|
713
456
|
}
|
|
714
457
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
458
|
+
/**
|
|
459
|
+
* Format entry type for display
|
|
460
|
+
*/
|
|
461
|
+
formatEntryType(type) {
|
|
462
|
+
const types = {
|
|
463
|
+
staged: 'Staged Changes',
|
|
464
|
+
unstaged: 'Unstaged Changes',
|
|
465
|
+
untracked: 'New Files',
|
|
719
466
|
}
|
|
467
|
+
return types[type] || type
|
|
720
468
|
}
|
|
721
469
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
470
|
+
/**
|
|
471
|
+
* Initialize workspace for analysis
|
|
472
|
+
* @returns {Promise<boolean>} True if initialization successful
|
|
473
|
+
*/
|
|
474
|
+
async initializeWorkspace() {
|
|
475
|
+
try {
|
|
476
|
+
console.log(colors.processingMessage('🔧 Initializing workspace...'))
|
|
477
|
+
|
|
478
|
+
// Reset metrics
|
|
479
|
+
this.cleanup()
|
|
480
|
+
|
|
481
|
+
// Validate git repository
|
|
482
|
+
const isValid = await this.validateWorkspace()
|
|
483
|
+
if (!isValid) {
|
|
484
|
+
return false
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Perform initial analysis
|
|
488
|
+
await this.analyzeWorkspaceChanges()
|
|
489
|
+
|
|
490
|
+
console.log(colors.successMessage('✅ Workspace initialized successfully'))
|
|
491
|
+
return true
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error(colors.errorMessage(`Failed to initialize workspace: ${error.message}`))
|
|
494
|
+
return false
|
|
726
495
|
}
|
|
727
496
|
}
|
|
728
497
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
498
|
+
/**
|
|
499
|
+
* Validate workspace structure
|
|
500
|
+
* @returns {boolean} True if workspace structure is valid
|
|
501
|
+
*/
|
|
502
|
+
validateWorkspaceStructure() {
|
|
503
|
+
try {
|
|
504
|
+
// Check if we have the required services
|
|
505
|
+
if (!this.gitService) {
|
|
506
|
+
console.error(colors.errorMessage('Git service not available'))
|
|
507
|
+
return false
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Check if workspace has any changes to analyze
|
|
511
|
+
if (!this.hasWorkspaceChanges()) {
|
|
512
|
+
console.log(colors.infoMessage('No workspace changes detected'))
|
|
513
|
+
return true // Still valid, just nothing to do
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
return true
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error(colors.errorMessage(`Workspace structure validation failed: ${error.message}`))
|
|
519
|
+
return false
|
|
734
520
|
}
|
|
735
521
|
}
|
|
736
522
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Clean up workspace analysis resources
|
|
525
|
+
*/
|
|
526
|
+
cleanup() {
|
|
527
|
+
this.workspaceMetrics = {
|
|
528
|
+
unstagedFiles: 0,
|
|
529
|
+
stagedFiles: 0,
|
|
530
|
+
untrackedFiles: 0,
|
|
531
|
+
modifiedLines: 0,
|
|
741
532
|
}
|
|
742
533
|
}
|
|
743
534
|
}
|