@theihtisham/ai-release-notes 1.0.0 โ†’ 1.1.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 (55) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.yml +43 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  5. package/.github/dependabot.yml +16 -0
  6. package/.github/workflows/ci.yml +24 -0
  7. package/CODE_OF_CONDUCT.md +27 -0
  8. package/LICENSE +21 -21
  9. package/README.md +126 -1493
  10. package/SECURITY.md +22 -0
  11. package/__tests__/analyzer.test.js +63 -63
  12. package/__tests__/categorizer.test.js +93 -93
  13. package/__tests__/config.test.js +92 -92
  14. package/__tests__/formatter.test.js +63 -63
  15. package/__tests__/formatters.test.js +394 -394
  16. package/__tests__/migration.test.js +322 -322
  17. package/__tests__/semver.test.js +94 -94
  18. package/__tests__/tones.test.js +252 -252
  19. package/action.yml +113 -113
  20. package/index.js +73 -73
  21. package/package.json +47 -41
  22. package/src/ai-writer.js +108 -108
  23. package/src/analyzer.js +232 -232
  24. package/src/analyzers/migration.js +355 -355
  25. package/src/categorizer.js +182 -182
  26. package/src/config.js +162 -162
  27. package/src/constants.js +137 -137
  28. package/src/contributor.js +144 -144
  29. package/src/diff-analyzer.js +202 -202
  30. package/src/formatter.js +336 -336
  31. package/src/formatters/discord.js +174 -174
  32. package/src/formatters/html.js +195 -195
  33. package/src/formatters/index.js +42 -42
  34. package/src/formatters/markdown.js +123 -123
  35. package/src/formatters/slack.js +176 -176
  36. package/src/formatters/twitter.js +242 -242
  37. package/src/formatters/types.js +48 -48
  38. package/src/generator.js +297 -297
  39. package/src/integrations/changelog.js +125 -125
  40. package/src/integrations/discord.js +96 -96
  41. package/src/integrations/github-release.js +75 -75
  42. package/src/integrations/indexer.js +119 -119
  43. package/src/integrations/slack.js +112 -112
  44. package/src/integrations/twitter.js +128 -128
  45. package/src/logger.js +52 -52
  46. package/src/prompts.js +210 -210
  47. package/src/rate-limiter.js +92 -92
  48. package/src/semver.js +129 -129
  49. package/src/tones/casual.js +114 -114
  50. package/src/tones/humorous.js +164 -164
  51. package/src/tones/index.js +38 -38
  52. package/src/tones/professional.js +125 -125
  53. package/src/tones/technical.js +164 -164
  54. package/src/tones/types.js +26 -26
  55. package/jest.config.js +0 -10
@@ -1,164 +1,164 @@
1
- 'use strict';
2
-
3
- /**
4
- * Humorous tone adapter.
5
- * Fun, witty descriptions with pop culture references.
6
- * Memorable and shareable.
7
- */
8
- class HumorousTone {
9
- constructor() {
10
- this.name = 'humorous';
11
- this._celebrations = [
12
- 'Hold onto your keyboards!',
13
- 'Drumroll please...',
14
- 'Spoiler alert: it\'s awesome.',
15
- 'Cue the confetti!',
16
- 'The release you\'ve been waiting for (probably).',
17
- ];
18
- this._fixPhrases = [
19
- 'We fixed it. You\'re welcome.',
20
- 'The bug met its maker.',
21
- 'Squashed like the creepy-crawly it was.',
22
- 'Take that, bug!',
23
- 'Another one bites the dust.',
24
- ];
25
- }
26
-
27
- /**
28
- * Apply humorous tone to release data.
29
- * @param {Object} data - ReleaseData object
30
- * @returns {Object} Modified release data with humorous tone
31
- */
32
- apply(data) {
33
- const result = { ...data };
34
-
35
- // Transform summary
36
- result.summary = this._humorousSummary(data);
37
-
38
- // Transform categories
39
- if (data.categories) {
40
- result.categories = {};
41
- for (const [category, changes] of Object.entries(data.categories)) {
42
- const newCategory = this._humorousCategoryName(category);
43
- result.categories[newCategory] = changes.map((c, i) => ({
44
- ...c,
45
- description: this._humorousDescription(c.description, c.type, i),
46
- }));
47
- }
48
- }
49
-
50
- // Transform breaking changes
51
- if (data.breaking) {
52
- result.breaking = data.breaking.map(bc => ({
53
- ...bc,
54
- description: this._humorousBreaking(bc.description),
55
- migration_guide: bc.migration_guide
56
- ? `Don't panic! ${bc.migration_guide}`
57
- : 'Update your code. We believe in you.',
58
- }));
59
- }
60
-
61
- return result;
62
- }
63
-
64
- /**
65
- * Generate a humorous summary.
66
- */
67
- _humorousSummary(data) {
68
- const version = data.version || '0.0.0';
69
- const opener = this._celebrations[Math.floor(Math.random() * this._celebrations.length)];
70
-
71
- const parts = [`${opener} v${version} has landed.`];
72
-
73
- if (data.breaking && data.breaking.length > 0) {
74
- parts.push(` Yes, there ${data.breaking.length === 1 ? 'is a' : 'are'} breaking change${data.breaking.length > 1 ? 's' : ''}. No pain, no gain, right?`);
75
- }
76
-
77
- const catCount = Object.keys(data.categories || {}).length;
78
- if (catCount > 0) {
79
- parts.push(` ${catCount} categor${catCount !== 1 ? 'ies' : 'y'} of stuff that's different now.`);
80
- }
81
-
82
- if (data.contributors && data.contributors.length > 0) {
83
- if (data.contributors.length === 1) {
84
- parts.push(` One brave soul made this happen.`);
85
- } else {
86
- parts.push(` ${data.contributors.length} amazing humans made this happen.`);
87
- }
88
- }
89
-
90
- return parts.join('');
91
- }
92
-
93
- /**
94
- * Map category names to humorous names.
95
- */
96
- _humorousCategoryName(category) {
97
- const map = {
98
- '๐Ÿš€ Features': ':rocket: Things That Are New and Shiny',
99
- '๐Ÿ› Bug Fixes': ':bug: Bugs We Sent to Bug Heaven',
100
- '๐Ÿ’ฅ Breaking Changes': ':boom: Things We Broke (Sorry, Not Sorry)',
101
- 'โšก Performance': ':zap: We Made It Go Brrr',
102
- 'โ™ป๏ธ Refactoring': ':recycle: Code We Rewrote for Fun',
103
- '๐Ÿ“ Documentation': ':memo: Words About Code',
104
- '๐ŸŽจ Style': ':art: Making Code Pretty Again',
105
- '๐Ÿงช Tests': ':test_tube: We Actually Test Things',
106
- '๐Ÿ”ง Chore': ':wrench: Things Nobody Sees But Everyone Needs',
107
- '๐Ÿ”’ Security': ':lock: Fort Knox Mode',
108
- 'New Features': ':rocket: Things That Are New and Shiny',
109
- 'Bug Fixes and Resolutions': ':bug: Bugs We Sent to Bug Heaven',
110
- 'Performance Improvements': ':zap: We Made It Go Brrr',
111
- 'Code Quality Improvements': ':recycle: Code We Rewrote for Fun',
112
- 'Documentation Updates': ':memo: Words About Code',
113
- 'Test Coverage': ':test_tube: We Actually Test Things',
114
- 'Security Enhancements': ':lock: Fort Knox Mode',
115
- };
116
- return map[category] || `:sparkles: ${category}`;
117
- }
118
-
119
- /**
120
- * Make a description more humorous.
121
- */
122
- _humorousDescription(desc, type, index) {
123
- if (!desc) return desc;
124
-
125
- if (type === 'fix') {
126
- const phrase = this._fixPhrases[index % this._fixPhrases.length];
127
- return `${desc} - ${phrase}`;
128
- }
129
-
130
- if (type === 'feat') {
131
- const additions = [
132
- ' (you\'re gonna love this)',
133
- ' (finally!)',
134
- ' (yes, really)',
135
- ' (the people demanded it)',
136
- ' (you\'re welcome)',
137
- ];
138
- return desc + additions[index % additions.length];
139
- }
140
-
141
- if (type === 'perf') {
142
- return `${desc} - it\'s fast now. Like, really fast.`;
143
- }
144
-
145
- return desc;
146
- }
147
-
148
- /**
149
- * Humorous breaking change description.
150
- */
151
- _humorousBreaking(desc) {
152
- if (!desc) return desc;
153
- const prefixes = [
154
- 'Plot twist:',
155
- 'In plot twist news:',
156
- 'Breaking news (literally):',
157
- 'Change of plans:',
158
- ];
159
- const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
160
- return `${prefix} ${desc}`;
161
- }
162
- }
163
-
164
- module.exports = { HumorousTone };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Humorous tone adapter.
5
+ * Fun, witty descriptions with pop culture references.
6
+ * Memorable and shareable.
7
+ */
8
+ class HumorousTone {
9
+ constructor() {
10
+ this.name = 'humorous';
11
+ this._celebrations = [
12
+ 'Hold onto your keyboards!',
13
+ 'Drumroll please...',
14
+ 'Spoiler alert: it\'s awesome.',
15
+ 'Cue the confetti!',
16
+ 'The release you\'ve been waiting for (probably).',
17
+ ];
18
+ this._fixPhrases = [
19
+ 'We fixed it. You\'re welcome.',
20
+ 'The bug met its maker.',
21
+ 'Squashed like the creepy-crawly it was.',
22
+ 'Take that, bug!',
23
+ 'Another one bites the dust.',
24
+ ];
25
+ }
26
+
27
+ /**
28
+ * Apply humorous tone to release data.
29
+ * @param {Object} data - ReleaseData object
30
+ * @returns {Object} Modified release data with humorous tone
31
+ */
32
+ apply(data) {
33
+ const result = { ...data };
34
+
35
+ // Transform summary
36
+ result.summary = this._humorousSummary(data);
37
+
38
+ // Transform categories
39
+ if (data.categories) {
40
+ result.categories = {};
41
+ for (const [category, changes] of Object.entries(data.categories)) {
42
+ const newCategory = this._humorousCategoryName(category);
43
+ result.categories[newCategory] = changes.map((c, i) => ({
44
+ ...c,
45
+ description: this._humorousDescription(c.description, c.type, i),
46
+ }));
47
+ }
48
+ }
49
+
50
+ // Transform breaking changes
51
+ if (data.breaking) {
52
+ result.breaking = data.breaking.map(bc => ({
53
+ ...bc,
54
+ description: this._humorousBreaking(bc.description),
55
+ migration_guide: bc.migration_guide
56
+ ? `Don't panic! ${bc.migration_guide}`
57
+ : 'Update your code. We believe in you.',
58
+ }));
59
+ }
60
+
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * Generate a humorous summary.
66
+ */
67
+ _humorousSummary(data) {
68
+ const version = data.version || '0.0.0';
69
+ const opener = this._celebrations[Math.floor(Math.random() * this._celebrations.length)];
70
+
71
+ const parts = [`${opener} v${version} has landed.`];
72
+
73
+ if (data.breaking && data.breaking.length > 0) {
74
+ parts.push(` Yes, there ${data.breaking.length === 1 ? 'is a' : 'are'} breaking change${data.breaking.length > 1 ? 's' : ''}. No pain, no gain, right?`);
75
+ }
76
+
77
+ const catCount = Object.keys(data.categories || {}).length;
78
+ if (catCount > 0) {
79
+ parts.push(` ${catCount} categor${catCount !== 1 ? 'ies' : 'y'} of stuff that's different now.`);
80
+ }
81
+
82
+ if (data.contributors && data.contributors.length > 0) {
83
+ if (data.contributors.length === 1) {
84
+ parts.push(` One brave soul made this happen.`);
85
+ } else {
86
+ parts.push(` ${data.contributors.length} amazing humans made this happen.`);
87
+ }
88
+ }
89
+
90
+ return parts.join('');
91
+ }
92
+
93
+ /**
94
+ * Map category names to humorous names.
95
+ */
96
+ _humorousCategoryName(category) {
97
+ const map = {
98
+ '๐Ÿš€ Features': ':rocket: Things That Are New and Shiny',
99
+ '๐Ÿ› Bug Fixes': ':bug: Bugs We Sent to Bug Heaven',
100
+ '๐Ÿ’ฅ Breaking Changes': ':boom: Things We Broke (Sorry, Not Sorry)',
101
+ 'โšก Performance': ':zap: We Made It Go Brrr',
102
+ 'โ™ป๏ธ Refactoring': ':recycle: Code We Rewrote for Fun',
103
+ '๐Ÿ“ Documentation': ':memo: Words About Code',
104
+ '๐ŸŽจ Style': ':art: Making Code Pretty Again',
105
+ '๐Ÿงช Tests': ':test_tube: We Actually Test Things',
106
+ '๐Ÿ”ง Chore': ':wrench: Things Nobody Sees But Everyone Needs',
107
+ '๐Ÿ”’ Security': ':lock: Fort Knox Mode',
108
+ 'New Features': ':rocket: Things That Are New and Shiny',
109
+ 'Bug Fixes and Resolutions': ':bug: Bugs We Sent to Bug Heaven',
110
+ 'Performance Improvements': ':zap: We Made It Go Brrr',
111
+ 'Code Quality Improvements': ':recycle: Code We Rewrote for Fun',
112
+ 'Documentation Updates': ':memo: Words About Code',
113
+ 'Test Coverage': ':test_tube: We Actually Test Things',
114
+ 'Security Enhancements': ':lock: Fort Knox Mode',
115
+ };
116
+ return map[category] || `:sparkles: ${category}`;
117
+ }
118
+
119
+ /**
120
+ * Make a description more humorous.
121
+ */
122
+ _humorousDescription(desc, type, index) {
123
+ if (!desc) return desc;
124
+
125
+ if (type === 'fix') {
126
+ const phrase = this._fixPhrases[index % this._fixPhrases.length];
127
+ return `${desc} - ${phrase}`;
128
+ }
129
+
130
+ if (type === 'feat') {
131
+ const additions = [
132
+ ' (you\'re gonna love this)',
133
+ ' (finally!)',
134
+ ' (yes, really)',
135
+ ' (the people demanded it)',
136
+ ' (you\'re welcome)',
137
+ ];
138
+ return desc + additions[index % additions.length];
139
+ }
140
+
141
+ if (type === 'perf') {
142
+ return `${desc} - it\'s fast now. Like, really fast.`;
143
+ }
144
+
145
+ return desc;
146
+ }
147
+
148
+ /**
149
+ * Humorous breaking change description.
150
+ */
151
+ _humorousBreaking(desc) {
152
+ if (!desc) return desc;
153
+ const prefixes = [
154
+ 'Plot twist:',
155
+ 'In plot twist news:',
156
+ 'Breaking news (literally):',
157
+ 'Change of plans:',
158
+ ];
159
+ const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
160
+ return `${prefix} ${desc}`;
161
+ }
162
+ }
163
+
164
+ module.exports = { HumorousTone };
@@ -1,38 +1,38 @@
1
- 'use strict';
2
-
3
- const { TONES, isValidTone } = require('./types');
4
- const { ProfessionalTone } = require('./professional');
5
- const { CasualTone } = require('./casual');
6
- const { HumorousTone } = require('./humorous');
7
- const { TechnicalTone } = require('./technical');
8
-
9
- /**
10
- * Tone factory. Returns the appropriate tone adapter for a given tone name.
11
- * @param {string} tone - Tone identifier ('professional', 'casual', 'humorous', 'technical')
12
- * @returns {Object} Tone adapter instance with name and apply() method
13
- * @throws {Error} If tone is not recognized
14
- */
15
- function getTone(tone) {
16
- switch (tone) {
17
- case 'professional':
18
- return new ProfessionalTone();
19
- case 'casual':
20
- return new CasualTone();
21
- case 'humorous':
22
- return new HumorousTone();
23
- case 'technical':
24
- return new TechnicalTone();
25
- default:
26
- throw new Error(`Unknown tone: "${tone}". Valid tones: ${TONES.join(', ')}`);
27
- }
28
- }
29
-
30
- module.exports = {
31
- getTone,
32
- TONES,
33
- isValidTone,
34
- ProfessionalTone,
35
- CasualTone,
36
- HumorousTone,
37
- TechnicalTone,
38
- };
1
+ 'use strict';
2
+
3
+ const { TONES, isValidTone } = require('./types');
4
+ const { ProfessionalTone } = require('./professional');
5
+ const { CasualTone } = require('./casual');
6
+ const { HumorousTone } = require('./humorous');
7
+ const { TechnicalTone } = require('./technical');
8
+
9
+ /**
10
+ * Tone factory. Returns the appropriate tone adapter for a given tone name.
11
+ * @param {string} tone - Tone identifier ('professional', 'casual', 'humorous', 'technical')
12
+ * @returns {Object} Tone adapter instance with name and apply() method
13
+ * @throws {Error} If tone is not recognized
14
+ */
15
+ function getTone(tone) {
16
+ switch (tone) {
17
+ case 'professional':
18
+ return new ProfessionalTone();
19
+ case 'casual':
20
+ return new CasualTone();
21
+ case 'humorous':
22
+ return new HumorousTone();
23
+ case 'technical':
24
+ return new TechnicalTone();
25
+ default:
26
+ throw new Error(`Unknown tone: "${tone}". Valid tones: ${TONES.join(', ')}`);
27
+ }
28
+ }
29
+
30
+ module.exports = {
31
+ getTone,
32
+ TONES,
33
+ isValidTone,
34
+ ProfessionalTone,
35
+ CasualTone,
36
+ HumorousTone,
37
+ TechnicalTone,
38
+ };
@@ -1,125 +1,125 @@
1
- 'use strict';
2
-
3
- /**
4
- * Professional tone adapter.
5
- * Enterprise/corporate language with formal headers and structured sections.
6
- */
7
- class ProfessionalTone {
8
- constructor() {
9
- this.name = 'professional';
10
- }
11
-
12
- /**
13
- * Apply professional tone to release data.
14
- * @param {Object} data - ReleaseData object
15
- * @returns {Object} Modified release data with professional tone
16
- */
17
- apply(data) {
18
- const result = { ...data };
19
-
20
- // Transform summary
21
- result.summary = this._professionalSummary(data);
22
-
23
- // Transform categories
24
- if (data.categories) {
25
- result.categories = {};
26
- for (const [category, changes] of Object.entries(data.categories)) {
27
- const newCategory = this._professionalCategoryName(category);
28
- result.categories[newCategory] = changes.map(c => ({
29
- ...c,
30
- description: this._professionalDescription(c.description, c.type),
31
- }));
32
- }
33
- }
34
-
35
- // Transform breaking changes
36
- if (data.breaking) {
37
- result.breaking = data.breaking.map(bc => ({
38
- ...bc,
39
- description: this._professionalBreaking(bc.description),
40
- migration_guide: bc.migration_guide
41
- ? this._professionalMigration(bc.migration_guide)
42
- : undefined,
43
- }));
44
- }
45
-
46
- return result;
47
- }
48
-
49
- /**
50
- * Generate a professional summary.
51
- */
52
- _professionalSummary(data) {
53
- const parts = [];
54
- const version = data.version || '0.0.0';
55
-
56
- if (data.breaking && data.breaking.length > 0) {
57
- parts.push(`We are pleased to announce the release of version ${version}, which includes ${data.breaking.length} breaking change${data.breaking.length > 1 ? 's' : ''}`);
58
- } else if (Object.keys(data.categories || {}).length > 0) {
59
- const catCount = Object.keys(data.categories).length;
60
- parts.push(`We are pleased to announce the release of version ${version}, comprising ${catCount} categor${catCount !== 1 ? 'ies' : 'y'} of improvements`);
61
- } else {
62
- parts.push(`We are pleased to announce the release of version ${version}`);
63
- }
64
-
65
- if (data.contributors && data.contributors.length > 0) {
66
- parts.push(`with contributions from ${data.contributors.length} developer${data.contributors.length > 1 ? 's' : ''}`);
67
- }
68
-
69
- parts.push('.');
70
- return parts.join(' ');
71
- }
72
-
73
- /**
74
- * Map emoji category names to professional names.
75
- */
76
- _professionalCategoryName(category) {
77
- const map = {
78
- '๐Ÿš€ Features': 'New Features',
79
- '๐Ÿ› Bug Fixes': 'Bug Fixes and Resolutions',
80
- '๐Ÿ’ฅ Breaking Changes': 'Breaking Changes',
81
- 'โšก Performance': 'Performance Improvements',
82
- 'โ™ป๏ธ Refactoring': 'Code Quality Improvements',
83
- '๐Ÿ“ Documentation': 'Documentation Updates',
84
- '๐ŸŽจ Style': 'Code Style and Formatting',
85
- '๐Ÿงช Tests': 'Test Coverage',
86
- '๐Ÿ”ง Chore': 'Maintenance and Infrastructure',
87
- '๐Ÿ”’ Security': 'Security Enhancements',
88
- };
89
- return map[category] || category.replace(/[^\w\s&]/g, '').trim();
90
- }
91
-
92
- /**
93
- * Make a description more professional.
94
- */
95
- _professionalDescription(desc, type) {
96
- if (!desc) return desc;
97
- // Capitalize first letter, ensure period at end if appropriate
98
- let result = desc.charAt(0).toUpperCase() + desc.slice(1);
99
- if (type === 'feat' && !result.startsWith('Implemented') && !result.startsWith('Added') && !result.startsWith('Introduced')) {
100
- return result;
101
- }
102
- if (type === 'fix' && !result.startsWith('Resolved') && !result.startsWith('Fixed') && !result.startsWith('Addressed')) {
103
- return result;
104
- }
105
- return result;
106
- }
107
-
108
- /**
109
- * Professional breaking change description.
110
- */
111
- _professionalBreaking(desc) {
112
- if (!desc) return desc;
113
- return `This release introduces a change to ${desc.charAt(0).toLowerCase() + desc.slice(1)}`;
114
- }
115
-
116
- /**
117
- * Professional migration guide.
118
- */
119
- _professionalMigration(guide) {
120
- if (!guide) return guide;
121
- return guide;
122
- }
123
- }
124
-
125
- module.exports = { ProfessionalTone };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Professional tone adapter.
5
+ * Enterprise/corporate language with formal headers and structured sections.
6
+ */
7
+ class ProfessionalTone {
8
+ constructor() {
9
+ this.name = 'professional';
10
+ }
11
+
12
+ /**
13
+ * Apply professional tone to release data.
14
+ * @param {Object} data - ReleaseData object
15
+ * @returns {Object} Modified release data with professional tone
16
+ */
17
+ apply(data) {
18
+ const result = { ...data };
19
+
20
+ // Transform summary
21
+ result.summary = this._professionalSummary(data);
22
+
23
+ // Transform categories
24
+ if (data.categories) {
25
+ result.categories = {};
26
+ for (const [category, changes] of Object.entries(data.categories)) {
27
+ const newCategory = this._professionalCategoryName(category);
28
+ result.categories[newCategory] = changes.map(c => ({
29
+ ...c,
30
+ description: this._professionalDescription(c.description, c.type),
31
+ }));
32
+ }
33
+ }
34
+
35
+ // Transform breaking changes
36
+ if (data.breaking) {
37
+ result.breaking = data.breaking.map(bc => ({
38
+ ...bc,
39
+ description: this._professionalBreaking(bc.description),
40
+ migration_guide: bc.migration_guide
41
+ ? this._professionalMigration(bc.migration_guide)
42
+ : undefined,
43
+ }));
44
+ }
45
+
46
+ return result;
47
+ }
48
+
49
+ /**
50
+ * Generate a professional summary.
51
+ */
52
+ _professionalSummary(data) {
53
+ const parts = [];
54
+ const version = data.version || '0.0.0';
55
+
56
+ if (data.breaking && data.breaking.length > 0) {
57
+ parts.push(`We are pleased to announce the release of version ${version}, which includes ${data.breaking.length} breaking change${data.breaking.length > 1 ? 's' : ''}`);
58
+ } else if (Object.keys(data.categories || {}).length > 0) {
59
+ const catCount = Object.keys(data.categories).length;
60
+ parts.push(`We are pleased to announce the release of version ${version}, comprising ${catCount} categor${catCount !== 1 ? 'ies' : 'y'} of improvements`);
61
+ } else {
62
+ parts.push(`We are pleased to announce the release of version ${version}`);
63
+ }
64
+
65
+ if (data.contributors && data.contributors.length > 0) {
66
+ parts.push(`with contributions from ${data.contributors.length} developer${data.contributors.length > 1 ? 's' : ''}`);
67
+ }
68
+
69
+ parts.push('.');
70
+ return parts.join(' ');
71
+ }
72
+
73
+ /**
74
+ * Map emoji category names to professional names.
75
+ */
76
+ _professionalCategoryName(category) {
77
+ const map = {
78
+ '๐Ÿš€ Features': 'New Features',
79
+ '๐Ÿ› Bug Fixes': 'Bug Fixes and Resolutions',
80
+ '๐Ÿ’ฅ Breaking Changes': 'Breaking Changes',
81
+ 'โšก Performance': 'Performance Improvements',
82
+ 'โ™ป๏ธ Refactoring': 'Code Quality Improvements',
83
+ '๐Ÿ“ Documentation': 'Documentation Updates',
84
+ '๐ŸŽจ Style': 'Code Style and Formatting',
85
+ '๐Ÿงช Tests': 'Test Coverage',
86
+ '๐Ÿ”ง Chore': 'Maintenance and Infrastructure',
87
+ '๐Ÿ”’ Security': 'Security Enhancements',
88
+ };
89
+ return map[category] || category.replace(/[^\w\s&]/g, '').trim();
90
+ }
91
+
92
+ /**
93
+ * Make a description more professional.
94
+ */
95
+ _professionalDescription(desc, type) {
96
+ if (!desc) return desc;
97
+ // Capitalize first letter, ensure period at end if appropriate
98
+ let result = desc.charAt(0).toUpperCase() + desc.slice(1);
99
+ if (type === 'feat' && !result.startsWith('Implemented') && !result.startsWith('Added') && !result.startsWith('Introduced')) {
100
+ return result;
101
+ }
102
+ if (type === 'fix' && !result.startsWith('Resolved') && !result.startsWith('Fixed') && !result.startsWith('Addressed')) {
103
+ return result;
104
+ }
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Professional breaking change description.
110
+ */
111
+ _professionalBreaking(desc) {
112
+ if (!desc) return desc;
113
+ return `This release introduces a change to ${desc.charAt(0).toLowerCase() + desc.slice(1)}`;
114
+ }
115
+
116
+ /**
117
+ * Professional migration guide.
118
+ */
119
+ _professionalMigration(guide) {
120
+ if (!guide) return guide;
121
+ return guide;
122
+ }
123
+ }
124
+
125
+ module.exports = { ProfessionalTone };