@entro314labs/ai-changelog-generator 3.2.1 → 3.6.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 +42 -2
- package/README.md +21 -1
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +6 -3
- package/manifest.json +177 -0
- package/package.json +76 -81
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +19 -203
- package/src/cli.js +16 -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 +623 -32
- package/src/domains/changelog/workspace-changelog.service.js +445 -622
- 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 +25 -11
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +105 -32
- 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 +8 -10
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +83 -63
- package/types/index.d.ts +61 -68
- package/src/domains/git/git-repository.analyzer.js +0 -678
|
@@ -1,743 +1,566 @@
|
|
|
1
1
|
import colors from '../../shared/constants/colors.js'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
assessFileImportance,
|
|
6
|
-
categorizeFile,
|
|
7
|
-
detectLanguage,
|
|
8
|
-
getWorkingDirectoryChanges,
|
|
9
|
-
summarizeFileChanges,
|
|
10
|
-
} from '../../shared/utils/utils.js'
|
|
2
|
+
import { getWorkingDirectoryChanges } from '../../shared/utils/utils.js'
|
|
3
|
+
import { ChangelogService } from './changelog.service.js'
|
|
11
4
|
|
|
12
5
|
/**
|
|
13
|
-
*
|
|
6
|
+
* WorkspaceChangelogService extends ChangelogService with workspace-specific functionality
|
|
7
|
+
* for analyzing uncommitted changes and workspace state
|
|
14
8
|
*
|
|
15
|
-
*
|
|
9
|
+
* @deprecated This class is being phased out. The workspace changelog logic has been
|
|
10
|
+
* consolidated into ChangelogService. This class is maintained for backward compatibility
|
|
11
|
+
* with existing tests but should not be used in new code.
|
|
16
12
|
*/
|
|
17
|
-
export class WorkspaceChangelogService {
|
|
18
|
-
constructor(aiAnalysisService,
|
|
19
|
-
|
|
20
|
-
this.
|
|
13
|
+
export class WorkspaceChangelogService extends ChangelogService {
|
|
14
|
+
constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
|
|
15
|
+
super(gitService, aiAnalysisService, analysisEngine, configManager)
|
|
16
|
+
this.workspaceMetrics = {
|
|
17
|
+
unstagedFiles: 0,
|
|
18
|
+
stagedFiles: 0,
|
|
19
|
+
untrackedFiles: 0,
|
|
20
|
+
modifiedLines: 0,
|
|
21
|
+
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Analyze workspace changes without committing
|
|
26
|
+
* @returns {Promise<Object>} Workspace analysis results
|
|
27
|
+
*/
|
|
28
|
+
async analyzeWorkspaceChanges() {
|
|
29
|
+
console.log(colors.processingMessage('🔍 Analyzing workspace changes...'))
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
try {
|
|
32
|
+
// Get git status information using utility function
|
|
33
|
+
const changes = getWorkingDirectoryChanges()
|
|
34
|
+
|
|
35
|
+
// Categorize changes
|
|
36
|
+
const status = {
|
|
37
|
+
staged: [],
|
|
38
|
+
unstaged: [],
|
|
39
|
+
untracked: []
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
enablePatternDetection: true,
|
|
42
|
+
changes.forEach(change => {
|
|
43
|
+
const statusCode = change.status || '??'
|
|
44
|
+
if (statusCode.startsWith('??')) {
|
|
45
|
+
status.untracked.push(change.filePath)
|
|
46
|
+
} else if (statusCode[0] !== ' ' && statusCode[0] !== '?') {
|
|
47
|
+
status.staged.push(change.filePath)
|
|
48
|
+
} else {
|
|
49
|
+
status.unstaged.push(change.filePath)
|
|
50
|
+
}
|
|
43
51
|
})
|
|
44
52
|
|
|
45
|
-
|
|
53
|
+
// Update workspace metrics
|
|
54
|
+
this.workspaceMetrics.unstagedFiles = status.unstaged?.length || 0
|
|
55
|
+
this.workspaceMetrics.stagedFiles = status.staged?.length || 0
|
|
56
|
+
this.workspaceMetrics.untrackedFiles = status.untracked?.length || 0
|
|
46
57
|
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
// Get detailed diff for staged/unstaged changes (empty for now)
|
|
59
|
+
const diff = ''
|
|
60
|
+
|
|
61
|
+
// Use analysis engine if available
|
|
62
|
+
let analysis = null
|
|
63
|
+
if (this.analysisEngine) {
|
|
64
|
+
analysis = await this.analysisEngine.analyzeCurrentChanges()
|
|
65
|
+
}
|
|
54
66
|
|
|
55
67
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
summary: changesSummary,
|
|
61
|
-
filesProcessed: processedResult.filesProcessed,
|
|
62
|
-
filesSkipped: processedResult.filesSkipped,
|
|
68
|
+
status,
|
|
69
|
+
diff,
|
|
70
|
+
analysis,
|
|
71
|
+
metrics: this.workspaceMetrics,
|
|
63
72
|
}
|
|
64
73
|
} catch (error) {
|
|
65
|
-
console.error(colors.errorMessage(
|
|
74
|
+
console.error(colors.errorMessage(`Failed to analyze workspace: ${error.message}`))
|
|
66
75
|
throw error
|
|
67
76
|
}
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Generate changelog preview for workspace changes
|
|
81
|
+
* @returns {Promise<string>} Preview changelog content
|
|
82
|
+
*/
|
|
83
|
+
async generateWorkspacePreview() {
|
|
84
|
+
console.log(colors.processingMessage('📝 Generating workspace preview...'))
|
|
75
85
|
|
|
76
|
-
|
|
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
|
-
}
|
|
86
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
193
87
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Ensure proper changelog format with Keep a Changelog header
|
|
198
|
-
if (!changelog.includes('# ')) {
|
|
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
|
-
}
|
|
88
|
+
if (!workspaceData.analysis || workspaceData.analysis.changes.length === 0) {
|
|
89
|
+
return colors.infoMessage('No significant workspace changes detected.')
|
|
90
|
+
}
|
|
201
91
|
|
|
202
|
-
|
|
203
|
-
|
|
92
|
+
// Generate preview using parent class methods
|
|
93
|
+
const previewContent = await this.generateChangelogFromAnalysis(workspaceData.analysis)
|
|
204
94
|
|
|
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
|
-
}
|
|
95
|
+
return previewContent
|
|
96
|
+
}
|
|
220
97
|
|
|
221
|
-
|
|
222
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Get workspace statistics
|
|
100
|
+
* @returns {Object} Workspace statistics
|
|
101
|
+
*/
|
|
102
|
+
getWorkspaceStats() {
|
|
103
|
+
return {
|
|
104
|
+
...this.workspaceMetrics,
|
|
105
|
+
hasChanges: this.hasWorkspaceChanges(),
|
|
106
|
+
summary: this.getWorkspaceSummary(),
|
|
223
107
|
}
|
|
224
108
|
}
|
|
225
109
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Check if workspace has any changes
|
|
112
|
+
* @returns {boolean} True if workspace has changes
|
|
113
|
+
*/
|
|
114
|
+
hasWorkspaceChanges() {
|
|
115
|
+
return (
|
|
116
|
+
this.workspaceMetrics.unstagedFiles > 0 ||
|
|
117
|
+
this.workspaceMetrics.stagedFiles > 0 ||
|
|
118
|
+
this.workspaceMetrics.untrackedFiles > 0
|
|
119
|
+
)
|
|
120
|
+
}
|
|
234
121
|
|
|
235
|
-
|
|
236
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Get workspace summary string
|
|
124
|
+
* @returns {string} Human-readable workspace summary
|
|
125
|
+
*/
|
|
126
|
+
getWorkspaceSummary() {
|
|
127
|
+
const { unstagedFiles, stagedFiles, untrackedFiles } = this.workspaceMetrics
|
|
128
|
+
const parts = []
|
|
237
129
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
130
|
+
if (stagedFiles > 0) {
|
|
131
|
+
parts.push(`${stagedFiles} staged`)
|
|
132
|
+
}
|
|
133
|
+
if (unstagedFiles > 0) {
|
|
134
|
+
parts.push(`${unstagedFiles} unstaged`)
|
|
135
|
+
}
|
|
136
|
+
if (untrackedFiles > 0) {
|
|
137
|
+
parts.push(`${untrackedFiles} untracked`)
|
|
138
|
+
}
|
|
243
139
|
|
|
244
|
-
return
|
|
140
|
+
return parts.length > 0 ? parts.join(', ') : 'No changes'
|
|
245
141
|
}
|
|
246
142
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Validate workspace state before operations
|
|
145
|
+
* @returns {Promise<boolean>} True if workspace is valid
|
|
146
|
+
*/
|
|
147
|
+
async validateWorkspace() {
|
|
148
|
+
try {
|
|
149
|
+
// Check if we're in a git repository
|
|
150
|
+
let isGitRepo = false
|
|
151
|
+
try {
|
|
152
|
+
const { execSync } = await import('node:child_process')
|
|
153
|
+
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
|
|
154
|
+
isGitRepo = true
|
|
155
|
+
} catch {
|
|
156
|
+
isGitRepo = false
|
|
257
157
|
}
|
|
258
158
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const diffAnalysis = await this.gitService.analyzeWorkingDirectoryFileChange(
|
|
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
|
-
}
|
|
159
|
+
if (!isGitRepo) {
|
|
160
|
+
console.error(colors.errorMessage('Not in a git repository'))
|
|
161
|
+
return false
|
|
278
162
|
}
|
|
279
163
|
|
|
280
|
-
|
|
281
|
-
}
|
|
164
|
+
const changes = getWorkingDirectoryChanges()
|
|
282
165
|
|
|
283
|
-
|
|
284
|
-
|
|
166
|
+
// Check if workspace has changes
|
|
167
|
+
if (!this.hasWorkspaceChanges() && changes.length === 0) {
|
|
168
|
+
console.log(colors.infoMessage('No workspace changes detected'))
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
285
171
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
primaryCategory: this.getPrimaryCategory(summary.categories),
|
|
291
|
-
riskLevel: this.assessWorkspaceRisk(changes),
|
|
292
|
-
complexity: this.assessWorkspaceComplexity(changes),
|
|
293
|
-
recommendations: this.generateRecommendations(changes),
|
|
172
|
+
return true
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(colors.errorMessage(`Workspace validation failed: ${error.message}`))
|
|
175
|
+
return false
|
|
294
176
|
}
|
|
295
|
-
|
|
296
|
-
return context
|
|
297
177
|
}
|
|
298
178
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Generate comprehensive workspace changelog
|
|
181
|
+
* @returns {Promise<string>} Comprehensive changelog content
|
|
182
|
+
*/
|
|
183
|
+
async generateComprehensiveWorkspaceChangelog() {
|
|
184
|
+
console.log(colors.processingMessage('📋 Generating comprehensive workspace changelog...'))
|
|
302
185
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
(change) =>
|
|
306
|
-
change.importance === 'critical' ||
|
|
307
|
-
change.category === 'configuration' ||
|
|
308
|
-
change.status === 'D'
|
|
309
|
-
)
|
|
186
|
+
try {
|
|
187
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
310
188
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (highRiskFiles.length > 0) {
|
|
315
|
-
return 'medium'
|
|
316
|
-
}
|
|
317
|
-
return 'low'
|
|
318
|
-
}
|
|
189
|
+
if (!workspaceData.analysis) {
|
|
190
|
+
return this.generateBasicWorkspaceChangelog(workspaceData)
|
|
191
|
+
}
|
|
319
192
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
193
|
+
return await this.generateAIChangelogContentFromChanges(workspaceData.analysis.changes)
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(
|
|
196
|
+
colors.errorMessage(`Failed to generate comprehensive changelog: ${error.message}`)
|
|
197
|
+
)
|
|
198
|
+
throw error
|
|
326
199
|
}
|
|
327
|
-
return 'low'
|
|
328
200
|
}
|
|
329
201
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (hasSource && !hasTests) {
|
|
339
|
-
recommendations.push('Consider adding tests for source code changes')
|
|
202
|
+
/**
|
|
203
|
+
* Generate AI-powered changelog content from changes
|
|
204
|
+
* @param {Array} changes - Array of change objects
|
|
205
|
+
* @returns {Promise<string>} Generated changelog content
|
|
206
|
+
*/
|
|
207
|
+
async generateAIChangelogContentFromChanges(changes) {
|
|
208
|
+
if (!changes || changes.length === 0) {
|
|
209
|
+
return colors.infoMessage('No changes to process for changelog.')
|
|
340
210
|
}
|
|
341
211
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
212
|
+
try {
|
|
213
|
+
// Use AI analysis service if available
|
|
214
|
+
if (this.aiAnalysisService && this.aiAnalysisService.hasAI) {
|
|
215
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(changes)
|
|
216
|
+
return await this.generateChangelogContent(enhancedChanges)
|
|
217
|
+
}
|
|
345
218
|
|
|
346
|
-
|
|
347
|
-
|
|
219
|
+
// Fallback to rule-based generation
|
|
220
|
+
return this.generateRuleBasedChangelog(changes)
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error(colors.errorMessage(`AI changelog generation failed: ${error.message}`))
|
|
223
|
+
return this.generateRuleBasedChangelog(changes)
|
|
348
224
|
}
|
|
225
|
+
}
|
|
349
226
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
227
|
+
/**
|
|
228
|
+
* Enhance changes with diff information
|
|
229
|
+
* @param {Array} changes - Array of change objects
|
|
230
|
+
* @returns {Promise<Array>} Enhanced changes with diff data
|
|
231
|
+
*/
|
|
232
|
+
async enhanceChangesWithDiff(changes) {
|
|
233
|
+
const enhancedChanges = []
|
|
353
234
|
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
235
|
+
for (const change of changes) {
|
|
236
|
+
try {
|
|
237
|
+
// For workspace changelog service, we don't have detailed diffs
|
|
238
|
+
// Just add basic enhancement
|
|
239
|
+
enhancedChanges.push({
|
|
240
|
+
...change,
|
|
241
|
+
diff: '',
|
|
242
|
+
complexity: this.assessChangeComplexity(''),
|
|
243
|
+
impact: this.assessChangeImpact(change.file, ''),
|
|
244
|
+
})
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.warn(
|
|
247
|
+
colors.warningMessage(`Failed to enhance change for ${change.file}: ${error.message}`)
|
|
248
|
+
)
|
|
249
|
+
enhancedChanges.push(change)
|
|
250
|
+
}
|
|
357
251
|
}
|
|
358
252
|
|
|
359
|
-
return
|
|
253
|
+
return enhancedChanges
|
|
360
254
|
}
|
|
361
255
|
|
|
362
|
-
|
|
363
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Generate changelog content from enhanced changes
|
|
258
|
+
* @param {Array} enhancedChanges - Enhanced change objects
|
|
259
|
+
* @returns {Promise<string>} Generated changelog content
|
|
260
|
+
*/
|
|
261
|
+
async generateChangelogContent(enhancedChanges) {
|
|
262
|
+
const sections = {
|
|
263
|
+
features: [],
|
|
264
|
+
fixes: [],
|
|
265
|
+
improvements: [],
|
|
266
|
+
docs: [],
|
|
267
|
+
tests: [],
|
|
268
|
+
chores: [],
|
|
269
|
+
}
|
|
364
270
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
271
|
+
// Categorize changes
|
|
272
|
+
for (const change of enhancedChanges) {
|
|
273
|
+
const category = this.categorizeChange(change)
|
|
274
|
+
if (sections[category]) {
|
|
275
|
+
sections[category].push(change)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
368
278
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
content += `- ${statusIcon} ${file.path}\n`
|
|
372
|
-
})
|
|
279
|
+
// Generate markdown content
|
|
280
|
+
let content = '# Workspace Changes\n\n'
|
|
373
281
|
|
|
374
|
-
|
|
375
|
-
|
|
282
|
+
for (const [section, changes] of Object.entries(sections)) {
|
|
283
|
+
if (changes.length > 0) {
|
|
284
|
+
content += `## ${this.formatSectionTitle(section)}\n\n`
|
|
376
285
|
|
|
377
|
-
|
|
378
|
-
|
|
286
|
+
for (const change of changes) {
|
|
287
|
+
content += `- ${this.formatChangeEntry(change)}\n`
|
|
288
|
+
}
|
|
379
289
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
source: '💻',
|
|
383
|
-
tests: '🧪',
|
|
384
|
-
documentation: '📚',
|
|
385
|
-
configuration: '⚙️',
|
|
386
|
-
frontend: '🎨',
|
|
387
|
-
assets: '🖼️',
|
|
388
|
-
build: '🔧',
|
|
389
|
-
other: '📄',
|
|
290
|
+
content += '\n'
|
|
291
|
+
}
|
|
390
292
|
}
|
|
391
|
-
return icons[category] || '📄'
|
|
392
|
-
}
|
|
393
293
|
|
|
394
|
-
|
|
395
|
-
const icons = {
|
|
396
|
-
A: '➕', // Added
|
|
397
|
-
M: '✏️', // Modified
|
|
398
|
-
D: '❌', // Deleted
|
|
399
|
-
R: '📝', // Renamed
|
|
400
|
-
C: '📋', // Copied
|
|
401
|
-
}
|
|
402
|
-
return icons[status] || '📄'
|
|
294
|
+
return content
|
|
403
295
|
}
|
|
404
296
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Generate commit-style working directory entries
|
|
299
|
+
* @returns {Promise<Array>} Array of commit-style entries
|
|
300
|
+
*/
|
|
301
|
+
async generateCommitStyleWorkingDirectoryEntries() {
|
|
302
|
+
const workspaceData = await this.analyzeWorkspaceChanges()
|
|
303
|
+
const entries = []
|
|
304
|
+
|
|
305
|
+
if (workspaceData.status) {
|
|
306
|
+
// Process staged files
|
|
307
|
+
if (workspaceData.status.staged) {
|
|
308
|
+
for (const file of workspaceData.status.staged) {
|
|
309
|
+
entries.push({
|
|
310
|
+
type: 'staged',
|
|
311
|
+
file,
|
|
312
|
+
message: `Add: ${file}`,
|
|
313
|
+
timestamp: new Date().toISOString(),
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
}
|
|
411
317
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
318
|
+
// Process unstaged files
|
|
319
|
+
if (workspaceData.status.unstaged) {
|
|
320
|
+
for (const file of workspaceData.status.unstaged) {
|
|
321
|
+
entries.push({
|
|
322
|
+
type: 'unstaged',
|
|
323
|
+
file,
|
|
324
|
+
message: `Modify: ${file}`,
|
|
325
|
+
timestamp: new Date().toISOString(),
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
}
|
|
421
329
|
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
330
|
+
// Process untracked files
|
|
331
|
+
if (workspaceData.status.untracked) {
|
|
332
|
+
for (const file of workspaceData.status.untracked) {
|
|
333
|
+
entries.push({
|
|
334
|
+
type: 'untracked',
|
|
335
|
+
file,
|
|
336
|
+
message: `Create: ${file}`,
|
|
337
|
+
timestamp: new Date().toISOString(),
|
|
338
|
+
})
|
|
339
|
+
}
|
|
425
340
|
}
|
|
341
|
+
}
|
|
426
342
|
|
|
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
|
-
})
|
|
343
|
+
return entries
|
|
344
|
+
}
|
|
439
345
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
}
|
|
346
|
+
/**
|
|
347
|
+
* Generate workspace changelog
|
|
348
|
+
* @returns {Promise<string>} Workspace changelog content
|
|
349
|
+
*/
|
|
350
|
+
async generateWorkspaceChangelog() {
|
|
351
|
+
console.log(colors.processingMessage('📝 Generating workspace changelog...'))
|
|
457
352
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
: ''
|
|
461
|
-
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
353
|
+
try {
|
|
354
|
+
const entries = await this.generateCommitStyleWorkingDirectoryEntries()
|
|
462
355
|
|
|
463
|
-
|
|
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
|
|
356
|
+
if (entries.length === 0) {
|
|
357
|
+
return colors.infoMessage('No workspace changes to include in changelog.')
|
|
518
358
|
}
|
|
519
359
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
maxTokens = Math.min(maxTokens + 1500, 6000)
|
|
523
|
-
}
|
|
360
|
+
let content = '# Workspace Changes\n\n'
|
|
361
|
+
content += `Generated on: ${new Date().toLocaleString()}\n\n`
|
|
524
362
|
|
|
525
|
-
const
|
|
526
|
-
max_tokens: maxTokens,
|
|
527
|
-
temperature: 0.3,
|
|
528
|
-
}
|
|
363
|
+
const groupedEntries = this.groupEntriesByType(entries)
|
|
529
364
|
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
)
|
|
365
|
+
for (const [type, typeEntries] of Object.entries(groupedEntries)) {
|
|
366
|
+
if (typeEntries.length > 0) {
|
|
367
|
+
content += `## ${this.formatEntryType(type)} (${typeEntries.length})\n\n`
|
|
534
368
|
|
|
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
|
-
})
|
|
369
|
+
for (const entry of typeEntries) {
|
|
370
|
+
content += `- ${entry.message}\n`
|
|
371
|
+
}
|
|
565
372
|
|
|
566
|
-
|
|
373
|
+
content += '\n'
|
|
374
|
+
}
|
|
567
375
|
}
|
|
568
376
|
|
|
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
|
-
}
|
|
377
|
+
return content
|
|
587
378
|
} 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 }
|
|
379
|
+
console.error(colors.errorMessage(`Failed to generate workspace changelog: ${error.message}`))
|
|
380
|
+
throw error
|
|
627
381
|
}
|
|
628
382
|
}
|
|
629
383
|
|
|
630
|
-
|
|
631
|
-
const result = await this.generateComprehensiveWorkspaceChangelog(options)
|
|
384
|
+
// Helper methods
|
|
632
385
|
|
|
633
|
-
|
|
634
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Generate basic workspace changelog without AI
|
|
388
|
+
*/
|
|
389
|
+
generateBasicWorkspaceChangelog(workspaceData) {
|
|
390
|
+
let content = '# Workspace Changes\n\n'
|
|
391
|
+
|
|
392
|
+
if (workspaceData.metrics) {
|
|
393
|
+
content += '## Summary\n\n'
|
|
394
|
+
content += `- Staged files: ${workspaceData.metrics.stagedFiles}\n`
|
|
395
|
+
content += `- Unstaged files: ${workspaceData.metrics.unstagedFiles}\n`
|
|
396
|
+
content += `- Untracked files: ${workspaceData.metrics.untrackedFiles}\n\n`
|
|
635
397
|
}
|
|
636
398
|
|
|
637
|
-
|
|
399
|
+
return content
|
|
400
|
+
}
|
|
638
401
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
)
|
|
645
|
-
}
|
|
402
|
+
/**
|
|
403
|
+
* Generate rule-based changelog fallback
|
|
404
|
+
*/
|
|
405
|
+
generateRuleBasedChangelog(changes) {
|
|
406
|
+
let content = '# Changes Summary\n\n'
|
|
646
407
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
changelog += this.generateContextSection(result.context)
|
|
408
|
+
for (const change of changes) {
|
|
409
|
+
content += `- ${change.type || 'Modified'}: ${change.file}\n`
|
|
650
410
|
}
|
|
651
411
|
|
|
652
|
-
return
|
|
653
|
-
...result,
|
|
654
|
-
changelog,
|
|
655
|
-
version,
|
|
656
|
-
}
|
|
412
|
+
return content
|
|
657
413
|
}
|
|
658
414
|
|
|
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
|
-
}
|
|
415
|
+
/**
|
|
416
|
+
* Assess change complexity
|
|
417
|
+
*/
|
|
418
|
+
assessChangeComplexity(diff) {
|
|
419
|
+
if (!diff) return 'low'
|
|
673
420
|
|
|
674
|
-
|
|
421
|
+
const lines = diff.split('\n').length
|
|
422
|
+
if (lines > 100) return 'high'
|
|
423
|
+
if (lines > 20) return 'medium'
|
|
424
|
+
return 'low'
|
|
675
425
|
}
|
|
676
426
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
427
|
+
/**
|
|
428
|
+
* Assess change impact
|
|
429
|
+
*/
|
|
430
|
+
assessChangeImpact(file, diff) {
|
|
431
|
+
const criticalFiles = ['package.json', 'README.md', 'Dockerfile', '.env']
|
|
432
|
+
const isCritical = criticalFiles.some((critical) => file.includes(critical))
|
|
433
|
+
|
|
434
|
+
if (isCritical) return 'high'
|
|
435
|
+
if (file.includes('test') || file.includes('spec')) return 'low'
|
|
436
|
+
return 'medium'
|
|
684
437
|
}
|
|
685
438
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
439
|
+
/**
|
|
440
|
+
* Categorize a change
|
|
441
|
+
*/
|
|
442
|
+
categorizeChange(change) {
|
|
443
|
+
const file = change.file.toLowerCase()
|
|
444
|
+
|
|
445
|
+
if (file.includes('test') || file.includes('spec')) return 'tests'
|
|
446
|
+
if (file.includes('readme') || file.includes('doc')) return 'docs'
|
|
447
|
+
if (file.includes('fix') || change.type === 'fix') return 'fixes'
|
|
448
|
+
if (file.includes('feat') || change.type === 'feature') return 'features'
|
|
449
|
+
if (file.includes('config') || file.includes('package')) return 'chores'
|
|
450
|
+
|
|
451
|
+
return 'improvements'
|
|
691
452
|
}
|
|
692
453
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Format section title
|
|
456
|
+
*/
|
|
457
|
+
formatSectionTitle(section) {
|
|
458
|
+
const titles = {
|
|
459
|
+
features: 'Features',
|
|
460
|
+
fixes: 'Bug Fixes',
|
|
461
|
+
improvements: 'Improvements',
|
|
462
|
+
docs: 'Documentation',
|
|
463
|
+
tests: 'Tests',
|
|
464
|
+
chores: 'Maintenance',
|
|
465
|
+
}
|
|
466
|
+
return titles[section] || section
|
|
699
467
|
}
|
|
700
468
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
469
|
+
/**
|
|
470
|
+
* Format change entry
|
|
471
|
+
*/
|
|
472
|
+
formatChangeEntry(change) {
|
|
473
|
+
const impact = change.impact ? `[${change.impact}]` : ''
|
|
474
|
+
const complexity = change.complexity ? `{${change.complexity}}` : ''
|
|
475
|
+
return `${change.file} ${impact} ${complexity}`.trim()
|
|
706
476
|
}
|
|
707
477
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
478
|
+
/**
|
|
479
|
+
* Group entries by type
|
|
480
|
+
*/
|
|
481
|
+
groupEntriesByType(entries) {
|
|
482
|
+
return entries.reduce((groups, entry) => {
|
|
483
|
+
const type = entry.type || 'unknown'
|
|
484
|
+
if (!groups[type]) groups[type] = []
|
|
485
|
+
groups[type].push(entry)
|
|
486
|
+
return groups
|
|
487
|
+
}, {})
|
|
713
488
|
}
|
|
714
489
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
490
|
+
/**
|
|
491
|
+
* Format entry type for display
|
|
492
|
+
*/
|
|
493
|
+
formatEntryType(type) {
|
|
494
|
+
const types = {
|
|
495
|
+
staged: 'Staged Changes',
|
|
496
|
+
unstaged: 'Unstaged Changes',
|
|
497
|
+
untracked: 'New Files',
|
|
719
498
|
}
|
|
499
|
+
return types[type] || type
|
|
720
500
|
}
|
|
721
501
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
502
|
+
/**
|
|
503
|
+
* Initialize workspace for analysis
|
|
504
|
+
* @returns {Promise<boolean>} True if initialization successful
|
|
505
|
+
*/
|
|
506
|
+
async initializeWorkspace() {
|
|
507
|
+
try {
|
|
508
|
+
console.log(colors.processingMessage('🔧 Initializing workspace...'))
|
|
509
|
+
|
|
510
|
+
// Reset metrics
|
|
511
|
+
this.cleanup()
|
|
512
|
+
|
|
513
|
+
// Validate git repository
|
|
514
|
+
const isValid = await this.validateWorkspace()
|
|
515
|
+
if (!isValid) {
|
|
516
|
+
return false
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Perform initial analysis
|
|
520
|
+
await this.analyzeWorkspaceChanges()
|
|
521
|
+
|
|
522
|
+
console.log(colors.successMessage('✅ Workspace initialized successfully'))
|
|
523
|
+
return true
|
|
524
|
+
} catch (error) {
|
|
525
|
+
console.error(colors.errorMessage(`Failed to initialize workspace: ${error.message}`))
|
|
526
|
+
return false
|
|
726
527
|
}
|
|
727
528
|
}
|
|
728
529
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
530
|
+
/**
|
|
531
|
+
* Validate workspace structure
|
|
532
|
+
* @returns {boolean} True if workspace structure is valid
|
|
533
|
+
*/
|
|
534
|
+
validateWorkspaceStructure() {
|
|
535
|
+
try {
|
|
536
|
+
// Check if we have the required services
|
|
537
|
+
if (!this.gitService) {
|
|
538
|
+
console.error(colors.errorMessage('Git service not available'))
|
|
539
|
+
return false
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Check if workspace has any changes to analyze
|
|
543
|
+
if (!this.hasWorkspaceChanges()) {
|
|
544
|
+
console.log(colors.infoMessage('No workspace changes detected'))
|
|
545
|
+
return true // Still valid, just nothing to do
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return true
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.error(colors.errorMessage(`Workspace structure validation failed: ${error.message}`))
|
|
551
|
+
return false
|
|
734
552
|
}
|
|
735
553
|
}
|
|
736
554
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
555
|
+
/**
|
|
556
|
+
* Clean up workspace analysis resources
|
|
557
|
+
*/
|
|
558
|
+
cleanup() {
|
|
559
|
+
this.workspaceMetrics = {
|
|
560
|
+
unstagedFiles: 0,
|
|
561
|
+
stagedFiles: 0,
|
|
562
|
+
untrackedFiles: 0,
|
|
563
|
+
modifiedLines: 0,
|
|
741
564
|
}
|
|
742
565
|
}
|
|
743
566
|
}
|