@entro314labs/ai-changelog-generator 3.2.1 โ†’ 3.6.0

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