@entro314labs/ai-changelog-generator 3.1.1 → 3.2.1
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 +412 -875
- package/README.md +8 -3
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +9 -9
- package/bin/ai-changelog-mcp.js +19 -17
- package/bin/ai-changelog.js +6 -6
- package/package.json +80 -48
- package/src/ai-changelog-generator.js +91 -81
- package/src/application/orchestrators/changelog.orchestrator.js +791 -516
- package/src/application/services/application.service.js +137 -128
- package/src/cli.js +76 -57
- package/src/domains/ai/ai-analysis.service.js +289 -209
- package/src/domains/analysis/analysis.engine.js +328 -192
- package/src/domains/changelog/changelog.service.js +1174 -783
- package/src/domains/changelog/workspace-changelog.service.js +487 -249
- package/src/domains/git/git-repository.analyzer.js +348 -258
- package/src/domains/git/git.service.js +132 -112
- package/src/infrastructure/cli/cli.controller.js +390 -274
- package/src/infrastructure/config/configuration.manager.js +220 -190
- package/src/infrastructure/interactive/interactive-staging.service.js +154 -135
- package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
- package/src/infrastructure/mcp/mcp-server.service.js +208 -207
- package/src/infrastructure/metrics/metrics.collector.js +140 -123
- package/src/infrastructure/providers/core/base-provider.js +87 -40
- package/src/infrastructure/providers/implementations/anthropic.js +101 -99
- package/src/infrastructure/providers/implementations/azure.js +124 -101
- package/src/infrastructure/providers/implementations/bedrock.js +136 -126
- package/src/infrastructure/providers/implementations/dummy.js +23 -23
- package/src/infrastructure/providers/implementations/google.js +123 -114
- package/src/infrastructure/providers/implementations/huggingface.js +94 -87
- package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
- package/src/infrastructure/providers/implementations/mock.js +69 -73
- package/src/infrastructure/providers/implementations/ollama.js +89 -66
- package/src/infrastructure/providers/implementations/openai.js +88 -89
- package/src/infrastructure/providers/implementations/vertex.js +227 -197
- package/src/infrastructure/providers/provider-management.service.js +245 -207
- package/src/infrastructure/providers/provider-manager.service.js +145 -125
- package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
- package/src/infrastructure/providers/utils/model-config.js +220 -195
- package/src/infrastructure/providers/utils/provider-utils.js +105 -100
- package/src/infrastructure/validation/commit-message-validation.service.js +259 -161
- package/src/shared/constants/colors.js +453 -180
- package/src/shared/utils/cli-demo.js +285 -0
- package/src/shared/utils/cli-entry-utils.js +257 -249
- package/src/shared/utils/cli-ui.js +447 -0
- package/src/shared/utils/diff-processor.js +513 -0
- package/src/shared/utils/error-classes.js +125 -156
- package/src/shared/utils/json-utils.js +93 -89
- package/src/shared/utils/utils.js +1117 -945
- 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
|
+
}
|