@entro314labs/ai-changelog-generator 3.0.5
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 +801 -0
- package/LICENSE +21 -0
- package/README.md +393 -0
- package/ai-changelog-mcp.sh +93 -0
- package/ai-changelog.sh +103 -0
- package/bin/ai-changelog-dxt.js +35 -0
- package/bin/ai-changelog-mcp.js +34 -0
- package/bin/ai-changelog.js +18 -0
- package/package.json +135 -0
- package/src/ai-changelog-generator.js +258 -0
- package/src/application/orchestrators/changelog.orchestrator.js +730 -0
- package/src/application/services/application.service.js +301 -0
- package/src/cli.js +157 -0
- package/src/domains/ai/ai-analysis.service.js +486 -0
- package/src/domains/analysis/analysis.engine.js +445 -0
- package/src/domains/changelog/changelog.service.js +1761 -0
- package/src/domains/changelog/workspace-changelog.service.js +505 -0
- package/src/domains/git/git-repository.analyzer.js +588 -0
- package/src/domains/git/git.service.js +302 -0
- package/src/infrastructure/cli/cli.controller.js +517 -0
- package/src/infrastructure/config/configuration.manager.js +538 -0
- package/src/infrastructure/interactive/interactive-workflow.service.js +444 -0
- package/src/infrastructure/mcp/mcp-server.service.js +540 -0
- package/src/infrastructure/metrics/metrics.collector.js +362 -0
- package/src/infrastructure/providers/core/base-provider.js +184 -0
- package/src/infrastructure/providers/implementations/anthropic.js +329 -0
- package/src/infrastructure/providers/implementations/azure.js +296 -0
- package/src/infrastructure/providers/implementations/bedrock.js +393 -0
- package/src/infrastructure/providers/implementations/dummy.js +112 -0
- package/src/infrastructure/providers/implementations/google.js +320 -0
- package/src/infrastructure/providers/implementations/huggingface.js +301 -0
- package/src/infrastructure/providers/implementations/lmstudio.js +189 -0
- package/src/infrastructure/providers/implementations/mock.js +275 -0
- package/src/infrastructure/providers/implementations/ollama.js +151 -0
- package/src/infrastructure/providers/implementations/openai.js +273 -0
- package/src/infrastructure/providers/implementations/vertex.js +438 -0
- package/src/infrastructure/providers/provider-management.service.js +415 -0
- package/src/infrastructure/providers/provider-manager.service.js +363 -0
- package/src/infrastructure/providers/utils/base-provider-helpers.js +660 -0
- package/src/infrastructure/providers/utils/model-config.js +610 -0
- package/src/infrastructure/providers/utils/provider-utils.js +286 -0
- package/src/shared/constants/colors.js +370 -0
- package/src/shared/utils/cli-entry-utils.js +525 -0
- package/src/shared/utils/error-classes.js +423 -0
- package/src/shared/utils/json-utils.js +318 -0
- package/src/shared/utils/utils.js +1997 -0
- package/types/index.d.ts +464 -0
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { getWorkingDirectoryChanges, summarizeFileChanges, categorizeFile, detectLanguage, assessFileImportance } from '../../shared/utils/utils.js';
|
|
2
|
+
import colors from '../../shared/constants/colors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Workspace Changelog Service
|
|
6
|
+
*
|
|
7
|
+
* Handles changelog generation from working directory changes
|
|
8
|
+
*/
|
|
9
|
+
export class WorkspaceChangelogService {
|
|
10
|
+
constructor(aiAnalysisService, gitService = null) {
|
|
11
|
+
this.aiAnalysisService = aiAnalysisService;
|
|
12
|
+
this.gitService = gitService;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async generateComprehensiveWorkspaceChangelog(options = {}) {
|
|
16
|
+
try {
|
|
17
|
+
|
|
18
|
+
// Get working directory changes as raw array
|
|
19
|
+
const rawChanges = getWorkingDirectoryChanges();
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if (!rawChanges || !Array.isArray(rawChanges) || rawChanges.length === 0) {
|
|
23
|
+
console.log(colors.infoMessage('No changes detected in working directory.'));
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Enhanced analysis of changes with diff content for AI analysis
|
|
28
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges);
|
|
29
|
+
const changesSummary = summarizeFileChanges(enhancedChanges);
|
|
30
|
+
|
|
31
|
+
// Generate workspace context
|
|
32
|
+
const workspaceContext = this.generateWorkspaceContext(enhancedChanges, changesSummary);
|
|
33
|
+
|
|
34
|
+
// Generate changelog content
|
|
35
|
+
const changelog = await this.generateChangelogContent(
|
|
36
|
+
enhancedChanges,
|
|
37
|
+
changesSummary,
|
|
38
|
+
workspaceContext,
|
|
39
|
+
options.analysisMode || 'standard'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
changelog,
|
|
44
|
+
changes: enhancedChanges,
|
|
45
|
+
summary: changesSummary,
|
|
46
|
+
context: workspaceContext
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(colors.errorMessage('Workspace changelog generation failed:'), error.message);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async generateAIChangelogContentFromChanges(changes, changesSummary, analysisMode = 'standard') {
|
|
56
|
+
if (!this.aiAnalysisService.hasAI) {
|
|
57
|
+
console.log(colors.infoMessage('AI not available, using rule-based analysis...'));
|
|
58
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Build comprehensive prompt with ALL change details
|
|
63
|
+
const prompt = `Generate a comprehensive AI changelog for the following working directory changes:
|
|
64
|
+
|
|
65
|
+
**Analysis Mode**: ${analysisMode}
|
|
66
|
+
**Total Files**: ${changesSummary.totalFiles}
|
|
67
|
+
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}
|
|
68
|
+
|
|
69
|
+
**Files by category with details**:
|
|
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')}
|
|
80
|
+
|
|
81
|
+
CRITICAL INSTRUCTIONS FOR ANALYSIS:
|
|
82
|
+
1. **ONLY DESCRIBE CHANGES VISIBLE IN THE DIFF CONTENT** - Do not invent or assume changes
|
|
83
|
+
2. **BE FACTUAL AND PRECISE** - Only mention specific lines, functions, imports that you can see
|
|
84
|
+
3. **NO ASSUMPTIONS OR SPECULATION** - If you can't see it in the diff, don't mention it
|
|
85
|
+
4. **STICK TO OBSERVABLE FACTS** - Describe what was added, removed, or modified line by line
|
|
86
|
+
5. **DO NOT MAKE UP INTEGRATION DETAILS** - Don't assume files work together unless explicitly shown
|
|
87
|
+
|
|
88
|
+
STRICT FORMATTING REQUIREMENTS:
|
|
89
|
+
Generate working directory change entries based ONLY on visible diff content:
|
|
90
|
+
- (type) brief factual description - List only the specific changes you can see in the diffs
|
|
91
|
+
|
|
92
|
+
EXAMPLES of CORRECT FACTUAL FORMAT:
|
|
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.
|
|
94
|
+
|
|
95
|
+
✅ (refactor) Updated model list in anthropic.js - Changed getDefaultModel() return value from 'claude-3-5-sonnet-20241022' to 'claude-sonnet-4-20250514'. Added new model entries with updated capabilities.
|
|
96
|
+
|
|
97
|
+
EXAMPLES of FORBIDDEN ASSUMPTIONS:
|
|
98
|
+
❌ "Updated other providers to recognize bedrock" (not visible in diff)
|
|
99
|
+
❌ "Refactored provider selection logic" (not shown in actual changes)
|
|
100
|
+
❌ "Improved integration across the system" (speculation)
|
|
101
|
+
❌ "Enhanced error handling throughout" (assumption)
|
|
102
|
+
|
|
103
|
+
ONLY describe what you can literally see in the diff content. Do not invent connections or integrations.`;
|
|
104
|
+
|
|
105
|
+
// Make AI call with all the context
|
|
106
|
+
const messages = [
|
|
107
|
+
{
|
|
108
|
+
role: "system",
|
|
109
|
+
content: "You are an expert at analyzing code changes and generating factual changelog entries. You MUST only describe changes that are visible in the provided diff content. Never make assumptions, never invent integrations, never speculate about how files work together. Be precise and factual - only describe what you can literally see in the diffs."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
role: "user",
|
|
113
|
+
content: prompt
|
|
114
|
+
}
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const options = {
|
|
118
|
+
max_tokens: analysisMode === 'enterprise' ? 2000 : analysisMode === 'detailed' ? 1500 : 1000,
|
|
119
|
+
temperature: 0.3
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(messages, options);
|
|
124
|
+
|
|
125
|
+
let changelog = response.content || response.text;
|
|
126
|
+
|
|
127
|
+
// Add metadata
|
|
128
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
129
|
+
|
|
130
|
+
// Ensure proper changelog format
|
|
131
|
+
if (!changelog.includes('# ')) {
|
|
132
|
+
changelog = `# Working Directory Changelog - ${timestamp}\n\n${changelog}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add generation metadata
|
|
136
|
+
changelog += `\n\n---\n\n*Generated from ${changesSummary.totalFiles} working directory changes*\n`;
|
|
137
|
+
|
|
138
|
+
return changelog;
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
// Specific error guidance for AI failures
|
|
142
|
+
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'));
|
|
145
|
+
} 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'));
|
|
148
|
+
} 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'));
|
|
151
|
+
} else {
|
|
152
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.warn(colors.infoMessage('🔄 Falling back to pattern-based analysis'));
|
|
156
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
generateBasicChangelogContentFromChanges(changes, changesSummary) {
|
|
161
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
162
|
+
|
|
163
|
+
let changelog = `# Working Directory Changes - ${timestamp}\n\n`;
|
|
164
|
+
|
|
165
|
+
// Basic summary
|
|
166
|
+
changelog += `## Summary\n`;
|
|
167
|
+
changelog += `${changes.length} files modified across ${Object.keys(changesSummary.categories).length} categories.\n\n`;
|
|
168
|
+
|
|
169
|
+
// Changes by category
|
|
170
|
+
changelog += this.buildChangesByCategory(changes, changesSummary);
|
|
171
|
+
|
|
172
|
+
// Basic recommendations
|
|
173
|
+
changelog += `## Recommendations\n`;
|
|
174
|
+
changelog += `- Review changes before committing\n`;
|
|
175
|
+
changelog += `- Consider adding tests for new functionality\n`;
|
|
176
|
+
changelog += `- Update documentation if needed\n\n`;
|
|
177
|
+
|
|
178
|
+
return changelog;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async enhanceChangesWithDiff(changes) {
|
|
182
|
+
const enhancedChanges = [];
|
|
183
|
+
|
|
184
|
+
for (const change of changes) {
|
|
185
|
+
let enhancedChange = {
|
|
186
|
+
...change,
|
|
187
|
+
category: categorizeFile(change.path || change.filePath),
|
|
188
|
+
language: detectLanguage(change.path || change.filePath),
|
|
189
|
+
importance: assessFileImportance(change.path || change.filePath, change.status),
|
|
190
|
+
enhanced: true
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Get diff content if git service is available
|
|
194
|
+
if (this.gitService) {
|
|
195
|
+
try {
|
|
196
|
+
const diffAnalysis = await this.gitService.analyzeWorkingDirectoryFileChange(
|
|
197
|
+
change.status,
|
|
198
|
+
change.path || change.filePath
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
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;
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
console.warn(`Failed to get diff for ${change.path || change.filePath}:`, error.message);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
enhancedChanges.push(enhancedChange);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return enhancedChanges;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
generateWorkspaceContext(changes, summary) {
|
|
222
|
+
const context = {
|
|
223
|
+
totalFiles: changes.length,
|
|
224
|
+
categories: Object.keys(summary.categories),
|
|
225
|
+
primaryCategory: this.getPrimaryCategory(summary.categories),
|
|
226
|
+
riskLevel: this.assessWorkspaceRisk(changes),
|
|
227
|
+
complexity: this.assessWorkspaceComplexity(changes),
|
|
228
|
+
recommendations: this.generateRecommendations(changes)
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return context;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getPrimaryCategory(categories) {
|
|
235
|
+
return Object.entries(categories)
|
|
236
|
+
.sort(([,a], [,b]) => b.length - a.length)[0]?.[0] || 'other';
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
assessWorkspaceRisk(changes) {
|
|
240
|
+
const highRiskFiles = changes.filter(change =>
|
|
241
|
+
change.importance === 'critical' ||
|
|
242
|
+
change.category === 'configuration' ||
|
|
243
|
+
change.status === 'D'
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (highRiskFiles.length > changes.length * 0.3) return 'high';
|
|
247
|
+
if (highRiskFiles.length > 0) return 'medium';
|
|
248
|
+
return 'low';
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
assessWorkspaceComplexity(changes) {
|
|
252
|
+
if (changes.length > 20) return 'high';
|
|
253
|
+
if (changes.length > 5) return 'medium';
|
|
254
|
+
return 'low';
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
generateRecommendations(changes) {
|
|
258
|
+
const recommendations = [];
|
|
259
|
+
|
|
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');
|
|
264
|
+
|
|
265
|
+
if (hasSource && !hasTests) {
|
|
266
|
+
recommendations.push('Consider adding tests for source code changes');
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (hasConfig) {
|
|
270
|
+
recommendations.push('Review configuration changes carefully');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (hasSource && !hasDocs) {
|
|
274
|
+
recommendations.push('Update documentation for new features');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (changes.length > 15) {
|
|
278
|
+
recommendations.push('Consider breaking this into smaller commits');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const deletedFiles = changes.filter(change => change.status === 'D');
|
|
282
|
+
if (deletedFiles.length > 0) {
|
|
283
|
+
recommendations.push(`Review ${deletedFiles.length} deleted files before committing`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return recommendations;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
buildChangesByCategory(changes, changesSummary) {
|
|
290
|
+
let content = `## Changes by Category\n\n`;
|
|
291
|
+
|
|
292
|
+
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`;
|
|
295
|
+
|
|
296
|
+
files.forEach(file => {
|
|
297
|
+
const statusIcon = this.getStatusIcon(file.status);
|
|
298
|
+
content += `- ${statusIcon} ${file.path}\n`;
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
content += '\n';
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
return content;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getCategoryIcon(category) {
|
|
308
|
+
const icons = {
|
|
309
|
+
'source': '💻',
|
|
310
|
+
'tests': '🧪',
|
|
311
|
+
'documentation': '📚',
|
|
312
|
+
'configuration': '⚙️',
|
|
313
|
+
'frontend': '🎨',
|
|
314
|
+
'assets': '🖼️',
|
|
315
|
+
'build': '🔧',
|
|
316
|
+
'other': '📄'
|
|
317
|
+
};
|
|
318
|
+
return icons[category] || '📄';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
getStatusIcon(status) {
|
|
322
|
+
const icons = {
|
|
323
|
+
'A': '➕', // Added
|
|
324
|
+
'M': '✏️', // Modified
|
|
325
|
+
'D': '❌', // Deleted
|
|
326
|
+
'R': '📝', // Renamed
|
|
327
|
+
'C': '📋' // Copied
|
|
328
|
+
};
|
|
329
|
+
return icons[status] || '📄';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async generateChangelogContent(changes, summary, context, analysisMode) {
|
|
333
|
+
if (analysisMode === 'detailed' || analysisMode === 'enterprise') {
|
|
334
|
+
return await this.generateAIChangelogContentFromChanges(changes, summary, analysisMode);
|
|
335
|
+
} else {
|
|
336
|
+
return this.generateBasicChangelogContentFromChanges(changes, summary);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Integration with main changelog service
|
|
341
|
+
async generateCommitStyleWorkingDirectoryEntries(options = {}) {
|
|
342
|
+
try {
|
|
343
|
+
// Use provided working directory analysis or get current changes
|
|
344
|
+
let rawChanges;
|
|
345
|
+
if (options.workingDirAnalysis && options.workingDirAnalysis.changes) {
|
|
346
|
+
rawChanges = options.workingDirAnalysis.changes;
|
|
347
|
+
} else {
|
|
348
|
+
rawChanges = getWorkingDirectoryChanges();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!rawChanges || !Array.isArray(rawChanges) || rawChanges.length === 0) {
|
|
352
|
+
return { entries: [] };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Enhanced analysis of changes with diff content for AI analysis
|
|
356
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges);
|
|
357
|
+
const changesSummary = summarizeFileChanges(enhancedChanges);
|
|
358
|
+
|
|
359
|
+
// Build prompt for commit-style entries
|
|
360
|
+
const prompt = `Generate working directory change entries in the SAME FORMAT as git commits:
|
|
361
|
+
|
|
362
|
+
**Analysis Mode**: ${options.analysisMode || 'standard'}
|
|
363
|
+
**Total Files**: ${changesSummary.totalFiles}
|
|
364
|
+
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}
|
|
365
|
+
|
|
366
|
+
**Files by category with details**:
|
|
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')}
|
|
377
|
+
|
|
378
|
+
STRICT FORMATTING REQUIREMENTS:
|
|
379
|
+
Generate working directory change entries based ONLY on visible diff content:
|
|
380
|
+
- (type) brief factual description - List only the specific changes you can see in the diffs
|
|
381
|
+
|
|
382
|
+
Where:
|
|
383
|
+
- type = feature, fix, refactor, docs, chore, etc. based on the actual changes
|
|
384
|
+
- brief description = concise summary of what was actually changed
|
|
385
|
+
- Detailed explanation = specific lines, functions, imports that were added/removed/modified
|
|
386
|
+
|
|
387
|
+
EXAMPLES of CORRECT FACTUAL FORMAT:
|
|
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.
|
|
389
|
+
|
|
390
|
+
- (refactor) Updated model list in anthropic.js - Changed getDefaultModel() return value from 'claude-3-5-sonnet-20241022' to 'claude-sonnet-4-20250514'. Added new model entries in getAvailableModels() method.
|
|
391
|
+
|
|
392
|
+
FORBIDDEN - DO NOT MAKE ASSUMPTIONS:
|
|
393
|
+
❌ Do not mention "integration" unless you see actual integration code
|
|
394
|
+
❌ Do not mention "provider selection logic" unless you see that specific code
|
|
395
|
+
❌ Do not assume files work together unless explicitly shown in diffs
|
|
396
|
+
|
|
397
|
+
Generate one entry per file or logical change group. Only describe what you can literally see.`;
|
|
398
|
+
|
|
399
|
+
// Make AI call
|
|
400
|
+
const messages = [
|
|
401
|
+
{
|
|
402
|
+
role: "system",
|
|
403
|
+
content: "You are an expert at analyzing code changes and generating factual commit-style changelog entries. You MUST only describe changes that are visible in the provided diff content. Never make assumptions, never invent integrations, never speculate. Be precise and factual - only describe what you can literally see in the diffs."
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
role: "user",
|
|
407
|
+
content: prompt
|
|
408
|
+
}
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const options_ai = {
|
|
412
|
+
max_tokens: 1000,
|
|
413
|
+
temperature: 0.3
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(messages, options_ai);
|
|
418
|
+
|
|
419
|
+
let content = response.content || response.text;
|
|
420
|
+
|
|
421
|
+
// Parse entries from response
|
|
422
|
+
const entries = content.split('\n')
|
|
423
|
+
.filter(line => line.trim().startsWith('-'))
|
|
424
|
+
.map(line => line.trim());
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
entries,
|
|
428
|
+
changes: enhancedChanges,
|
|
429
|
+
summary: changesSummary
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
} catch (error) {
|
|
433
|
+
// Provide specific guidance based on error type
|
|
434
|
+
if (error.message.includes('fetch failed') || error.message.includes('connection')) {
|
|
435
|
+
console.warn(colors.warningMessage('⚠️ AI provider connection failed'));
|
|
436
|
+
console.warn(colors.infoMessage('💡 Check your internet connection and provider configuration'));
|
|
437
|
+
} 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'));
|
|
440
|
+
} else {
|
|
441
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`));
|
|
442
|
+
console.warn(colors.infoMessage('💡 Using basic file change detection instead'));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 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 = status === 'M' ? 'update' : status === 'A' ? 'feature' : status === 'D' ? 'remove' : 'chore';
|
|
451
|
+
const changeDesc = status === 'M' ? 'updated' : status === 'A' ? 'added' : status === 'D' ? 'deleted' : 'changed';
|
|
452
|
+
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`;
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
return { entries: basicEntries };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async generateWorkspaceChangelog(version = null, options = {}) {
|
|
460
|
+
const result = await this.generateComprehensiveWorkspaceChangelog(options);
|
|
461
|
+
|
|
462
|
+
if (!result) {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let changelog = result.changelog;
|
|
467
|
+
|
|
468
|
+
// Add version information if provided
|
|
469
|
+
if (version) {
|
|
470
|
+
changelog = changelog.replace(
|
|
471
|
+
/# Working Directory Changelog/,
|
|
472
|
+
`# Working Directory Changelog - Version ${version}`
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Add context information for detailed modes
|
|
477
|
+
if (options.analysisMode === 'detailed' || options.analysisMode === 'enterprise') {
|
|
478
|
+
changelog += this.generateContextSection(result.context);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
...result,
|
|
483
|
+
changelog,
|
|
484
|
+
version
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
generateContextSection(context) {
|
|
489
|
+
let section = `## Context Analysis\n\n`;
|
|
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`;
|
|
494
|
+
|
|
495
|
+
if (context.recommendations.length > 0) {
|
|
496
|
+
section += `### Recommendations\n\n`;
|
|
497
|
+
context.recommendations.forEach(rec => {
|
|
498
|
+
section += `- ${rec}\n`;
|
|
499
|
+
});
|
|
500
|
+
section += '\n';
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return section;
|
|
504
|
+
}
|
|
505
|
+
}
|