@entro314labs/ai-changelog-generator 3.0.5 → 3.2.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 (51) hide show
  1. package/CHANGELOG.md +383 -785
  2. package/README.md +30 -3
  3. package/ai-changelog-mcp.sh +0 -0
  4. package/ai-changelog.sh +0 -0
  5. package/bin/ai-changelog-dxt.js +9 -9
  6. package/bin/ai-changelog-mcp.js +19 -17
  7. package/bin/ai-changelog.js +6 -6
  8. package/package.json +84 -52
  9. package/src/ai-changelog-generator.js +83 -81
  10. package/src/application/orchestrators/changelog.orchestrator.js +1040 -296
  11. package/src/application/services/application.service.js +145 -123
  12. package/src/cli.js +76 -57
  13. package/src/domains/ai/ai-analysis.service.js +289 -209
  14. package/src/domains/analysis/analysis.engine.js +253 -193
  15. package/src/domains/changelog/changelog.service.js +1062 -784
  16. package/src/domains/changelog/workspace-changelog.service.js +420 -249
  17. package/src/domains/git/git-repository.analyzer.js +348 -258
  18. package/src/domains/git/git.service.js +132 -112
  19. package/src/infrastructure/cli/cli.controller.js +415 -247
  20. package/src/infrastructure/config/configuration.manager.js +220 -190
  21. package/src/infrastructure/interactive/interactive-staging.service.js +332 -0
  22. package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
  23. package/src/infrastructure/mcp/mcp-server.service.js +208 -207
  24. package/src/infrastructure/metrics/metrics.collector.js +140 -123
  25. package/src/infrastructure/providers/core/base-provider.js +87 -40
  26. package/src/infrastructure/providers/implementations/anthropic.js +101 -99
  27. package/src/infrastructure/providers/implementations/azure.js +124 -101
  28. package/src/infrastructure/providers/implementations/bedrock.js +136 -126
  29. package/src/infrastructure/providers/implementations/dummy.js +23 -23
  30. package/src/infrastructure/providers/implementations/google.js +123 -114
  31. package/src/infrastructure/providers/implementations/huggingface.js +94 -87
  32. package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
  33. package/src/infrastructure/providers/implementations/mock.js +69 -73
  34. package/src/infrastructure/providers/implementations/ollama.js +89 -66
  35. package/src/infrastructure/providers/implementations/openai.js +88 -89
  36. package/src/infrastructure/providers/implementations/vertex.js +227 -197
  37. package/src/infrastructure/providers/provider-management.service.js +245 -207
  38. package/src/infrastructure/providers/provider-manager.service.js +145 -125
  39. package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
  40. package/src/infrastructure/providers/utils/model-config.js +220 -195
  41. package/src/infrastructure/providers/utils/provider-utils.js +105 -100
  42. package/src/infrastructure/validation/commit-message-validation.service.js +556 -0
  43. package/src/shared/constants/colors.js +467 -172
  44. package/src/shared/utils/cli-demo.js +285 -0
  45. package/src/shared/utils/cli-entry-utils.js +257 -249
  46. package/src/shared/utils/cli-ui.js +447 -0
  47. package/src/shared/utils/diff-processor.js +513 -0
  48. package/src/shared/utils/error-classes.js +125 -156
  49. package/src/shared/utils/json-utils.js +93 -89
  50. package/src/shared/utils/utils.js +1299 -775
  51. package/types/index.d.ts +353 -344
@@ -0,0 +1,332 @@
1
+ import { execSync } from 'node:child_process'
2
+
3
+ import colors from '../../shared/constants/colors.js'
4
+
5
+ /**
6
+ * Interactive Staging Service
7
+ *
8
+ * Provides interactive git staging functionality similar to better-commits
9
+ * Handles file selection, staging, and unstaging operations
10
+ */
11
+ export class InteractiveStagingService {
12
+ constructor(gitManager) {
13
+ this.gitManager = gitManager
14
+ }
15
+
16
+ /**
17
+ * Show git status with staged and unstaged changes
18
+ */
19
+ async showGitStatus() {
20
+ console.log(colors.processingMessage(' Checking Git Status '))
21
+
22
+ const statusResult = this.getDetailedStatus()
23
+
24
+ if (statusResult.staged.length > 0) {
25
+ console.log(colors.successMessage('Changes to be committed:'))
26
+ statusResult.staged.forEach((file) => {
27
+ const icon = this.getStatusIcon(file.status)
28
+ console.log(colors.success(` ${icon} ${file.status} ${file.path}`))
29
+ })
30
+ }
31
+
32
+ if (statusResult.unstaged.length > 0) {
33
+ console.log(colors.warningMessage('\nChanges not staged for commit:'))
34
+ statusResult.unstaged.forEach((file) => {
35
+ const icon = this.getStatusIcon(file.status)
36
+ console.log(colors.warning(` ${icon} ${file.status} ${file.path}`))
37
+ })
38
+ }
39
+
40
+ if (statusResult.untracked.length > 0) {
41
+ console.log(colors.infoMessage('\nUntracked files:'))
42
+ statusResult.untracked.forEach((file) => {
43
+ console.log(colors.dim(` ✨ ?? ${file.path}`))
44
+ })
45
+ }
46
+
47
+ return statusResult
48
+ }
49
+
50
+ /**
51
+ * Interactive file selection for staging
52
+ */
53
+ async selectFilesToStage(files = null) {
54
+ const { multiselect, confirm } = await import('@clack/prompts')
55
+
56
+ // Get current status if files not provided
57
+ if (!files) {
58
+ const status = this.getDetailedStatus()
59
+ files = [...status.unstaged, ...status.untracked]
60
+ }
61
+
62
+ if (files.length === 0) {
63
+ console.log(colors.infoMessage('No files available for staging.'))
64
+ return []
65
+ }
66
+
67
+ // Create choices for the multiselect prompt
68
+ const choices = files.map((file) => ({
69
+ value: file.path,
70
+ label: `${this.getStatusIcon(file.status)} ${file.status} ${file.path}`,
71
+ hint: this.getStatusDescription(file.status),
72
+ }))
73
+
74
+ try {
75
+ const selectedFiles = await multiselect({
76
+ message: 'Select files to stage for commit:',
77
+ options: choices,
78
+ required: false,
79
+ })
80
+
81
+ if (!selectedFiles || selectedFiles.length === 0) {
82
+ console.log(colors.infoMessage('No files selected for staging.'))
83
+ return []
84
+ }
85
+
86
+ // Confirm the selection
87
+ const shouldStage = await confirm({
88
+ message: `Stage ${selectedFiles.length} file(s)?`,
89
+ initialValue: true,
90
+ })
91
+
92
+ if (shouldStage) {
93
+ await this.stageFiles(selectedFiles)
94
+ console.log(colors.successMessage(`✅ Staged ${selectedFiles.length} file(s)`))
95
+ return selectedFiles
96
+ }
97
+ console.log(colors.infoMessage('Staging cancelled.'))
98
+ return []
99
+ } catch (error) {
100
+ if (error.message.includes('cancelled')) {
101
+ console.log(colors.infoMessage('File selection cancelled.'))
102
+ } else {
103
+ console.error(colors.errorMessage(`Error during file selection: ${error.message}`))
104
+ }
105
+ return []
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Interactive unstaging of files
111
+ */
112
+ async selectFilesToUnstage() {
113
+ const { multiselect, confirm } = await import('@clack/prompts')
114
+
115
+ const status = this.getDetailedStatus()
116
+ const stagedFiles = status.staged
117
+
118
+ if (stagedFiles.length === 0) {
119
+ console.log(colors.infoMessage('No staged files to unstage.'))
120
+ return []
121
+ }
122
+
123
+ const choices = stagedFiles.map((file) => ({
124
+ value: file.path,
125
+ label: `${this.getStatusIcon(file.status)} ${file.status} ${file.path}`,
126
+ hint: 'Remove from staging area',
127
+ }))
128
+
129
+ try {
130
+ const selectedFiles = await multiselect({
131
+ message: 'Select files to unstage:',
132
+ options: choices,
133
+ required: false,
134
+ })
135
+
136
+ if (!selectedFiles || selectedFiles.length === 0) {
137
+ console.log(colors.infoMessage('No files selected for unstaging.'))
138
+ return []
139
+ }
140
+
141
+ const shouldUnstage = await confirm({
142
+ message: `Unstage ${selectedFiles.length} file(s)?`,
143
+ initialValue: true,
144
+ })
145
+
146
+ if (shouldUnstage) {
147
+ await this.unstageFiles(selectedFiles)
148
+ console.log(colors.successMessage(`✅ Unstaged ${selectedFiles.length} file(s)`))
149
+ return selectedFiles
150
+ }
151
+ console.log(colors.infoMessage('Unstaging cancelled.'))
152
+ return []
153
+ } catch (error) {
154
+ if (error.message.includes('cancelled')) {
155
+ console.log(colors.infoMessage('File unstaging cancelled.'))
156
+ } else {
157
+ console.error(colors.errorMessage(`Error during file unstaging: ${error.message}`))
158
+ }
159
+ return []
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Stage all changes
165
+ */
166
+ async stageAllChanges() {
167
+ try {
168
+ console.log(colors.processingMessage('Staging all changes...'))
169
+ execSync('git add .', { stdio: 'pipe' })
170
+ console.log(colors.successMessage('✅ All changes staged'))
171
+ return true
172
+ } catch (error) {
173
+ console.error(colors.errorMessage(`Error staging all changes: ${error.message}`))
174
+ return false
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Stage specific files
180
+ */
181
+ async stageFiles(filePaths) {
182
+ try {
183
+ const files = Array.isArray(filePaths) ? filePaths : [filePaths]
184
+
185
+ // Use spawn with argument array to avoid command injection
186
+ if (files.length > 0) {
187
+ const { spawnSync } = await import('node:child_process')
188
+ const result = spawnSync('git', ['add', ...files], {
189
+ stdio: 'pipe',
190
+ encoding: 'utf8',
191
+ })
192
+
193
+ if (result.status !== 0) {
194
+ throw new Error(`git add failed: ${result.stderr}`)
195
+ }
196
+ }
197
+
198
+ return true
199
+ } catch (error) {
200
+ console.error(colors.errorMessage(`Error staging files: ${error.message}`))
201
+ return false
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Unstage specific files
207
+ */
208
+ async unstageFiles(filePaths) {
209
+ try {
210
+ const files = Array.isArray(filePaths) ? filePaths : [filePaths]
211
+
212
+ // Use spawn with argument array to avoid command injection
213
+ if (files.length > 0) {
214
+ const { spawnSync } = await import('node:child_process')
215
+ const result = spawnSync('git', ['reset', 'HEAD', ...files], {
216
+ stdio: 'pipe',
217
+ encoding: 'utf8',
218
+ })
219
+
220
+ if (result.status !== 0) {
221
+ throw new Error(`git reset failed: ${result.stderr}`)
222
+ }
223
+ }
224
+
225
+ return true
226
+ } catch (error) {
227
+ console.error(colors.errorMessage(`Error unstaging files: ${error.message}`))
228
+ return false
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Get detailed git status (staged, unstaged, untracked)
234
+ */
235
+ getDetailedStatus() {
236
+ try {
237
+ const output = execSync('git status --porcelain', { encoding: 'utf8' })
238
+ const lines = output.split('\n').filter(Boolean)
239
+
240
+ const staged = []
241
+ const unstaged = []
242
+ const untracked = []
243
+
244
+ lines.forEach((line) => {
245
+ if (line.length < 3) {
246
+ return
247
+ }
248
+
249
+ const indexStatus = line.charAt(0)
250
+ const workTreeStatus = line.charAt(1)
251
+ const filePath = line.substring(3).trim()
252
+
253
+ // Handle different status combinations
254
+ if (indexStatus === '?' && workTreeStatus === '?') {
255
+ // Untracked file
256
+ untracked.push({ status: '??', path: filePath })
257
+ } else {
258
+ // Staged changes (index status)
259
+ if (indexStatus !== ' ') {
260
+ staged.push({ status: indexStatus, path: filePath })
261
+ }
262
+
263
+ // Unstaged changes (work tree status)
264
+ if (workTreeStatus !== ' ') {
265
+ unstaged.push({ status: workTreeStatus, path: filePath })
266
+ }
267
+ }
268
+ })
269
+
270
+ return { staged, unstaged, untracked }
271
+ } catch (error) {
272
+ console.error(colors.errorMessage(`Error getting git status: ${error.message}`))
273
+ return { staged: [], unstaged: [], untracked: [] }
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Get icon for status
279
+ */
280
+ getStatusIcon(status) {
281
+ const icons = {
282
+ M: '📝', // Modified
283
+ A: '✨', // Added
284
+ D: '🗑️', // Deleted
285
+ R: '🔄', // Renamed
286
+ C: '📋', // Copied
287
+ U: '⚠️', // Unmerged
288
+ '??': '✨', // Untracked
289
+ }
290
+ return icons[status] || '📄'
291
+ }
292
+
293
+ /**
294
+ * Get description for status
295
+ */
296
+ getStatusDescription(status) {
297
+ const descriptions = {
298
+ M: 'Modified file',
299
+ A: 'New file',
300
+ D: 'Deleted file',
301
+ R: 'Renamed file',
302
+ C: 'Copied file',
303
+ U: 'Unmerged file',
304
+ '??': 'Untracked file',
305
+ }
306
+ return descriptions[status] || 'Changed file'
307
+ }
308
+
309
+ /**
310
+ * Check if there are any staged changes
311
+ */
312
+ hasStagedChanges() {
313
+ try {
314
+ const output = execSync('git diff --cached --name-only', { encoding: 'utf8' })
315
+ return output.trim().length > 0
316
+ } catch (_error) {
317
+ return false
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Check if there are any unstaged changes
323
+ */
324
+ hasUnstagedChanges() {
325
+ try {
326
+ const output = execSync('git diff --name-only', { encoding: 'utf8' })
327
+ return output.trim().length > 0
328
+ } catch (_error) {
329
+ return false
330
+ }
331
+ }
332
+ }