snipe-pr 1.0.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.
@@ -0,0 +1,15 @@
1
+ name: Snipe PR
2
+ on:
3
+ pull_request:
4
+ types: [opened, synchronize]
5
+
6
+ permissions:
7
+ pull-requests: write
8
+
9
+ jobs:
10
+ describe:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: klawgulp-ship-it/snipe-pr@v1
14
+ with:
15
+ github-token: ${{ secrets.GITHUB_TOKEN }}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SnipeLink LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Snipe PR โšก
2
+
3
+ **Auto-generated PR descriptions that actually make sense.**
4
+
5
+ Snipe PR analyzes your pull request diffs and generates structured, readable descriptions โ€” so your team spends less time writing and more time shipping.
6
+
7
+ ## Quick Start
8
+
9
+ Add to `.github/workflows/snipe-pr.yml`:
10
+
11
+ ```yaml
12
+ name: Snipe PR
13
+ on:
14
+ pull_request:
15
+ types: [opened, synchronize]
16
+
17
+ jobs:
18
+ describe:
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ pull-requests: write
22
+ steps:
23
+ - uses: klawgulp-ship-it/snipe-pr@v1
24
+ with:
25
+ github-token: ${{ secrets.GITHUB_TOKEN }}
26
+ ```
27
+
28
+ That's it. Every PR gets an auto-generated description with:
29
+ - Change type detection (feature, bugfix, refactor, docs, etc.)
30
+ - Smart file categorization (API, UI, Tests, Config, etc.)
31
+ - Visual stats bar showing additions vs deletions
32
+ - Key highlights (new files, large changes, renames)
33
+
34
+ ## Example Output
35
+
36
+ > ## โœจ **New Feature** โ€” 5 files changed (+120/-15)
37
+ >
38
+ > ### Highlights
39
+ > - Added 2 new files: `UserService.ts`, `types.ts`
40
+ > - Largest change: `UserService.ts` (+80/-0)
41
+ >
42
+ > ### Changes
43
+ > - ๐Ÿ”Œ **API**: `src/routes/users.ts`, `src/controllers/auth.ts`
44
+ > - ๐Ÿ–ผ๏ธ **UI Components**: `src/components/UserCard.tsx`
45
+ > - ๐Ÿงช **Tests**: `src/__tests__/users.test.ts`
46
+ > - โš™๏ธ **Configuration**: `tsconfig.json`
47
+ >
48
+ > ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฅ๐ŸŸฅ +120 / -15 (net +105)
49
+
50
+ ## Options
51
+
52
+ | Input | Description | Default |
53
+ |-------|-------------|---------|
54
+ | `github-token` | GitHub token for API access | `${{ github.token }}` |
55
+ | `snipelink-key` | SnipeLink API key for AI-powered descriptions | โ€” |
56
+ | `mode` | `comment` (PR comment) or `body` (update PR body) | `comment` |
57
+ | `include-stats` | Include file change statistics | `true` |
58
+ | `max-diff-size` | Max diff characters to analyze | `10000` |
59
+
60
+ ## AI-Powered Descriptions (Pro)
61
+
62
+ Want smarter, context-aware descriptions? Add a SnipeLink API key:
63
+
64
+ ```yaml
65
+ - uses: klawgulp-ship-it/snipe-pr@v1
66
+ with:
67
+ github-token: ${{ secrets.GITHUB_TOKEN }}
68
+ snipelink-key: ${{ secrets.SNIPELINK_KEY }}
69
+ ```
70
+
71
+ Get your API key at [snipelink.com/tools](https://snipelink.com/tools)
72
+
73
+ ## How It Works
74
+
75
+ 1. PR is opened or updated
76
+ 2. Snipe PR reads the diff and changed files
77
+ 3. Files are categorized by type (API, UI, Tests, etc.)
78
+ 4. Change type is detected from diff content
79
+ 5. A structured description is generated and posted
80
+
81
+ No external API calls needed for the free tier โ€” everything runs locally in the GitHub Action.
82
+
83
+ ## License
84
+
85
+ MIT โ€” by [SnipeLink LLC](https://snipelink.com)
@@ -0,0 +1,107 @@
1
+ import { analyzeChanges, formatDescription, FileChange } from '../src/analyzer';
2
+
3
+ describe('analyzeChanges', () => {
4
+ it('detects feature changes', () => {
5
+ const files: FileChange[] = [
6
+ { filename: 'src/newFeature.ts', status: 'added', additions: 50, deletions: 0 },
7
+ { filename: 'src/index.ts', status: 'modified', additions: 5, deletions: 2 },
8
+ ];
9
+ const diff = 'add new feature implement create';
10
+ const result = analyzeChanges(files, diff);
11
+
12
+ expect(result.changeType).toBe('feature');
13
+ expect(result.stats.filesChanged).toBe(2);
14
+ expect(result.stats.additions).toBe(55);
15
+ expect(result.stats.deletions).toBe(2);
16
+ });
17
+
18
+ it('detects bugfix changes', () => {
19
+ const files: FileChange[] = [
20
+ { filename: 'src/auth.ts', status: 'modified', additions: 3, deletions: 5 },
21
+ ];
22
+ const diff = 'fix bug error crash issue';
23
+ const result = analyzeChanges(files, diff);
24
+
25
+ expect(result.changeType).toBe('bugfix');
26
+ });
27
+
28
+ it('detects test-only changes', () => {
29
+ const files: FileChange[] = [
30
+ { filename: 'src/__tests__/auth.test.ts', status: 'added', additions: 40, deletions: 0 },
31
+ { filename: 'src/auth.spec.ts', status: 'modified', additions: 10, deletions: 5 },
32
+ ];
33
+ const result = analyzeChanges(files, '');
34
+
35
+ expect(result.changeType).toBe('test');
36
+ });
37
+
38
+ it('detects docs-only changes', () => {
39
+ const files: FileChange[] = [
40
+ { filename: 'README.md', status: 'modified', additions: 20, deletions: 5 },
41
+ { filename: 'docs/setup.md', status: 'added', additions: 50, deletions: 0 },
42
+ ];
43
+ const result = analyzeChanges(files, '');
44
+
45
+ expect(result.changeType).toBe('docs');
46
+ });
47
+
48
+ it('categorizes files correctly', () => {
49
+ const files: FileChange[] = [
50
+ { filename: 'src/components/Button.tsx', status: 'modified', additions: 10, deletions: 5 },
51
+ { filename: 'src/api/routes/users.ts', status: 'added', additions: 30, deletions: 0 },
52
+ { filename: 'package.json', status: 'modified', additions: 2, deletions: 1 },
53
+ { filename: 'src/__tests__/button.test.tsx', status: 'added', additions: 20, deletions: 0 },
54
+ ];
55
+ const result = analyzeChanges(files, '');
56
+
57
+ const categoryNames = result.categories.map((c) => c.name);
58
+ expect(categoryNames).toContain('Tests');
59
+ expect(categoryNames).toContain('Configuration');
60
+ });
61
+
62
+ it('highlights new files', () => {
63
+ const files: FileChange[] = [
64
+ { filename: 'src/newService.ts', status: 'added', additions: 100, deletions: 0 },
65
+ { filename: 'src/types.ts', status: 'added', additions: 30, deletions: 0 },
66
+ ];
67
+ const result = analyzeChanges(files, '');
68
+
69
+ expect(result.highlights.some((h) => h.includes('Added 2 new files'))).toBe(true);
70
+ });
71
+
72
+ it('highlights large changes', () => {
73
+ const files: FileChange[] = [
74
+ { filename: 'src/bigRefactor.ts', status: 'modified', additions: 200, deletions: 150 },
75
+ ];
76
+ const result = analyzeChanges(files, '');
77
+
78
+ expect(result.highlights.some((h) => h.includes('Largest change'))).toBe(true);
79
+ });
80
+ });
81
+
82
+ describe('formatDescription', () => {
83
+ it('generates valid markdown', () => {
84
+ const files: FileChange[] = [
85
+ { filename: 'src/index.ts', status: 'modified', additions: 10, deletions: 5 },
86
+ ];
87
+ const analysis = analyzeChanges(files, 'fix a bug in auth');
88
+ const md = formatDescription(analysis, 'Fix auth bug');
89
+
90
+ expect(md).toContain('##');
91
+ expect(md).toContain('Changes');
92
+ expect(md).toContain('Snipe PR');
93
+ expect(md).toContain('snipelink.com');
94
+ });
95
+
96
+ it('includes stats bar', () => {
97
+ const files: FileChange[] = [
98
+ { filename: 'src/a.ts', status: 'modified', additions: 50, deletions: 10 },
99
+ ];
100
+ const analysis = analyzeChanges(files, '');
101
+ const md = formatDescription(analysis, 'Test');
102
+
103
+ expect(md).toContain('๐ŸŸฉ');
104
+ expect(md).toContain('+50');
105
+ expect(md).toContain('-10');
106
+ });
107
+ });
package/action.yml ADDED
@@ -0,0 +1,39 @@
1
+ name: 'Snipe PR โ€” AI PR Descriptions'
2
+ author: 'SnipeLink LLC'
3
+ description: 'Auto-generates smart PR descriptions from your diffs. Free tier included, AI-powered with SnipeLink Pro.'
4
+
5
+ inputs:
6
+ github-token:
7
+ description: 'GitHub token for API access (usually secrets.GITHUB_TOKEN)'
8
+ required: true
9
+ default: ${{ github.token }}
10
+ snipelink-key:
11
+ description: 'SnipeLink API key for AI-powered descriptions (optional, get one at snipelink.com)'
12
+ required: false
13
+ default: ''
14
+ mode:
15
+ description: 'Output mode: "comment" posts a PR comment, "body" updates the PR body'
16
+ required: false
17
+ default: 'comment'
18
+ include-stats:
19
+ description: 'Include file change statistics'
20
+ required: false
21
+ default: 'true'
22
+ max-diff-size:
23
+ description: 'Max diff characters to analyze (larger = more detail)'
24
+ required: false
25
+ default: '10000'
26
+
27
+ outputs:
28
+ description:
29
+ description: 'The generated PR description'
30
+ comment-id:
31
+ description: 'ID of the posted comment (if mode=comment)'
32
+
33
+ runs:
34
+ using: 'node20'
35
+ main: 'dist/index.js'
36
+
37
+ branding:
38
+ icon: 'zap'
39
+ color: 'green'