-
Notifications
You must be signed in to change notification settings - Fork 57
497 lines (442 loc) · 25.8 KB
/
pr-review.yml
File metadata and controls
497 lines (442 loc) · 25.8 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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
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)
# Blocked users — hard block at workflow level
BLOCKED_USERS="AlexChen31337,xidik12,chulinhcql-art"
if echo ",$BLOCKED_USERS," | grep -qi ",$PR_AUTHOR,"; then
echo "BLOCKED USER: $PR_AUTHOR"
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\":\"🚫 @$PR_AUTHOR — your account has been blocked from the SolFoundry bounty program due to previous violations.\n\n---\n*SolFoundry Review Bot*\"}"
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
exit 0
fi
# Rate limiting disabled during maintenance
# Extract ALL linked issue numbers (to detect multi-bounty stuffing)
ALL_ISSUES=$(python3 -c "
import re
text = '''$PR_TITLE''' + ' ' + '''$PR_BODY'''
matches = re.findall(r'(?:closes|fixes|resolves|issue|bounty)\s*:?\s*(?:#(?:\S+#)?)(\d+)', text, re.IGNORECASE)
# Deduplicate
seen = []
for m in matches:
if m not in seen:
seen.append(m)
print(','.join(seen))
" 2>/dev/null)
ISSUE_COUNT=$(echo "$ALL_ISSUES" | tr ',' '\n' | grep -c '[0-9]' || echo 0)
ISSUE_NUM=$(echo "$ALL_ISSUES" | cut -d',' -f1)
if [ -z "$ISSUE_NUM" ]; then
echo "No linked issue — proceeding to dispatch"
echo "skip=false" >> $GITHUB_OUTPUT
exit 0
fi
# Block multi-bounty PRs (one PR = one bounty)
if [ "$ISSUE_COUNT" -gt 1 ]; then
echo "BLOCKED: PR #$PR_NUM references $ISSUE_COUNT bounty issues ($ALL_ISSUES)"
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\":\"❌ **Multiple Bounties Detected**\n\n@$PR_AUTHOR, this PR references **$ISSUE_COUNT bounty issues** (#$(echo $ALL_ISSUES | sed 's/,/, #/g')).\n\n**Each bounty must be submitted as a separate PR** — one PR per bounty issue. This ensures proper review scope, fair attribution, and clean merge history.\n\nPlease:\n1. Split your work into separate PRs, each referencing only one bounty issue\n2. Use \`Closes #N\` to link each PR to its specific bounty\n\n---\n*SolFoundry Review Bot*\"}"
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
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|issue|bounty)\s*:?\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|issue|bounty)\s*:?\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
else:
# Eligible by reputation — but T3 also requires approved claim
# Check if contributor is assigned to the bounty issue (claim approved)
issue_data = gh_get(f"repos/{repo}/issues/{issue_num}")
assignees = [a["login"].lower() for a in issue_data.get("assignees", [])]
if user.lower() not in assignees:
# Check issue comments for bot approval message as fallback
comments = gh_get(f"repos/{repo}/issues/{issue_num}/comments?per_page=100")
has_approval = False
for c in comments:
cbody = (c.get("body") or "")
cauthor = c.get("user", {}).get("login", "")
# Look for our bot's claim approval comment mentioning this user
if cauthor == "chronoeth-creator" and user.lower() in cbody.lower() and ("approved" in cbody.lower() or "assigned" in cbody.lower()):
has_approval = True
break
if not has_approval:
msg = (
f"⚠️ @{user} — **Tier 3 bounties require claiming before submitting.**\n\n"
f"You meet the reputation requirements ✅ but you haven't claimed this bounty yet.\n\n"
f"**To claim:** Comment on [issue #{issue_num}](https://github.com/{repo}/issues/{issue_num}) "
f"with why you're a good fit. A maintainer will review and assign you.\n\n"
f"Once assigned, resubmit your PR.\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
# Skip re-review if PR already has our APPROVED review
- name: Check if already approved
id: already_approved
if: steps.bounty_check.outputs.skip != 'true' && steps.tier_check.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }}
run: |
PR_NUM="${{ steps.bounty_check.outputs.pr_num }}"
APPROVED_REVIEWS=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}/reviews" \
--jq '[.[] | select(.user.login == "chronoeth-creator" and .state == "APPROVED")]' 2>/dev/null || echo "[]")
APPROVED=$(echo "$APPROVED_REVIEWS" | python3 -c "import sys,json; print(len(json.loads(sys.stdin.read())))" 2>/dev/null || echo 0)
# If new commits pushed after approval (synchronize event), dismiss the
# APPROVED review and force re-review — prevents bait-and-switch attacks
ACTION="${{ github.event.action }}"
if [ "$APPROVED" -gt "0" ] && [ "$ACTION" = "synchronize" ]; then
echo "⚠️ New commits pushed after approval — dismissing review and forcing re-review"
# Extract bounty issue number from PR body (escrow label lives on BOUNTY ISSUE, not PR)
PR_BODY_SYNC=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.body // ""' 2>/dev/null)
BOUNTY_NUM_SYNC=$(echo "$PR_BODY_SYNC" | python3 -c "
import sys, re
text = sys.stdin.read()
m = re.search(r'(?:closes|fixes|resolves|issue|bounty)\s*:?\s*#(\d+)', text, re.IGNORECASE)
print(m.group(1) if m else '')
" 2>/dev/null)
echo "Extracted bounty issue: ${BOUNTY_NUM_SYNC:-none}"
# Check if bot recently asked this contributor to rebase (friendly vs security messaging)
IS_REBASE="false"
if [ -n "$BOUNTY_NUM_SYNC" ]; then
RECENT_BOT_COMMENTS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUM}/comments?per_page=10" \
--jq '[.[] | select(.user.login == "chronoeth-creator") | .body] | join(" ")' 2>/dev/null || echo "")
if echo "$RECENT_BOT_COMMENTS" | grep -qi "rebase\|conflict\|merge conflict\|paid-pending-merge"; then
IS_REBASE="true"
echo "Detected rebase after conflict notification — using friendly messaging"
fi
fi
# Dismiss each APPROVED review
REVIEW_IDS=$(echo "$APPROVED_REVIEWS" | python3 -c "import sys,json; [print(r['id']) for r in json.loads(sys.stdin.read())]" 2>/dev/null)
if [ "$IS_REBASE" = "true" ]; then
DISMISS_MSG="Rebase detected — previous approval cleared for a quick re-review. Thanks for resolving conflicts!"
else
DISMISS_MSG="New commits pushed after approval — re-review required for security. Previous approval dismissed."
fi
for RID in $REVIEW_IDS; do
curl -sf -X PUT \
-H "Authorization: token $GH_TOKEN" \
-H "Content-Type: application/json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/${PR_NUM}/reviews/${RID}/dismissals" \
-d "{\"message\":\"$DISMISS_MSG\",\"event\":\"DISMISS\"}" || true
done
# Remove escrow lock from the BOUNTY ISSUE (not the PR — that was the bug)
if [ -n "$BOUNTY_NUM_SYNC" ]; then
curl -sf -X DELETE \
-H "Authorization: token $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/issues/${BOUNTY_NUM_SYNC}/labels/review-passed" 2>/dev/null || true
echo "Removed review-passed label from bounty issue #${BOUNTY_NUM_SYNC}"
else
echo "WARNING: Could not extract bounty issue — escrow label removal skipped"
fi
# Post comment — friendly for rebases, security warning for real pushes
if [ "$IS_REBASE" = "true" ]; then
COMMENT_BODY="🔄 **Rebase Detected**\n\nThanks for resolving the merge conflict! A quick re-review is running now.\n\nYour escrow lock has been temporarily cleared and will be re-set once the review passes again. No action needed from you.\n\n---\n*SolFoundry Review Bot*"
else
COMMENT_BODY="⚠️ **Review Invalidated**\n\nNew commits were pushed after approval. The previous approval has been dismissed and a fresh review will run.\n\nFor security, the escrow lock has been removed.\n\n---\n*SolFoundry Review Bot*"
fi
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\":\"${COMMENT_BODY}\"}" || true
# Notify owner on Telegram (this was a blind spot — owner had no idea approvals were being dismissed)
TELEGRAM_TOKEN="${{ secrets.SOLFOUNDRY_TELEGRAM_BOT_TOKEN }}"
TELEGRAM_CHAT="${{ secrets.SOLFOUNDRY_TELEGRAM_CHAT_ID }}"
if [ -n "$TELEGRAM_TOKEN" ] && [ -n "$TELEGRAM_CHAT" ]; then
PR_TITLE_SYNC=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.title' 2>/dev/null)
PR_AUTHOR_SYNC=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.user.login' 2>/dev/null)
if [ "$IS_REBASE" = "true" ]; then
TG_MSG="🔄 <b>Rebase Detected</b>%0A%0APR #${PR_NUM}: ${PR_TITLE_SYNC}%0Aby @${PR_AUTHOR_SYNC}%0A%0AContributor rebased after conflict. Re-review running. Escrow will re-lock if it passes."
else
TG_MSG="⚠️ <b>Approval Dismissed</b>%0A%0APR #${PR_NUM}: ${PR_TITLE_SYNC}%0Aby @${PR_AUTHOR_SYNC}%0A%0ANew commits pushed after approval. Review invalidated, re-review dispatched."
fi
curl -sf "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT}" \
-d "text=${TG_MSG}" \
-d "parse_mode=HTML" \
-d "disable_web_page_preview=true" > /dev/null 2>&1
fi
# Don't skip — force re-review
echo "skip=false" >> $GITHUB_OUTPUT
elif [ "$APPROVED" -gt "0" ]; then
echo "⏭️ PR #${PR_NUM} already approved — not dispatching re-review"
echo "skip=true" >> $GITHUB_OUTPUT
# Check if PR is now mergeable (conflict resolved after rebase)
MERGEABLE=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.mergeable // false' 2>/dev/null || echo "false")
PR_TITLE=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.title' 2>/dev/null || echo "Unknown")
PR_AUTHOR=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.user.login' 2>/dev/null || echo "unknown")
PR_URL="https://github.com/${{ github.repository }}/pull/${PR_NUM}"
if [ "$MERGEABLE" = "true" ]; then
# Skip notification if PR is already paid (bot already handled it)
PR_LABELS=$(gh api "repos/${{ github.repository }}/issues/${PR_NUM}/labels" --jq '.[].name' 2>/dev/null || echo "")
if echo "$PR_LABELS" | grep -q "^paid$"; then
echo "PR #${PR_NUM} already paid — skipping duplicate 'Ready to Merge' notification"
exit 0
fi
# Extract bounty info for payout context
PR_BODY=$(gh api "repos/${{ github.repository }}/pulls/${PR_NUM}" --jq '.body // ""' 2>/dev/null || echo "")
BOUNTY_NUM=$(echo "$PR_BODY" | python3 -c "
import sys, re
text = sys.stdin.read()
m = re.search(r'(?:closes|fixes|resolves|issue|bounty)\s*:?\s*#(\d+)', text, re.IGNORECASE)
print(m.group(1) if m else '')
" 2>/dev/null || echo "")
WALLET=$(echo "$PR_BODY" | python3 -c "
import sys, re
text = sys.stdin.read()
m = re.search(r'(?:wallet|address)[:\s]*\`?([1-9A-HJ-NP-Za-km-z]{32,44})\`?', text, re.IGNORECASE)
if not m:
m = re.search(r'\b([1-9A-HJ-NP-Za-km-z]{32,44})\b', text)
print(m.group(1) if m else 'not found')
" 2>/dev/null || echo "not found")
BOUNTY_INFO=""
if [ -n "$BOUNTY_NUM" ]; then
REWARD=$(gh api "repos/${{ github.repository }}/issues/${BOUNTY_NUM}" --jq '.body' 2>/dev/null | python3 -c "
import sys, re
text = sys.stdin.read()
m = re.search(r'(\d[\d,]*)\s*(?:\\\$FNDRY|FNDRY|\\\$)', text)
print(m.group(1) if m else 'unknown')
" 2>/dev/null || echo "unknown")
BOUNTY_INFO="Bounty: #${BOUNTY_NUM} (${REWARD} \$FNDRY)"
fi
# Send Telegram notification
TELEGRAM_TOKEN="${{ secrets.SOLFOUNDRY_TELEGRAM_BOT_TOKEN }}"
TELEGRAM_CHAT="${{ secrets.SOLFOUNDRY_TELEGRAM_CHAT_ID }}"
if [ -n "$TELEGRAM_TOKEN" ] && [ -n "$TELEGRAM_CHAT" ]; then
MSG="✅ <b>Ready to Merge & Pay Out</b>%0A%0APR #${PR_NUM}: ${PR_TITLE}%0Aby @${PR_AUTHOR}%0A${BOUNTY_INFO}%0AWallet: ${WALLET}%0A%0A🔗 ${PR_URL}%0A%0AReply <code>merge 620</code> to merge and trigger payout."
curl -sf "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT}" \
-d "text=${MSG}" \
-d "parse_mode=HTML" \
-d "disable_web_page_preview=true" > /dev/null 2>&1 || true
fi
fi
exit 0
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Trigger private review pipeline
if: steps.bounty_check.outputs.skip != 'true' && steps.tier_check.outputs.skip != 'true' && steps.already_approved.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}\", \"tier_validated\": true}}"
echo "✅ Review dispatched for PR #${PR_NUM}"