proagents 1.6.11 โ†’ 1.6.13

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.
@@ -0,0 +1,1007 @@
1
+ import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync } from 'child_process';
5
+ import { createInterface } from 'readline';
6
+ import chalk from 'chalk';
7
+
8
+ // JSON output flag (set by --json option)
9
+ let jsonOutput = false;
10
+ let jsonResult = {};
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+
15
+ // Release type configurations
16
+ const RELEASE_TYPES = {
17
+ detailed: {
18
+ name: 'Detailed Release',
19
+ description: 'Full comprehensive notes with all sections (technical + business)',
20
+ emoji: '๐Ÿ“š'
21
+ },
22
+ short: {
23
+ name: 'Short Release',
24
+ description: 'Quick summary of key changes',
25
+ emoji: '๐Ÿ“'
26
+ },
27
+ client: {
28
+ name: 'Client Release',
29
+ description: 'Business-focused, non-technical for stakeholders',
30
+ emoji: '๐Ÿ’ผ'
31
+ },
32
+ developer: {
33
+ name: 'Developer Release',
34
+ description: 'Technical details, code changes, breaking changes',
35
+ emoji: '๐Ÿ”ง'
36
+ },
37
+ hotfix: {
38
+ name: 'Hotfix Release',
39
+ description: 'Quick patch notes for urgent fixes',
40
+ emoji: '๐Ÿš‘'
41
+ },
42
+ prerelease: {
43
+ name: 'Pre-release (Beta/RC)',
44
+ description: 'Beta or release candidate notes',
45
+ emoji: '๐Ÿงช'
46
+ }
47
+ };
48
+
49
+ // Change categories
50
+ const CHANGE_CATEGORIES = {
51
+ features: { name: 'Features', emoji: 'โœจ', keywords: ['feat:', 'feature', 'add ', 'new ', 'implement'] },
52
+ fixes: { name: 'Bug Fixes', emoji: '๐Ÿ›', keywords: ['fix:', 'fix ', 'bug', 'resolve', 'patch'] },
53
+ improvements: { name: 'Improvements', emoji: 'โšก', keywords: ['improve', 'enhance', 'update', 'refactor', 'optimize'] },
54
+ breaking: { name: 'Breaking Changes', emoji: '๐Ÿ’ฅ', keywords: ['breaking', 'break:'] },
55
+ security: { name: 'Security', emoji: '๐Ÿ”’', keywords: ['security', 'vulnerability', 'cve', 'auth'] },
56
+ docs: { name: 'Documentation', emoji: '๐Ÿ“–', keywords: ['doc:', 'docs:', 'readme', 'documentation'] },
57
+ deps: { name: 'Dependencies', emoji: '๐Ÿ“ฆ', keywords: ['deps:', 'dependency', 'upgrade ', 'downgrade'] },
58
+ perf: { name: 'Performance', emoji: '๐Ÿš€', keywords: ['perf:', 'performance', 'speed', 'faster'] },
59
+ other: { name: 'Other', emoji: '๐Ÿ“‹', keywords: [] }
60
+ };
61
+
62
+ /**
63
+ * Get git commits with various filters
64
+ */
65
+ function getGitCommits(options = {}) {
66
+ const { since, until, path, limit = 100 } = options;
67
+
68
+ try {
69
+ let command = 'git log';
70
+
71
+ // Date or tag range
72
+ if (since && until) {
73
+ command += ` ${since}..${until}`;
74
+ } else if (since) {
75
+ command += ` ${since}..HEAD`;
76
+ } else if (until) {
77
+ command += ` HEAD..${until}`;
78
+ } else {
79
+ // Try to get since last tag
80
+ try {
81
+ const lastTag = execSync('git describe --tags --abbrev=0 2>/dev/null', { encoding: 'utf-8' }).trim();
82
+ command += ` ${lastTag}..HEAD`;
83
+ options.detectedSince = lastTag;
84
+ } catch {
85
+ command += ` -${limit}`;
86
+ options.detectedSince = null;
87
+ }
88
+ }
89
+
90
+ command += ' --oneline --no-merges';
91
+
92
+ // Filter by path/module
93
+ if (path) {
94
+ command += ` -- ${path}`;
95
+ }
96
+
97
+ const commits = execSync(command, { encoding: 'utf-8' });
98
+ return commits.trim();
99
+ } catch {
100
+ return '';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get current version from package.json
106
+ */
107
+ function getCurrentVersion(targetDir) {
108
+ const packagePath = join(targetDir, 'package.json');
109
+ if (existsSync(packagePath)) {
110
+ try {
111
+ const pkg = JSON.parse(readFileSync(packagePath, 'utf-8'));
112
+ return pkg.version || 'unknown';
113
+ } catch {
114
+ // Package.json unreadable
115
+ }
116
+ }
117
+ return 'unknown';
118
+ }
119
+
120
+ /**
121
+ * Suggest version bump based on changes
122
+ */
123
+ function suggestVersionBump(currentVersion, categories) {
124
+ const [major, minor, patch] = currentVersion.split('.').map(Number);
125
+
126
+ if (categories.breaking.length > 0) {
127
+ return {
128
+ type: 'major',
129
+ reason: `${categories.breaking.length} breaking change(s)`,
130
+ suggested: `${major + 1}.0.0`
131
+ };
132
+ }
133
+
134
+ if (categories.features.length > 0) {
135
+ return {
136
+ type: 'minor',
137
+ reason: `${categories.features.length} new feature(s)`,
138
+ suggested: `${major}.${minor + 1}.0`
139
+ };
140
+ }
141
+
142
+ return {
143
+ type: 'patch',
144
+ reason: `${categories.fixes.length} fix(es), ${categories.improvements.length} improvement(s)`,
145
+ suggested: `${major}.${minor}.${patch + 1}`
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Parse commits into categories
151
+ */
152
+ function categorizeCommits(commitsText, options = {}) {
153
+ const { include, exclude, module } = options;
154
+ const lines = commitsText.split('\n').filter(line => line.trim());
155
+
156
+ const categories = {
157
+ features: [],
158
+ fixes: [],
159
+ improvements: [],
160
+ breaking: [],
161
+ security: [],
162
+ docs: [],
163
+ deps: [],
164
+ perf: [],
165
+ other: []
166
+ };
167
+
168
+ for (const line of lines) {
169
+ const lower = line.toLowerCase();
170
+ const message = line.replace(/^[a-f0-9]+ /, ''); // Remove hash
171
+
172
+ // Module filter - check if commit message contains module name
173
+ if (module && !lower.includes(module.toLowerCase())) {
174
+ // Also check for common patterns like (module): or [module]
175
+ const modulePatterns = [
176
+ `(${module.toLowerCase()})`,
177
+ `[${module.toLowerCase()}]`,
178
+ `${module.toLowerCase()}:`
179
+ ];
180
+ if (!modulePatterns.some(p => lower.includes(p))) {
181
+ continue; // Skip if module not mentioned
182
+ }
183
+ }
184
+
185
+ // Categorize
186
+ let categorized = false;
187
+ for (const [category, config] of Object.entries(CHANGE_CATEGORIES)) {
188
+ if (category === 'other') continue;
189
+
190
+ if (config.keywords.some(kw => lower.includes(kw))) {
191
+ categories[category].push(message);
192
+ categorized = true;
193
+ break;
194
+ }
195
+ }
196
+
197
+ if (!categorized) {
198
+ categories.other.push(message);
199
+ }
200
+ }
201
+
202
+ // Apply include/exclude filters
203
+ if (include && include.length > 0) {
204
+ const validCategories = include.map(i => i.toLowerCase());
205
+ for (const category of Object.keys(categories)) {
206
+ if (!validCategories.includes(category)) {
207
+ categories[category] = [];
208
+ }
209
+ }
210
+ }
211
+
212
+ if (exclude && exclude.length > 0) {
213
+ const excludeCategories = exclude.map(e => e.toLowerCase());
214
+ for (const category of excludeCategories) {
215
+ if (categories[category]) {
216
+ categories[category] = [];
217
+ }
218
+ }
219
+ }
220
+
221
+ return categories;
222
+ }
223
+
224
+ /**
225
+ * Generate DETAILED release notes
226
+ */
227
+ function generateDetailedNotes(version, categories, since, options = {}) {
228
+ const date = new Date().toISOString().split('T')[0];
229
+ const totalChanges = Object.values(categories).flat().length;
230
+
231
+ let notes = `# Release Notes v${version}
232
+
233
+ **Release Date:** ${date}
234
+ **Previous Version:** ${since || 'Initial Release'}
235
+ ${options.prerelease ? `**Status:** ${options.prerelease.toUpperCase()}\n` : ''}
236
+ ---
237
+
238
+ ## Overview
239
+
240
+ This release includes ${totalChanges} changes: ${categories.features.length} new features, ${categories.fixes.length} bug fixes, and ${categories.improvements.length} improvements.
241
+
242
+ ---
243
+
244
+ ## What's New
245
+
246
+ `;
247
+
248
+ // Add each non-empty category
249
+ const categoryOrder = ['breaking', 'security', 'features', 'fixes', 'improvements', 'perf', 'deps', 'docs', 'other'];
250
+
251
+ for (const category of categoryOrder) {
252
+ if (categories[category] && categories[category].length > 0) {
253
+ const config = CHANGE_CATEGORIES[category];
254
+ notes += `### ${config.emoji} ${config.name}\n\n`;
255
+ notes += categories[category].map(c => `- ${c}`).join('\n');
256
+ notes += '\n\n';
257
+ }
258
+ }
259
+
260
+ notes += `---
261
+
262
+ ## Upgrade Instructions
263
+
264
+ \`\`\`bash
265
+ npm update proagents
266
+ # or
267
+ npx proagents init
268
+ \`\`\`
269
+
270
+ ## Full Changelog
271
+
272
+ See all changes: [GitHub Commits](../../commits/main)
273
+
274
+ ---
275
+
276
+ *Generated by ProAgents v${getCurrentVersion(process.cwd())}*
277
+ `;
278
+
279
+ return notes;
280
+ }
281
+
282
+ /**
283
+ * Generate SHORT release notes
284
+ */
285
+ function generateShortNotes(version, categories, since, options = {}) {
286
+ const date = new Date().toISOString().split('T')[0];
287
+ const totalChanges = Object.values(categories).flat().length;
288
+
289
+ let notes = `# v${version} Release Notes${options.prerelease ? ` (${options.prerelease})` : ''}
290
+
291
+ **Date:** ${date} | **Changes:** ${totalChanges}
292
+
293
+ `;
294
+
295
+ if (categories.breaking.length > 0) {
296
+ notes += `**โš ๏ธ Breaking:** ${categories.breaking.length} changes\n`;
297
+ }
298
+
299
+ const summary = [];
300
+ if (categories.features.length > 0) summary.push(`Features: ${categories.features.length}`);
301
+ if (categories.fixes.length > 0) summary.push(`Fixes: ${categories.fixes.length}`);
302
+ if (categories.improvements.length > 0) summary.push(`Improvements: ${categories.improvements.length}`);
303
+ if (categories.security.length > 0) summary.push(`Security: ${categories.security.length}`);
304
+
305
+ notes += `**${summary.join(' | ')}**\n\n`;
306
+
307
+ // Show top highlights
308
+ const highlights = [
309
+ ...categories.breaking.slice(0, 2),
310
+ ...categories.security.slice(0, 1),
311
+ ...categories.features.slice(0, 2),
312
+ ...categories.fixes.slice(0, 1)
313
+ ].slice(0, 5);
314
+
315
+ if (highlights.length > 0) {
316
+ notes += `## Highlights\n\n`;
317
+ notes += highlights.map(c => `- ${c}`).join('\n');
318
+ notes += '\n\n';
319
+ }
320
+
321
+ notes += `---\n*Run \`proagents release --type detailed\` for full notes*\n`;
322
+
323
+ return notes;
324
+ }
325
+
326
+ /**
327
+ * Generate CLIENT release notes (non-technical)
328
+ */
329
+ function generateClientNotes(version, categories, since, options = {}) {
330
+ const date = new Date().toLocaleDateString('en-US', {
331
+ year: 'numeric',
332
+ month: 'long',
333
+ day: 'numeric'
334
+ });
335
+
336
+ let notes = `# Product Update - Version ${version}${options.prerelease ? ` (${options.prerelease})` : ''}
337
+
338
+ **Release Date:** ${date}
339
+
340
+ ---
341
+
342
+ ## What's New for You
343
+
344
+ `;
345
+
346
+ if (categories.features.length > 0) {
347
+ notes += `### โœจ New Capabilities\n\n`;
348
+ notes += `We've added ${categories.features.length} new features to improve your experience:\n\n`;
349
+ categories.features.forEach(f => {
350
+ const friendly = f
351
+ .replace(/^feat(\(.*?\))?:?\s*/i, '')
352
+ .replace(/^add\s+/i, 'Added ')
353
+ .replace(/^implement\s+/i, 'New ');
354
+ notes += `- ${friendly}\n`;
355
+ });
356
+ notes += '\n';
357
+ }
358
+
359
+ if (categories.fixes.length > 0) {
360
+ notes += `### ๐Ÿ”ง Resolved Issues\n\n`;
361
+ notes += `We've fixed ${categories.fixes.length} issues to make things work better:\n\n`;
362
+ categories.fixes.slice(0, 5).forEach(f => {
363
+ const friendly = f
364
+ .replace(/^fix(\(.*?\))?:?\s*/i, '')
365
+ .replace(/^bug:?\s*/i, '');
366
+ notes += `- ${friendly}\n`;
367
+ });
368
+ if (categories.fixes.length > 5) {
369
+ notes += `- And ${categories.fixes.length - 5} more fixes\n`;
370
+ }
371
+ notes += '\n';
372
+ }
373
+
374
+ if (categories.improvements.length > 0) {
375
+ notes += `### โšก Better Experience\n\n`;
376
+ categories.improvements.slice(0, 3).forEach(i => {
377
+ const friendly = i
378
+ .replace(/^(improve|enhance|update|refactor)(\(.*?\))?:?\s*/i, 'Improved ');
379
+ notes += `- ${friendly}\n`;
380
+ });
381
+ notes += '\n';
382
+ }
383
+
384
+ if (categories.security.length > 0) {
385
+ notes += `### ๐Ÿ”’ Security Updates\n\n`;
386
+ notes += `We've strengthened security with ${categories.security.length} update(s).\n\n`;
387
+ }
388
+
389
+ if (categories.breaking.length > 0) {
390
+ notes += `### โš ๏ธ Important Notice\n\n`;
391
+ notes += `This update includes some changes that may require your attention. Please review the upgrade guide or contact support if you have questions.\n\n`;
392
+ }
393
+
394
+ notes += `---
395
+
396
+ ## How to Update
397
+
398
+ Your system will be automatically updated, or you can manually update by following the standard upgrade process.
399
+
400
+ ## Questions?
401
+
402
+ Contact our support team if you have any questions about this release.
403
+
404
+ ---
405
+
406
+ *Thank you for being a valued user!*
407
+ `;
408
+
409
+ return notes;
410
+ }
411
+
412
+ /**
413
+ * Generate DEVELOPER release notes
414
+ */
415
+ function generateDeveloperNotes(version, categories, since, options = {}) {
416
+ const date = new Date().toISOString().split('T')[0];
417
+
418
+ let notes = `# Developer Release Notes - v${version}${options.prerelease ? `-${options.prerelease}` : ''}
419
+
420
+ **Released:** ${date}
421
+ **Diff:** ${since ? `${since}...v${version}` : 'See commits'}
422
+ ${options.module ? `**Module:** ${options.module}\n` : ''}
423
+ ---
424
+
425
+ ## TL;DR
426
+
427
+ `;
428
+
429
+ const summary = [];
430
+ if (categories.breaking.length > 0) summary.push(`${categories.breaking.length} BREAKING`);
431
+ if (categories.security.length > 0) summary.push(`${categories.security.length} security`);
432
+ if (categories.features.length > 0) summary.push(`${categories.features.length} features`);
433
+ if (categories.fixes.length > 0) summary.push(`${categories.fixes.length} fixes`);
434
+ if (categories.improvements.length > 0) summary.push(`${categories.improvements.length} improvements`);
435
+ if (categories.perf.length > 0) summary.push(`${categories.perf.length} perf`);
436
+
437
+ notes += `\`${summary.join(' | ') || 'No changes'}\`\n\n`;
438
+
439
+ // Add each non-empty category
440
+ const categoryOrder = ['breaking', 'security', 'features', 'fixes', 'improvements', 'perf', 'deps', 'docs', 'other'];
441
+
442
+ for (const category of categoryOrder) {
443
+ if (categories[category] && categories[category].length > 0) {
444
+ const config = CHANGE_CATEGORIES[category];
445
+ notes += `## ${config.emoji} ${config.name}\n\n`;
446
+ notes += categories[category].map(c => `- ${c}`).join('\n');
447
+ notes += '\n\n';
448
+ }
449
+ }
450
+
451
+ if (categories.breaking.length > 0) {
452
+ notes += `### Migration Guide\n\n`;
453
+ notes += `Review breaking changes above and update your code accordingly.\n\n`;
454
+ }
455
+
456
+ notes += `---
457
+
458
+ ## Technical Details
459
+
460
+ \`\`\`bash
461
+ # Update
462
+ npm update proagents
463
+
464
+ # Verify
465
+ npx proagents version
466
+ npx proagents doctor
467
+ \`\`\`
468
+
469
+ ## API Changes
470
+
471
+ ${categories.breaking.length > 0 ? 'See breaking changes above.' : 'No API changes in this release.'}
472
+
473
+ ## Dependencies
474
+
475
+ Run \`npm audit\` to check for security updates.
476
+
477
+ ---
478
+
479
+ *Generated by ProAgents CLI*
480
+ `;
481
+
482
+ return notes;
483
+ }
484
+
485
+ /**
486
+ * Generate HOTFIX release notes
487
+ */
488
+ function generateHotfixNotes(version, categories, since, options = {}) {
489
+ const date = new Date().toISOString();
490
+ const timestamp = date.replace('T', ' ').slice(0, 19);
491
+
492
+ let notes = `# ๐Ÿš‘ HOTFIX v${version}
493
+
494
+ **Released:** ${timestamp} UTC
495
+ **Urgency:** ${options.urgency || 'High'}
496
+
497
+ ---
498
+
499
+ ## Critical Fixes
500
+
501
+ `;
502
+
503
+ // Prioritize security and fixes
504
+ if (categories.security.length > 0) {
505
+ notes += `### ๐Ÿ”’ Security Patches\n\n`;
506
+ notes += categories.security.map(c => `- **SECURITY:** ${c}`).join('\n');
507
+ notes += '\n\n';
508
+ }
509
+
510
+ if (categories.fixes.length > 0) {
511
+ notes += `### ๐Ÿ› Bug Fixes\n\n`;
512
+ notes += categories.fixes.map(c => `- ${c}`).join('\n');
513
+ notes += '\n\n';
514
+ }
515
+
516
+ if (categories.breaking.length > 0) {
517
+ notes += `### โš ๏ธ Breaking Changes\n\n`;
518
+ notes += categories.breaking.map(c => `- **BREAKING:** ${c}`).join('\n');
519
+ notes += '\n\n';
520
+ }
521
+
522
+ notes += `---
523
+
524
+ ## Immediate Action Required
525
+
526
+ \`\`\`bash
527
+ # Update immediately
528
+ npm update proagents
529
+
530
+ # Verify fix applied
531
+ npx proagents version
532
+ \`\`\`
533
+
534
+ ## Rollback (if needed)
535
+
536
+ \`\`\`bash
537
+ npm install proagents@${since || 'previous-version'}
538
+ \`\`\`
539
+
540
+ ---
541
+
542
+ *This is a hotfix release. For full release notes, see the next scheduled release.*
543
+ `;
544
+
545
+ return notes;
546
+ }
547
+
548
+ /**
549
+ * Generate PRE-RELEASE notes (Beta/RC)
550
+ */
551
+ function generatePrereleaseNotes(version, categories, since, options = {}) {
552
+ const date = new Date().toISOString().split('T')[0];
553
+ const stage = options.prerelease || 'beta';
554
+
555
+ let notes = `# ๐Ÿงช Pre-release v${version}-${stage}
556
+
557
+ **Date:** ${date}
558
+ **Stage:** ${stage.toUpperCase()}
559
+ **Status:** Testing / Not for production
560
+
561
+ ---
562
+
563
+ ## โš ๏ธ Pre-release Notice
564
+
565
+ This is a **${stage}** release. It may contain bugs and is not recommended for production use.
566
+
567
+ **Please report issues:** [GitHub Issues](../../issues)
568
+
569
+ ---
570
+
571
+ ## Changes in this ${stage}
572
+
573
+ `;
574
+
575
+ const categoryOrder = ['breaking', 'features', 'fixes', 'improvements', 'other'];
576
+
577
+ for (const category of categoryOrder) {
578
+ if (categories[category] && categories[category].length > 0) {
579
+ const config = CHANGE_CATEGORIES[category];
580
+ notes += `### ${config.emoji} ${config.name}\n\n`;
581
+ notes += categories[category].map(c => `- ${c}`).join('\n');
582
+ notes += '\n\n';
583
+ }
584
+ }
585
+
586
+ notes += `---
587
+
588
+ ## Testing Instructions
589
+
590
+ \`\`\`bash
591
+ # Install pre-release
592
+ npm install proagents@${version}-${stage}
593
+
594
+ # Test features
595
+ npx proagents doctor
596
+ \`\`\`
597
+
598
+ ## Known Issues
599
+
600
+ - [ ] List any known issues here
601
+
602
+ ## Feedback
603
+
604
+ Please test and provide feedback before the stable release.
605
+
606
+ ---
607
+
608
+ *This is a pre-release version. Use at your own risk.*
609
+ `;
610
+
611
+ return notes;
612
+ }
613
+
614
+ /**
615
+ * Interactive release type selection
616
+ */
617
+ async function selectReleaseType() {
618
+ const rl = createInterface({
619
+ input: process.stdin,
620
+ output: process.stdout
621
+ });
622
+
623
+ const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
624
+
625
+ console.log(chalk.bold('\nSelect Release Note Type:\n'));
626
+
627
+ const types = Object.entries(RELEASE_TYPES);
628
+ types.forEach(([key, value], index) => {
629
+ console.log(chalk.cyan(` ${index + 1}. ${value.emoji} ${value.name}`));
630
+ console.log(chalk.gray(` ${value.description}`));
631
+ });
632
+ console.log('');
633
+
634
+ const answer = await question(chalk.yellow('Enter choice (1-6): '));
635
+ rl.close();
636
+
637
+ const index = parseInt(answer) - 1;
638
+ if (index >= 0 && index < types.length) {
639
+ return types[index][0];
640
+ }
641
+
642
+ console.log(chalk.yellow('\nInvalid choice, defaulting to detailed release notes.'));
643
+ return 'detailed';
644
+ }
645
+
646
+ /**
647
+ * Interactive filter selection
648
+ */
649
+ async function selectFilters() {
650
+ const rl = createInterface({
651
+ input: process.stdin,
652
+ output: process.stdout
653
+ });
654
+
655
+ const question = (prompt) => new Promise(resolve => rl.question(prompt, resolve));
656
+
657
+ console.log(chalk.bold('\nFilter Changes (optional):\n'));
658
+
659
+ const categories = ['features', 'fixes', 'improvements', 'security', 'breaking', 'docs', 'deps', 'perf'];
660
+ categories.forEach((cat, i) => {
661
+ const config = CHANGE_CATEGORIES[cat];
662
+ console.log(chalk.cyan(` ${i + 1}. ${config.emoji} ${config.name}`));
663
+ });
664
+ console.log(chalk.gray(' 0. All changes (no filter)'));
665
+ console.log('');
666
+
667
+ const answer = await question(chalk.yellow('Include only (comma-separated, e.g., 1,2): '));
668
+ rl.close();
669
+
670
+ if (!answer || answer === '0') {
671
+ return null;
672
+ }
673
+
674
+ const indices = answer.split(',').map(s => parseInt(s.trim()) - 1);
675
+ const selected = indices
676
+ .filter(i => i >= 0 && i < categories.length)
677
+ .map(i => categories[i]);
678
+
679
+ return selected.length > 0 ? selected : null;
680
+ }
681
+
682
+ /**
683
+ * Append to existing release notes file
684
+ */
685
+ function appendToExistingNotes(existingPath, newNotes, version) {
686
+ if (!existsSync(existingPath)) {
687
+ writeFileSync(existingPath, newNotes);
688
+ return { created: true };
689
+ }
690
+
691
+ const existingContent = readFileSync(existingPath, 'utf-8');
692
+
693
+ // Add separator and append
694
+ const separator = `\n\n---\n\n## Additional Changes (v${version})\n\n`;
695
+ const appendContent = newNotes
696
+ .replace(/^#.*?\n/, '') // Remove first heading
697
+ .replace(/\*Generated by.*?\*\n?$/, ''); // Remove footer
698
+
699
+ const updatedContent = existingContent.trimEnd() + separator + appendContent + `\n\n*Updated: ${new Date().toISOString()}*\n`;
700
+
701
+ writeFileSync(existingPath, updatedContent);
702
+ return { appended: true };
703
+ }
704
+
705
+ /**
706
+ * Update CHANGELOG.md with release notes
707
+ */
708
+ function updateChangelog(targetDir, version, categories, since) {
709
+ const changelogPath = join(targetDir, 'CHANGELOG.md');
710
+ const date = new Date().toISOString().split('T')[0];
711
+
712
+ // Generate changelog entry
713
+ let entry = `## [${version}] - ${date}\n\n`;
714
+
715
+ const categoryOrder = ['breaking', 'security', 'features', 'fixes', 'improvements', 'perf', 'deps', 'docs'];
716
+ const categoryLabels = {
717
+ breaking: 'BREAKING CHANGES',
718
+ security: 'Security',
719
+ features: 'Added',
720
+ fixes: 'Fixed',
721
+ improvements: 'Changed',
722
+ perf: 'Performance',
723
+ deps: 'Dependencies',
724
+ docs: 'Documentation'
725
+ };
726
+
727
+ for (const category of categoryOrder) {
728
+ if (categories[category] && categories[category].length > 0) {
729
+ entry += `### ${categoryLabels[category]}\n\n`;
730
+ entry += categories[category].map(c => `- ${c}`).join('\n');
731
+ entry += '\n\n';
732
+ }
733
+ }
734
+
735
+ // Create or update CHANGELOG.md
736
+ if (existsSync(changelogPath)) {
737
+ const existing = readFileSync(changelogPath, 'utf-8');
738
+
739
+ // Find insertion point (after header, before first version)
740
+ const headerMatch = existing.match(/^# Changelog.*?\n\n/s);
741
+ if (headerMatch) {
742
+ const header = headerMatch[0];
743
+ const rest = existing.slice(header.length);
744
+ const updated = header + entry + rest;
745
+ writeFileSync(changelogPath, updated);
746
+ } else {
747
+ // No header found, prepend
748
+ writeFileSync(changelogPath, `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n${entry}${existing}`);
749
+ }
750
+ } else {
751
+ // Create new CHANGELOG.md
752
+ const content = `# Changelog
753
+
754
+ All notable changes to this project will be documented in this file.
755
+
756
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
757
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
758
+
759
+ ${entry}`;
760
+ writeFileSync(changelogPath, content);
761
+ }
762
+
763
+ return { path: changelogPath, version };
764
+ }
765
+
766
+ /**
767
+ * Create git tag for release
768
+ */
769
+ function createGitTag(version, message) {
770
+ const tagName = version.startsWith('v') ? version : `v${version}`;
771
+
772
+ try {
773
+ // Check if tag already exists
774
+ try {
775
+ execSync(`git rev-parse ${tagName}`, { encoding: 'utf-8', stdio: 'pipe' });
776
+ return { error: `Tag ${tagName} already exists` };
777
+ } catch {
778
+ // Tag doesn't exist, good to create
779
+ }
780
+
781
+ // Create annotated tag
782
+ const tagMessage = message || `Release ${tagName}`;
783
+ execSync(`git tag -a ${tagName} -m "${tagMessage}"`, { encoding: 'utf-8' });
784
+
785
+ return { success: true, tag: tagName };
786
+ } catch (error) {
787
+ return { error: error.message };
788
+ }
789
+ }
790
+
791
+ /**
792
+ * Generate compact changelog entry for --changelog option
793
+ */
794
+ function generateChangelogEntry(version, categories) {
795
+ const date = new Date().toISOString().split('T')[0];
796
+ let entry = `\n## [${version}] - ${date}\n`;
797
+
798
+ if (categories.breaking.length > 0) {
799
+ entry += `\n### BREAKING CHANGES\n${categories.breaking.map(c => `- ${c}`).join('\n')}\n`;
800
+ }
801
+ if (categories.features.length > 0) {
802
+ entry += `\n### Added\n${categories.features.map(c => `- ${c}`).join('\n')}\n`;
803
+ }
804
+ if (categories.fixes.length > 0) {
805
+ entry += `\n### Fixed\n${categories.fixes.map(c => `- ${c}`).join('\n')}\n`;
806
+ }
807
+ if (categories.improvements.length > 0) {
808
+ entry += `\n### Changed\n${categories.improvements.map(c => `- ${c}`).join('\n')}\n`;
809
+ }
810
+ if (categories.security.length > 0) {
811
+ entry += `\n### Security\n${categories.security.map(c => `- ${c}`).join('\n')}\n`;
812
+ }
813
+
814
+ return entry;
815
+ }
816
+
817
+ /**
818
+ * Release command - generate release notes
819
+ */
820
+ export async function releaseCommand(options = {}) {
821
+ const targetDir = process.cwd();
822
+
823
+ console.log(chalk.bold('\nProAgents Release Notes Generator'));
824
+ console.log(chalk.gray('==================================\n'));
825
+
826
+ // Get version
827
+ const currentVersion = getCurrentVersion(targetDir);
828
+ let version = options.version || currentVersion;
829
+ console.log(`Current Version: ${chalk.cyan('v' + currentVersion)}`);
830
+
831
+ // Get commits with filters
832
+ const commitOptions = {
833
+ since: options.since,
834
+ until: options.until,
835
+ path: options.path,
836
+ limit: options.limit || 100
837
+ };
838
+
839
+ const commits = getGitCommits(commitOptions);
840
+ const detectedSince = commitOptions.detectedSince;
841
+
842
+ if (!commits) {
843
+ console.log(chalk.yellow('\nNo commits found. Make sure you are in a git repository.'));
844
+ return;
845
+ }
846
+
847
+ const commitCount = commits.split('\n').filter(l => l.trim()).length;
848
+ console.log(`Commits: ${chalk.cyan(commitCount)} ${detectedSince ? `since ${detectedSince}` : ''}`);
849
+
850
+ if (options.path) {
851
+ console.log(`Path filter: ${chalk.cyan(options.path)}`);
852
+ }
853
+ if (options.module) {
854
+ console.log(`Module filter: ${chalk.cyan(options.module)}`);
855
+ }
856
+
857
+ // Select release type
858
+ let releaseType = options.type;
859
+ if (!releaseType) {
860
+ releaseType = await selectReleaseType();
861
+ }
862
+
863
+ // Select filters if interactive
864
+ let includeFilter = options.include ? options.include.split(',') : null;
865
+ let excludeFilter = options.exclude ? options.exclude.split(',') : null;
866
+
867
+ if (!includeFilter && !excludeFilter && options.interactive) {
868
+ includeFilter = await selectFilters();
869
+ }
870
+
871
+ console.log(chalk.cyan(`\nGenerating ${RELEASE_TYPES[releaseType].emoji} ${RELEASE_TYPES[releaseType].name}...`));
872
+
873
+ // Categorize commits
874
+ const categories = categorizeCommits(commits, {
875
+ include: includeFilter,
876
+ exclude: excludeFilter,
877
+ module: options.module
878
+ });
879
+
880
+ // Show version bump suggestion
881
+ if (options.bump) {
882
+ const suggestion = suggestVersionBump(currentVersion, categories);
883
+ console.log(chalk.bold('\nVersion Bump Suggestion:'));
884
+ console.log(` Type: ${chalk.yellow(suggestion.type.toUpperCase())}`);
885
+ console.log(` Reason: ${chalk.gray(suggestion.reason)}`);
886
+ console.log(` Suggested: ${chalk.green('v' + suggestion.suggested)}`);
887
+
888
+ if (!options.version) {
889
+ version = suggestion.suggested;
890
+ console.log(chalk.cyan(`\nUsing suggested version: v${version}`));
891
+ }
892
+ }
893
+
894
+ // Generate notes based on type
895
+ const genOptions = {
896
+ prerelease: options.prerelease,
897
+ module: options.module,
898
+ urgency: options.urgency
899
+ };
900
+
901
+ let notes;
902
+ switch (releaseType) {
903
+ case 'detailed':
904
+ notes = generateDetailedNotes(version, categories, detectedSince, genOptions);
905
+ break;
906
+ case 'short':
907
+ notes = generateShortNotes(version, categories, detectedSince, genOptions);
908
+ break;
909
+ case 'client':
910
+ notes = generateClientNotes(version, categories, detectedSince, genOptions);
911
+ break;
912
+ case 'developer':
913
+ notes = generateDeveloperNotes(version, categories, detectedSince, genOptions);
914
+ break;
915
+ case 'hotfix':
916
+ notes = generateHotfixNotes(version, categories, detectedSince, genOptions);
917
+ break;
918
+ case 'prerelease':
919
+ notes = generatePrereleaseNotes(version, categories, detectedSince, genOptions);
920
+ break;
921
+ default:
922
+ notes = generateDetailedNotes(version, categories, detectedSince, genOptions);
923
+ }
924
+
925
+ // Output options
926
+ if (options.output) {
927
+ const outputPath = typeof options.output === 'string'
928
+ ? options.output
929
+ : join(targetDir, `RELEASE_NOTES_v${version}.md`);
930
+
931
+ if (options.append && existsSync(outputPath)) {
932
+ const result = appendToExistingNotes(outputPath, notes, version);
933
+ console.log(chalk.green(`\n${RELEASE_TYPES[releaseType].emoji} Release notes appended to: ${outputPath}`));
934
+ } else {
935
+ writeFileSync(outputPath, notes);
936
+ console.log(chalk.green(`\n${RELEASE_TYPES[releaseType].emoji} Release notes saved to: ${outputPath}`));
937
+ }
938
+ } else {
939
+ console.log(chalk.gray('\n' + 'โ”€'.repeat(50) + '\n'));
940
+ console.log(notes);
941
+ console.log(chalk.gray('โ”€'.repeat(50)));
942
+ console.log(chalk.gray('\nTip: Use --output to save to file'));
943
+ }
944
+
945
+ // Update CHANGELOG.md if requested
946
+ if (options.changelog) {
947
+ const changelogResult = updateChangelog(targetDir, version, categories, detectedSince);
948
+ if (!options.json) {
949
+ console.log(chalk.green(`\n๐Ÿ“‹ CHANGELOG.md updated with v${version}`));
950
+ }
951
+ if (options.json) {
952
+ jsonResult.changelog = changelogResult;
953
+ }
954
+ }
955
+
956
+ // Create git tag if requested
957
+ if (options.tag) {
958
+ const tagMessage = options.tagMessage || `Release v${version}`;
959
+ const tagResult = createGitTag(version, tagMessage);
960
+
961
+ if (!options.json) {
962
+ if (tagResult.success) {
963
+ console.log(chalk.green(`\n๐Ÿท๏ธ Created git tag: ${tagResult.tag}`));
964
+ } else {
965
+ console.log(chalk.yellow(`\nโš ๏ธ Tag creation failed: ${tagResult.error}`));
966
+ }
967
+ }
968
+ if (options.json) {
969
+ jsonResult.tag = tagResult;
970
+ }
971
+ }
972
+
973
+ // Summary
974
+ const summaryItems = [
975
+ { label: 'Features', count: categories.features.length, color: chalk.green },
976
+ { label: 'Fixes', count: categories.fixes.length, color: chalk.blue },
977
+ { label: 'Improvements', count: categories.improvements.length, color: chalk.cyan },
978
+ { label: 'Security', count: categories.security.length, color: chalk.yellow },
979
+ { label: 'Breaking', count: categories.breaking.length, color: chalk.red },
980
+ { label: 'Performance', count: categories.perf.length, color: chalk.magenta },
981
+ { label: 'Docs', count: categories.docs.length, color: chalk.gray },
982
+ { label: 'Dependencies', count: categories.deps.length, color: chalk.white }
983
+ ];
984
+
985
+ // JSON output mode
986
+ if (options.json) {
987
+ jsonResult.version = version;
988
+ jsonResult.releaseType = releaseType;
989
+ jsonResult.categories = {};
990
+ for (const item of summaryItems) {
991
+ if (item.count > 0) {
992
+ jsonResult.categories[item.label.toLowerCase()] = item.count;
993
+ }
994
+ }
995
+ jsonResult.notes = notes;
996
+ console.log(JSON.stringify(jsonResult, null, 2));
997
+ return;
998
+ }
999
+
1000
+ console.log(chalk.bold('\nSummary:'));
1001
+ for (const item of summaryItems) {
1002
+ if (item.count > 0) {
1003
+ console.log(` ${item.color(item.label + ':')} ${item.count}`);
1004
+ }
1005
+ }
1006
+ console.log('');
1007
+ }