@entro314labs/ai-changelog-generator 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +383 -877
- package/README.md +8 -3
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +9 -9
- package/bin/ai-changelog-mcp.js +19 -17
- package/bin/ai-changelog.js +6 -6
- package/package.json +80 -48
- package/src/ai-changelog-generator.js +83 -81
- package/src/application/orchestrators/changelog.orchestrator.js +791 -516
- package/src/application/services/application.service.js +137 -128
- package/src/cli.js +76 -57
- package/src/domains/ai/ai-analysis.service.js +289 -209
- package/src/domains/analysis/analysis.engine.js +253 -193
- package/src/domains/changelog/changelog.service.js +1062 -784
- package/src/domains/changelog/workspace-changelog.service.js +420 -249
- package/src/domains/git/git-repository.analyzer.js +348 -258
- package/src/domains/git/git.service.js +132 -112
- package/src/infrastructure/cli/cli.controller.js +390 -274
- package/src/infrastructure/config/configuration.manager.js +220 -190
- package/src/infrastructure/interactive/interactive-staging.service.js +154 -135
- package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
- package/src/infrastructure/mcp/mcp-server.service.js +208 -207
- package/src/infrastructure/metrics/metrics.collector.js +140 -123
- package/src/infrastructure/providers/core/base-provider.js +87 -40
- package/src/infrastructure/providers/implementations/anthropic.js +101 -99
- package/src/infrastructure/providers/implementations/azure.js +124 -101
- package/src/infrastructure/providers/implementations/bedrock.js +136 -126
- package/src/infrastructure/providers/implementations/dummy.js +23 -23
- package/src/infrastructure/providers/implementations/google.js +123 -114
- package/src/infrastructure/providers/implementations/huggingface.js +94 -87
- package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
- package/src/infrastructure/providers/implementations/mock.js +69 -73
- package/src/infrastructure/providers/implementations/ollama.js +89 -66
- package/src/infrastructure/providers/implementations/openai.js +88 -89
- package/src/infrastructure/providers/implementations/vertex.js +227 -197
- package/src/infrastructure/providers/provider-management.service.js +245 -207
- package/src/infrastructure/providers/provider-manager.service.js +145 -125
- package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
- package/src/infrastructure/providers/utils/model-config.js +220 -195
- package/src/infrastructure/providers/utils/provider-utils.js +105 -100
- package/src/infrastructure/validation/commit-message-validation.service.js +259 -161
- package/src/shared/constants/colors.js +453 -180
- package/src/shared/utils/cli-demo.js +285 -0
- package/src/shared/utils/cli-entry-utils.js +257 -249
- package/src/shared/utils/cli-ui.js +447 -0
- package/src/shared/utils/diff-processor.js +513 -0
- package/src/shared/utils/error-classes.js +125 -156
- package/src/shared/utils/json-utils.js +93 -89
- package/src/shared/utils/utils.js +1117 -945
- package/types/index.d.ts +353 -344
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import colors from '../../shared/constants/colors.js'
|
|
2
|
+
import { EnhancedConsole } from '../../shared/utils/cli-ui.js'
|
|
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'
|
|
3
11
|
|
|
4
12
|
/**
|
|
5
13
|
* Workspace Changelog Service
|
|
@@ -8,75 +16,106 @@ import colors from '../../shared/constants/colors.js';
|
|
|
8
16
|
*/
|
|
9
17
|
export class WorkspaceChangelogService {
|
|
10
18
|
constructor(aiAnalysisService, gitService = null) {
|
|
11
|
-
this.aiAnalysisService = aiAnalysisService
|
|
12
|
-
this.gitService = gitService
|
|
19
|
+
this.aiAnalysisService = aiAnalysisService
|
|
20
|
+
this.gitService = gitService
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
async generateComprehensiveWorkspaceChangelog(options = {}) {
|
|
16
24
|
try {
|
|
17
|
-
|
|
18
25
|
// Get working directory changes as raw array
|
|
19
|
-
const rawChanges = getWorkingDirectoryChanges()
|
|
20
|
-
|
|
26
|
+
const rawChanges = getWorkingDirectoryChanges()
|
|
21
27
|
|
|
22
|
-
if (!rawChanges
|
|
23
|
-
|
|
24
|
-
return null
|
|
28
|
+
if (!(rawChanges && Array.isArray(rawChanges)) || rawChanges.length === 0) {
|
|
29
|
+
EnhancedConsole.info('No changes detected in working directory.')
|
|
30
|
+
return null
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
// Enhanced analysis of changes with diff content for AI analysis
|
|
28
|
-
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
29
|
-
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
34
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
35
|
+
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
30
36
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
37
|
+
// Use DiffProcessor for intelligent processing
|
|
38
|
+
const analysisMode = options.analysisMode || 'standard'
|
|
39
|
+
const diffProcessor = new DiffProcessor({
|
|
40
|
+
analysisMode,
|
|
41
|
+
enableFiltering: true,
|
|
42
|
+
enablePatternDetection: true,
|
|
43
|
+
})
|
|
33
44
|
|
|
34
|
-
|
|
45
|
+
const processedResult = diffProcessor.processFiles(enhancedChanges)
|
|
46
|
+
|
|
47
|
+
// Generate changelog content with processed files
|
|
35
48
|
const changelog = await this.generateChangelogContent(
|
|
36
|
-
|
|
49
|
+
processedResult.processedFiles,
|
|
37
50
|
changesSummary,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
)
|
|
51
|
+
processedResult,
|
|
52
|
+
analysisMode
|
|
53
|
+
)
|
|
41
54
|
|
|
42
55
|
return {
|
|
43
56
|
changelog,
|
|
44
57
|
changes: enhancedChanges,
|
|
58
|
+
processedFiles: processedResult.processedFiles,
|
|
59
|
+
patterns: processedResult.patterns,
|
|
45
60
|
summary: changesSummary,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
filesProcessed: processedResult.filesProcessed,
|
|
62
|
+
filesSkipped: processedResult.filesSkipped,
|
|
63
|
+
}
|
|
49
64
|
} catch (error) {
|
|
50
|
-
console.error(colors.errorMessage('Workspace changelog generation failed:'), error.message)
|
|
51
|
-
throw error
|
|
65
|
+
console.error(colors.errorMessage('Workspace changelog generation failed:'), error.message)
|
|
66
|
+
throw error
|
|
52
67
|
}
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
async generateAIChangelogContentFromChanges(changes, changesSummary, analysisMode = 'standard') {
|
|
56
71
|
if (!this.aiAnalysisService.hasAI) {
|
|
57
|
-
console.log(colors.infoMessage('AI not available, using rule-based analysis...'))
|
|
58
|
-
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
72
|
+
console.log(colors.infoMessage('AI not available, using rule-based analysis...'))
|
|
73
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
try {
|
|
62
|
-
//
|
|
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
|
|
63
112
|
const prompt = `Generate a comprehensive AI changelog for the following working directory changes:
|
|
64
113
|
|
|
65
114
|
**Analysis Mode**: ${analysisMode}
|
|
66
|
-
**Total Files**: ${changesSummary.totalFiles}
|
|
67
|
-
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}
|
|
68
|
-
|
|
69
|
-
**
|
|
70
|
-
${Object.entries(changesSummary.categories).map(([cat, files]) =>
|
|
71
|
-
`**${cat}**: ${files.map(f => {
|
|
72
|
-
// Find the full change object with diff content
|
|
73
|
-
const fullChange = changes.find(change => (change.path || change.filePath) === (f.path || f.filePath));
|
|
74
|
-
const diffPreview = fullChange?.diff ?
|
|
75
|
-
(fullChange.diff.length > 200 ? fullChange.diff.substring(0, 200) + '...' : fullChange.diff) :
|
|
76
|
-
'No diff available';
|
|
77
|
-
return `${f.status} ${f.path} (${diffPreview.replace(/\n/g, ' ')})`;
|
|
78
|
-
}).join('\n ')}`
|
|
79
|
-
).join('\n')}
|
|
115
|
+
**Total Files**: ${changesSummary.totalFiles} (${processedResult.filesProcessed} analyzed, ${processedResult.filesSkipped} summarized)
|
|
116
|
+
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}${patternSummary}
|
|
117
|
+
|
|
118
|
+
**PROCESSED FILES:**${filesSection}
|
|
80
119
|
|
|
81
120
|
CRITICAL INSTRUCTIONS FOR ANALYSIS:
|
|
82
121
|
1. **ONLY DESCRIBE CHANGES VISIBLE IN THE DIFF CONTENT** - Do not invent or assume changes
|
|
@@ -87,12 +126,14 @@ CRITICAL INSTRUCTIONS FOR ANALYSIS:
|
|
|
87
126
|
|
|
88
127
|
STRICT FORMATTING REQUIREMENTS:
|
|
89
128
|
Generate working directory change entries based ONLY on visible diff content:
|
|
90
|
-
- (type)
|
|
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.
|
|
91
133
|
|
|
92
|
-
|
|
93
|
-
✅ (feature) Created new bedrock.js file - Added BedrockProvider class with generateCompletion(), initializeClient(), and getAvailableModels() methods. Imported AWS SDK BedrockRuntimeClient and added support for Claude and Llama models.
|
|
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.
|
|
94
135
|
|
|
95
|
-
✅ (
|
|
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.
|
|
96
137
|
|
|
97
138
|
EXAMPLES of FORBIDDEN ASSUMPTIONS:
|
|
98
139
|
❌ "Updated other providers to recognize bedrock" (not visible in diff)
|
|
@@ -100,95 +141,120 @@ EXAMPLES of FORBIDDEN ASSUMPTIONS:
|
|
|
100
141
|
❌ "Improved integration across the system" (speculation)
|
|
101
142
|
❌ "Enhanced error handling throughout" (assumption)
|
|
102
143
|
|
|
103
|
-
ONLY describe what you can literally see in the diff content. Do not invent connections or integrations
|
|
144
|
+
ONLY describe what you can literally see in the diff content. Do not invent connections or integrations.`
|
|
104
145
|
|
|
105
146
|
// Make AI call with all the context
|
|
106
147
|
const messages = [
|
|
107
148
|
{
|
|
108
|
-
role:
|
|
109
|
-
content:
|
|
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.',
|
|
110
152
|
},
|
|
111
153
|
{
|
|
112
|
-
role:
|
|
113
|
-
content: prompt
|
|
114
|
-
}
|
|
115
|
-
]
|
|
154
|
+
role: 'user',
|
|
155
|
+
content: prompt,
|
|
156
|
+
},
|
|
157
|
+
]
|
|
116
158
|
|
|
117
159
|
const options = {
|
|
118
|
-
max_tokens:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const response = await this.aiAnalysisService.aiProvider.generateCompletion(messages, options);
|
|
160
|
+
max_tokens:
|
|
161
|
+
analysisMode === 'enterprise' ? 2500 : analysisMode === 'detailed' ? 2000 : 1200,
|
|
162
|
+
temperature: 0.2,
|
|
163
|
+
}
|
|
124
164
|
|
|
125
|
-
|
|
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
|
+
}
|
|
126
193
|
|
|
127
194
|
// Add metadata
|
|
128
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
195
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
129
196
|
|
|
130
|
-
// Ensure proper changelog format
|
|
197
|
+
// Ensure proper changelog format with Keep a Changelog header
|
|
131
198
|
if (!changelog.includes('# ')) {
|
|
132
|
-
changelog = `#
|
|
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}`
|
|
133
200
|
}
|
|
134
201
|
|
|
135
202
|
// Add generation metadata
|
|
136
|
-
changelog += `\n\n---\n\n*Generated from ${changesSummary.totalFiles} working directory changes*\n
|
|
137
|
-
|
|
138
|
-
return changelog;
|
|
203
|
+
changelog += `\n\n---\n\n*Generated from ${changesSummary.totalFiles} working directory changes*\n`
|
|
139
204
|
|
|
205
|
+
return changelog
|
|
140
206
|
} catch (error) {
|
|
141
207
|
// Specific error guidance for AI failures
|
|
142
208
|
if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
|
|
143
|
-
console.warn(colors.warningMessage('⚠️ Cannot connect to AI provider'))
|
|
144
|
-
console.warn(colors.infoMessage('💡 Check internet connection and provider service status'))
|
|
209
|
+
console.warn(colors.warningMessage('⚠️ Cannot connect to AI provider'))
|
|
210
|
+
console.warn(colors.infoMessage('💡 Check internet connection and provider service status'))
|
|
145
211
|
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
146
|
-
console.warn(colors.warningMessage('⚠️ API authentication failed'))
|
|
147
|
-
console.warn(colors.infoMessage('💡 Run: ai-changelog init'))
|
|
212
|
+
console.warn(colors.warningMessage('⚠️ API authentication failed'))
|
|
213
|
+
console.warn(colors.infoMessage('💡 Run: ai-changelog init'))
|
|
148
214
|
} else if (error.message.includes('rate limit')) {
|
|
149
|
-
console.warn(colors.warningMessage('⚠️ Rate limit exceeded'))
|
|
150
|
-
console.warn(colors.infoMessage('💡 Wait a moment before retrying'))
|
|
215
|
+
console.warn(colors.warningMessage('⚠️ Rate limit exceeded'))
|
|
216
|
+
console.warn(colors.infoMessage('💡 Wait a moment before retrying'))
|
|
151
217
|
} else {
|
|
152
|
-
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
218
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
153
219
|
}
|
|
154
220
|
|
|
155
|
-
console.warn(colors.infoMessage('🔄 Falling back to pattern-based analysis'))
|
|
156
|
-
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
221
|
+
console.warn(colors.infoMessage('🔄 Falling back to pattern-based analysis'))
|
|
222
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
157
223
|
}
|
|
158
224
|
}
|
|
159
225
|
|
|
160
226
|
generateBasicChangelogContentFromChanges(changes, changesSummary) {
|
|
161
|
-
const timestamp = new Date().toISOString().split('T')[0]
|
|
227
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
162
228
|
|
|
163
|
-
let changelog = `# Working Directory Changes - ${timestamp}\n\n
|
|
229
|
+
let changelog = `# Working Directory Changes - ${timestamp}\n\n`
|
|
164
230
|
|
|
165
231
|
// Basic summary
|
|
166
|
-
changelog +=
|
|
167
|
-
changelog += `${changes.length} files modified across ${Object.keys(changesSummary.categories).length} categories.\n\n
|
|
232
|
+
changelog += '## Summary\n'
|
|
233
|
+
changelog += `${changes.length} files modified across ${Object.keys(changesSummary.categories).length} categories.\n\n`
|
|
168
234
|
|
|
169
235
|
// Changes by category
|
|
170
|
-
changelog += this.buildChangesByCategory(changes, changesSummary)
|
|
236
|
+
changelog += this.buildChangesByCategory(changes, changesSummary)
|
|
171
237
|
|
|
172
238
|
// Basic recommendations
|
|
173
|
-
changelog +=
|
|
174
|
-
changelog +=
|
|
175
|
-
changelog +=
|
|
176
|
-
changelog +=
|
|
239
|
+
changelog += '## Recommendations\n'
|
|
240
|
+
changelog += '- Review changes before committing\n'
|
|
241
|
+
changelog += '- Consider adding tests for new functionality\n'
|
|
242
|
+
changelog += '- Update documentation if needed\n\n'
|
|
177
243
|
|
|
178
|
-
return changelog
|
|
244
|
+
return changelog
|
|
179
245
|
}
|
|
180
246
|
|
|
181
247
|
async enhanceChangesWithDiff(changes) {
|
|
182
|
-
const enhancedChanges = []
|
|
248
|
+
const enhancedChanges = []
|
|
183
249
|
|
|
184
250
|
for (const change of changes) {
|
|
185
|
-
|
|
251
|
+
const enhancedChange = {
|
|
186
252
|
...change,
|
|
187
253
|
category: categorizeFile(change.path || change.filePath),
|
|
188
254
|
language: detectLanguage(change.path || change.filePath),
|
|
189
255
|
importance: assessFileImportance(change.path || change.filePath, change.status),
|
|
190
|
-
enhanced: true
|
|
191
|
-
}
|
|
256
|
+
enhanced: true,
|
|
257
|
+
}
|
|
192
258
|
|
|
193
259
|
// Get diff content if git service is available
|
|
194
260
|
if (this.gitService) {
|
|
@@ -196,28 +262,27 @@ ONLY describe what you can literally see in the diff content. Do not invent conn
|
|
|
196
262
|
const diffAnalysis = await this.gitService.analyzeWorkingDirectoryFileChange(
|
|
197
263
|
change.status,
|
|
198
264
|
change.path || change.filePath
|
|
199
|
-
)
|
|
265
|
+
)
|
|
200
266
|
|
|
201
267
|
if (diffAnalysis) {
|
|
202
|
-
enhancedChange.diff = diffAnalysis.diff
|
|
203
|
-
enhancedChange.beforeContent = diffAnalysis.beforeContent
|
|
204
|
-
enhancedChange.afterContent = diffAnalysis.afterContent
|
|
205
|
-
enhancedChange.semanticChanges = diffAnalysis.semanticChanges
|
|
206
|
-
enhancedChange.functionalImpact = diffAnalysis.functionalImpact
|
|
207
|
-
enhancedChange.complexity = diffAnalysis.complexity
|
|
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
|
|
208
274
|
}
|
|
209
275
|
} catch (error) {
|
|
210
|
-
console.warn(`Failed to get diff for ${change.path || change.filePath}:`, error.message)
|
|
276
|
+
console.warn(`Failed to get diff for ${change.path || change.filePath}:`, error.message)
|
|
211
277
|
}
|
|
212
278
|
}
|
|
213
279
|
|
|
214
|
-
enhancedChanges.push(enhancedChange)
|
|
280
|
+
enhancedChanges.push(enhancedChange)
|
|
215
281
|
}
|
|
216
282
|
|
|
217
|
-
return enhancedChanges
|
|
283
|
+
return enhancedChanges
|
|
218
284
|
}
|
|
219
285
|
|
|
220
|
-
|
|
221
286
|
generateWorkspaceContext(changes, summary) {
|
|
222
287
|
const context = {
|
|
223
288
|
totalFiles: changes.length,
|
|
@@ -225,281 +290,387 @@ ONLY describe what you can literally see in the diff content. Do not invent conn
|
|
|
225
290
|
primaryCategory: this.getPrimaryCategory(summary.categories),
|
|
226
291
|
riskLevel: this.assessWorkspaceRisk(changes),
|
|
227
292
|
complexity: this.assessWorkspaceComplexity(changes),
|
|
228
|
-
recommendations: this.generateRecommendations(changes)
|
|
229
|
-
}
|
|
293
|
+
recommendations: this.generateRecommendations(changes),
|
|
294
|
+
}
|
|
230
295
|
|
|
231
|
-
return context
|
|
296
|
+
return context
|
|
232
297
|
}
|
|
233
298
|
|
|
234
299
|
getPrimaryCategory(categories) {
|
|
235
|
-
return Object.entries(categories)
|
|
236
|
-
.sort(([,a], [,b]) => b.length - a.length)[0]?.[0] || 'other';
|
|
300
|
+
return Object.entries(categories).sort(([, a], [, b]) => b.length - a.length)[0]?.[0] || 'other'
|
|
237
301
|
}
|
|
238
302
|
|
|
239
303
|
assessWorkspaceRisk(changes) {
|
|
240
|
-
const highRiskFiles = changes.filter(
|
|
241
|
-
change
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (highRiskFiles.length > 0)
|
|
248
|
-
|
|
304
|
+
const highRiskFiles = changes.filter(
|
|
305
|
+
(change) =>
|
|
306
|
+
change.importance === 'critical' ||
|
|
307
|
+
change.category === 'configuration' ||
|
|
308
|
+
change.status === 'D'
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if (highRiskFiles.length > changes.length * 0.3) {
|
|
312
|
+
return 'high'
|
|
313
|
+
}
|
|
314
|
+
if (highRiskFiles.length > 0) {
|
|
315
|
+
return 'medium'
|
|
316
|
+
}
|
|
317
|
+
return 'low'
|
|
249
318
|
}
|
|
250
319
|
|
|
251
320
|
assessWorkspaceComplexity(changes) {
|
|
252
|
-
if (changes.length > 20)
|
|
253
|
-
|
|
254
|
-
|
|
321
|
+
if (changes.length > 20) {
|
|
322
|
+
return 'high'
|
|
323
|
+
}
|
|
324
|
+
if (changes.length > 5) {
|
|
325
|
+
return 'medium'
|
|
326
|
+
}
|
|
327
|
+
return 'low'
|
|
255
328
|
}
|
|
256
329
|
|
|
257
330
|
generateRecommendations(changes) {
|
|
258
|
-
const recommendations = []
|
|
331
|
+
const recommendations = []
|
|
259
332
|
|
|
260
|
-
const hasTests = changes.some(change => change.category === 'tests')
|
|
261
|
-
const hasSource = changes.some(change => change.category === 'source')
|
|
262
|
-
const hasConfig = changes.some(change => change.category === 'configuration')
|
|
263
|
-
const hasDocs = changes.some(change => change.category === 'documentation')
|
|
333
|
+
const hasTests = changes.some((change) => change.category === 'tests')
|
|
334
|
+
const hasSource = changes.some((change) => change.category === 'source')
|
|
335
|
+
const hasConfig = changes.some((change) => change.category === 'configuration')
|
|
336
|
+
const hasDocs = changes.some((change) => change.category === 'documentation')
|
|
264
337
|
|
|
265
338
|
if (hasSource && !hasTests) {
|
|
266
|
-
recommendations.push('Consider adding tests for source code changes')
|
|
339
|
+
recommendations.push('Consider adding tests for source code changes')
|
|
267
340
|
}
|
|
268
341
|
|
|
269
342
|
if (hasConfig) {
|
|
270
|
-
recommendations.push('Review configuration changes carefully')
|
|
343
|
+
recommendations.push('Review configuration changes carefully')
|
|
271
344
|
}
|
|
272
345
|
|
|
273
346
|
if (hasSource && !hasDocs) {
|
|
274
|
-
recommendations.push('Update documentation for new features')
|
|
347
|
+
recommendations.push('Update documentation for new features')
|
|
275
348
|
}
|
|
276
349
|
|
|
277
350
|
if (changes.length > 15) {
|
|
278
|
-
recommendations.push('Consider breaking this into smaller commits')
|
|
351
|
+
recommendations.push('Consider breaking this into smaller commits')
|
|
279
352
|
}
|
|
280
353
|
|
|
281
|
-
const deletedFiles = changes.filter(change => change.status === 'D')
|
|
354
|
+
const deletedFiles = changes.filter((change) => change.status === 'D')
|
|
282
355
|
if (deletedFiles.length > 0) {
|
|
283
|
-
recommendations.push(`Review ${deletedFiles.length} deleted files before committing`)
|
|
356
|
+
recommendations.push(`Review ${deletedFiles.length} deleted files before committing`)
|
|
284
357
|
}
|
|
285
358
|
|
|
286
|
-
return recommendations
|
|
359
|
+
return recommendations
|
|
287
360
|
}
|
|
288
361
|
|
|
289
|
-
buildChangesByCategory(
|
|
290
|
-
let content =
|
|
362
|
+
buildChangesByCategory(_changes, changesSummary) {
|
|
363
|
+
let content = '## Changes by Category\n\n'
|
|
291
364
|
|
|
292
365
|
Object.entries(changesSummary.categories).forEach(([category, files]) => {
|
|
293
|
-
const categoryIcon = this.getCategoryIcon(category)
|
|
294
|
-
content += `### ${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)} (${files.length} files)\n\n
|
|
366
|
+
const categoryIcon = this.getCategoryIcon(category)
|
|
367
|
+
content += `### ${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)} (${files.length} files)\n\n`
|
|
295
368
|
|
|
296
|
-
files.forEach(file => {
|
|
297
|
-
const statusIcon = this.getStatusIcon(file.status)
|
|
298
|
-
content += `- ${statusIcon} ${file.path}\n
|
|
299
|
-
})
|
|
369
|
+
files.forEach((file) => {
|
|
370
|
+
const statusIcon = this.getStatusIcon(file.status)
|
|
371
|
+
content += `- ${statusIcon} ${file.path}\n`
|
|
372
|
+
})
|
|
300
373
|
|
|
301
|
-
content += '\n'
|
|
302
|
-
})
|
|
374
|
+
content += '\n'
|
|
375
|
+
})
|
|
303
376
|
|
|
304
|
-
return content
|
|
377
|
+
return content
|
|
305
378
|
}
|
|
306
379
|
|
|
307
380
|
getCategoryIcon(category) {
|
|
308
381
|
const icons = {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
return icons[category] || '📄'
|
|
382
|
+
source: '💻',
|
|
383
|
+
tests: '🧪',
|
|
384
|
+
documentation: '📚',
|
|
385
|
+
configuration: '⚙️',
|
|
386
|
+
frontend: '🎨',
|
|
387
|
+
assets: '🖼️',
|
|
388
|
+
build: '🔧',
|
|
389
|
+
other: '📄',
|
|
390
|
+
}
|
|
391
|
+
return icons[category] || '📄'
|
|
319
392
|
}
|
|
320
393
|
|
|
321
394
|
getStatusIcon(status) {
|
|
322
395
|
const icons = {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
return icons[status] || '📄'
|
|
396
|
+
A: '➕', // Added
|
|
397
|
+
M: '✏️', // Modified
|
|
398
|
+
D: '❌', // Deleted
|
|
399
|
+
R: '📝', // Renamed
|
|
400
|
+
C: '📋', // Copied
|
|
401
|
+
}
|
|
402
|
+
return icons[status] || '📄'
|
|
330
403
|
}
|
|
331
404
|
|
|
332
|
-
async generateChangelogContent(changes, summary,
|
|
405
|
+
async generateChangelogContent(changes, summary, _context, analysisMode) {
|
|
333
406
|
if (analysisMode === 'detailed' || analysisMode === 'enterprise') {
|
|
334
|
-
return await this.generateAIChangelogContentFromChanges(changes, summary, analysisMode)
|
|
335
|
-
} else {
|
|
336
|
-
return this.generateBasicChangelogContentFromChanges(changes, summary);
|
|
407
|
+
return await this.generateAIChangelogContentFromChanges(changes, summary, analysisMode)
|
|
337
408
|
}
|
|
409
|
+
return this.generateBasicChangelogContentFromChanges(changes, summary)
|
|
338
410
|
}
|
|
339
411
|
|
|
340
412
|
// Integration with main changelog service
|
|
341
413
|
async generateCommitStyleWorkingDirectoryEntries(options = {}) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
414
|
+
// Use provided working directory analysis or get current changes
|
|
415
|
+
let rawChanges
|
|
416
|
+
if (options.workingDirAnalysis?.changes) {
|
|
417
|
+
rawChanges = options.workingDirAnalysis.changes
|
|
418
|
+
} else {
|
|
419
|
+
rawChanges = getWorkingDirectoryChanges()
|
|
420
|
+
}
|
|
350
421
|
|
|
351
|
-
|
|
352
|
-
|
|
422
|
+
try {
|
|
423
|
+
if (!(rawChanges && Array.isArray(rawChanges)) || rawChanges.length === 0) {
|
|
424
|
+
return { entries: [] }
|
|
353
425
|
}
|
|
354
426
|
|
|
355
427
|
// Enhanced analysis of changes with diff content for AI analysis
|
|
356
|
-
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
357
|
-
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
428
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
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')
|
|
358
466
|
|
|
359
467
|
// Build prompt for commit-style entries
|
|
360
468
|
const prompt = `Generate working directory change entries in the SAME FORMAT as git commits:
|
|
361
469
|
|
|
362
|
-
**Analysis Mode**: ${
|
|
363
|
-
**Total Files**: ${changesSummary.totalFiles}
|
|
364
|
-
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}
|
|
365
|
-
|
|
366
|
-
**
|
|
367
|
-
${Object.entries(changesSummary.categories).map(([cat, files]) =>
|
|
368
|
-
`**${cat}**: ${files.map(f => {
|
|
369
|
-
// Find the full change object with diff content
|
|
370
|
-
const fullChange = enhancedChanges.find(change => (change.path || change.filePath) === (f.path || f.filePath));
|
|
371
|
-
const diffPreview = fullChange?.diff ?
|
|
372
|
-
(fullChange.diff.length > 200 ? fullChange.diff.substring(0, 200) + '...' : fullChange.diff) :
|
|
373
|
-
'No diff available';
|
|
374
|
-
return `${f.status} ${f.path} (${diffPreview.replace(/\n/g, ' ')})`;
|
|
375
|
-
}).join('\n ')}`
|
|
376
|
-
).join('\n')}
|
|
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}
|
|
377
475
|
|
|
378
476
|
STRICT FORMATTING REQUIREMENTS:
|
|
379
477
|
Generate working directory change entries based ONLY on visible diff content:
|
|
380
|
-
- (type)
|
|
478
|
+
- (type) Detailed but focused description - Include key functional changes, method/function names, and important technical details without overwhelming verbosity
|
|
381
479
|
|
|
382
480
|
Where:
|
|
383
481
|
- type = feature, fix, refactor, docs, chore, etc. based on the actual changes
|
|
384
|
-
-
|
|
385
|
-
-
|
|
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.
|
|
386
487
|
|
|
387
|
-
|
|
388
|
-
- (feature) Created new bedrock.js file - Added BedrockProvider class with generateCompletion(), initializeClient(), and getAvailableModels() methods. Imported AWS SDK BedrockRuntimeClient and added support for Claude and Llama models.
|
|
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.
|
|
389
489
|
|
|
390
|
-
- (
|
|
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.
|
|
391
491
|
|
|
392
492
|
FORBIDDEN - DO NOT MAKE ASSUMPTIONS:
|
|
393
493
|
❌ Do not mention "integration" unless you see actual integration code
|
|
394
494
|
❌ Do not mention "provider selection logic" unless you see that specific code
|
|
395
495
|
❌ Do not assume files work together unless explicitly shown in diffs
|
|
396
496
|
|
|
397
|
-
Generate one entry per file or logical change group. Only describe what you can literally see
|
|
497
|
+
Generate one entry per file or logical change group. Only describe what you can literally see.`
|
|
398
498
|
|
|
399
499
|
// Make AI call
|
|
400
500
|
const messages = [
|
|
401
501
|
{
|
|
402
|
-
role:
|
|
403
|
-
content:
|
|
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.',
|
|
404
505
|
},
|
|
405
506
|
{
|
|
406
|
-
role:
|
|
407
|
-
content: prompt
|
|
408
|
-
}
|
|
409
|
-
]
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
+
}
|
|
415
519
|
|
|
520
|
+
// Increase token limit for large numbers of working directory changes
|
|
521
|
+
if (enhancedChanges.length > 50) {
|
|
522
|
+
maxTokens = Math.min(maxTokens + 1500, 6000)
|
|
523
|
+
}
|
|
416
524
|
|
|
417
|
-
const
|
|
525
|
+
const options_ai = {
|
|
526
|
+
max_tokens: maxTokens,
|
|
527
|
+
temperature: 0.3,
|
|
528
|
+
}
|
|
418
529
|
|
|
419
|
-
|
|
530
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
531
|
+
messages,
|
|
532
|
+
options_ai
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
const content = response.content || response.text
|
|
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
|
+
})
|
|
565
|
+
|
|
566
|
+
return { entries: basicEntries }
|
|
567
|
+
}
|
|
420
568
|
|
|
421
569
|
// Parse entries from response
|
|
422
|
-
const entries = content
|
|
423
|
-
.
|
|
424
|
-
.
|
|
425
|
-
|
|
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
|
+
})
|
|
426
582
|
return {
|
|
427
583
|
entries,
|
|
428
584
|
changes: enhancedChanges,
|
|
429
|
-
summary: changesSummary
|
|
430
|
-
}
|
|
431
|
-
|
|
585
|
+
summary: changesSummary,
|
|
586
|
+
}
|
|
432
587
|
} catch (error) {
|
|
433
588
|
// Provide specific guidance based on error type
|
|
434
589
|
if (error.message.includes('fetch failed') || error.message.includes('connection')) {
|
|
435
|
-
console.warn(colors.warningMessage('⚠️ AI provider connection failed'))
|
|
436
|
-
console.warn(
|
|
590
|
+
console.warn(colors.warningMessage('⚠️ AI provider connection failed'))
|
|
591
|
+
console.warn(
|
|
592
|
+
colors.infoMessage('💡 Check your internet connection and provider configuration')
|
|
593
|
+
)
|
|
437
594
|
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
438
|
-
console.warn(colors.warningMessage('⚠️ Authentication failed'))
|
|
439
|
-
console.warn(colors.infoMessage('💡 Run `ai-changelog init` to configure your API key'))
|
|
595
|
+
console.warn(colors.warningMessage('⚠️ Authentication failed'))
|
|
596
|
+
console.warn(colors.infoMessage('💡 Run `ai-changelog init` to configure your API key'))
|
|
440
597
|
} else {
|
|
441
|
-
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
442
|
-
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
598
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
599
|
+
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
443
600
|
}
|
|
444
601
|
|
|
445
602
|
// Return basic entries from the changes we were given instead of getting fresh ones
|
|
446
|
-
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
447
|
-
const basicEntries = fallbackChanges.map(change => {
|
|
448
|
-
const filePath = change.filePath || change.path || 'unknown file'
|
|
449
|
-
const status = change.status || 'M'
|
|
450
|
-
const changeType =
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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 }
|
|
456
627
|
}
|
|
457
628
|
}
|
|
458
629
|
|
|
459
630
|
async generateWorkspaceChangelog(version = null, options = {}) {
|
|
460
|
-
const result = await this.generateComprehensiveWorkspaceChangelog(options)
|
|
631
|
+
const result = await this.generateComprehensiveWorkspaceChangelog(options)
|
|
461
632
|
|
|
462
633
|
if (!result) {
|
|
463
|
-
return null
|
|
634
|
+
return null
|
|
464
635
|
}
|
|
465
636
|
|
|
466
|
-
let changelog = result.changelog
|
|
637
|
+
let changelog = result.changelog
|
|
467
638
|
|
|
468
639
|
// Add version information if provided
|
|
469
640
|
if (version) {
|
|
470
641
|
changelog = changelog.replace(
|
|
471
642
|
/# Working Directory Changelog/,
|
|
472
643
|
`# Working Directory Changelog - Version ${version}`
|
|
473
|
-
)
|
|
644
|
+
)
|
|
474
645
|
}
|
|
475
646
|
|
|
476
647
|
// Add context information for detailed modes
|
|
477
648
|
if (options.analysisMode === 'detailed' || options.analysisMode === 'enterprise') {
|
|
478
|
-
changelog += this.generateContextSection(result.context)
|
|
649
|
+
changelog += this.generateContextSection(result.context)
|
|
479
650
|
}
|
|
480
651
|
|
|
481
652
|
return {
|
|
482
653
|
...result,
|
|
483
654
|
changelog,
|
|
484
|
-
version
|
|
485
|
-
}
|
|
655
|
+
version,
|
|
656
|
+
}
|
|
486
657
|
}
|
|
487
658
|
|
|
488
659
|
generateContextSection(context) {
|
|
489
|
-
let section =
|
|
490
|
-
section += `- **Total Files:** ${context.totalFiles}\n
|
|
491
|
-
section += `- **Primary Category:** ${context.primaryCategory}\n
|
|
492
|
-
section += `- **Risk Level:** ${context.riskLevel}\n
|
|
493
|
-
section += `- **Complexity:** ${context.complexity}\n\n
|
|
660
|
+
let section = '## Context Analysis\n\n'
|
|
661
|
+
section += `- **Total Files:** ${context.totalFiles}\n`
|
|
662
|
+
section += `- **Primary Category:** ${context.primaryCategory}\n`
|
|
663
|
+
section += `- **Risk Level:** ${context.riskLevel}\n`
|
|
664
|
+
section += `- **Complexity:** ${context.complexity}\n\n`
|
|
494
665
|
|
|
495
666
|
if (context.recommendations.length > 0) {
|
|
496
|
-
section +=
|
|
497
|
-
context.recommendations.forEach(rec => {
|
|
498
|
-
section += `- ${rec}\n
|
|
499
|
-
})
|
|
500
|
-
section += '\n'
|
|
667
|
+
section += '### Recommendations\n\n'
|
|
668
|
+
context.recommendations.forEach((rec) => {
|
|
669
|
+
section += `- ${rec}\n`
|
|
670
|
+
})
|
|
671
|
+
section += '\n'
|
|
501
672
|
}
|
|
502
673
|
|
|
503
|
-
return section
|
|
674
|
+
return section
|
|
504
675
|
}
|
|
505
|
-
}
|
|
676
|
+
}
|