@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
@@ -3,7 +3,9 @@ import process from 'node:process'
3
3
  import { AIAnalysisService } from '../../domains/ai/ai-analysis.service.js'
4
4
  import { AnalysisEngine } from '../../domains/analysis/analysis.engine.js'
5
5
  import { ChangelogService } from '../../domains/changelog/changelog.service.js'
6
+ import { CommitTagger } from '../../domains/git/commit-tagger.js'
6
7
  import { GitService } from '../../domains/git/git.service.js'
8
+ import { GitManager } from '../../domains/git/git-manager.js'
7
9
  import { InteractiveStagingService } from '../../infrastructure/interactive/interactive-staging.service.js'
8
10
  import { InteractiveWorkflowService } from '../../infrastructure/interactive/interactive-workflow.service.js'
9
11
  import { ProviderManagerService } from '../../infrastructure/providers/provider-manager.service.js'
@@ -58,9 +60,9 @@ export class ChangelogOrchestrator {
58
60
  })
59
61
  this.aiProvider = this.providerManager.getActiveProvider()
60
62
 
61
- // Create lightweight implementations for missing dependencies
62
- this.gitManager = await this.createGitManager()
63
- this.tagger = await this.createTagger()
63
+ // Create proper implementations using the new classes
64
+ this.gitManager = new GitManager()
65
+ this.tagger = new CommitTagger()
64
66
  this.promptEngine = await this.createPromptEngine()
65
67
 
66
68
  // Initialize domain services with proper dependencies
@@ -121,207 +123,17 @@ export class ChangelogOrchestrator {
121
123
  }
122
124
  }
123
125
 
124
- async createGitManager() {
125
- const { execSync } = await this._getCachedImport('child_process')
126
-
127
- return {
128
- isGitRepo: (() => {
129
- try {
130
- execSync('git rev-parse --git-dir', { stdio: 'ignore' })
131
- return true
132
- } catch {
133
- return false
134
- }
135
- })(),
136
-
137
- execGit(command) {
138
- try {
139
- return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
140
- } catch (error) {
141
- // Enhanced error handling with more specific messages
142
- if (error.code === 128) {
143
- throw new Error(
144
- `Git repository error: ${error.message.includes('not a git repository') ? 'Not in a git repository' : error.message}`
145
- )
146
- }
147
- if (error.code === 129) {
148
- throw new Error(`Git command syntax error: ${command}`)
149
- }
150
- throw new Error(`Git command failed (${command}): ${error.message}`)
151
- }
152
- },
153
-
154
- execGitSafe(command) {
155
- try {
156
- return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
157
- } catch {
158
- return ''
159
- }
160
- },
161
-
162
- execGitShow(command) {
163
- try {
164
- return execSync(command, { encoding: 'utf8', stdio: 'pipe' })
165
- } catch (_error) {
166
- // console.warn(`Git command failed: ${command}`);
167
- // console.warn(`Error: ${error.message}`);
168
- return null
169
- }
170
- },
171
-
172
- validateCommitHash(hash) {
173
- try {
174
- execSync(`git cat-file -e ${hash}`, { stdio: 'ignore' })
175
- return true
176
- } catch {
177
- return false
178
- }
179
- },
180
-
181
- getAllBranches() {
182
- try {
183
- const output = execSync('git branch -a', { encoding: 'utf8' })
184
- return output
185
- .split('\n')
186
- .filter(Boolean)
187
- .map((branch) => branch.trim().replace(/^\*\s*/, ''))
188
- } catch {
189
- return []
190
- }
191
- },
192
-
193
- getUnmergedCommits() {
194
- try {
195
- const output = execSync('git log --oneline --no-merges HEAD ^origin/main', {
196
- encoding: 'utf8',
197
- })
198
- return output.split('\n').filter(Boolean)
199
- } catch {
200
- return []
201
- }
202
- },
203
-
204
- getDanglingCommits() {
205
- try {
206
- const output = execSync('git fsck --no-reflog | grep "dangling commit"', {
207
- encoding: 'utf8',
208
- })
209
- return output.split('\n').filter(Boolean)
210
- } catch {
211
- return []
212
- }
213
- },
214
-
215
- getUntrackedFiles() {
216
- try {
217
- const output = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' })
218
- return output.split('\n').filter(Boolean)
219
- } catch {
220
- return []
221
- }
222
- },
223
-
224
- getRecentCommits(limit = 10) {
225
- try {
226
- const output = execSync(`git log --oneline -${limit}`, { encoding: 'utf8' })
227
- return output.split('\n').filter(Boolean)
228
- } catch {
229
- return []
230
- }
231
- },
232
-
233
- getComprehensiveAnalysis() {
234
- return {
235
- totalCommits: this.getRecentCommits(1000).length,
236
- branches: this.getAllBranches(),
237
- untrackedFiles: this.getUntrackedFiles(),
238
- }
239
- },
240
-
241
- hasFile(filename) {
242
- try {
243
- execSync(`test -f ${filename}`, { stdio: 'ignore' })
244
- return true
245
- } catch {
246
- return false
247
- }
248
- },
249
- }
250
- }
251
-
252
- async createTagger() {
253
- const { analyzeSemanticChanges, analyzeFunctionalImpact } = await import(
254
- '../../shared/utils/utils.js'
255
- )
256
-
257
- return {
258
- analyzeCommit(commit) {
259
- const semanticChanges = []
260
- const breakingChanges = []
261
- const categories = []
262
- const tags = []
263
-
264
- // Basic analysis based on commit message
265
- const message = commit.message.toLowerCase()
266
-
267
- if (message.includes('breaking') || message.includes('!:')) {
268
- breakingChanges.push('Breaking change detected in commit message')
269
- categories.push('breaking')
270
- tags.push('breaking')
271
- }
272
-
273
- if (message.startsWith('feat')) {
274
- categories.push('feature')
275
- tags.push('feature')
276
- } else if (message.startsWith('fix')) {
277
- categories.push('fix')
278
- tags.push('bugfix')
279
- } else if (message.startsWith('docs')) {
280
- categories.push('docs')
281
- tags.push('documentation')
282
- }
283
-
284
- // Analyze files if available
285
- if (commit.files && commit.files.length > 0) {
286
- commit.files.forEach((file) => {
287
- const semantic = analyzeSemanticChanges('', file.path)
288
- if (semantic.frameworks) {
289
- semanticChanges.push(...semantic.frameworks)
290
- }
291
- })
292
- }
293
-
294
- // Determine importance
295
- let importance = 'medium'
296
- if (breakingChanges.length > 0 || commit.files?.length > 10) {
297
- importance = 'high'
298
- } else if (categories.includes('docs') || commit.files?.length < 3) {
299
- importance = 'low'
300
- }
301
-
302
- return {
303
- semanticChanges,
304
- breakingChanges,
305
- categories,
306
- importance,
307
- tags,
308
- }
309
- },
310
- }
311
- }
312
-
313
126
  async createPromptEngine() {
314
- const { buildEnhancedPrompt } = await import('../../shared/utils/utils.js')
315
-
316
127
  return {
317
128
  systemPrompts: {
318
129
  master:
319
- 'You are an expert software analyst specializing in code change analysis and changelog generation.',
320
- standard: 'Provide clear, concise analysis focusing on the practical impact of changes.',
130
+ 'You are an expert software analyst specializing in code change analysis and changelog generation. Analyze code changes with precision and provide definitive, factual assessments. Never use uncertain language like "likely", "probably", "appears to", "seems to", or "possibly". Base your analysis solely on the actual changes shown in the code diffs.',
131
+ standard:
132
+ 'Provide clear, concise analysis focusing on the practical impact of changes. Be definitive and factual in your descriptions.',
321
133
  detailed:
322
- 'Provide comprehensive technical analysis with detailed explanations and implications.',
134
+ 'Provide comprehensive technical analysis with detailed explanations and implications. Use precise, confident language backed by evidence from the code changes.',
323
135
  enterprise:
324
- 'Provide enterprise-grade analysis suitable for stakeholder communication and decision-making.',
136
+ 'Provide enterprise-grade analysis suitable for stakeholder communication and decision-making. Use authoritative, factual language that conveys confidence and accuracy.',
325
137
  changesAnalysis: 'You are an expert at analyzing code changes and their business impact.',
326
138
  },
327
139
 
@@ -356,7 +168,7 @@ export class ChangelogOrchestrator {
356
168
  }
357
169
 
358
170
  // Generate changelog using the service
359
- const result = await this.changelogService.generateChangelog(version, since)
171
+ const result = await this.changelogService.generateChangelog(version, since, this.options)
360
172
 
361
173
  if (!result) {
362
174
  console.log(colors.warningMessage('No changelog generated'))
@@ -399,9 +211,13 @@ export class ChangelogOrchestrator {
399
211
  async runInteractive() {
400
212
  await this.ensureInitialized()
401
213
 
402
- const { runInteractiveMode, selectSpecificCommits } = await import(
403
- '../../shared/utils/utils.js'
404
- )
214
+ // Check for interactive environment
215
+ if (!process.stdin.isTTY && !process.env.CI) {
216
+ console.log(colors.warningMessage('Interactive mode requires a TTY terminal.'))
217
+ return { interactive: false, status: 'skipped' }
218
+ }
219
+
220
+ const { runInteractiveMode } = await import('../../shared/utils/utils.js')
405
221
  const { confirm } = await this._getCachedImport('@clack/prompts')
406
222
 
407
223
  console.log(colors.processingMessage('šŸŽ® Starting interactive mode...'))
@@ -1276,7 +1092,7 @@ Generate only the commit message, no explanation.`
1276
1092
  if (!this.aiAnalysisService?.aiProvider?.isAvailable()) {
1277
1093
  throw new Error('AI provider not available for suggestions')
1278
1094
  }
1279
-
1095
+
1280
1096
  const { select } = await this._getCachedImport('@clack/prompts')
1281
1097
 
1282
1098
  console.log(colors.processingMessage('šŸ¤– Generating AI suggestions...'))
package/src/cli.js CHANGED
@@ -1,5 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * DEPRECATED: Legacy CLI Entry Point
5
+ *
6
+ * This file is deprecated and maintained only for backward compatibility.
7
+ * Please use bin/ai-changelog.js which delegates to CLIController instead.
8
+ *
9
+ * @deprecated Use bin/ai-changelog.js with CLIController
10
+ */
11
+
12
+ console.warn('\nāš ļø WARNING: This CLI entry point is deprecated.')
13
+ console.warn('Please use the main CLI: ai-changelog (or bin/ai-changelog.js)\n')
14
+
3
15
  /**
4
16
  * CLI Entry Point for AI Changelog Generator
5
17
  * Provides command-line interface for changelog generation functionality
@@ -11,7 +23,6 @@ import { hideBin } from 'yargs/helpers'
11
23
  import yargs from 'yargs/yargs'
12
24
 
13
25
  import { AIChangelogGenerator } from './ai-changelog-generator.js'
14
- import colors from './shared/constants/colors.js'
15
26
  import { EnhancedConsole, SimpleSpinner } from './shared/utils/cli-ui.js'
16
27
 
17
28
  async function runCLI() {
@@ -91,14 +102,14 @@ async function runCLI() {
91
102
  }
92
103
 
93
104
  if (argv.validate) {
94
- console.log(colors.infoMessage('šŸ”§ Validating configuration...'))
105
+ EnhancedConsole.info('šŸ”§ Validating configuration...')
95
106
  await generator.validateConfiguration()
96
- console.log(colors.successMessage('āœ… Configuration is valid'))
107
+ EnhancedConsole.success('āœ… Configuration is valid')
97
108
  return
98
109
  }
99
110
 
100
111
  if (argv.interactive) {
101
- console.log(colors.infoMessage('šŸŽ® Starting interactive mode...'))
112
+ EnhancedConsole.info('šŸŽ® Starting interactive mode...')
102
113
  await generator.runInteractive()
103
114
  return
104
115
  }
@@ -170,7 +181,7 @@ export { runCLI }
170
181
  // If this file is run directly, execute CLI
171
182
  if (import.meta.url === `file://${process.argv[1]}`) {
172
183
  runCLI().catch((error) => {
173
- console.error('CLI Error:', error.message)
184
+ EnhancedConsole.error('CLI Error:', error.message)
174
185
  process.exit(1)
175
186
  })
176
187
  }
@@ -143,6 +143,8 @@ export class AIAnalysisService {
143
143
  temperature: 0.3,
144
144
  })
145
145
 
146
+ // Debug logging removed
147
+
146
148
  if (response?.usage) {
147
149
  this.metrics.totalTokens +=
148
150
  (response.usage.prompt_tokens || 0) + (response.usage.completion_tokens || 0)