@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,202 +1,202 @@
1
- 'use strict';
2
-
3
- const { IMPACT_THRESHOLDS } = require('./constants');
4
- const logger = require('./logger');
5
-
6
- /**
7
- * Analyze diffs for a set of commits via the GitHub API.
8
- * @param {Array} commits - Commits to analyze
9
- * @param {Object} octokit - Octokit instance
10
- * @param {Object} context - GitHub Actions context
11
- * @returns {Object} Diff summary with file stats, impact, affected areas
12
- */
13
- async function analyzeDiff(commits, octokit, context) {
14
- const result = {
15
- files_changed: 0,
16
- files_added: 0,
17
- files_modified: 0,
18
- files_deleted: 0,
19
- additions: 0,
20
- deletions: 0,
21
- impact: 'low',
22
- affected_areas: [],
23
- potential_breaking: [],
24
- files: [],
25
- };
26
-
27
- if (!commits || commits.length === 0 || !octokit || !context) {
28
- logger.debug('Skipping diff analysis: no commits or no octokit/context');
29
- return result;
30
- }
31
-
32
- const owner = context.repo.owner;
33
- const repo = context.repo.repo;
34
-
35
- for (const commit of commits) {
36
- try {
37
- const sha = commit.sha || commit.id;
38
- if (!sha) continue;
39
-
40
- const { data: commitData } = await octokit.rest.repos.getCommit({
41
- owner,
42
- repo,
43
- ref: sha,
44
- });
45
-
46
- const files = commitData.files || [];
47
- for (const file of files) {
48
- result.files.push({
49
- filename: file.filename,
50
- status: file.status,
51
- additions: file.additions,
52
- deletions: file.deletions,
53
- changes: file.changes,
54
- });
55
-
56
- result.additions += file.additions || 0;
57
- result.deletions += file.deletions || 0;
58
- result.files_changed += 1;
59
-
60
- if (file.status === 'added') result.files_added += 1;
61
- else if (file.status === 'modified') result.files_modified += 1;
62
- else if (file.status === 'removed') result.files_deleted += 1;
63
-
64
- // Detect affected areas
65
- detectAffectedAreas(file.filename, result.affected_areas);
66
-
67
- // Detect potential breaking changes
68
- detectPotentialBreaking(file, result.potential_breaking);
69
- }
70
- } catch (err) {
71
- logger.debug(`Failed to get diff for commit ${commit.sha}: ${err.message}`);
72
- }
73
- }
74
-
75
- // Remove duplicate affected areas
76
- result.affected_areas = [...new Set(result.affected_areas)];
77
-
78
- // Determine impact level
79
- if (result.files_changed <= IMPACT_THRESHOLDS.LOW) {
80
- result.impact = 'low';
81
- } else if (result.files_changed <= IMPACT_THRESHOLDS.MEDIUM) {
82
- result.impact = 'medium';
83
- } else {
84
- result.impact = 'high';
85
- }
86
-
87
- logger.info(`Diff analysis: ${result.files_changed} files changed, impact=${result.impact}, areas=${result.affected_areas.join(',')}`);
88
-
89
- return result;
90
- }
91
-
92
- /**
93
- * Detect which areas of the codebase are affected by a file change.
94
- */
95
- function detectAffectedAreas(filename, areas) {
96
- if (!filename) return;
97
-
98
- const lower = filename.toLowerCase();
99
-
100
- if (lower.includes('route') || lower.includes('controller') || lower.includes('api/') || lower.includes('endpoint')) {
101
- areas.push('API');
102
- }
103
- if (lower.includes('model') || lower.includes('schema') || lower.includes('migration') || lower.includes('db/') || lower.includes('database')) {
104
- areas.push('Database');
105
- }
106
- if (lower.includes('auth') || lower.includes('login') || lower.includes('token') || lower.includes('session') || lower.includes('password')) {
107
- areas.push('Authentication');
108
- }
109
- if (lower.includes('component') || lower.includes('page') || lower.includes('view') || lower.includes('ui/') || lower.includes('style')) {
110
- areas.push('UI');
111
- }
112
- if (lower.includes('config') || lower.includes('.env') || lower.includes('setting')) {
113
- areas.push('Configuration');
114
- }
115
- if (lower.includes('test') || lower.includes('spec') || lower.includes('__test__')) {
116
- areas.push('Tests');
117
- }
118
- if (lower.includes('middleware') || lower.includes('interceptor') || lower.includes('filter')) {
119
- areas.push('Middleware');
120
- }
121
- if (lower.includes('util') || lower.includes('helper') || lower.includes('service')) {
122
- areas.push('Utilities');
123
- }
124
- }
125
-
126
- /**
127
- * Detect potential breaking changes from a diff.
128
- */
129
- function detectPotentialBreaking(file, potentialBreaking) {
130
- if (!file) return;
131
-
132
- const filename = (file.filename || '').toLowerCase();
133
-
134
- // Removed exports in index files
135
- if (file.status === 'removed' && (filename.includes('index.') || filename.includes('export'))) {
136
- potentialBreaking.push({
137
- file: file.filename,
138
- reason: 'Export file was removed',
139
- });
140
- }
141
-
142
- // Large deletion ratio might indicate removed functionality
143
- if (file.deletions > file.additions * 3 && file.deletions > 20) {
144
- potentialBreaking.push({
145
- file: file.filename,
146
- reason: `Significant code removal (${file.deletions} deletions vs ${file.additions} additions)`,
147
- });
148
- }
149
-
150
- // Changes to public API files
151
- if (filename.includes('api') && filename.includes('public')) {
152
- if (file.status === 'modified') {
153
- potentialBreaking.push({
154
- file: file.filename,
155
- reason: 'Public API file was modified',
156
- });
157
- }
158
- }
159
- }
160
-
161
- /**
162
- * Format diff stats for display in release notes.
163
- */
164
- function formatDiffStats(diffSummary) {
165
- if (!diffSummary || diffSummary.files_changed === 0) {
166
- return '';
167
- }
168
-
169
- const lines = [];
170
- lines.push(`- **${diffSummary.files_changed}** file${diffSummary.files_changed !== 1 ? 's' : ''} changed`);
171
- if (diffSummary.files_added > 0) {
172
- lines.push(`- **${diffSummary.files_added}** file${diffSummary.files_added !== 1 ? 's' : ''} added`);
173
- }
174
- if (diffSummary.files_modified > 0) {
175
- lines.push(`- **${diffSummary.files_modified}** file${diffSummary.files_modified !== 1 ? 's' : ''} modified`);
176
- }
177
- if (diffSummary.files_deleted > 0) {
178
- lines.push(`- **${diffSummary.files_deleted}** file${diffSummary.files_deleted !== 1 ? 's' : ''} deleted`);
179
- }
180
- lines.push(`- **+${diffSummary.additions}** additions, **-${diffSummary.deletions}** deletions`);
181
-
182
- if (diffSummary.impact === 'high') {
183
- lines.push(`- Impact: **High** (20+ files changed)`);
184
- } else if (diffSummary.impact === 'medium') {
185
- lines.push(`- Impact: **Medium** (6-20 files changed)`);
186
- } else {
187
- lines.push(`- Impact: **Low** (1-5 files changed)`);
188
- }
189
-
190
- if (diffSummary.affected_areas.length > 0) {
191
- lines.push(`- Affected areas: ${diffSummary.affected_areas.join(', ')}`);
192
- }
193
-
194
- return lines.join('\n');
195
- }
196
-
197
- module.exports = {
198
- analyzeDiff,
199
- detectAffectedAreas,
200
- detectPotentialBreaking,
201
- formatDiffStats,
202
- };
1
+ 'use strict';
2
+
3
+ const { IMPACT_THRESHOLDS } = require('./constants');
4
+ const logger = require('./logger');
5
+
6
+ /**
7
+ * Analyze diffs for a set of commits via the GitHub API.
8
+ * @param {Array} commits - Commits to analyze
9
+ * @param {Object} octokit - Octokit instance
10
+ * @param {Object} context - GitHub Actions context
11
+ * @returns {Object} Diff summary with file stats, impact, affected areas
12
+ */
13
+ async function analyzeDiff(commits, octokit, context) {
14
+ const result = {
15
+ files_changed: 0,
16
+ files_added: 0,
17
+ files_modified: 0,
18
+ files_deleted: 0,
19
+ additions: 0,
20
+ deletions: 0,
21
+ impact: 'low',
22
+ affected_areas: [],
23
+ potential_breaking: [],
24
+ files: [],
25
+ };
26
+
27
+ if (!commits || commits.length === 0 || !octokit || !context) {
28
+ logger.debug('Skipping diff analysis: no commits or no octokit/context');
29
+ return result;
30
+ }
31
+
32
+ const owner = context.repo.owner;
33
+ const repo = context.repo.repo;
34
+
35
+ for (const commit of commits) {
36
+ try {
37
+ const sha = commit.sha || commit.id;
38
+ if (!sha) continue;
39
+
40
+ const { data: commitData } = await octokit.rest.repos.getCommit({
41
+ owner,
42
+ repo,
43
+ ref: sha,
44
+ });
45
+
46
+ const files = commitData.files || [];
47
+ for (const file of files) {
48
+ result.files.push({
49
+ filename: file.filename,
50
+ status: file.status,
51
+ additions: file.additions,
52
+ deletions: file.deletions,
53
+ changes: file.changes,
54
+ });
55
+
56
+ result.additions += file.additions || 0;
57
+ result.deletions += file.deletions || 0;
58
+ result.files_changed += 1;
59
+
60
+ if (file.status === 'added') result.files_added += 1;
61
+ else if (file.status === 'modified') result.files_modified += 1;
62
+ else if (file.status === 'removed') result.files_deleted += 1;
63
+
64
+ // Detect affected areas
65
+ detectAffectedAreas(file.filename, result.affected_areas);
66
+
67
+ // Detect potential breaking changes
68
+ detectPotentialBreaking(file, result.potential_breaking);
69
+ }
70
+ } catch (err) {
71
+ logger.debug(`Failed to get diff for commit ${commit.sha}: ${err.message}`);
72
+ }
73
+ }
74
+
75
+ // Remove duplicate affected areas
76
+ result.affected_areas = [...new Set(result.affected_areas)];
77
+
78
+ // Determine impact level
79
+ if (result.files_changed <= IMPACT_THRESHOLDS.LOW) {
80
+ result.impact = 'low';
81
+ } else if (result.files_changed <= IMPACT_THRESHOLDS.MEDIUM) {
82
+ result.impact = 'medium';
83
+ } else {
84
+ result.impact = 'high';
85
+ }
86
+
87
+ logger.info(`Diff analysis: ${result.files_changed} files changed, impact=${result.impact}, areas=${result.affected_areas.join(',')}`);
88
+
89
+ return result;
90
+ }
91
+
92
+ /**
93
+ * Detect which areas of the codebase are affected by a file change.
94
+ */
95
+ function detectAffectedAreas(filename, areas) {
96
+ if (!filename) return;
97
+
98
+ const lower = filename.toLowerCase();
99
+
100
+ if (lower.includes('route') || lower.includes('controller') || lower.includes('api/') || lower.includes('endpoint')) {
101
+ areas.push('API');
102
+ }
103
+ if (lower.includes('model') || lower.includes('schema') || lower.includes('migration') || lower.includes('db/') || lower.includes('database')) {
104
+ areas.push('Database');
105
+ }
106
+ if (lower.includes('auth') || lower.includes('login') || lower.includes('token') || lower.includes('session') || lower.includes('password')) {
107
+ areas.push('Authentication');
108
+ }
109
+ if (lower.includes('component') || lower.includes('page') || lower.includes('view') || lower.includes('ui/') || lower.includes('style')) {
110
+ areas.push('UI');
111
+ }
112
+ if (lower.includes('config') || lower.includes('.env') || lower.includes('setting')) {
113
+ areas.push('Configuration');
114
+ }
115
+ if (lower.includes('test') || lower.includes('spec') || lower.includes('__test__')) {
116
+ areas.push('Tests');
117
+ }
118
+ if (lower.includes('middleware') || lower.includes('interceptor') || lower.includes('filter')) {
119
+ areas.push('Middleware');
120
+ }
121
+ if (lower.includes('util') || lower.includes('helper') || lower.includes('service')) {
122
+ areas.push('Utilities');
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Detect potential breaking changes from a diff.
128
+ */
129
+ function detectPotentialBreaking(file, potentialBreaking) {
130
+ if (!file) return;
131
+
132
+ const filename = (file.filename || '').toLowerCase();
133
+
134
+ // Removed exports in index files
135
+ if (file.status === 'removed' && (filename.includes('index.') || filename.includes('export'))) {
136
+ potentialBreaking.push({
137
+ file: file.filename,
138
+ reason: 'Export file was removed',
139
+ });
140
+ }
141
+
142
+ // Large deletion ratio might indicate removed functionality
143
+ if (file.deletions > file.additions * 3 && file.deletions > 20) {
144
+ potentialBreaking.push({
145
+ file: file.filename,
146
+ reason: `Significant code removal (${file.deletions} deletions vs ${file.additions} additions)`,
147
+ });
148
+ }
149
+
150
+ // Changes to public API files
151
+ if (filename.includes('api') && filename.includes('public')) {
152
+ if (file.status === 'modified') {
153
+ potentialBreaking.push({
154
+ file: file.filename,
155
+ reason: 'Public API file was modified',
156
+ });
157
+ }
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Format diff stats for display in release notes.
163
+ */
164
+ function formatDiffStats(diffSummary) {
165
+ if (!diffSummary || diffSummary.files_changed === 0) {
166
+ return '';
167
+ }
168
+
169
+ const lines = [];
170
+ lines.push(`- **${diffSummary.files_changed}** file${diffSummary.files_changed !== 1 ? 's' : ''} changed`);
171
+ if (diffSummary.files_added > 0) {
172
+ lines.push(`- **${diffSummary.files_added}** file${diffSummary.files_added !== 1 ? 's' : ''} added`);
173
+ }
174
+ if (diffSummary.files_modified > 0) {
175
+ lines.push(`- **${diffSummary.files_modified}** file${diffSummary.files_modified !== 1 ? 's' : ''} modified`);
176
+ }
177
+ if (diffSummary.files_deleted > 0) {
178
+ lines.push(`- **${diffSummary.files_deleted}** file${diffSummary.files_deleted !== 1 ? 's' : ''} deleted`);
179
+ }
180
+ lines.push(`- **+${diffSummary.additions}** additions, **-${diffSummary.deletions}** deletions`);
181
+
182
+ if (diffSummary.impact === 'high') {
183
+ lines.push(`- Impact: **High** (20+ files changed)`);
184
+ } else if (diffSummary.impact === 'medium') {
185
+ lines.push(`- Impact: **Medium** (6-20 files changed)`);
186
+ } else {
187
+ lines.push(`- Impact: **Low** (1-5 files changed)`);
188
+ }
189
+
190
+ if (diffSummary.affected_areas.length > 0) {
191
+ lines.push(`- Affected areas: ${diffSummary.affected_areas.join(', ')}`);
192
+ }
193
+
194
+ return lines.join('\n');
195
+ }
196
+
197
+ module.exports = {
198
+ analyzeDiff,
199
+ detectAffectedAreas,
200
+ detectPotentialBreaking,
201
+ formatDiffStats,
202
+ };