@entro314labs/ai-changelog-generator 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +0 -0
- package/package.json +72 -80
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +12 -202
- package/src/cli.js +4 -5
- package/src/domains/ai/ai-analysis.service.js +2 -0
- package/src/domains/analysis/analysis.engine.js +714 -37
- package/src/domains/changelog/changelog.service.js +615 -30
- package/src/domains/changelog/workspace-changelog.service.js +418 -627
- package/src/domains/git/commit-tagger.js +552 -0
- package/src/domains/git/git-manager.js +357 -0
- package/src/domains/git/git.service.js +865 -16
- package/src/infrastructure/cli/cli.controller.js +14 -9
- package/src/infrastructure/config/configuration.manager.js +24 -2
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +35 -11
- package/src/infrastructure/providers/core/base-provider.js +1 -1
- package/src/infrastructure/providers/implementations/anthropic.js +16 -173
- package/src/infrastructure/providers/implementations/azure.js +16 -63
- package/src/infrastructure/providers/implementations/dummy.js +13 -16
- package/src/infrastructure/providers/implementations/mock.js +13 -26
- package/src/infrastructure/providers/implementations/ollama.js +12 -4
- package/src/infrastructure/providers/implementations/openai.js +13 -165
- package/src/infrastructure/providers/provider-management.service.js +126 -412
- package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
- package/src/shared/utils/cli-ui.js +1 -1
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +65 -60
- package/src/domains/git/git-repository.analyzer.js +0 -678
|
@@ -2,15 +2,22 @@ import path from 'node:path'
|
|
|
2
2
|
import process from 'node:process'
|
|
3
3
|
|
|
4
4
|
import colors from '../../shared/constants/colors.js'
|
|
5
|
+
import { EnhancedConsole } from '../../shared/utils/cli-ui.js'
|
|
6
|
+
import { DiffProcessor } from '../../shared/utils/diff-processor.js'
|
|
5
7
|
import {
|
|
8
|
+
assessFileImportance,
|
|
9
|
+
categorizeFile,
|
|
10
|
+
detectLanguage,
|
|
11
|
+
getWorkingDirectoryChanges,
|
|
6
12
|
handleUnifiedOutput,
|
|
7
13
|
markdownCommitLink,
|
|
8
14
|
markdownCommitRangeLink,
|
|
15
|
+
markdownIssueLink,
|
|
9
16
|
parseConventionalCommit,
|
|
10
17
|
processIssueReferences,
|
|
11
18
|
sleep,
|
|
19
|
+
summarizeFileChanges,
|
|
12
20
|
} from '../../shared/utils/utils.js'
|
|
13
|
-
import { WorkspaceChangelogService } from './workspace-changelog.service.js'
|
|
14
21
|
|
|
15
22
|
export class ChangelogService {
|
|
16
23
|
constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
|
|
@@ -18,7 +25,6 @@ export class ChangelogService {
|
|
|
18
25
|
this.aiAnalysisService = aiAnalysisService
|
|
19
26
|
this.analysisEngine = analysisEngine
|
|
20
27
|
this.configManager = configManager
|
|
21
|
-
this.workspaceChangelogService = new WorkspaceChangelogService(aiAnalysisService, gitService)
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
/**
|
|
@@ -597,7 +603,7 @@ export class ChangelogService {
|
|
|
597
603
|
*/
|
|
598
604
|
async generateChangelogFromChanges(version = null) {
|
|
599
605
|
// Use the proper WorkspaceChangelogService with optimized single AI call approach
|
|
600
|
-
const result = await this.
|
|
606
|
+
const result = await this.generateWorkspaceChangelog(version, {
|
|
601
607
|
analysisMode: 'detailed', // Use detailed mode for better analysis
|
|
602
608
|
})
|
|
603
609
|
|
|
@@ -620,11 +626,10 @@ export class ChangelogService {
|
|
|
620
626
|
*/
|
|
621
627
|
async buildWorkingDirectorySection(workingDirAnalysis) {
|
|
622
628
|
// Use the optimized WorkspaceChangelogService for consistent formatting
|
|
623
|
-
const workspaceResult =
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
})
|
|
629
|
+
const workspaceResult = await this.generateCommitStyleWorkingDirectoryEntries({
|
|
630
|
+
analysisMode: 'detailed',
|
|
631
|
+
workingDirAnalysis,
|
|
632
|
+
})
|
|
628
633
|
|
|
629
634
|
if (!workspaceResult?.entries || workspaceResult.entries.length === 0) {
|
|
630
635
|
return ''
|
|
@@ -1639,11 +1644,9 @@ export class ChangelogService {
|
|
|
1639
1644
|
|
|
1640
1645
|
// Configuration changes
|
|
1641
1646
|
if (path?.includes('package.json')) {
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (addedContent.includes('"scripts"')) {
|
|
1646
|
-
changes.push('modified scripts')
|
|
1647
|
+
const configChanges = this.analyzePackageJsonChanges(addedContent, removedContent)
|
|
1648
|
+
if (configChanges.length > 0) {
|
|
1649
|
+
changes.push(...configChanges)
|
|
1647
1650
|
}
|
|
1648
1651
|
}
|
|
1649
1652
|
|
|
@@ -2043,11 +2046,15 @@ export class ChangelogService {
|
|
|
2043
2046
|
}
|
|
2044
2047
|
|
|
2045
2048
|
generateJSONChangelog(data) {
|
|
2046
|
-
return JSON.stringify(
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2049
|
+
return JSON.stringify(
|
|
2050
|
+
{
|
|
2051
|
+
version: data.version || 'Unreleased',
|
|
2052
|
+
changes: data.changes || [],
|
|
2053
|
+
metadata: data.metadata || {},
|
|
2054
|
+
},
|
|
2055
|
+
null,
|
|
2056
|
+
2
|
|
2057
|
+
)
|
|
2051
2058
|
}
|
|
2052
2059
|
|
|
2053
2060
|
generatePlainTextChangelog(data) {
|
|
@@ -2058,7 +2065,7 @@ export class ChangelogService {
|
|
|
2058
2065
|
return {
|
|
2059
2066
|
versions: [],
|
|
2060
2067
|
format: 'markdown',
|
|
2061
|
-
metadata: {}
|
|
2068
|
+
metadata: {},
|
|
2062
2069
|
}
|
|
2063
2070
|
}
|
|
2064
2071
|
|
|
@@ -2070,14 +2077,14 @@ export class ChangelogService {
|
|
|
2070
2077
|
return {
|
|
2071
2078
|
valid: true,
|
|
2072
2079
|
issues: [],
|
|
2073
|
-
score: 100
|
|
2080
|
+
score: 100,
|
|
2074
2081
|
}
|
|
2075
2082
|
}
|
|
2076
2083
|
|
|
2077
2084
|
optimizeChangelogStructure(content) {
|
|
2078
2085
|
return {
|
|
2079
2086
|
optimized: content,
|
|
2080
|
-
improvements: []
|
|
2087
|
+
improvements: [],
|
|
2081
2088
|
}
|
|
2082
2089
|
}
|
|
2083
2090
|
|
|
@@ -2085,14 +2092,14 @@ export class ChangelogService {
|
|
|
2085
2092
|
return {
|
|
2086
2093
|
structure: 'standard',
|
|
2087
2094
|
sections: ['unreleased', 'versions'],
|
|
2088
|
-
completeness: 90
|
|
2095
|
+
completeness: 90,
|
|
2089
2096
|
}
|
|
2090
2097
|
}
|
|
2091
2098
|
|
|
2092
2099
|
detectChangelogPatterns(content) {
|
|
2093
2100
|
return {
|
|
2094
2101
|
patterns: ['keepachangelog', 'conventional'],
|
|
2095
|
-
confidence: 'high'
|
|
2102
|
+
confidence: 'high',
|
|
2096
2103
|
}
|
|
2097
2104
|
}
|
|
2098
2105
|
|
|
@@ -2100,7 +2107,7 @@ export class ChangelogService {
|
|
|
2100
2107
|
return {
|
|
2101
2108
|
compliant: true,
|
|
2102
2109
|
standard: 'keepachangelog',
|
|
2103
|
-
violations: []
|
|
2110
|
+
violations: [],
|
|
2104
2111
|
}
|
|
2105
2112
|
}
|
|
2106
2113
|
|
|
@@ -2108,7 +2115,7 @@ export class ChangelogService {
|
|
|
2108
2115
|
return {
|
|
2109
2116
|
score: 85,
|
|
2110
2117
|
strengths: ['consistent format'],
|
|
2111
|
-
weaknesses: []
|
|
2118
|
+
weaknesses: [],
|
|
2112
2119
|
}
|
|
2113
2120
|
}
|
|
2114
2121
|
|
|
@@ -2116,7 +2123,7 @@ export class ChangelogService {
|
|
|
2116
2123
|
return {
|
|
2117
2124
|
similarity: 75,
|
|
2118
2125
|
differences: [],
|
|
2119
|
-
recommendations: []
|
|
2126
|
+
recommendations: [],
|
|
2120
2127
|
}
|
|
2121
2128
|
}
|
|
2122
2129
|
|
|
@@ -2124,21 +2131,21 @@ export class ChangelogService {
|
|
|
2124
2131
|
return {
|
|
2125
2132
|
title: 'Changelog',
|
|
2126
2133
|
format: 'keepachangelog',
|
|
2127
|
-
versions: []
|
|
2134
|
+
versions: [],
|
|
2128
2135
|
}
|
|
2129
2136
|
}
|
|
2130
2137
|
|
|
2131
2138
|
identifyMissingEntries(commits, changelog) {
|
|
2132
2139
|
return {
|
|
2133
2140
|
missing: [],
|
|
2134
|
-
suggestions: []
|
|
2141
|
+
suggestions: [],
|
|
2135
2142
|
}
|
|
2136
2143
|
}
|
|
2137
2144
|
|
|
2138
2145
|
suggestImprovements(changelog) {
|
|
2139
2146
|
return {
|
|
2140
2147
|
improvements: [],
|
|
2141
|
-
priority: 'low'
|
|
2148
|
+
priority: 'low',
|
|
2142
2149
|
}
|
|
2143
2150
|
}
|
|
2144
2151
|
|
|
@@ -2146,7 +2153,585 @@ export class ChangelogService {
|
|
|
2146
2153
|
return {
|
|
2147
2154
|
versions: 0,
|
|
2148
2155
|
entries: 0,
|
|
2149
|
-
lastUpdate: null
|
|
2156
|
+
lastUpdate: null,
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// Workspace changelog methods (formerly in WorkspaceChangelogService)
|
|
2161
|
+
async generateWorkspaceChangelog(version = null, options = {}) {
|
|
2162
|
+
const result = await this.generateComprehensiveWorkspaceChangelog(options)
|
|
2163
|
+
|
|
2164
|
+
if (!result) {
|
|
2165
|
+
return null
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
let changelog = result.changelog
|
|
2169
|
+
|
|
2170
|
+
// Add version information if provided
|
|
2171
|
+
if (version) {
|
|
2172
|
+
changelog = changelog.replace(
|
|
2173
|
+
/# Working Directory Changelog/,
|
|
2174
|
+
`# Working Directory Changelog - Version ${version}`
|
|
2175
|
+
)
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// Add context information for detailed modes
|
|
2179
|
+
if (options.analysisMode === 'detailed' || options.analysisMode === 'enterprise') {
|
|
2180
|
+
changelog += this.generateContextSection(result.context)
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
return {
|
|
2184
|
+
...result,
|
|
2185
|
+
changelog,
|
|
2186
|
+
version,
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
async generateComprehensiveWorkspaceChangelog(options = {}) {
|
|
2191
|
+
try {
|
|
2192
|
+
// Get working directory changes as raw array
|
|
2193
|
+
const rawChanges = getWorkingDirectoryChanges()
|
|
2194
|
+
|
|
2195
|
+
if (!(rawChanges && Array.isArray(rawChanges)) || rawChanges.length === 0) {
|
|
2196
|
+
EnhancedConsole.info('No changes detected in working directory.')
|
|
2197
|
+
return null
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// Enhanced analysis of changes with diff content for AI analysis
|
|
2201
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
2202
|
+
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
2203
|
+
|
|
2204
|
+
// Use DiffProcessor for intelligent processing
|
|
2205
|
+
const analysisMode = options.analysisMode || 'standard'
|
|
2206
|
+
const diffProcessor = new DiffProcessor({
|
|
2207
|
+
analysisMode,
|
|
2208
|
+
enableFiltering: true,
|
|
2209
|
+
enablePatternDetection: true,
|
|
2210
|
+
})
|
|
2211
|
+
|
|
2212
|
+
const processedResult = diffProcessor.processFiles(enhancedChanges)
|
|
2213
|
+
|
|
2214
|
+
// Generate changelog content with processed files
|
|
2215
|
+
const changelog = await this.generateWorkspaceChangelogContent(
|
|
2216
|
+
processedResult.processedFiles,
|
|
2217
|
+
changesSummary,
|
|
2218
|
+
processedResult,
|
|
2219
|
+
analysisMode
|
|
2220
|
+
)
|
|
2221
|
+
|
|
2222
|
+
return {
|
|
2223
|
+
changelog,
|
|
2224
|
+
changes: enhancedChanges,
|
|
2225
|
+
processedFiles: processedResult.processedFiles,
|
|
2226
|
+
patterns: processedResult.patterns,
|
|
2227
|
+
summary: changesSummary,
|
|
2228
|
+
filesProcessed: processedResult.filesProcessed,
|
|
2229
|
+
filesSkipped: processedResult.filesSkipped,
|
|
2230
|
+
}
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
console.error(colors.errorMessage('Workspace changelog generation failed:'), error.message)
|
|
2233
|
+
throw error
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
async generateCommitStyleWorkingDirectoryEntries(options = {}) {
|
|
2238
|
+
// Use provided working directory analysis or get current changes
|
|
2239
|
+
let rawChanges
|
|
2240
|
+
if (options.workingDirAnalysis?.changes) {
|
|
2241
|
+
rawChanges = options.workingDirAnalysis.changes
|
|
2242
|
+
} else {
|
|
2243
|
+
rawChanges = getWorkingDirectoryChanges()
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
try {
|
|
2247
|
+
if (!(rawChanges && Array.isArray(rawChanges)) || rawChanges.length === 0) {
|
|
2248
|
+
return { entries: [] }
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
// Enhanced analysis of changes with diff content for AI analysis
|
|
2252
|
+
const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
|
|
2253
|
+
const changesSummary = summarizeFileChanges(enhancedChanges)
|
|
2254
|
+
|
|
2255
|
+
// Use DiffProcessor for intelligent diff processing
|
|
2256
|
+
const analysisMode =
|
|
2257
|
+
this.aiAnalysisService?.analysisMode || options.analysisMode || 'standard'
|
|
2258
|
+
const diffProcessor = new DiffProcessor({
|
|
2259
|
+
analysisMode,
|
|
2260
|
+
enableFiltering: true,
|
|
2261
|
+
enablePatternDetection: true,
|
|
2262
|
+
})
|
|
2263
|
+
|
|
2264
|
+
const processedResult = diffProcessor.processFiles(enhancedChanges)
|
|
2265
|
+
const { processedFiles, patterns } = processedResult
|
|
2266
|
+
|
|
2267
|
+
// Build pattern summary if patterns were detected
|
|
2268
|
+
const patternSummary =
|
|
2269
|
+
Object.keys(patterns).length > 0
|
|
2270
|
+
? `\n\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
|
|
2271
|
+
.map((p) => `- ${p.description}`)
|
|
2272
|
+
.join('\n')}`
|
|
2273
|
+
: ''
|
|
2274
|
+
|
|
2275
|
+
// Build files section with processed diffs
|
|
2276
|
+
const filesSection = processedFiles
|
|
2277
|
+
.map((file) => {
|
|
2278
|
+
if (file.isSummary) {
|
|
2279
|
+
return `\n**[REMAINING FILES]:** ${file.diff}`
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
const compressionInfo = file.compressionApplied
|
|
2283
|
+
? ` [compressed from ${file.originalSize || 'unknown'} chars]`
|
|
2284
|
+
: ''
|
|
2285
|
+
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
2286
|
+
|
|
2287
|
+
return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}`
|
|
2288
|
+
})
|
|
2289
|
+
.join('\n')
|
|
2290
|
+
|
|
2291
|
+
// Build prompt for commit-style entries
|
|
2292
|
+
const prompt = `Generate working directory change entries in the SAME FORMAT as git commits:
|
|
2293
|
+
|
|
2294
|
+
**Analysis Mode**: ${analysisMode}
|
|
2295
|
+
**Total Files**: ${changesSummary.totalFiles} (${processedResult.filesProcessed} analyzed, ${processedResult.filesSkipped} summarized)
|
|
2296
|
+
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}${patternSummary}
|
|
2297
|
+
|
|
2298
|
+
**PROCESSED FILES:**${filesSection}
|
|
2299
|
+
|
|
2300
|
+
STRICT FORMATTING REQUIREMENTS:
|
|
2301
|
+
Generate working directory change entries based ONLY on visible diff content:
|
|
2302
|
+
- (type) Detailed but focused description - Include key functional changes, method/function names, and important technical details without overwhelming verbosity
|
|
2303
|
+
|
|
2304
|
+
Where:
|
|
2305
|
+
- type = feature, fix, refactor, docs, chore, etc. based on the actual changes
|
|
2306
|
+
- Detailed description = specific functions/methods affected, key technical changes, and functional purpose
|
|
2307
|
+
- Include exact method names, variable names, and technical specifics from the diffs
|
|
2308
|
+
|
|
2309
|
+
EXAMPLES of CORRECT DETAILED FORMAT:
|
|
2310
|
+
- (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.
|
|
2311
|
+
|
|
2312
|
+
- (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.
|
|
2313
|
+
|
|
2314
|
+
- (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.
|
|
2315
|
+
|
|
2316
|
+
FORBIDDEN - DO NOT MAKE ASSUMPTIONS:
|
|
2317
|
+
❌ Do not mention "integration" unless you see actual integration code
|
|
2318
|
+
❌ Do not mention "provider selection logic" unless you see that specific code
|
|
2319
|
+
❌ Do not assume files work together unless explicitly shown in diffs
|
|
2320
|
+
|
|
2321
|
+
Generate one entry per file or logical change group. Only describe what you can literally see.`
|
|
2322
|
+
|
|
2323
|
+
// Make AI call
|
|
2324
|
+
const messages = [
|
|
2325
|
+
{
|
|
2326
|
+
role: 'system',
|
|
2327
|
+
content:
|
|
2328
|
+
'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.',
|
|
2329
|
+
},
|
|
2330
|
+
{
|
|
2331
|
+
role: 'user',
|
|
2332
|
+
content: prompt,
|
|
2333
|
+
},
|
|
2334
|
+
]
|
|
2335
|
+
|
|
2336
|
+
// Set token limits based on analysis mode and number of changes
|
|
2337
|
+
let maxTokens = 1200 // Default
|
|
2338
|
+
if (analysisMode === 'enterprise') {
|
|
2339
|
+
maxTokens = 3000
|
|
2340
|
+
} else if (analysisMode === 'detailed') {
|
|
2341
|
+
maxTokens = 2500
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// Increase token limit for large numbers of working directory changes
|
|
2345
|
+
if (enhancedChanges.length > 50) {
|
|
2346
|
+
maxTokens = Math.min(maxTokens + 1500, 6000)
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
const aiOptions = {
|
|
2350
|
+
max_tokens: maxTokens,
|
|
2351
|
+
temperature: 0.3,
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(
|
|
2355
|
+
messages,
|
|
2356
|
+
aiOptions
|
|
2357
|
+
)
|
|
2358
|
+
|
|
2359
|
+
const content = response.content || response.text
|
|
2360
|
+
|
|
2361
|
+
// Check if content is valid before processing
|
|
2362
|
+
if (!content || typeof content !== 'string') {
|
|
2363
|
+
console.warn(colors.warningMessage('⚠️ AI response was empty or invalid'))
|
|
2364
|
+
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
2365
|
+
|
|
2366
|
+
// Fallback to basic entries from the changes we were given
|
|
2367
|
+
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
2368
|
+
const basicEntries = fallbackChanges.map((change) => {
|
|
2369
|
+
const filePath = change.filePath || change.path || 'unknown file'
|
|
2370
|
+
const status = change.status || 'M'
|
|
2371
|
+
const changeType =
|
|
2372
|
+
status === 'M'
|
|
2373
|
+
? 'update'
|
|
2374
|
+
: status === 'A'
|
|
2375
|
+
? 'feature'
|
|
2376
|
+
: status === 'D'
|
|
2377
|
+
? 'remove'
|
|
2378
|
+
: 'chore'
|
|
2379
|
+
const changeDesc =
|
|
2380
|
+
status === 'M'
|
|
2381
|
+
? 'updated'
|
|
2382
|
+
: status === 'A'
|
|
2383
|
+
? 'added'
|
|
2384
|
+
: status === 'D'
|
|
2385
|
+
? 'deleted'
|
|
2386
|
+
: 'changed'
|
|
2387
|
+
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`
|
|
2388
|
+
})
|
|
2389
|
+
|
|
2390
|
+
return { entries: basicEntries }
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
// Parse entries from response
|
|
2394
|
+
const entries = content
|
|
2395
|
+
.split('\n')
|
|
2396
|
+
.filter((line) => {
|
|
2397
|
+
const trimmed = line.trim()
|
|
2398
|
+
// Accept lines starting with '- (' or directly with '(' for changelog entries
|
|
2399
|
+
return trimmed.startsWith('- (') || trimmed.startsWith('(')
|
|
2400
|
+
})
|
|
2401
|
+
.map((line) => {
|
|
2402
|
+
const trimmed = line.trim()
|
|
2403
|
+
// Ensure all entries start with '- ' for consistent formatting
|
|
2404
|
+
return trimmed.startsWith('- ') ? trimmed : `- ${trimmed}`
|
|
2405
|
+
})
|
|
2406
|
+
return {
|
|
2407
|
+
entries,
|
|
2408
|
+
changes: enhancedChanges,
|
|
2409
|
+
summary: changesSummary,
|
|
2410
|
+
}
|
|
2411
|
+
} catch (error) {
|
|
2412
|
+
// Provide specific guidance based on error type
|
|
2413
|
+
if (error.message.includes('fetch failed') || error.message.includes('connection')) {
|
|
2414
|
+
console.warn(colors.warningMessage('⚠️ AI provider connection failed'))
|
|
2415
|
+
console.warn(
|
|
2416
|
+
colors.infoMessage('💡 Check your internet connection and provider configuration')
|
|
2417
|
+
)
|
|
2418
|
+
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
2419
|
+
console.warn(colors.warningMessage('⚠️ Authentication failed'))
|
|
2420
|
+
console.warn(colors.infoMessage('💡 Run `ai-changelog init` to configure your API key'))
|
|
2421
|
+
} else {
|
|
2422
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
2423
|
+
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
// Return basic entries from the changes we were given instead of getting fresh ones
|
|
2427
|
+
const fallbackChanges = rawChanges || getWorkingDirectoryChanges()
|
|
2428
|
+
const basicEntries = fallbackChanges.map((change) => {
|
|
2429
|
+
const filePath = change.filePath || change.path || 'unknown file'
|
|
2430
|
+
const status = change.status || 'M'
|
|
2431
|
+
const changeType =
|
|
2432
|
+
status === 'M'
|
|
2433
|
+
? 'update'
|
|
2434
|
+
: status === 'A'
|
|
2435
|
+
? 'feature'
|
|
2436
|
+
: status === 'D'
|
|
2437
|
+
? 'remove'
|
|
2438
|
+
: 'chore'
|
|
2439
|
+
const changeDesc =
|
|
2440
|
+
status === 'M'
|
|
2441
|
+
? 'updated'
|
|
2442
|
+
: status === 'A'
|
|
2443
|
+
? 'added'
|
|
2444
|
+
: status === 'D'
|
|
2445
|
+
? 'deleted'
|
|
2446
|
+
: 'changed'
|
|
2447
|
+
return `- (${changeType}) Modified ${filePath} - File ${changeDesc} (pattern-based analysis)`
|
|
2448
|
+
})
|
|
2449
|
+
|
|
2450
|
+
return { entries: basicEntries }
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
async enhanceChangesWithDiff(changes) {
|
|
2455
|
+
const enhancedChanges = []
|
|
2456
|
+
|
|
2457
|
+
for (const change of changes) {
|
|
2458
|
+
const enhancedChange = {
|
|
2459
|
+
...change,
|
|
2460
|
+
category: categorizeFile(change.path || change.filePath),
|
|
2461
|
+
language: detectLanguage(change.path || change.filePath),
|
|
2462
|
+
importance: assessFileImportance(change.path || change.filePath, change.status),
|
|
2463
|
+
enhanced: true,
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// Get diff content if git service is available
|
|
2467
|
+
if (this.gitService) {
|
|
2468
|
+
try {
|
|
2469
|
+
const diffAnalysis = await this.gitService.analyzeWorkingDirectoryFileChange(
|
|
2470
|
+
change.status,
|
|
2471
|
+
change.path || change.filePath
|
|
2472
|
+
)
|
|
2473
|
+
|
|
2474
|
+
if (diffAnalysis) {
|
|
2475
|
+
enhancedChange.diff = diffAnalysis.diff
|
|
2476
|
+
enhancedChange.beforeContent = diffAnalysis.beforeContent
|
|
2477
|
+
enhancedChange.afterContent = diffAnalysis.afterContent
|
|
2478
|
+
enhancedChange.semanticChanges = diffAnalysis.semanticChanges
|
|
2479
|
+
enhancedChange.functionalImpact = diffAnalysis.functionalImpact
|
|
2480
|
+
enhancedChange.complexity = diffAnalysis.complexity
|
|
2481
|
+
}
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
console.warn(`Failed to get diff for ${change.path || change.filePath}:`, error.message)
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
enhancedChanges.push(enhancedChange)
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
return enhancedChanges
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2493
|
+
async generateWorkspaceChangelogContent(changes, summary, _context, analysisMode) {
|
|
2494
|
+
if (analysisMode === 'detailed' || analysisMode === 'enterprise') {
|
|
2495
|
+
return await this.generateAIChangelogContentFromChanges(changes, summary, analysisMode)
|
|
2496
|
+
}
|
|
2497
|
+
return this.generateBasicChangelogContentFromChanges(changes, summary)
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
async generateAIChangelogContentFromChanges(changes, changesSummary, analysisMode = 'standard') {
|
|
2501
|
+
if (!this.aiAnalysisService.hasAI) {
|
|
2502
|
+
console.log(colors.infoMessage('AI not available, using rule-based analysis...'))
|
|
2503
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
try {
|
|
2507
|
+
// Use DiffProcessor for intelligent diff processing
|
|
2508
|
+
const diffProcessor = new DiffProcessor({
|
|
2509
|
+
analysisMode,
|
|
2510
|
+
enableFiltering: true,
|
|
2511
|
+
enablePatternDetection: true,
|
|
2512
|
+
})
|
|
2513
|
+
|
|
2514
|
+
const processedResult = diffProcessor.processFiles(changes)
|
|
2515
|
+
const { processedFiles, patterns } = processedResult
|
|
2516
|
+
|
|
2517
|
+
// Build pattern summary if patterns were detected
|
|
2518
|
+
const patternSummary =
|
|
2519
|
+
Object.keys(patterns).length > 0
|
|
2520
|
+
? `\n\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
|
|
2521
|
+
.map((p) => `- ${p.description}`)
|
|
2522
|
+
.join('\n')}`
|
|
2523
|
+
: ''
|
|
2524
|
+
|
|
2525
|
+
// Build files section with processed diffs
|
|
2526
|
+
const filesSection = processedFiles
|
|
2527
|
+
.map((file) => {
|
|
2528
|
+
if (file.isSummary) {
|
|
2529
|
+
return `\n**[REMAINING FILES]:** ${file.diff}`
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
const compressionInfo = file.compressionApplied
|
|
2533
|
+
? ` [compressed from ${file.originalSize || 'unknown'} chars]`
|
|
2534
|
+
: ''
|
|
2535
|
+
const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
|
|
2536
|
+
|
|
2537
|
+
return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}`
|
|
2538
|
+
})
|
|
2539
|
+
.join('\n')
|
|
2540
|
+
|
|
2541
|
+
// Build comprehensive prompt with processed changes
|
|
2542
|
+
const prompt = `Generate a comprehensive AI changelog for the following working directory changes:
|
|
2543
|
+
|
|
2544
|
+
**Analysis Mode**: ${analysisMode}
|
|
2545
|
+
**Total Files**: ${changesSummary.totalFiles} (${processedResult.filesProcessed} analyzed, ${processedResult.filesSkipped} summarized)
|
|
2546
|
+
**Categories**: ${Object.keys(changesSummary.categories).join(', ')}${patternSummary}
|
|
2547
|
+
|
|
2548
|
+
**PROCESSED FILES:**${filesSection}
|
|
2549
|
+
|
|
2550
|
+
CRITICAL INSTRUCTIONS FOR ANALYSIS:
|
|
2551
|
+
1. **ONLY DESCRIBE CHANGES VISIBLE IN THE DIFF CONTENT** - Do not invent or assume changes
|
|
2552
|
+
2. **BE FACTUAL AND PRECISE** - Only mention specific lines, functions, imports that you can see
|
|
2553
|
+
3. **NO ASSUMPTIONS OR SPECULATION** - If you can't see it in the diff, don't mention it
|
|
2554
|
+
4. **STICK TO OBSERVABLE FACTS** - Describe what was added, removed, or modified line by line
|
|
2555
|
+
5. **DO NOT MAKE UP INTEGRATION DETAILS** - Don't assume files work together unless explicitly shown
|
|
2556
|
+
|
|
2557
|
+
STRICT FORMATTING REQUIREMENTS:
|
|
2558
|
+
Generate working directory change entries based ONLY on visible diff content:
|
|
2559
|
+
- (type) Detailed but focused description - Include key functional changes, method/function names, and important technical details without overwhelming verbosity
|
|
2560
|
+
|
|
2561
|
+
EXAMPLES of CORRECT DETAILED FORMAT:
|
|
2562
|
+
✅ (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.
|
|
2563
|
+
|
|
2564
|
+
✅ (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.
|
|
2565
|
+
|
|
2566
|
+
✅ (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.
|
|
2567
|
+
|
|
2568
|
+
EXAMPLES of FORBIDDEN ASSUMPTIONS:
|
|
2569
|
+
❌ "Updated other providers to recognize bedrock" (not visible in diff)
|
|
2570
|
+
❌ "Refactored provider selection logic" (not shown in actual changes)
|
|
2571
|
+
❌ "Improved integration across the system" (speculation)
|
|
2572
|
+
❌ "Enhanced error handling throughout" (assumption)
|
|
2573
|
+
|
|
2574
|
+
ONLY describe what you can literally see in the diff content. Do not invent connections or integrations.`
|
|
2575
|
+
|
|
2576
|
+
// Make AI call with all the context
|
|
2577
|
+
const messages = [
|
|
2578
|
+
{
|
|
2579
|
+
role: 'system',
|
|
2580
|
+
content:
|
|
2581
|
+
'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.',
|
|
2582
|
+
},
|
|
2583
|
+
{
|
|
2584
|
+
role: 'user',
|
|
2585
|
+
content: prompt,
|
|
2586
|
+
},
|
|
2587
|
+
]
|
|
2588
|
+
|
|
2589
|
+
const options = {
|
|
2590
|
+
max_tokens:
|
|
2591
|
+
analysisMode === 'enterprise' ? 2500 : analysisMode === 'detailed' ? 2000 : 1200,
|
|
2592
|
+
temperature: 0.2,
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
const response = await this.aiAnalysisService.aiProvider.generateCompletion(messages, options)
|
|
2596
|
+
|
|
2597
|
+
let changelog = response.content || response.text
|
|
2598
|
+
|
|
2599
|
+
// Check if changelog content is valid
|
|
2600
|
+
if (!changelog || typeof changelog !== 'string') {
|
|
2601
|
+
console.warn(colors.warningMessage('⚠️ AI response was empty or invalid'))
|
|
2602
|
+
console.warn(colors.infoMessage('💡 Using basic file change detection instead'))
|
|
2603
|
+
|
|
2604
|
+
// Generate basic changelog from file changes
|
|
2605
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
2606
|
+
const fallbackChanges = changes
|
|
2607
|
+
const basicEntries = fallbackChanges.map((change) => {
|
|
2608
|
+
const filePath = change.filePath || change.path || 'unknown file'
|
|
2609
|
+
const status = change.status || 'M'
|
|
2610
|
+
const changeType =
|
|
2611
|
+
status === 'M'
|
|
2612
|
+
? 'Modified'
|
|
2613
|
+
: status === 'A'
|
|
2614
|
+
? 'Added'
|
|
2615
|
+
: status === 'D'
|
|
2616
|
+
? 'Deleted'
|
|
2617
|
+
: 'Changed'
|
|
2618
|
+
return `- ${changeType} ${filePath}`
|
|
2619
|
+
})
|
|
2620
|
+
|
|
2621
|
+
changelog = `# Working Directory Changelog - ${timestamp}\n\n## Changes\n\n${basicEntries.join('\n')}`
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
// Add metadata
|
|
2625
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
2626
|
+
|
|
2627
|
+
// Ensure proper changelog format with Keep a Changelog header
|
|
2628
|
+
if (!changelog.includes('# ')) {
|
|
2629
|
+
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}`
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// Add generation metadata
|
|
2633
|
+
changelog += `\n\n---\n\n*Generated from ${changesSummary.totalFiles} working directory changes*\n`
|
|
2634
|
+
|
|
2635
|
+
return changelog
|
|
2636
|
+
} catch (error) {
|
|
2637
|
+
// Specific error guidance for AI failures
|
|
2638
|
+
if (error.message.includes('fetch failed') || error.message.includes('ECONNREFUSED')) {
|
|
2639
|
+
console.warn(colors.warningMessage('⚠️ Cannot connect to AI provider'))
|
|
2640
|
+
console.warn(colors.infoMessage('💡 Check internet connection and provider service status'))
|
|
2641
|
+
} else if (error.message.includes('API key') || error.message.includes('401')) {
|
|
2642
|
+
console.warn(colors.warningMessage('⚠️ API authentication failed'))
|
|
2643
|
+
console.warn(colors.infoMessage('💡 Run: ai-changelog init'))
|
|
2644
|
+
} else if (error.message.includes('rate limit')) {
|
|
2645
|
+
console.warn(colors.warningMessage('⚠️ Rate limit exceeded'))
|
|
2646
|
+
console.warn(colors.infoMessage('💡 Wait a moment before retrying'))
|
|
2647
|
+
} else {
|
|
2648
|
+
console.warn(colors.warningMessage(`⚠️ AI analysis failed: ${error.message}`))
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
console.warn(colors.infoMessage('🔄 Falling back to pattern-based analysis'))
|
|
2652
|
+
return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
|
|
2150
2653
|
}
|
|
2151
2654
|
}
|
|
2655
|
+
|
|
2656
|
+
generateBasicChangelogContentFromChanges(changes, changesSummary) {
|
|
2657
|
+
const timestamp = new Date().toISOString().split('T')[0]
|
|
2658
|
+
|
|
2659
|
+
let changelog = `# Working Directory Changes - ${timestamp}\n\n`
|
|
2660
|
+
|
|
2661
|
+
// Basic summary
|
|
2662
|
+
changelog += '## Summary\n'
|
|
2663
|
+
changelog += `${changes.length} files modified across ${Object.keys(changesSummary.categories).length} categories.\n\n`
|
|
2664
|
+
|
|
2665
|
+
// Changes by category
|
|
2666
|
+
changelog += this.buildChangesByCategory(changes, changesSummary)
|
|
2667
|
+
|
|
2668
|
+
// Basic recommendations
|
|
2669
|
+
changelog += '## Recommendations\n'
|
|
2670
|
+
changelog += '- Review changes before committing\n'
|
|
2671
|
+
changelog += '- Consider adding tests for new functionality\n'
|
|
2672
|
+
changelog += '- Update documentation if needed\n\n'
|
|
2673
|
+
|
|
2674
|
+
return changelog
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
buildChangesByCategory(_changes, changesSummary) {
|
|
2678
|
+
let content = '## Changes by Category\n\n'
|
|
2679
|
+
|
|
2680
|
+
Object.entries(changesSummary.categories).forEach(([category, files]) => {
|
|
2681
|
+
const categoryIcon = this.getCategoryIcon(category)
|
|
2682
|
+
content += `### ${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)} (${files.length} files)\n\n`
|
|
2683
|
+
|
|
2684
|
+
files.forEach((file) => {
|
|
2685
|
+
const statusIcon = this.getStatusIcon(file.status)
|
|
2686
|
+
content += `- ${statusIcon} ${file.path}\n`
|
|
2687
|
+
})
|
|
2688
|
+
|
|
2689
|
+
content += '\n'
|
|
2690
|
+
})
|
|
2691
|
+
|
|
2692
|
+
return content
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
getCategoryIcon(category) {
|
|
2696
|
+
const icons = {
|
|
2697
|
+
source: '💻',
|
|
2698
|
+
tests: '🧪',
|
|
2699
|
+
documentation: '📚',
|
|
2700
|
+
configuration: '⚙️',
|
|
2701
|
+
frontend: '🎨',
|
|
2702
|
+
assets: '🖼️',
|
|
2703
|
+
build: '🔧',
|
|
2704
|
+
other: '📄',
|
|
2705
|
+
}
|
|
2706
|
+
return icons[category] || '📄'
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
getStatusIcon(status) {
|
|
2710
|
+
const icons = {
|
|
2711
|
+
A: '➕', // Added
|
|
2712
|
+
M: '✏️', // Modified
|
|
2713
|
+
D: '❌', // Deleted
|
|
2714
|
+
R: '📝', // Renamed
|
|
2715
|
+
C: '📋', // Copied
|
|
2716
|
+
}
|
|
2717
|
+
return icons[status] || '📄'
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
generateContextSection(context) {
|
|
2721
|
+
let section = '## Context Analysis\n\n'
|
|
2722
|
+
section += `- **Total Files:** ${context.totalFiles}\n`
|
|
2723
|
+
section += `- **Primary Category:** ${context.primaryCategory}\n`
|
|
2724
|
+
section += `- **Risk Level:** ${context.riskLevel}\n`
|
|
2725
|
+
section += `- **Complexity:** ${context.complexity}\n\n`
|
|
2726
|
+
|
|
2727
|
+
if (context.recommendations.length > 0) {
|
|
2728
|
+
section += '### Recommendations\n\n'
|
|
2729
|
+
context.recommendations.forEach((rec) => {
|
|
2730
|
+
section += `- ${rec}\n`
|
|
2731
|
+
})
|
|
2732
|
+
section += '\n'
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
return section
|
|
2736
|
+
}
|
|
2152
2737
|
}
|