-
Notifications
You must be signed in to change notification settings - Fork 55
263 lines (229 loc) · 11.5 KB
/
pr-review.yml
File metadata and controls
263 lines (229 loc) · 11.5 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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
name: AI Code Review
# This is a thin trigger shim — the real review logic runs in the private repo.
# Contributors cannot break the review system by modifying this file.
# Any changes to this file will only affect the trigger mechanism,
# not the scoring, LLM pipeline, or review logic.
on:
pull_request_target:
types: [opened, synchronize]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: string
permissions:
contents: read
jobs:
trigger-review:
runs-on: ubuntu-latest
concurrency:
group: pr-review-${{ inputs.pr_number || github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Check if bounty is still open
id: bounty_check
env:
GH_TOKEN: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }}
run: |
if [ -n "${{ inputs.pr_number }}" ]; then
PR_NUM="${{ inputs.pr_number }}"
else
PR_NUM="${{ github.event.pull_request.number }}"
fi
echo "pr_num=$PR_NUM" >> $GITHUB_OUTPUT
# Extract linked issue from PR title + body
PR_JSON=$(curl -sf -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM")
PR_TITLE=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('title',''))" 2>/dev/null)
PR_AUTHOR=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('user',{}).get('login',''))" 2>/dev/null)
PR_BODY=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('body','') or '')" 2>/dev/null)
ISSUE_NUM=$(python3 -c "
import re
text = '''$PR_TITLE''' + ' ' + '''$PR_BODY'''
m = re.search(r'(?:closes|fixes|resolves)\s+(?:#(?:\S+#)?)(\d+)', text, re.IGNORECASE)
print(m.group(1) if m else '')
" 2>/dev/null)
if [ -z "$ISSUE_NUM" ]; then
echo "No linked issue — proceeding to dispatch"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if the bounty issue is closed
ISSUE_STATE=$(curl -sf -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/issues/$ISSUE_NUM" \
| python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('state','open'))" 2>/dev/null)
if [ "$ISSUE_STATE" = "closed" ]; then
echo "BLOCKED: Bounty #$ISSUE_NUM is closed — closing PR #$PR_NUM"
# Post comment
curl -sf -X POST \
-H "Authorization: token $GH_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUM/comments" \
-d "{\"body\":\"❌ **Bounty Closed**\n\n@$PR_AUTHOR, bounty #$ISSUE_NUM has already been completed and closed.\n\nThis PR cannot be reviewed because the bounty is no longer active. Please check the [open bounties](https://github.com/${{ github.repository }}/issues?q=is%3Aissue+is%3Aopen+label%3Abounty) for available work.\n\n---\n*SolFoundry Review Bot*\"}"
# Close PR
curl -sf -X PATCH \
-H "Authorization: token $GH_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUM" \
-d '{"state":"closed"}'
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "Bounty #$ISSUE_NUM is open — proceeding"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Check tier eligibility
if: steps.bounty_check.outputs.skip != 'true'
id: tier_check
env:
GH_TOKEN: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }}
run: |
PR_NUM="${{ steps.bounty_check.outputs.pr_num }}"
REPO="${{ github.repository }}"
# Get PR info
PR_JSON=$(curl -sf -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/$REPO/pulls/$PR_NUM")
PR_AUTHOR=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('user',{}).get('login',''))" 2>/dev/null)
PR_TITLE=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('title',''))" 2>/dev/null)
PR_BODY=$(echo "$PR_JSON" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('body','') or '')" 2>/dev/null)
# Extract linked issue number
ISSUE_NUM=$(python3 -c "
import re
text = '''$PR_TITLE''' + ' ' + '''$PR_BODY'''
m = re.search(r'(?:closes|fixes|resolves)\s+(?:\S+#)?(\d+)', text, re.IGNORECASE)
print(m.group(1) if m else '')
" 2>/dev/null)
if [ -z "$ISSUE_NUM" ]; then
echo "No linked issue — skipping tier check"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Get issue labels to determine tier
ISSUE_JSON=$(curl -sf -H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/$REPO/issues/$ISSUE_NUM")
LABELS=$(echo "$ISSUE_JSON" | python3 -c "import sys,json; print(','.join(l['name'] for l in json.loads(sys.stdin.read()).get('labels',[])))" 2>/dev/null)
echo "Issue #$ISSUE_NUM labels: $LABELS"
# T1 bounties — open to everyone, no check needed
if echo "$LABELS" | grep -q "tier-1"; then
echo "T1 bounty — open race, no tier gate"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Skip non-bounty issues
if ! echo "$LABELS" | grep -q "bounty"; then
echo "Not a bounty issue — no tier gate"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# For T2/T3: check contributor's merged bounty PR history
python3 << PYEOF
import json, re, urllib.request, os, sys
token = os.environ["GH_TOKEN"]
repo = "$REPO"
user = "$PR_AUTHOR"
issue_num = "$ISSUE_NUM"
labels = "$LABELS"
pr_num = "$PR_NUM"
def gh_get(path):
req = urllib.request.Request(f"https://api.github.com/{path}")
req.add_header("Authorization", f"token {token}")
req.add_header("Accept", "application/vnd.github.v3+json")
return json.loads(urllib.request.urlopen(req).read().decode())
def gh_post(path, data):
req = urllib.request.Request(f"https://api.github.com/{path}", json.dumps(data).encode(), method="POST")
req.add_header("Authorization", f"token {token}")
req.add_header("Accept", "application/vnd.github.v3+json")
req.add_header("Content-Type", "application/json")
urllib.request.urlopen(req)
def gh_patch(path, data):
req = urllib.request.Request(f"https://api.github.com/{path}", json.dumps(data).encode(), method="PATCH")
req.add_header("Authorization", f"token {token}")
req.add_header("Accept", "application/vnd.github.v3+json")
req.add_header("Content-Type", "application/json")
urllib.request.urlopen(req)
def get_issue_tier(inum):
try:
issue = gh_get(f"repos/{repo}/issues/{inum}")
ilabels = [l["name"] for l in issue.get("labels", [])]
if "bounty" not in ilabels:
return None
for t in ("tier-3", "tier-2", "tier-1"):
if t in ilabels:
return t
return "tier-1"
except:
return None
# Count merged bounty PRs by tier
t1_count = t2_count = 0
page = 1
while True:
prs = gh_get(f"repos/{repo}/pulls?state=closed&per_page=100&page={page}")
for pr in prs:
if pr["user"]["login"] != user or not pr.get("merged_at"):
continue
body = pr.get("body") or ""
linked = re.findall(r"(?:closes|fixes|resolves)\s*#(\d+)", body, re.IGNORECASE)
for ln in linked:
tier = get_issue_tier(ln)
if tier == "tier-1": t1_count += 1
elif tier == "tier-2": t2_count += 1
if len(prs) < 100: break
page += 1
print(f"{user}: T1={t1_count}, T2={t2_count}")
is_t2 = "tier-2" in labels
is_t3 = "tier-3" in labels
blocked = False
if is_t2 and t1_count < 4:
remaining = 4 - t1_count
msg = (
f"⚠️ @{user} — **Tier 2 bounties require 4+ completed Tier 1 bounties.**\n\n"
f"You have **{t1_count}** merged T1 PR(s) — **{remaining} more** to unlock T2.\n\n"
f"This PR cannot proceed to review. Please build your reputation with "
f"[Tier 1 bounties](https://github.com/{repo}/labels/tier-1) first.\n\n"
f"*— SolFoundry Bot 🏭*"
)
blocked = True
elif is_t3:
t3_std = t2_count >= 3
t3_alt = t1_count >= 5 and t2_count >= 1
if not t3_std and not t3_alt:
msg = (
f"⚠️ @{user} — **Tier 3 bounties are reputation-gated.**\n\n"
f"Requirements (one of):\n"
f"- **Standard:** 3+ completed Tier 2 bounties (you have {t2_count})\n"
f"- **Alternative:** 5+ T1 AND 1+ T2 (you have {t1_count} T1, {t2_count} T2)\n\n"
f"This PR cannot proceed to review until you meet the requirements.\n\n"
f"*— SolFoundry Bot 🏭*"
)
blocked = True
if blocked:
gh_post(f"repos/{repo}/issues/{pr_num}/comments", {"body": msg})
gh_patch(f"repos/{repo}/pulls/{pr_num}", {"state": "closed"})
print(f"BLOCKED: {user} not eligible — PR #{pr_num} closed")
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write("skip=true\n")
else:
print(f"ELIGIBLE: {user} passes tier gate")
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write("skip=false\n")
PYEOF
- name: Trigger private review pipeline
if: steps.bounty_check.outputs.skip != 'true' && steps.tier_check.outputs.skip != 'true'
env:
REVIEW_PAT: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }}
run: |
PR_NUM="${{ steps.bounty_check.outputs.pr_num }}"
# Determine action type for smart dedup in private pipeline
if [ -n "${{ github.event.action }}" ]; then
ACTION="${{ github.event.action }}"
else
ACTION="manual"
fi
echo "Dispatching review for PR #${PR_NUM} (action: ${ACTION}) to private pipeline..."
# Fire repository_dispatch to the private review repo
curl -sf -X POST \
-H "Authorization: token $REVIEW_PAT" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/SolFoundry/solfoundry-review/dispatches" \
-d "{\"event_type\": \"review-pr\", \"client_payload\": {\"pr_number\": \"${PR_NUM}\", \"action\": \"${ACTION}\"}}"
echo "✅ Review dispatched for PR #${PR_NUM}"