@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.
- package/CHANGELOG.md +42 -2
- package/README.md +21 -1
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +6 -3
- package/manifest.json +177 -0
- package/package.json +76 -81
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +19 -203
- package/src/cli.js +16 -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 +623 -32
- package/src/domains/changelog/workspace-changelog.service.js +445 -622
- 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 +25 -11
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +105 -32
- 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 +8 -10
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +83 -63
- package/types/index.d.ts +61 -68
- 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
|
|
62
|
-
this.gitManager =
|
|
63
|
-
this.tagger =
|
|
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:
|
|
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
|
-
|
|
403
|
-
|
|
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
|
-
|
|
105
|
+
EnhancedConsole.info('š§ Validating configuration...')
|
|
95
106
|
await generator.validateConfiguration()
|
|
96
|
-
|
|
107
|
+
EnhancedConsole.success('ā
Configuration is valid')
|
|
97
108
|
return
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
if (argv.interactive) {
|
|
101
|
-
|
|
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
|
-
|
|
184
|
+
EnhancedConsole.error('CLI Error:', error.message)
|
|
174
185
|
process.exit(1)
|
|
175
186
|
})
|
|
176
187
|
}
|