@entro314labs/ai-changelog-generator 3.6.1 ā 3.7.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/manifest.json +13 -1
- package/package.json +9 -9
- package/src/application/orchestrators/changelog.orchestrator.js +28 -2
- package/src/application/services/application.service.js +8 -2
- package/src/domains/changelog/changelog.service.js +5 -2
- package/src/domains/git/git-manager.js +144 -2
- package/src/domains/git/git.service.js +22 -2
- package/src/infrastructure/cli/cli.controller.js +442 -6
- package/src/shared/utils/utils.js +0 -18
- package/src/domains/changelog/workspace-changelog.service.js +0 -566
|
@@ -44,6 +44,7 @@ export class CLIController {
|
|
|
44
44
|
this.commands.set('commit-message', new CommitMessageCommand())
|
|
45
45
|
this.commands.set('commit', new CommitCommand())
|
|
46
46
|
this.commands.set('providers', new ProvidersCommand())
|
|
47
|
+
this.commands.set('stash', new StashCommand())
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
async runCLI() {
|
|
@@ -122,6 +123,22 @@ export class CLIController {
|
|
|
122
123
|
type: 'string',
|
|
123
124
|
description: 'Generate changelog since a specific git ref (tag/commit).',
|
|
124
125
|
})
|
|
126
|
+
.option('author', {
|
|
127
|
+
alias: 'a',
|
|
128
|
+
type: 'string',
|
|
129
|
+
description: 'Filter commits by author name or email.',
|
|
130
|
+
})
|
|
131
|
+
.option('tag-range', {
|
|
132
|
+
type: 'string',
|
|
133
|
+
description: 'Generate changelog between two tags (format: v1.0.0..v2.0.0).',
|
|
134
|
+
})
|
|
135
|
+
.option('format', {
|
|
136
|
+
alias: 'f',
|
|
137
|
+
type: 'string',
|
|
138
|
+
choices: ['markdown', 'json', 'html'],
|
|
139
|
+
default: 'markdown',
|
|
140
|
+
description: 'Output format for the changelog.',
|
|
141
|
+
})
|
|
125
142
|
.option('model', {
|
|
126
143
|
alias: 'm',
|
|
127
144
|
type: 'string',
|
|
@@ -137,6 +154,11 @@ export class CLIController {
|
|
|
137
154
|
type: 'boolean',
|
|
138
155
|
description: 'Disable the attribution footer.',
|
|
139
156
|
})
|
|
157
|
+
.option('output', {
|
|
158
|
+
alias: 'o',
|
|
159
|
+
type: 'string',
|
|
160
|
+
description: 'Output file path.',
|
|
161
|
+
})
|
|
140
162
|
})
|
|
141
163
|
|
|
142
164
|
// Analysis commands
|
|
@@ -223,12 +245,38 @@ export class CLIController {
|
|
|
223
245
|
})
|
|
224
246
|
.option('model', { type: 'string', description: 'Override the default AI model.' })
|
|
225
247
|
})
|
|
248
|
+
.command('stash', 'Analyze stashed changes.', (yargs) => {
|
|
249
|
+
yargs
|
|
250
|
+
.command('list', 'List all stashed changes.')
|
|
251
|
+
.command('analyze [stash]', 'Analyze a specific stash entry.', (y) => {
|
|
252
|
+
y.positional('stash', {
|
|
253
|
+
describe: 'Stash reference (e.g., stash@{0})',
|
|
254
|
+
default: 'stash@{0}',
|
|
255
|
+
type: 'string',
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
.command('changelog [stash]', 'Generate changelog from stashed changes.', (y) => {
|
|
259
|
+
y.positional('stash', {
|
|
260
|
+
describe: 'Stash reference (e.g., stash@{0})',
|
|
261
|
+
default: 'stash@{0}',
|
|
262
|
+
type: 'string',
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
.demandCommand(1, 'Please specify a stash subcommand.')
|
|
266
|
+
})
|
|
226
267
|
.command('providers', 'Manage AI providers.', (yargs) => {
|
|
227
268
|
yargs
|
|
228
269
|
.command('list', 'List available providers.')
|
|
229
270
|
.command('switch <provider>', 'Switch to a different provider.')
|
|
230
271
|
.command('configure [provider]', 'Configure AI provider settings.')
|
|
231
272
|
.command('validate [provider]', 'Validate provider models and capabilities.')
|
|
273
|
+
.command('status', 'Check health status of all providers.')
|
|
274
|
+
.command('models [provider]', 'List available models for a provider.', (y) => {
|
|
275
|
+
y.positional('provider', {
|
|
276
|
+
describe: 'Provider name (optional, shows all if not specified)',
|
|
277
|
+
type: 'string',
|
|
278
|
+
})
|
|
279
|
+
})
|
|
232
280
|
.demandCommand(1, 'Please specify a provider subcommand.')
|
|
233
281
|
})
|
|
234
282
|
|
|
@@ -251,12 +299,14 @@ export class CLIController {
|
|
|
251
299
|
.option('format', {
|
|
252
300
|
alias: 'f',
|
|
253
301
|
type: 'string',
|
|
254
|
-
choices: ['markdown', 'json'],
|
|
302
|
+
choices: ['markdown', 'json', 'html'],
|
|
255
303
|
default: 'markdown',
|
|
256
304
|
description: 'Output format',
|
|
257
305
|
})
|
|
258
306
|
.option('output', { alias: 'o', type: 'string', description: 'Output file path' })
|
|
259
307
|
.option('since', { type: 'string', description: 'Analyze changes since this git ref' })
|
|
308
|
+
.option('author', { alias: 'a', type: 'string', description: 'Filter commits by author' })
|
|
309
|
+
.option('tag-range', { type: 'string', description: 'Generate changelog between tags (v1.0..v2.0)' })
|
|
260
310
|
.option('silent', { type: 'boolean', description: 'Suppress non-essential output' })
|
|
261
311
|
.option('dry-run', { type: 'boolean', description: 'Preview without writing files' })
|
|
262
312
|
.option('detailed', { type: 'boolean', description: 'Use detailed analysis mode' })
|
|
@@ -304,6 +354,8 @@ class BaseCommand {
|
|
|
304
354
|
format: argv.format || 'markdown',
|
|
305
355
|
output: argv.output,
|
|
306
356
|
since: argv.since,
|
|
357
|
+
author: argv.author,
|
|
358
|
+
tagRange: argv.tagRange,
|
|
307
359
|
silent: argv.silent,
|
|
308
360
|
dryRun: argv.dryRun,
|
|
309
361
|
}
|
|
@@ -326,17 +378,83 @@ class BaseCommand {
|
|
|
326
378
|
// Command implementations
|
|
327
379
|
class DefaultCommand extends BaseCommand {
|
|
328
380
|
async execute(argv, appService) {
|
|
329
|
-
const
|
|
381
|
+
const config = this.processStandardFlags(argv, appService)
|
|
330
382
|
|
|
331
383
|
if (argv.interactive) {
|
|
332
384
|
await appService.runInteractive()
|
|
333
385
|
} else {
|
|
334
|
-
await appService.generateChangelog({
|
|
386
|
+
const result = await appService.generateChangelog({
|
|
335
387
|
version: argv.releaseVersion,
|
|
336
388
|
since: argv.since,
|
|
389
|
+
author: argv.author,
|
|
390
|
+
tagRange: argv.tagRange,
|
|
391
|
+
format: config.format,
|
|
392
|
+
output: config.output,
|
|
393
|
+
dryRun: config.dryRun,
|
|
337
394
|
})
|
|
395
|
+
|
|
396
|
+
// Handle output formatting
|
|
397
|
+
if (result?.changelog && config.format !== 'markdown') {
|
|
398
|
+
const formattedOutput = this.formatOutput(result.changelog, config.format)
|
|
399
|
+
if (config.dryRun || !config.output) {
|
|
400
|
+
console.log(formattedOutput)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
338
403
|
}
|
|
339
404
|
}
|
|
405
|
+
|
|
406
|
+
formatOutput(changelog, format) {
|
|
407
|
+
if (format === 'json') {
|
|
408
|
+
return JSON.stringify({ changelog, generatedAt: new Date().toISOString() }, null, 2)
|
|
409
|
+
}
|
|
410
|
+
if (format === 'html') {
|
|
411
|
+
return this.convertToHtml(changelog)
|
|
412
|
+
}
|
|
413
|
+
return changelog
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
convertToHtml(markdown) {
|
|
417
|
+
// Simple markdown to HTML conversion
|
|
418
|
+
let html = markdown
|
|
419
|
+
// Headers
|
|
420
|
+
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
421
|
+
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
422
|
+
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
423
|
+
// Bold
|
|
424
|
+
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
|
|
425
|
+
// Italic
|
|
426
|
+
.replace(/\*(.*)\*/gim, '<em>$1</em>')
|
|
427
|
+
// Links
|
|
428
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/gim, '<a href="$2">$1</a>')
|
|
429
|
+
// List items
|
|
430
|
+
.replace(/^\s*-\s+(.*$)/gim, '<li>$1</li>')
|
|
431
|
+
// Inline code
|
|
432
|
+
.replace(/`([^`]+)`/gim, '<code>$1</code>')
|
|
433
|
+
// Paragraphs
|
|
434
|
+
.replace(/\n\n/gim, '</p><p>')
|
|
435
|
+
|
|
436
|
+
return `<!DOCTYPE html>
|
|
437
|
+
<html lang="en">
|
|
438
|
+
<head>
|
|
439
|
+
<meta charset="UTF-8">
|
|
440
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
441
|
+
<title>Changelog</title>
|
|
442
|
+
<style>
|
|
443
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 800px; margin: 0 auto; padding: 2rem; line-height: 1.6; }
|
|
444
|
+
h1, h2, h3 { color: #333; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
|
|
445
|
+
ul { padding-left: 2em; }
|
|
446
|
+
li { margin: 0.5em 0; }
|
|
447
|
+
code { background: #f4f4f4; padding: 0.2em 0.4em; border-radius: 3px; }
|
|
448
|
+
a { color: #0066cc; }
|
|
449
|
+
.generated { color: #666; font-size: 0.9em; margin-top: 2em; border-top: 1px solid #eee; padding-top: 1em; }
|
|
450
|
+
</style>
|
|
451
|
+
</head>
|
|
452
|
+
<body>
|
|
453
|
+
<p>${html}</p>
|
|
454
|
+
<div class="generated">Generated by AI Changelog Generator on ${new Date().toLocaleString()}</div>
|
|
455
|
+
</body>
|
|
456
|
+
</html>`
|
|
457
|
+
}
|
|
340
458
|
}
|
|
341
459
|
|
|
342
460
|
class InitCommand extends BaseCommand {
|
|
@@ -426,8 +544,21 @@ class WorkingDirCommand extends BaseCommand {
|
|
|
426
544
|
class FromCommitsCommand extends BaseCommand {
|
|
427
545
|
async execute(argv, appService) {
|
|
428
546
|
const _config = this.processStandardFlags(argv, appService)
|
|
429
|
-
|
|
430
|
-
|
|
547
|
+
console.log(colors.processingMessage(`š Generating changelog from commits: ${argv.commits.join(', ')}`))
|
|
548
|
+
|
|
549
|
+
try {
|
|
550
|
+
const result = await appService.generateChangelogFromCommits(argv.commits)
|
|
551
|
+
|
|
552
|
+
if (result?.changelog) {
|
|
553
|
+
console.log(colors.successMessage('\nā
Changelog generated successfully!'))
|
|
554
|
+
console.log(colors.dim('ā'.repeat(50)))
|
|
555
|
+
console.log(result.changelog)
|
|
556
|
+
} else {
|
|
557
|
+
console.log(colors.warningMessage('No changelog could be generated from the specified commits.'))
|
|
558
|
+
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
console.error(colors.errorMessage(`Error generating changelog: ${error.message}`))
|
|
561
|
+
}
|
|
431
562
|
}
|
|
432
563
|
}
|
|
433
564
|
|
|
@@ -526,9 +657,15 @@ class ProvidersCommand extends BaseCommand {
|
|
|
526
657
|
case 'validate':
|
|
527
658
|
await this.validateProvider(appService, argv.provider)
|
|
528
659
|
break
|
|
660
|
+
case 'status':
|
|
661
|
+
await this.checkProviderStatus(appService)
|
|
662
|
+
break
|
|
663
|
+
case 'models':
|
|
664
|
+
await this.listModels(appService, argv.provider)
|
|
665
|
+
break
|
|
529
666
|
default:
|
|
530
667
|
console.log(colors.errorMessage('Unknown provider subcommand'))
|
|
531
|
-
console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate'))
|
|
668
|
+
console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate, status, models'))
|
|
532
669
|
}
|
|
533
670
|
}
|
|
534
671
|
|
|
@@ -684,6 +821,305 @@ class ProvidersCommand extends BaseCommand {
|
|
|
684
821
|
EnhancedConsole.error(`Error validating provider: ${error.message}`)
|
|
685
822
|
}
|
|
686
823
|
}
|
|
824
|
+
|
|
825
|
+
async checkProviderStatus(appService) {
|
|
826
|
+
console.log(colors.processingMessage('š„ Checking provider health status...'))
|
|
827
|
+
|
|
828
|
+
try {
|
|
829
|
+
const providers = await appService.listProviders()
|
|
830
|
+
const healthResults = []
|
|
831
|
+
|
|
832
|
+
for (const provider of providers) {
|
|
833
|
+
if (provider.available) {
|
|
834
|
+
const startTime = Date.now()
|
|
835
|
+
try {
|
|
836
|
+
const validation = await appService.validateProvider(provider.name)
|
|
837
|
+
const responseTime = Date.now() - startTime
|
|
838
|
+
|
|
839
|
+
healthResults.push({
|
|
840
|
+
name: provider.name,
|
|
841
|
+
status: validation.success ? 'healthy' : 'unhealthy',
|
|
842
|
+
responseTime,
|
|
843
|
+
model: validation.model || 'N/A',
|
|
844
|
+
error: validation.error || null,
|
|
845
|
+
active: provider.active,
|
|
846
|
+
})
|
|
847
|
+
} catch (error) {
|
|
848
|
+
healthResults.push({
|
|
849
|
+
name: provider.name,
|
|
850
|
+
status: 'error',
|
|
851
|
+
responseTime: Date.now() - startTime,
|
|
852
|
+
error: error.message,
|
|
853
|
+
active: provider.active,
|
|
854
|
+
})
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
healthResults.push({
|
|
858
|
+
name: provider.name,
|
|
859
|
+
status: 'unconfigured',
|
|
860
|
+
responseTime: null,
|
|
861
|
+
error: 'Not configured',
|
|
862
|
+
active: false,
|
|
863
|
+
})
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Display results
|
|
868
|
+
console.log(colors.header('\nš„ Provider Health Status:\n'))
|
|
869
|
+
|
|
870
|
+
const statusIcons = {
|
|
871
|
+
healthy: 'š¢',
|
|
872
|
+
unhealthy: 'š“',
|
|
873
|
+
error: 'š“',
|
|
874
|
+
unconfigured: 'āŖ',
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
healthResults.forEach((result) => {
|
|
878
|
+
const icon = statusIcons[result.status] || 'āŖ'
|
|
879
|
+
const activeMarker = result.active ? ' šÆ' : ''
|
|
880
|
+
const responseInfo = result.responseTime ? ` (${result.responseTime}ms)` : ''
|
|
881
|
+
|
|
882
|
+
console.log(` ${icon} ${colors.highlight(result.name)}${activeMarker}`)
|
|
883
|
+
console.log(` Status: ${result.status}${responseInfo}`)
|
|
884
|
+
|
|
885
|
+
if (result.model && result.status === 'healthy') {
|
|
886
|
+
console.log(` Model: ${colors.dim(result.model)}`)
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (result.error && result.status !== 'unconfigured') {
|
|
890
|
+
console.log(` Error: ${colors.errorMessage(result.error)}`)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
console.log('')
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
// Summary
|
|
897
|
+
const healthy = healthResults.filter((r) => r.status === 'healthy').length
|
|
898
|
+
const unhealthy = healthResults.filter((r) => ['unhealthy', 'error'].includes(r.status)).length
|
|
899
|
+
const unconfigured = healthResults.filter((r) => r.status === 'unconfigured').length
|
|
900
|
+
|
|
901
|
+
console.log(colors.dim('ā'.repeat(40)))
|
|
902
|
+
console.log(
|
|
903
|
+
`Summary: ${colors.successMessage(`${healthy} healthy`)}, ${unhealthy > 0 ? colors.errorMessage(`${unhealthy} unhealthy`) : `${unhealthy} unhealthy`}, ${unconfigured} unconfigured`
|
|
904
|
+
)
|
|
905
|
+
} catch (error) {
|
|
906
|
+
EnhancedConsole.error(`Error checking provider status: ${error.message}`)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
async listModels(appService, providerName) {
|
|
911
|
+
console.log(colors.processingMessage('š Discovering available models...'))
|
|
912
|
+
|
|
913
|
+
try {
|
|
914
|
+
const providers = await appService.listProviders()
|
|
915
|
+
|
|
916
|
+
if (providerName) {
|
|
917
|
+
// List models for specific provider
|
|
918
|
+
const provider = providers.find((p) => p.name.toLowerCase() === providerName.toLowerCase())
|
|
919
|
+
if (!provider) {
|
|
920
|
+
console.log(colors.errorMessage(`Provider '${providerName}' not found.`))
|
|
921
|
+
console.log(colors.infoMessage('Use "ai-changelog providers list" to see available providers.'))
|
|
922
|
+
return
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
console.log(colors.header(`\nš¦ Models for ${provider.name}:\n`))
|
|
926
|
+
|
|
927
|
+
if (provider.models && provider.models.length > 0) {
|
|
928
|
+
provider.models.forEach((model) => {
|
|
929
|
+
const isDefault = model === provider.defaultModel ? ' šÆ (default)' : ''
|
|
930
|
+
console.log(` ${colors.highlight(model)}${isDefault}`)
|
|
931
|
+
})
|
|
932
|
+
} else {
|
|
933
|
+
// Show known models from config
|
|
934
|
+
const knownModels = this.getKnownModelsForProvider(provider.name)
|
|
935
|
+
if (knownModels.length > 0) {
|
|
936
|
+
console.log(colors.dim(' Known models (from configuration):'))
|
|
937
|
+
knownModels.forEach((model) => {
|
|
938
|
+
console.log(` ${colors.highlight(model)}`)
|
|
939
|
+
})
|
|
940
|
+
} else {
|
|
941
|
+
console.log(colors.infoMessage(' No model information available.'))
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
// List models for all available providers
|
|
946
|
+
console.log(colors.header('\nš¦ Available Models by Provider:\n'))
|
|
947
|
+
|
|
948
|
+
for (const provider of providers) {
|
|
949
|
+
if (!provider.available) continue
|
|
950
|
+
|
|
951
|
+
console.log(`${colors.highlight(provider.name)}:`)
|
|
952
|
+
|
|
953
|
+
if (provider.models && provider.models.length > 0) {
|
|
954
|
+
provider.models.slice(0, 5).forEach((model) => {
|
|
955
|
+
const isDefault = model === provider.defaultModel ? ' šÆ' : ''
|
|
956
|
+
console.log(` ${colors.dim('ā¢')} ${model}${isDefault}`)
|
|
957
|
+
})
|
|
958
|
+
if (provider.models.length > 5) {
|
|
959
|
+
console.log(` ${colors.dim(`... and ${provider.models.length - 5} more`)}`)
|
|
960
|
+
}
|
|
961
|
+
} else {
|
|
962
|
+
const knownModels = this.getKnownModelsForProvider(provider.name)
|
|
963
|
+
knownModels.slice(0, 3).forEach((model) => {
|
|
964
|
+
console.log(` ${colors.dim('ā¢')} ${model}`)
|
|
965
|
+
})
|
|
966
|
+
}
|
|
967
|
+
console.log('')
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
EnhancedConsole.error(`Error listing models: ${error.message}`)
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
getKnownModelsForProvider(providerName) {
|
|
976
|
+
const knownModels = {
|
|
977
|
+
openai: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-3.5-turbo', 'o1', 'o1-mini', 'o3-mini'],
|
|
978
|
+
anthropic: ['claude-sonnet-4-20250514', 'claude-3-5-sonnet-20241022', 'claude-3-opus-20240229', 'claude-3-haiku-20240307'],
|
|
979
|
+
google: ['gemini-2.0-flash-exp', 'gemini-1.5-pro', 'gemini-1.5-flash', 'gemini-pro'],
|
|
980
|
+
azure: ['gpt-4o', 'gpt-4-turbo', 'gpt-35-turbo'],
|
|
981
|
+
ollama: ['llama3.2', 'llama3.1', 'mistral', 'codellama', 'deepseek-coder'],
|
|
982
|
+
lmstudio: ['local-model'],
|
|
983
|
+
bedrock: ['anthropic.claude-3-sonnet', 'anthropic.claude-3-haiku', 'amazon.titan-text-express'],
|
|
984
|
+
vertex: ['gemini-1.5-pro', 'gemini-1.5-flash'],
|
|
985
|
+
huggingface: ['mistralai/Mistral-7B-Instruct-v0.2', 'google/flan-t5-xxl'],
|
|
986
|
+
}
|
|
987
|
+
return knownModels[providerName.toLowerCase()] || []
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
class StashCommand extends BaseCommand {
|
|
992
|
+
async execute(argv, appService) {
|
|
993
|
+
const subcommand = argv._[1]
|
|
994
|
+
|
|
995
|
+
switch (subcommand) {
|
|
996
|
+
case 'list':
|
|
997
|
+
await this.listStashes(appService)
|
|
998
|
+
break
|
|
999
|
+
case 'analyze':
|
|
1000
|
+
await this.analyzeStash(appService, argv.stash)
|
|
1001
|
+
break
|
|
1002
|
+
case 'changelog':
|
|
1003
|
+
await this.generateStashChangelog(appService, argv.stash)
|
|
1004
|
+
break
|
|
1005
|
+
default:
|
|
1006
|
+
console.log(colors.errorMessage('Unknown stash subcommand'))
|
|
1007
|
+
console.log(colors.infoMessage('Available subcommands: list, analyze, changelog'))
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
async listStashes(appService) {
|
|
1012
|
+
try {
|
|
1013
|
+
const stashes = appService.orchestrator.gitManager.getStashList()
|
|
1014
|
+
|
|
1015
|
+
if (stashes.length === 0) {
|
|
1016
|
+
console.log(colors.infoMessage('No stashed changes found.'))
|
|
1017
|
+
return
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
console.log(colors.header(`\nš¦ Stashed Changes (${stashes.length}):\n`))
|
|
1021
|
+
|
|
1022
|
+
stashes.forEach((stash, index) => {
|
|
1023
|
+
console.log(` ${colors.highlight(stash.index)}`)
|
|
1024
|
+
console.log(` ${colors.dim('Message:')} ${stash.message}`)
|
|
1025
|
+
console.log(` ${colors.dim('Date:')} ${stash.date}`)
|
|
1026
|
+
if (index < stashes.length - 1) console.log('')
|
|
1027
|
+
})
|
|
1028
|
+
|
|
1029
|
+
console.log(colors.dim('\nUse "ai-changelog stash analyze <stash>" to see details'))
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
EnhancedConsole.error(`Error listing stashes: ${error.message}`)
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async analyzeStash(appService, stashRef = 'stash@{0}') {
|
|
1036
|
+
console.log(colors.processingMessage(`š Analyzing ${stashRef}...`))
|
|
1037
|
+
|
|
1038
|
+
try {
|
|
1039
|
+
const details = appService.orchestrator.gitManager.getStashDetails(stashRef)
|
|
1040
|
+
|
|
1041
|
+
if (!details) {
|
|
1042
|
+
console.log(colors.errorMessage(`Stash '${stashRef}' not found.`))
|
|
1043
|
+
return
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
console.log(colors.header(`\nš¦ Stash Analysis: ${stashRef}\n`))
|
|
1047
|
+
console.log(`${colors.dim('Message:')} ${details.message}`)
|
|
1048
|
+
console.log(`${colors.dim('Files changed:')} ${details.stats.filesChanged}`)
|
|
1049
|
+
console.log(`${colors.dim('Insertions:')} ${colors.success(`+${details.stats.insertions}`)}`)
|
|
1050
|
+
console.log(`${colors.dim('Deletions:')} ${colors.error(`-${details.stats.deletions}`)}`)
|
|
1051
|
+
|
|
1052
|
+
console.log(colors.header('\nš Files:\n'))
|
|
1053
|
+
details.files.forEach((file) => {
|
|
1054
|
+
console.log(` ${colors.highlight(file.path)} (${file.changes} changes)`)
|
|
1055
|
+
})
|
|
1056
|
+
|
|
1057
|
+
console.log(colors.dim('\nUse "ai-changelog stash changelog" to generate changelog'))
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
EnhancedConsole.error(`Error analyzing stash: ${error.message}`)
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
async generateStashChangelog(appService, stashRef = 'stash@{0}') {
|
|
1064
|
+
console.log(colors.processingMessage(`š Generating changelog from ${stashRef}...`))
|
|
1065
|
+
|
|
1066
|
+
try {
|
|
1067
|
+
const details = appService.orchestrator.gitManager.getStashDetails(stashRef)
|
|
1068
|
+
|
|
1069
|
+
if (!details) {
|
|
1070
|
+
console.log(colors.errorMessage(`Stash '${stashRef}' not found.`))
|
|
1071
|
+
return
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Create pseudo-commit data for AI analysis
|
|
1075
|
+
const stashData = {
|
|
1076
|
+
hash: stashRef.replace(/[{}@]/g, ''),
|
|
1077
|
+
message: details.message || 'Stashed changes',
|
|
1078
|
+
files: details.files.map((f) => ({
|
|
1079
|
+
filePath: f.path,
|
|
1080
|
+
status: 'modified',
|
|
1081
|
+
diff: '',
|
|
1082
|
+
})),
|
|
1083
|
+
diff: details.diff,
|
|
1084
|
+
stats: details.stats,
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Analyze with AI if available
|
|
1088
|
+
if (appService.orchestrator.aiAnalysisService?.hasAI) {
|
|
1089
|
+
console.log(colors.processingMessage('š¤ Analyzing stashed changes with AI...'))
|
|
1090
|
+
|
|
1091
|
+
const analysis = await appService.orchestrator.aiAnalysisService.analyzeCommit(stashData)
|
|
1092
|
+
|
|
1093
|
+
console.log(colors.header('\nš Stash Changelog:\n'))
|
|
1094
|
+
console.log(`## Stashed Changes\n`)
|
|
1095
|
+
console.log(`**Summary:** ${analysis?.summary || details.message}`)
|
|
1096
|
+
console.log(`**Impact:** ${analysis?.impact || 'medium'}`)
|
|
1097
|
+
console.log(`**Category:** ${analysis?.category || 'chore'}`)
|
|
1098
|
+
|
|
1099
|
+
if (analysis?.description) {
|
|
1100
|
+
console.log(`\n${analysis.description}`)
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
console.log(`\n**Files affected:** ${details.files.length}`)
|
|
1104
|
+
details.files.forEach((f) => {
|
|
1105
|
+
console.log(`- ${f.path}`)
|
|
1106
|
+
})
|
|
1107
|
+
} else {
|
|
1108
|
+
// Basic changelog without AI
|
|
1109
|
+
console.log(colors.header('\nš Stash Changelog:\n'))
|
|
1110
|
+
console.log(`## Stashed Changes\n`)
|
|
1111
|
+
console.log(`**Message:** ${details.message}`)
|
|
1112
|
+
console.log(`**Stats:** ${details.stats.filesChanged} files, +${details.stats.insertions}/-${details.stats.deletions}`)
|
|
1113
|
+
|
|
1114
|
+
console.log(`\n**Files affected:**`)
|
|
1115
|
+
details.files.forEach((f) => {
|
|
1116
|
+
console.log(`- ${f.path}`)
|
|
1117
|
+
})
|
|
1118
|
+
}
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
EnhancedConsole.error(`Error generating stash changelog: ${error.message}`)
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
687
1123
|
}
|
|
688
1124
|
|
|
689
1125
|
// Export the controller
|
|
@@ -1134,24 +1134,6 @@ ${enhancedMergeSummary}
|
|
|
1134
1134
|
: ''
|
|
1135
1135
|
}
|
|
1136
1136
|
|
|
1137
|
-
// Debug logging for merge commits with "pull request"
|
|
1138
|
-
if (isMergeCommit && subject.includes('pull request')) {
|
|
1139
|
-
try {
|
|
1140
|
-
const fs = require('fs')
|
|
1141
|
-
const debugContent = '=== EXACT AI INPUT FOR fd97ab7 ===\n' +
|
|
1142
|
-
'SUBJECT: ' + subject + '\n' +
|
|
1143
|
-
'FILES COUNT: ' + files.length + '\n' +
|
|
1144
|
-
'ENHANCED MERGE SUMMARY:\n' + (enhancedMergeSummary || 'None') + '\n\n' +
|
|
1145
|
-
'FILES SECTION (first 3000 chars):\n' + filesSection.substring(0, 3000) + '\n\n' +
|
|
1146
|
-
'FULL PROMPT PREVIEW:\n' + prompt.substring(0, 2000) + '\n=== END DEBUG ==='
|
|
1147
|
-
|
|
1148
|
-
fs.writeFileSync('AI_INPUT_DEBUG.txt', debugContent)
|
|
1149
|
-
console.log('*** AI INPUT SAVED TO AI_INPUT_DEBUG.txt ***')
|
|
1150
|
-
} catch (error) {
|
|
1151
|
-
console.log('DEBUG ERROR:', error.message)
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
1137
|
**TARGET AUDIENCE:** End users and project stakeholders who need to understand what changed and why it matters.
|
|
1156
1138
|
|
|
1157
1139
|
**YOUR ROLE:** You are a release manager translating technical changes into clear, user-focused release notes.
|