@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.
- package/.editorconfig +12 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +43 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- package/.github/dependabot.yml +16 -0
- package/.github/workflows/ci.yml +24 -0
- package/CODE_OF_CONDUCT.md +27 -0
- package/LICENSE +21 -21
- package/README.md +126 -1493
- package/SECURITY.md +22 -0
- package/__tests__/analyzer.test.js +63 -63
- package/__tests__/categorizer.test.js +93 -93
- package/__tests__/config.test.js +92 -92
- package/__tests__/formatter.test.js +63 -63
- package/__tests__/formatters.test.js +394 -394
- package/__tests__/migration.test.js +322 -322
- package/__tests__/semver.test.js +94 -94
- package/__tests__/tones.test.js +252 -252
- package/action.yml +113 -113
- package/index.js +73 -73
- package/package.json +47 -41
- package/src/ai-writer.js +108 -108
- package/src/analyzer.js +232 -232
- package/src/analyzers/migration.js +355 -355
- package/src/categorizer.js +182 -182
- package/src/config.js +162 -162
- package/src/constants.js +137 -137
- package/src/contributor.js +144 -144
- package/src/diff-analyzer.js +202 -202
- package/src/formatter.js +336 -336
- package/src/formatters/discord.js +174 -174
- package/src/formatters/html.js +195 -195
- package/src/formatters/index.js +42 -42
- package/src/formatters/markdown.js +123 -123
- package/src/formatters/slack.js +176 -176
- package/src/formatters/twitter.js +242 -242
- package/src/formatters/types.js +48 -48
- package/src/generator.js +297 -297
- package/src/integrations/changelog.js +125 -125
- package/src/integrations/discord.js +96 -96
- package/src/integrations/github-release.js +75 -75
- package/src/integrations/indexer.js +119 -119
- package/src/integrations/slack.js +112 -112
- package/src/integrations/twitter.js +128 -128
- package/src/logger.js +52 -52
- package/src/prompts.js +210 -210
- package/src/rate-limiter.js +92 -92
- package/src/semver.js +129 -129
- package/src/tones/casual.js +114 -114
- package/src/tones/humorous.js +164 -164
- package/src/tones/index.js +38 -38
- package/src/tones/professional.js +125 -125
- package/src/tones/technical.js +164 -164
- package/src/tones/types.js +26 -26
- package/jest.config.js +0 -10
package/src/diff-analyzer.js
CHANGED
|
@@ -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
|
+
};
|