@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,447 @@
1
+ /**
2
+ * Modern CLI UI utilities for enhanced visual feedback
3
+ * Provides loading indicators, progress bars, and modern CLI aesthetics
4
+ */
5
+
6
+ import process from 'node:process'
7
+
8
+ import colors from '../constants/colors.js'
9
+
10
+ /**
11
+ * Simple spinner implementation for loading states
12
+ */
13
+ export class SimpleSpinner {
14
+ constructor(text = 'Loading...', interval = 80) {
15
+ this.text = text
16
+ this.interval = interval
17
+ this.frames = colors.spinnerFrames
18
+ this.currentFrame = 0
19
+ this.timer = null
20
+ this.isSpinning = false
21
+ }
22
+
23
+ start() {
24
+ if (this.isSpinning) {
25
+ return this
26
+ }
27
+
28
+ this.isSpinning = true
29
+ this.timer = setInterval(() => {
30
+ const frame = this.frames[this.currentFrame]
31
+ process.stdout.write(`\r${colors.highlight(frame)} ${this.text}`)
32
+ this.currentFrame = (this.currentFrame + 1) % this.frames.length
33
+ }, this.interval)
34
+
35
+ return this
36
+ }
37
+
38
+ stop(finalText = null) {
39
+ if (!this.isSpinning) {
40
+ return this
41
+ }
42
+
43
+ clearInterval(this.timer)
44
+ this.timer = null
45
+ this.isSpinning = false
46
+
47
+ if (finalText) {
48
+ process.stdout.write(`\r${finalText}\n`)
49
+ } else {
50
+ process.stdout.write(`\r${' '.repeat(this.text.length + 2)}\r`)
51
+ }
52
+
53
+ return this
54
+ }
55
+
56
+ succeed(text = null) {
57
+ return this.stop(colors.statusSymbol('success', text || this.text))
58
+ }
59
+
60
+ fail(text = null) {
61
+ return this.stop(colors.statusSymbol('error', text || this.text))
62
+ }
63
+
64
+ warn(text = null) {
65
+ return this.stop(colors.statusSymbol('warning', text || this.text))
66
+ }
67
+
68
+ info(text = null) {
69
+ return this.stop(colors.statusSymbol('info', text || this.text))
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Progress bar for long-running operations
75
+ */
76
+ export class ProgressBar {
77
+ constructor(total, width = 40, options = {}) {
78
+ this.total = total
79
+ this.current = 0
80
+ this.width = width
81
+ this.startTime = Date.now()
82
+
83
+ const {
84
+ format = '{bar} {percentage}% | {current}/{total} | ETA: {eta}s',
85
+ complete = '█',
86
+ incomplete = '░',
87
+ renderThrottle = 100,
88
+ } = options
89
+
90
+ this.format = format
91
+ this.complete = complete
92
+ this.incomplete = incomplete
93
+ this.renderThrottle = renderThrottle
94
+ this.lastRender = 0
95
+ }
96
+
97
+ update(current, label = '') {
98
+ this.current = Math.min(current, this.total)
99
+
100
+ const now = Date.now()
101
+ if (now - this.lastRender < this.renderThrottle && this.current < this.total) {
102
+ return
103
+ }
104
+ this.lastRender = now
105
+
106
+ const percentage = Math.floor((this.current / this.total) * 100)
107
+ const filledWidth = Math.floor((this.current / this.total) * this.width)
108
+ const emptyWidth = this.width - filledWidth
109
+
110
+ const bar =
111
+ colors.success(this.complete.repeat(filledWidth)) +
112
+ colors.dim(this.incomplete.repeat(emptyWidth))
113
+
114
+ // Calculate ETA
115
+ const elapsed = (now - this.startTime) / 1000
116
+ const rate = this.current / elapsed
117
+ const eta = rate > 0 ? Math.ceil((this.total - this.current) / rate) : 0
118
+
119
+ let output = this.format
120
+ .replace('{bar}', bar)
121
+ .replace('{percentage}', percentage.toString().padStart(3))
122
+ .replace('{current}', this.current.toString())
123
+ .replace('{total}', this.total.toString())
124
+ .replace('{eta}', eta.toString())
125
+
126
+ if (label) {
127
+ output += ` | ${label}`
128
+ }
129
+
130
+ process.stdout.write(`\r${output}`)
131
+
132
+ if (this.current >= this.total) {
133
+ process.stdout.write('\n')
134
+ }
135
+ }
136
+
137
+ increment(label = '') {
138
+ this.update(this.current + 1, label)
139
+ }
140
+
141
+ finish(message = '') {
142
+ this.update(this.total)
143
+ if (message) {
144
+ console.log(colors.statusSymbol('success', message))
145
+ }
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Multi-line progress display for multiple concurrent operations
151
+ */
152
+ export class MultiProgress {
153
+ constructor() {
154
+ this.bars = new Map()
155
+ this.isActive = false
156
+ }
157
+
158
+ create(id, total, label = '', options = {}) {
159
+ const bar = new ProgressBar(total, options.width || 30, {
160
+ ...options,
161
+ format: `${label.padEnd(20)} {bar} {percentage}% | {current}/{total}`,
162
+ })
163
+
164
+ this.bars.set(id, { bar, label, lastOutput: '' })
165
+ return bar
166
+ }
167
+
168
+ update(id, current, label = '') {
169
+ const entry = this.bars.get(id)
170
+ if (!entry) {
171
+ return
172
+ }
173
+
174
+ entry.bar.update(current, label)
175
+ }
176
+
177
+ remove(id, finalMessage = '') {
178
+ const entry = this.bars.get(id)
179
+ if (!entry) {
180
+ return
181
+ }
182
+
183
+ this.bars.delete(id)
184
+ if (finalMessage) {
185
+ console.log(colors.statusSymbol('success', `${entry.label}: ${finalMessage}`))
186
+ }
187
+ }
188
+
189
+ clear() {
190
+ this.bars.clear()
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Enhanced console with better formatting
196
+ */
197
+ export class EnhancedConsole {
198
+ static log(message, type = 'info') {
199
+ console.log(colors.statusSymbol(type, message))
200
+ }
201
+
202
+ static success(message) {
203
+ EnhancedConsole.log(message, 'success')
204
+ }
205
+
206
+ static error(message) {
207
+ EnhancedConsole.log(message, 'error')
208
+ }
209
+
210
+ static warn(message) {
211
+ EnhancedConsole.log(message, 'warning')
212
+ }
213
+
214
+ static info(message) {
215
+ EnhancedConsole.log(message, 'info')
216
+ }
217
+
218
+ static processing(message) {
219
+ EnhancedConsole.log(message, 'processing')
220
+ }
221
+
222
+ static ai(message) {
223
+ EnhancedConsole.log(message, 'ai')
224
+ }
225
+
226
+ static metrics(message) {
227
+ EnhancedConsole.log(message, 'metrics')
228
+ }
229
+
230
+ static section(title, content = '') {
231
+ console.log(`\n${colors.sectionHeader(title)}`)
232
+ console.log(colors.separator('─', 50))
233
+ if (content) {
234
+ console.log(content)
235
+ }
236
+ }
237
+
238
+ static box(title, content, options = {}) {
239
+ console.log(colors.boxed(content, { title, ...options }))
240
+ }
241
+
242
+ static table(data, options = {}) {
243
+ if (data && data.length > 0) {
244
+ console.log(colors.table(data, options))
245
+ }
246
+ }
247
+
248
+ static fileList(files, title = 'Files') {
249
+ if (files && files.length > 0) {
250
+ EnhancedConsole.section(title)
251
+ console.log(colors.formatFileList(files))
252
+ }
253
+ }
254
+
255
+ static metrics(metrics, title = 'Metrics') {
256
+ if (metrics && Object.keys(metrics).length > 0) {
257
+ EnhancedConsole.section(title)
258
+ console.log(colors.formatMetrics(metrics))
259
+ }
260
+ }
261
+
262
+ static divider(char = '─', length = 60) {
263
+ console.log(colors.separator(char, length))
264
+ }
265
+
266
+ static space(lines = 1) {
267
+ console.log('\n'.repeat(lines - 1))
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Task list display for showing progress on multiple items
273
+ */
274
+ export class TaskList {
275
+ constructor(tasks = []) {
276
+ this.tasks = tasks.map((task) => ({
277
+ text: task,
278
+ status: 'pending',
279
+ startTime: null,
280
+ endTime: null,
281
+ }))
282
+ this.currentIndex = -1
283
+ }
284
+
285
+ add(task) {
286
+ this.tasks.push({
287
+ text: task,
288
+ status: 'pending',
289
+ startTime: null,
290
+ endTime: null,
291
+ })
292
+ }
293
+
294
+ start(index) {
295
+ if (index < 0 || index >= this.tasks.length) {
296
+ return
297
+ }
298
+
299
+ this.currentIndex = index
300
+ this.tasks[index].status = 'running'
301
+ this.tasks[index].startTime = Date.now()
302
+ this.render()
303
+ }
304
+
305
+ complete(index, message = '') {
306
+ if (index < 0 || index >= this.tasks.length) {
307
+ return
308
+ }
309
+
310
+ this.tasks[index].status = 'completed'
311
+ this.tasks[index].endTime = Date.now()
312
+ if (message) {
313
+ this.tasks[index].text += ` (${message})`
314
+ }
315
+ this.render()
316
+ }
317
+
318
+ fail(index, message = '') {
319
+ if (index < 0 || index >= this.tasks.length) {
320
+ return
321
+ }
322
+
323
+ this.tasks[index].status = 'failed'
324
+ this.tasks[index].endTime = Date.now()
325
+ if (message) {
326
+ this.tasks[index].text += ` (${colors.error(message)})`
327
+ }
328
+ this.render()
329
+ }
330
+
331
+ render() {
332
+ // Clear previous output
333
+ if (this.lastRenderHeight) {
334
+ process.stdout.write(`\u001b[${this.lastRenderHeight}A`)
335
+ process.stdout.write('\u001b[0J')
336
+ }
337
+
338
+ const output = this.tasks
339
+ .map((task, _index) => {
340
+ let symbol
341
+ let color
342
+
343
+ switch (task.status) {
344
+ case 'completed':
345
+ symbol = colors.symbols.success
346
+ color = colors.success
347
+ break
348
+ case 'failed':
349
+ symbol = colors.symbols.error
350
+ color = colors.error
351
+ break
352
+ case 'running':
353
+ symbol = colors.symbols.refresh
354
+ color = colors.highlight
355
+ break
356
+ default:
357
+ symbol = colors.symbols.bullet
358
+ color = colors.dim
359
+ }
360
+
361
+ return `${color(symbol)} ${task.text}`
362
+ })
363
+ .join('\n')
364
+
365
+ console.log(output)
366
+ this.lastRenderHeight = this.tasks.length
367
+ }
368
+
369
+ summary() {
370
+ const completed = this.tasks.filter((t) => t.status === 'completed').length
371
+ const failed = this.tasks.filter((t) => t.status === 'failed').length
372
+ const total = this.tasks.length
373
+
374
+ console.log(`\n${colors.separator('─', 50)}`)
375
+ console.log(
376
+ colors.statusSymbol('info', `Tasks completed: ${colors.success(completed)}/${total}`)
377
+ )
378
+
379
+ if (failed > 0) {
380
+ console.log(colors.statusSymbol('error', `Tasks failed: ${colors.error(failed)}`))
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Utility functions for common CLI patterns
387
+ */
388
+ export const cliUtils = {
389
+ // Wait for user input
390
+ async waitForKey(message = 'Press any key to continue...') {
391
+ console.log(colors.dim(message))
392
+ process.stdin.setRawMode(true)
393
+ process.stdin.resume()
394
+
395
+ return new Promise((resolve) => {
396
+ process.stdin.once('data', () => {
397
+ process.stdin.setRawMode(false)
398
+ process.stdin.pause()
399
+ resolve()
400
+ })
401
+ })
402
+ },
403
+
404
+ // Clear screen
405
+ clear() {
406
+ process.stdout.write('\u001b[2J\u001b[0;0H')
407
+ },
408
+
409
+ // Move cursor up
410
+ cursorUp(lines = 1) {
411
+ process.stdout.write(`\u001b[${lines}A`)
412
+ },
413
+
414
+ // Clear current line
415
+ clearLine() {
416
+ process.stdout.write('\u001b[0K')
417
+ },
418
+
419
+ // Format duration
420
+ formatDuration(ms) {
421
+ if (ms <= 0) {
422
+ return '0s'
423
+ }
424
+
425
+ const seconds = Math.floor(ms / 1000)
426
+ const minutes = Math.floor(seconds / 60)
427
+ const hours = Math.floor(minutes / 60)
428
+
429
+ if (hours > 0) {
430
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`
431
+ }
432
+ if (minutes > 0) {
433
+ return `${minutes}m ${seconds % 60}s`
434
+ }
435
+ return `${seconds}s`
436
+ },
437
+
438
+ // Format bytes
439
+ formatBytes(bytes) {
440
+ const sizes = ['B', 'KB', 'MB', 'GB']
441
+ if (bytes === 0) {
442
+ return '0B'
443
+ }
444
+ const i = Math.floor(Math.log(bytes) / Math.log(1024))
445
+ return `${Math.round((bytes / 1024 ** i) * 100) / 100}${sizes[i]}`
446
+ },
447
+ }