@entro314labs/ai-changelog-generator 3.2.0 → 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/CHANGELOG.md +41 -10
- 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 +11 -2
- 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 +758 -5
- package/src/domains/changelog/changelog.service.js +711 -13
- package/src/domains/changelog/workspace-changelog.service.js +429 -571
- 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,676 +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
|
-
|
|
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...'))
|
|
62
|
+
|
|
63
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
64
|
+
|
|
65
|
+
if (!workspaceData.analysis || workspaceData.analysis.changes.length === 0) {
|
|
66
|
+
return colors.infoMessage('No significant workspace changes detected.')
|
|
74
67
|
}
|
|
75
68
|
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
}
|
|
69
|
+
// Generate preview using parent class methods
|
|
70
|
+
const previewContent = await this.generateChangelogFromAnalysis(workspaceData.analysis)
|
|
101
71
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
: ''
|
|
105
|
-
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
72
|
+
return previewContent
|
|
73
|
+
}
|
|
106
74
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
}
|
|
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(),
|
|
84
|
+
}
|
|
85
|
+
}
|
|
164
86
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
})
|
|
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
|
+
}
|
|
190
98
|
|
|
191
|
-
|
|
192
|
-
|
|
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 = []
|
|
193
106
|
|
|
194
|
-
|
|
195
|
-
|
|
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
|
+
}
|
|
196
116
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
changelog = `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased] - ${timestamp}\n\n${changelog}`
|
|
200
|
-
}
|
|
117
|
+
return parts.length > 0 ? parts.join(', ') : 'No changes'
|
|
118
|
+
}
|
|
201
119
|
|
|
202
|
-
|
|
203
|
-
|
|
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
|
|
131
|
+
}
|
|
204
132
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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}`))
|
|
133
|
+
// Check if workspace has changes
|
|
134
|
+
if (!this.hasWorkspaceChanges()) {
|
|
135
|
+
console.log(colors.infoMessage('No workspace changes detected'))
|
|
136
|
+
return false
|
|
219
137
|
}
|
|
220
138
|
|
|
221
|
-
|
|
222
|
-
|
|
139
|
+
return true
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(colors.errorMessage(`Workspace validation failed: ${error.message}`))
|
|
142
|
+
return false
|
|
223
143
|
}
|
|
224
144
|
}
|
|
225
145
|
|
|
226
|
-
|
|
227
|
-
|
|
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...'))
|
|
228
152
|
|
|
229
|
-
|
|
153
|
+
try {
|
|
154
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
230
155
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
156
|
+
if (!workspaceData.analysis) {
|
|
157
|
+
return this.generateBasicWorkspaceChangelog(workspaceData)
|
|
158
|
+
}
|
|
234
159
|
|
|
235
|
-
|
|
236
|
-
|
|
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
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
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.')
|
|
177
|
+
}
|
|
237
178
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
+
}
|
|
243
185
|
|
|
244
|
-
|
|
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)
|
|
191
|
+
}
|
|
245
192
|
}
|
|
246
193
|
|
|
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
|
+
*/
|
|
247
199
|
async enhanceChangesWithDiff(changes) {
|
|
248
200
|
const enhancedChanges = []
|
|
249
201
|
|
|
250
202
|
for (const change of changes) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
}
|
|
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)
|
|
278
218
|
}
|
|
279
|
-
|
|
280
|
-
enhancedChanges.push(enhancedChange)
|
|
281
219
|
}
|
|
282
220
|
|
|
283
221
|
return enhancedChanges
|
|
284
222
|
}
|
|
285
223
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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: [],
|
|
294
237
|
}
|
|
295
238
|
|
|
296
|
-
|
|
297
|
-
|
|
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
|
+
}
|
|
298
246
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
247
|
+
// Generate markdown content
|
|
248
|
+
let content = '# Workspace Changes\n\n'
|
|
302
249
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
change.importance === 'critical' ||
|
|
307
|
-
change.category === 'configuration' ||
|
|
308
|
-
change.status === 'D'
|
|
309
|
-
)
|
|
250
|
+
for (const [section, changes] of Object.entries(sections)) {
|
|
251
|
+
if (changes.length > 0) {
|
|
252
|
+
content += `## ${this.formatSectionTitle(section)}\n\n`
|
|
310
253
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (highRiskFiles.length > 0) {
|
|
315
|
-
return 'medium'
|
|
316
|
-
}
|
|
317
|
-
return 'low'
|
|
318
|
-
}
|
|
254
|
+
for (const change of changes) {
|
|
255
|
+
content += `- ${this.formatChangeEntry(change)}\n`
|
|
256
|
+
}
|
|
319
257
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return 'high'
|
|
323
|
-
}
|
|
324
|
-
if (changes.length > 5) {
|
|
325
|
-
return 'medium'
|
|
258
|
+
content += '\n'
|
|
259
|
+
}
|
|
326
260
|
}
|
|
327
|
-
return 'low'
|
|
328
|
-
}
|
|
329
261
|
|
|
330
|
-
|
|
331
|
-
|
|
262
|
+
return content
|
|
263
|
+
}
|
|
332
264
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
+
}
|
|
337
285
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
+
}
|
|
341
297
|
|
|
342
|
-
|
|
343
|
-
|
|
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
|
+
}
|
|
344
309
|
}
|
|
345
310
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
311
|
+
return entries
|
|
312
|
+
}
|
|
349
313
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Generate workspace changelog
|
|
316
|
+
* @returns {Promise<string>} Workspace changelog content
|
|
317
|
+
*/
|
|
318
|
+
async generateWorkspaceChangelog() {
|
|
319
|
+
console.log(colors.processingMessage('📝 Generating workspace changelog...'))
|
|
353
320
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
recommendations.push(`Review ${deletedFiles.length} deleted files before committing`)
|
|
357
|
-
}
|
|
321
|
+
try {
|
|
322
|
+
const entries = await this.generateCommitStyleWorkingDirectoryEntries()
|
|
358
323
|
|
|
359
|
-
|
|
360
|
-
|
|
324
|
+
if (entries.length === 0) {
|
|
325
|
+
return colors.infoMessage('No workspace changes to include in changelog.')
|
|
326
|
+
}
|
|
361
327
|
|
|
362
|
-
|
|
363
|
-
|
|
328
|
+
let content = '# Workspace Changes\n\n'
|
|
329
|
+
content += `Generated on: ${new Date().toLocaleString()}\n\n`
|
|
364
330
|
|
|
365
|
-
|
|
366
|
-
const categoryIcon = this.getCategoryIcon(category)
|
|
367
|
-
content += `### ${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)} (${files.length} files)\n\n`
|
|
331
|
+
const groupedEntries = this.groupEntriesByType(entries)
|
|
368
332
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
})
|
|
333
|
+
for (const [type, typeEntries] of Object.entries(groupedEntries)) {
|
|
334
|
+
if (typeEntries.length > 0) {
|
|
335
|
+
content += `## ${this.formatEntryType(type)} (${typeEntries.length})\n\n`
|
|
373
336
|
|
|
374
|
-
|
|
375
|
-
|
|
337
|
+
for (const entry of typeEntries) {
|
|
338
|
+
content += `- ${entry.message}\n`
|
|
339
|
+
}
|
|
376
340
|
|
|
377
|
-
|
|
378
|
-
|
|
341
|
+
content += '\n'
|
|
342
|
+
}
|
|
343
|
+
}
|
|
379
344
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
documentation: '📚',
|
|
385
|
-
configuration: '⚙️',
|
|
386
|
-
frontend: '🎨',
|
|
387
|
-
assets: '🖼️',
|
|
388
|
-
build: '🔧',
|
|
389
|
-
other: '📄',
|
|
345
|
+
return content
|
|
346
|
+
} catch (error) {
|
|
347
|
+
console.error(colors.errorMessage(`Failed to generate workspace changelog: ${error.message}`))
|
|
348
|
+
throw error
|
|
390
349
|
}
|
|
391
|
-
return icons[category] || '📄'
|
|
392
350
|
}
|
|
393
351
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
return icons[status] || '📄'
|
|
403
|
-
}
|
|
352
|
+
// Helper methods
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Generate basic workspace changelog without AI
|
|
356
|
+
*/
|
|
357
|
+
generateBasicWorkspaceChangelog(workspaceData) {
|
|
358
|
+
let content = '# Workspace Changes\n\n'
|
|
404
359
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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`
|
|
408
365
|
}
|
|
409
|
-
|
|
366
|
+
|
|
367
|
+
return content
|
|
410
368
|
}
|
|
411
369
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
370
|
+
/**
|
|
371
|
+
* Generate rule-based changelog fallback
|
|
372
|
+
*/
|
|
373
|
+
generateRuleBasedChangelog(changes) {
|
|
374
|
+
let content = '# Changes Summary\n\n'
|
|
375
|
+
|
|
376
|
+
for (const change of changes) {
|
|
377
|
+
content += `- ${change.type || 'Modified'}: ${change.file}\n`
|
|
420
378
|
}
|
|
421
379
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
return { entries: [] }
|
|
425
|
-
}
|
|
380
|
+
return content
|
|
381
|
+
}
|
|
426
382
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
}
|
|
383
|
+
/**
|
|
384
|
+
* Assess change complexity
|
|
385
|
+
*/
|
|
386
|
+
assessChangeComplexity(diff) {
|
|
387
|
+
if (!diff) return 'low'
|
|
457
388
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
389
|
+
const lines = diff.split('\n').length
|
|
390
|
+
if (lines > 100) return 'high'
|
|
391
|
+
if (lines > 20) return 'medium'
|
|
392
|
+
return 'low'
|
|
393
|
+
}
|
|
462
394
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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
|
|
518
|
-
}
|
|
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))
|
|
519
401
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
402
|
+
if (isCritical) return 'high'
|
|
403
|
+
if (file.includes('test') || file.includes('spec')) return 'low'
|
|
404
|
+
return 'medium'
|
|
405
|
+
}
|
|
524
406
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
407
|
+
/**
|
|
408
|
+
* Categorize a change
|
|
409
|
+
*/
|
|
410
|
+
categorizeChange(change) {
|
|
411
|
+
const file = change.file.toLowerCase()
|
|
529
412
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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'
|
|
534
418
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
// Check if content is valid before processing
|
|
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
|
-
})
|
|
419
|
+
return 'improvements'
|
|
420
|
+
}
|
|
565
421
|
|
|
566
|
-
|
|
567
|
-
|
|
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
|
|
435
|
+
}
|
|
568
436
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
}
|
|
587
|
-
} catch (error) {
|
|
588
|
-
// Provide specific guidance based on error type
|
|
589
|
-
if (error.message.includes('fetch failed') || error.message.includes('connection')) {
|
|
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
|
-
}
|
|
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()
|
|
444
|
+
}
|
|
601
445
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
: 'changed'
|
|
623
|
-
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
return { entries: basicEntries }
|
|
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
|
+
}, {})
|
|
456
|
+
}
|
|
457
|
+
|
|
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',
|
|
627
466
|
}
|
|
467
|
+
return types[type] || type
|
|
628
468
|
}
|
|
629
469
|
|
|
630
|
-
|
|
631
|
-
|
|
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...'))
|
|
632
477
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
478
|
+
// Reset metrics
|
|
479
|
+
this.cleanup()
|
|
636
480
|
|
|
637
|
-
|
|
481
|
+
// Validate git repository
|
|
482
|
+
const isValid = await this.validateWorkspace()
|
|
483
|
+
if (!isValid) {
|
|
484
|
+
return false
|
|
485
|
+
}
|
|
638
486
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
changelog = changelog.replace(
|
|
642
|
-
/# Working Directory Changelog/,
|
|
643
|
-
`# Working Directory Changelog - Version ${version}`
|
|
644
|
-
)
|
|
645
|
-
}
|
|
487
|
+
// Perform initial analysis
|
|
488
|
+
await this.analyzeWorkspaceChanges()
|
|
646
489
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
|
650
495
|
}
|
|
496
|
+
}
|
|
651
497
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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
|
|
656
520
|
}
|
|
657
521
|
}
|
|
658
522
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
context.recommendations.forEach((rec) => {
|
|
669
|
-
section += `- ${rec}\n`
|
|
670
|
-
})
|
|
671
|
-
section += '\n'
|
|
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,
|
|
672
532
|
}
|
|
673
|
-
|
|
674
|
-
return section
|
|
675
533
|
}
|
|
676
534
|
}
|