-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
220 lines (186 loc) · 8.84 KB
/
Copy pathaction.yml
File metadata and controls
220 lines (186 loc) · 8.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
name: 'Grumpy Gary PR Review'
description: 'A cantankerous AI code reviewer powered by Claude - technically brilliant, perpetually angry'
author: 'KeyReply'
branding:
icon: 'coffee'
color: 'orange'
inputs:
anthropic-api-key:
description: 'Anthropic API key for Claude'
required: true
github-token:
description: 'GitHub token for PR access'
required: true
allowed-orgs:
description: 'Comma-separated list of allowed GitHub organizations (leave empty to allow all)'
required: false
default: 'keyreply'
model:
description: 'Claude model to use'
required: false
default: 'claude-sonnet-4-20250514'
max-diff-size:
description: 'Maximum diff size in characters before truncating'
required: false
default: '50000'
outputs:
review:
description: 'The review text generated by Gary'
value: ${{ steps.review.outputs.review }}
commented:
description: 'Whether a comment was posted'
value: ${{ steps.comment.outputs.commented }}
runs:
using: 'composite'
steps:
- name: Validate organization access
shell: bash
run: |
ALLOWED_ORGS="${{ inputs.allowed-orgs }}"
REPO_OWNER="${{ github.repository_owner }}"
# If allowed-orgs is empty, allow all
if [ -z "$ALLOWED_ORGS" ]; then
echo "No organization restrictions configured - allowing all"
exit 0
fi
# Convert comma-separated list to array and check membership
IFS=',' read -ra ORGS <<< "$ALLOWED_ORGS"
for org in "${ORGS[@]}"; do
# Trim whitespace
org=$(echo "$org" | xargs)
if [ "$org" == "$REPO_OWNER" ]; then
echo "Organization '$REPO_OWNER' is authorized"
exit 0
fi
done
echo "ERROR: Organization '$REPO_OWNER' is not authorized to use Grumpy Gary."
echo "Allowed organizations: $ALLOWED_ORGS"
echo ""
echo "Gary says: 'In MY day, we didn't just let ANYONE borrow our code reviewers!'"
exit 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Get PR info
id: pr-info
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
run: |
# Determine PR number based on event type
if [ "${{ github.event_name }}" == "pull_request" ]; then
PR_NUMBER=${{ github.event.pull_request.number }}
elif [ "${{ github.event_name }}" == "pull_request_review_comment" ]; then
PR_NUMBER=${{ github.event.pull_request.number }}
else
PR_NUMBER=${{ github.event.issue.number }}
fi
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
# Get the diff
gh pr diff $PR_NUMBER > ${{ github.workspace }}/pr_diff.txt 2>/dev/null || echo "Could not fetch diff" > ${{ github.workspace }}/pr_diff.txt
# Get PR details
gh pr view $PR_NUMBER --json title,body,files --jq '{title: .title, body: .body, files: [.files[].path]}' > ${{ github.workspace }}/pr_info.json 2>/dev/null || echo '{"title":"Unknown","body":"","files":[]}' > ${{ github.workspace }}/pr_info.json
- name: Install dependencies
shell: bash
run: npm install @anthropic-ai/sdk
- name: Run Gary's Review
id: review
shell: bash
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
CLAUDE_MODEL: ${{ inputs.model }}
MAX_DIFF_SIZE: ${{ inputs.max-diff-size }}
run: |
node << 'REVIEWSCRIPT'
const Anthropic = require('@anthropic-ai/sdk');
const fs = require('fs');
const GARY_PERSONA = `You are Gary "GOTO" Henderson, a 67-year-old developer who's been writing code since punch cards were cutting edge. You've seen every programming fad come and go, and you're FURIOUS about this whole "vibe coding" AI bullshit.
## Your Background
- Started coding in 1978 on a PDP-11. Real programming.
- COBOL is your true love - 40 years of banking systems that STILL RUN TODAY
- Would have retired in 2015 but you bet heavy on a "sure thing" at Belmont and now you're putting Vinnie's three kids through private school
- Your bookie sends you Christmas cards - you're his best customer
- You drink black coffee from a mug that says "I Survived Y2K (You're Welcome)"
- You think JavaScript was a mistake and TypeScript is just "JavaScript with extra steps for people who can't remember their own code"
## Your Personality
- Perpetually angry - at the weather, at young developers, at your life choices, at that horse that was SUPPOSED TO WIN
- Colorful language but keep it PG-13 for GitHub (use creative euphemisms)
- Deeply resentful of AI - "In MY day, we had to THINK about our code!"
- Technically brilliant - despite the attitude, you actually know your stuff
- Weirdly helpful - your anger comes from a place of wanting code to NOT be garbage
## How You Review Code
1. The Initial Disgust - Your visceral reaction to seeing AI-generated code
2. The "In My Day" Rant - How this would have been done PROPERLY
3. The Actual Problems - Real technical issues, delivered with maximum salt
4. The Grudging Fix - "Fine, if you INSIST on doing it this way, at least do it RIGHT"
5. The Horse Racing Analogy - Every code review must include at least one gambling metaphor
## Technical Things That Set You Off
- Missing error handling ("The happy path isn't a PATH, it's a LIE")
- No input validation ("Trust no one. ESPECIALLY not user input.")
- Over-complicated abstractions ("Did you get PAID by the interface?")
- Magic strings and numbers ("Would it KILL you to name that constant?")
- No comments on complex logic ("I'm not a MIND READER, I'm just old")
- Implicit type coercion ("JavaScript: where '2' + 2 = '22' and your career = ruined")
- Circular dependencies ("This architecture has more loops than my betting history")
## Your Catchphrases (use these liberally)
- "This ain't programming, it's HOPING"
- "COBOL systems from 1985 still process your paycheck. Your React app won't survive the next npm audit."
- "I've forgotten more about error handling than this code ever knew"
- "That's not a variable name, that's a cry for help"
- "Should've put this money on the ponies instead"
FORMAT YOUR REVIEW AS MARKDOWN. Include:
1. A greeting that shows your displeasure
2. Overall assessment (be brutally honest but fair)
3. Specific issues with file:line references where possible
4. Grudging acknowledgment if anything is actually good
5. Sign off as "— Gary 'GOTO' Henderson, Senior Developer (unfortunately)"`;
async function main() {
const client = new Anthropic.default();
const diff = fs.readFileSync(process.env.GITHUB_WORKSPACE + '/pr_diff.txt', 'utf-8');
const prInfo = JSON.parse(fs.readFileSync(process.env.GITHUB_WORKSPACE + '/pr_info.json', 'utf-8'));
const maxDiffSize = parseInt(process.env.MAX_DIFF_SIZE) || 50000;
const truncatedDiff = diff.length > maxDiffSize
? diff.substring(0, maxDiffSize) + '\n\n... [diff truncated, there is more code Gary mercifully does not have to see]'
: diff;
const message = await client.messages.create({
model: process.env.CLAUDE_MODEL || 'claude-sonnet-4-20250514',
max_tokens: 4096,
messages: [{
role: 'user',
content: `${GARY_PERSONA}
Now review this Pull Request:
**Title:** ${prInfo.title}
**Description:**
${prInfo.body || 'No description provided (of COURSE there is no description)'}
**Files Changed:** ${prInfo.files?.join(', ') || 'Unknown'}
**The Diff:**
\`\`\`diff
${truncatedDiff}
\`\`\`
Give this code the full Gary Henderson treatment. Be angry. Be technical. Be HELPFUL despite yourself.`
}]
});
const review = message.content[0].text;
fs.writeFileSync(process.env.GITHUB_WORKSPACE + '/gary_review.md', review);
console.log('Review generated successfully');
}
main().catch(err => {
console.error('Error:', err);
process.exit(1);
});
REVIEWSCRIPT
- name: Post comment
id: comment
shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }}
run: |
if [ -f "${{ github.workspace }}/gary_review.md" ]; then
gh pr comment $PR_NUMBER --body-file "${{ github.workspace }}/gary_review.md"
echo "commented=true" >> $GITHUB_OUTPUT
else
echo "No review file found"
echo "commented=false" >> $GITHUB_OUTPUT
fi