-
Notifications
You must be signed in to change notification settings - Fork 0
579 lines (488 loc) · 25.9 KB
/
pull-review.yml
File metadata and controls
579 lines (488 loc) · 25.9 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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
name: AI PR Review
on:
issue_comment:
types: [created]
jobs:
review:
name: Automated PR Review
if: >-
github.event.issue.pull_request &&
startsWith(github.event.comment.body, '/review') &&
(github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'COLLABORATOR')
runs-on: ubuntu-latest
concurrency:
group: pr-review-${{ github.event.issue.number }}
cancel-in-progress: true
timeout-minutes: 30
permissions:
contents: read
pull-requests: write
issues: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
# ── Provider configuration ─────────────────────────────
# Set via Settings → Secrets and variables → Actions → Variables:
# REVIEW_PROVIDER: openrouter | zai-coding-plan
# REVIEW_MODEL: stepfun/step-3.5-flash | glm-5
REVIEW_PROVIDER: ${{ vars.REVIEW_PROVIDER || 'openrouter' }}
REVIEW_MODEL: ${{ vars.REVIEW_MODEL || 'stepfun/step-3.5-flash' }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
steps:
- name: Update comment with status
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
gh api "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \
-f content='eyes'
gh api "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}" \
-X PATCH \
-f body="${COMMENT_BODY}
---
_AI review [started](${RUN_URL})._"
- name: Detect fast mode
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
REVIEW_COMMAND=$(echo "$COMMENT_BODY" | grep -m1 -oP '^\s*\K/\S+(\s+\{\{[^}]*\}\})?')
echo "REVIEW_COMMAND=$REVIEW_COMMAND" >> "$GITHUB_ENV"
if echo "$COMMENT_BODY" | grep -qP '^\s*/review_fast'; then
echo "FAST_MODE=true" >> "$GITHUB_ENV"
echo "Fast mode enabled — skipping raw review and combine step"
else
echo "FAST_MODE=false" >> "$GITHUB_ENV"
fi
- name: Parse provider/model override
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Check for {{provider, model}} or {{provider, model, small_model}} override syntax after /review or /review_fast command
if [[ "$COMMENT_BODY" =~ ^[[:space:]]*/review(_fast)?[[:space:]]+\{\{[[:space:]]*([^,]+)[[:space:]]*,[[:space:]]*([^,}]+)[[:space:]]*(,[[:space:]]*([^}]+)[[:space:]]*)?\}\} ]]; then
PROVIDER=$(echo "${BASH_REMATCH[2]}" | xargs)
MODEL=$(echo "${BASH_REMATCH[3]}" | xargs)
SMALL_MODEL=$(echo "${BASH_REMATCH[5]}" | xargs)
# Validate provider and models contain only safe characters
if [[ "$PROVIDER" =~ ^[a-zA-Z0-9_-]+$ ]] && [[ "$MODEL" =~ ^[a-zA-Z0-9_./-]+$ ]] && { [[ -z "$SMALL_MODEL" ]] || [[ "$SMALL_MODEL" =~ ^[a-zA-Z0-9_./-]+$ ]]; }; then
echo "Overriding provider to: $PROVIDER"
echo "Overriding model to: $MODEL"
echo "REVIEW_PROVIDER=$PROVIDER" >> "$GITHUB_ENV"
echo "REVIEW_MODEL=$MODEL" >> "$GITHUB_ENV"
if [[ -n "$SMALL_MODEL" ]]; then
echo "Overriding small model to: $SMALL_MODEL"
echo "REVIEW_SMALL_MODEL=$SMALL_MODEL" >> "$GITHUB_ENV"
fi
else
echo "::warning::Invalid provider or model format in override, using defaults"
fi
fi
- name: Resolve provider settings
run: |
case "$REVIEW_PROVIDER" in
openrouter)
{
echo "PROVIDER_API_URL=https://openrouter.ai/api/v1/chat/completions"
echo "PROVIDER_API_KEY=$OPENROUTER_API_KEY"
echo "OPENCODE_MODEL=openrouter/$REVIEW_MODEL"
echo "PROVIDER_DISPLAY=OpenRouter"
echo "PROVIDER_API_KEY_REF={env:OPENROUTER_API_KEY}"
} >> "$GITHUB_ENV"
;;
zai-coding-plan)
{
echo "PROVIDER_API_URL=https://api.z.ai/api/coding/paas/v4/chat/completions"
echo "PROVIDER_API_KEY=$ZAI_API_KEY"
echo "OPENCODE_MODEL=zai-coding-plan/$REVIEW_MODEL"
echo "PROVIDER_DISPLAY=Z.AI"
echo "PROVIDER_API_KEY_REF={env:ZAI_API_KEY}"
} >> "$GITHUB_ENV"
;;
*)
echo "::error::Unknown REVIEW_PROVIDER '$REVIEW_PROVIDER' (supported: openrouter, zai-coding-plan)"
exit 1
;;
esac
# Set SMALL_MODEL based on provider (with override support)
if [ -n "$REVIEW_SMALL_MODEL" ]; then
SMALL_MODEL="$REVIEW_SMALL_MODEL"
elif [ "$REVIEW_PROVIDER" = "zai-coding-plan" ]; then
SMALL_MODEL="glm-4.7"
else
SMALL_MODEL="google/gemini-3-flash-preview"
fi
# Prefix small model with provider
case "$REVIEW_PROVIDER" in
openrouter) echo "SMALL_MODEL=openrouter/$SMALL_MODEL" >> "$GITHUB_ENV" ;;
zai-coding-plan) echo "SMALL_MODEL=zai-coding-plan/$SMALL_MODEL" >> "$GITHUB_ENV" ;;
*) echo "SMALL_MODEL=zai-coding-plan/glm-4.7" >> "$GITHUB_ENV" ;;
esac
- name: Get PR number
id: pr-info
run: |
echo "number=${{ github.event.issue.number }}" >> "$GITHUB_OUTPUT"
- name: Get PR details
id: pr-details
run: |
gh api "/repos/${{ github.repository }}/pulls/${{ steps.pr-info.outputs.number }}" > pr_data.json
{
echo "head_ref=$(jq -r '.head.ref' pr_data.json)"
echo "head_sha=$(jq -r '.head.sha' pr_data.json)"
echo "base_ref=$(jq -r '.base.ref' pr_data.json)"
} >> "$GITHUB_OUTPUT"
jq -r '.body // ""' pr_data.json > /tmp/pr_body.txt
jq -r '.title // ""' pr_data.json > /tmp/pr_title.txt
- name: Check for linked issue
id: check-issue
run: |
ISSUE_NUM=$(grep -oiP '\b(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?|ref(?:erences?)?|see)\s*#(\d+)' /tmp/pr_body.txt | grep -oP '\d+' | head -1 || true)
if [ -z "$ISSUE_NUM" ]; then
echo "has_issue=false" >> "$GITHUB_OUTPUT"
else
echo "has_issue=true" >> "$GITHUB_OUTPUT"
echo "issue_number=$ISSUE_NUM" >> "$GITHUB_OUTPUT"
fi
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ steps.pr-details.outputs.head_sha }}
fetch-depth: 0
- name: Merge master into PR branch
env:
BASE_REF: ${{ steps.pr-details.outputs.base_ref }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git fetch origin "$BASE_REF"
git merge "origin/$BASE_REF" --no-edit -m "Merge $BASE_REF for review" || {
echo "::warning::Merge conflict detected, reviewing PR branch as-is"
git merge --abort
git reset --hard HEAD
}
- name: Generate diff
env:
BASE_REF: ${{ steps.pr-details.outputs.base_ref }}
run: |
git diff "origin/$BASE_REF" -U15 \
-- . \
':!package-lock.json' ':!**/package-lock.json' \
':!pnpm-lock.yaml' ':!**/pnpm-lock.yaml' \
':!yarn.lock' ':!**/yarn.lock' \
':!go.sum' ':!**/go.sum' \
':!*.min.js' ':!*.min.css' \
':!node_modules/**' ':!**/node_modules/**' \
':!vendor/**' ':!**/vendor/**' \
':!dist/**' ':!**/dist/**' \
':!build/**' ':!**/build/**' \
':!*.svg' ':!**/*.svg' \
':!*.png' ':!**/*.png' \
':!*.jpg' ':!**/*.jpg' ':!*.jpeg' ':!**/*.jpeg' \
':!*.gif' ':!**/*.gif' \
':!*.ico' ':!**/*.ico' \
':!*.webp' ':!**/*.webp' \
':!*.woff' ':!**/*.woff' ':!*.woff2' ':!**/*.woff2' \
':!*.ttf' ':!**/*.ttf' ':!*.eot' ':!**/*.eot' \
> /tmp/pr_diff.txt
MAX_CHARS=100000
DIFF_SIZE=$(wc -c < /tmp/pr_diff.txt)
if [ "$DIFF_SIZE" -gt "$MAX_CHARS" ]; then
head -c "$MAX_CHARS" /tmp/pr_diff.txt > /tmp/pr_diff_truncated.txt
printf '\n\n... [diff truncated — %s bytes total, showing first %s]\n' "$DIFF_SIZE" "$MAX_CHARS" >> /tmp/pr_diff_truncated.txt
mv /tmp/pr_diff_truncated.txt /tmp/pr_diff.txt
fi
echo "Diff size: $DIFF_SIZE bytes"
echo "::group::PR diff"
cat /tmp/pr_diff.txt
echo "::endgroup::"
- name: Generate helpful context
env:
HAS_ISSUE: ${{ steps.check-issue.outputs.has_issue }}
ISSUE_NUMBER: ${{ steps.check-issue.outputs.issue_number }}
run: |
# Start with PR title, then always include PR description
printf '<pr_title>\n%s\n</pr_title>\n' "$(cat /tmp/pr_title.txt)" > /tmp/helpful_context.txt
printf '<pr_description>\n%s\n</pr_description>\n' "$(cat /tmp/pr_body.txt)" >> /tmp/helpful_context.txt
if [ "$HAS_ISSUE" = "true" ]; then
# Fetch the issue
if ! gh api "/repos/$GITHUB_REPOSITORY/issues/$ISSUE_NUMBER" > issue_data.json 2>/dev/null; then
echo "::warning::Could not fetch issue #$ISSUE_NUMBER"
echo "<issue_summary>Issue context unavailable.</issue_summary>" >> /tmp/helpful_context.txt
exit 0
fi
# Build issue conversation text (title + body + all comments)
{
echo "## Issue #$ISSUE_NUMBER: $(jq -r '.title // ""' issue_data.json)"
echo ""
jq -r '.body // ""' issue_data.json
echo ""
} > /tmp/issue_conversation.txt
# Fetch all comments on the issue
gh api "/repos/$GITHUB_REPOSITORY/issues/$ISSUE_NUMBER/comments" --paginate \
| jq -s 'add // []' > issue_comments.json 2>/dev/null || echo "[]" > issue_comments.json
COMMENT_COUNT=$(jq 'length' issue_comments.json 2>/dev/null || echo "0")
if [ "$COMMENT_COUNT" -gt 0 ]; then
{
echo "---"
echo ""
echo "### Comments"
echo ""
jq -r '.[] | "**\(.user.login)** (\(.created_at)):\n\(.body)\n\n---\n"' issue_comments.json
} >> /tmp/issue_conversation.txt
fi
# Summarize the issue conversation via LLM
jq -n \
--arg model "$REVIEW_MODEL" \
--rawfile conversation /tmp/issue_conversation.txt \
'{
model: $model,
messages: [{role: "user", content: ("Summarize the following GitHub issue conversation. Focus on: 1) What the issue is about, 2) Key discussion points, 3) Any final decision or conclusion reached about what to do (if one exists). Be concise but thorough.\n\n" + $conversation)}]
}' > /tmp/context_payload.json
HTTP_CODE=$(curl -s -w '%{http_code}' \
"$PROVIDER_API_URL" \
-H "Authorization: Bearer $PROVIDER_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/context_payload.json \
-o /tmp/context_response.json)
if [ "$HTTP_CODE" -eq 200 ] && jq -e '.choices[0].message.content' /tmp/context_response.json > /dev/null 2>&1; then
SUMMARY=$(jq -r '.choices[0].message.content' /tmp/context_response.json)
else
echo "::warning::Issue summarization failed (HTTP $HTTP_CODE), using raw issue text"
SUMMARY=$(cat /tmp/issue_conversation.txt)
fi
printf '<issue_summary>\n%s\n</issue_summary>\n' "$SUMMARY" >> /tmp/helpful_context.txt
fi
echo "::group::Helpful context"
cat /tmp/helpful_context.txt
echo "::endgroup::"
- name: Build review prompts
run: |
cat > /tmp/raw_prompt.txt << 'PROMPT_END'
Attached are a set of changes to a Go project that uses Vuejs. It is based on Gitea and has been modified into a type of online encyclopedia, where each subject can have multiple articles, and each article is represented by a git repository with a primary markdown file that contains the article. Articles can be forked, forming a tree of articles under the subject.
Please have a look at these changes and thoroughly check it for any bugs or security issues. Also check if anything could be improved through simplification. When providing feedback, be specific and quote the concrete lines that are problematic, and then give your code improvement suggestions complete with actual code. When replying, always use Markdown and Markdown code fences to quote any lines. Avoid commenting on code that has no issues (no "you did a good job here!" fluff).
When referencing sections of code, always mention the specific lines being referenced in the following manner to make it easy for developers to know where to look:
- `file.js:841`
- `file.vue:50-70`
For each issue you identify, give it a rating of 🔴 for high importance & high confidence, 🟡 for medium importance & high confidence, and ⚪️ for issues of either lower confidence or lower importance. Prioritize finding 🔴 and 🟡 issues. Give your feedback in order of importance from most to least.
Also, for each issue, immediately after the issue heading please add these two checkboxes:
- [ ] Addressed
- [ ] Dismissed
If no problems are found, state that and do nothing else.
### Helpful Context
PROMPT_END
{
cat /tmp/helpful_context.txt
cat << 'SECTION_END'
### Changes to Review
```diff
SECTION_END
cat /tmp/pr_diff.txt
printf '\n```\n'
} >> /tmp/raw_prompt.txt
cat > /tmp/agentic_prompt.txt << 'PROMPT_END'
Attached are a set of changes to a Go project that uses Vuejs. It is based on Gitea and has been modified into a type of online encyclopedia, where each subject can have multiple articles, and each article is represented by a git repository with a primary markdown file that contains the article. Articles can be forked, forming a tree of articles under the subject.
Please have a look at these changes and thoroughly check it for any bugs or security issues. Also check if anything could be improved through simplification. When providing feedback, be specific and quote the concrete lines that are problematic, and then give your code improvement suggestions complete with actual code. When replying, always use Markdown and Markdown code fences to quote any lines. Avoid commenting on code that has no issues (no "you did a good job here!" fluff).
When referencing sections of code, always mention the specific lines being referenced in the following manner to make it easy for developers to know where to look:
- `file.js:841`
- `file.vue:50-70`
For each issue you identify, give it a rating of 🔴 for high importance & high confidence, 🟡 for medium importance & high confidence, and ⚪️ for issues of either lower confidence or lower importance. Prioritize finding 🔴 and 🟡 issues. Give your feedback in order of importance from most to least and number the issues sequentially in their headings.
Also, for each issue, immediately after the issue heading please add these two checkboxes:
- [ ] Addressed
- [ ] Dismissed
You have full access to all of the source code. You can query different parts of the project if you need additional context to help with your review. If you have access to a subagent tool please make use of it when investigating specific questions about the codebase (e.g. "where are all the locations where this function is called?", etc.) to help conserve on tokens. Pay attention to any parts of the project that might break or conflict because of the changes, or any bits that are no longer relevant and should be removed as a result of the changes.
DO NOT make any edits or modifications to the code! DO NOT modify any files! Just output your review. If no problems are found, state that and do nothing else.
### Helpful Context
PROMPT_END
{
cat /tmp/helpful_context.txt
cat << 'SECTION_END'
### Changes to Review
```diff
SECTION_END
cat /tmp/pr_diff.txt
printf '\n```\n'
} >> /tmp/agentic_prompt.txt
- name: Check prompt size
id: size-check
run: |
MAX_TOKEN_CHARS=260000
PROMPT_SIZE=$(wc -c < /tmp/raw_prompt.txt)
echo "Prompt size: $PROMPT_SIZE chars (~$((PROMPT_SIZE / 4)) tokens)"
if [ "$PROMPT_SIZE" -gt "$MAX_TOKEN_CHARS" ]; then
echo "too_large=true" >> "$GITHUB_OUTPUT"
else
echo "too_large=false" >> "$GITHUB_OUTPUT"
fi
- name: Post size limit comment
if: steps.size-check.outputs.too_large == 'true'
run: |
PROMPT_SIZE=$(wc -c < /tmp/raw_prompt.txt)
TOKEN_EST=$((PROMPT_SIZE / 4))
gh pr comment "${{ steps.pr-info.outputs.number }}" \
--repo "${{ github.repository }}" \
--body "**Automated Review Notice**
This PR exceeds the 260k character limit (~${TOKEN_EST} tokens estimated). The review cannot be performed automatically.
Please break the PR into smaller pieces, or request a manual review.
_[🔧 Workflow Run](${RUN_URL})_"
- name: Run raw review
if: steps.size-check.outputs.too_large == 'false' && env.FAST_MODE != 'true'
run: |
jq -n \
--arg model "$REVIEW_MODEL" \
--rawfile prompt /tmp/raw_prompt.txt \
'{
model: $model,
messages: [{role: "user", content: $prompt}]
}' > /tmp/raw_payload.json
HTTP_CODE=$(curl -s -w '%{http_code}' \
"$PROVIDER_API_URL" \
-H "Authorization: Bearer $PROVIDER_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/raw_payload.json \
-o /tmp/raw_response.json)
RAW_REVIEW_OK=false
if [ "$HTTP_CODE" -ne 200 ]; then
echo "::warning::Raw review API call failed with HTTP $HTTP_CODE"
echo "Raw review unavailable (API returned HTTP $HTTP_CODE)." > /tmp/raw_review.txt
elif jq -e '.choices[0].message.content' /tmp/raw_response.json > /dev/null 2>&1; then
jq -r '.choices[0].message.content' /tmp/raw_response.json > /tmp/raw_review.txt
RAW_REVIEW_OK=true
else
echo "::warning::Raw review returned unexpected response"
echo "Raw review unavailable (unexpected API response)." > /tmp/raw_review.txt
fi
echo "Raw review complete ($(wc -c < /tmp/raw_review.txt) bytes)"
echo "RAW_REVIEW_OK=${RAW_REVIEW_OK:-false}" >> "$GITHUB_ENV"
echo "::group::Raw LLM review"
cat /tmp/raw_review.txt
echo "::endgroup::"
- name: Install opencode
if: steps.size-check.outputs.too_large == 'false'
run: curl -fsSL https://opencode.ai/install | bash
- name: Configure opencode
if: steps.size-check.outputs.too_large == 'false'
run: |
mkdir -p ~/.config/opencode
jq -n \
--arg provider "$REVIEW_PROVIDER" \
--arg api_key_ref "$PROVIDER_API_KEY_REF" \
'{
"$schema": "https://opencode.ai/config.json",
"provider": {
($provider): {
"options": { "apiKey": $api_key_ref }
}
}
}' > ~/.config/opencode/opencode.json
- name: Run agentic review
if: steps.size-check.outputs.too_large == 'false'
env:
OPENCODE_PERMISSION: '{ "edit": "deny", "write": "deny", "bash": "deny", "read": "allow", "grep": "allow", "glob": "allow", "list": "allow", "task": "allow", "todowrite": "allow" }'
run: |
# TODO: implement $SMALL_MODEL for opencode
# NOTE: opencode has a bug when switching to small models: https://github.com/anomalyco/opencode/issues/6636
# To configure subagents with a specific model you need to create one as a file first
# - https://opencode.ai/docs/agents/#model
# - https://opencode.ai/docs/agents/#examples
opencode run -m "$OPENCODE_MODEL" < /tmp/agentic_prompt.txt > /tmp/agentic_review.txt 2>/tmp/opencode_stderr.txt || {
echo "::warning::Agentic review via opencode failed"
cat /tmp/opencode_stderr.txt >&2
echo "Agentic review unavailable (opencode encountered an error)." > /tmp/agentic_review.txt
}
echo "Agentic review complete ($(wc -c < /tmp/agentic_review.txt) bytes)"
echo "::group::Agentic (opencode) review"
cat /tmp/agentic_review.txt
echo "::endgroup::"
- name: Combine reviews
if: steps.size-check.outputs.too_large == 'false' && env.FAST_MODE != 'true'
run: |
cat > /tmp/combine_prompt.txt << 'COMBINE_HEADER'
You are given two code reviews of the same PR. Combine them into a single comprehensive review:
1. De-duplicate issues that appear in both reviews (keep the better description)
2. Sort all issues by priority: 🔴 (high importance & high confidence) first, then 🟡 (medium), then ⚪️ (low)
3. Make sure the issue headings are all numbered sequentially, starting from 1, and preserve the priority indicator in the heading
4. The Addressed/Dismissed checkboxes in the issues should immediately follow the heading for each issue
5. Preserve specific code references (file:line) and code suggestions
6. Do not place a header like `# Combined Code Review` at the top of the combined review, just immediately output the issues (if there are any)
7. If there are no issues, state that and do nothing else.
Output ONLY the combined review in Markdown. No preamble about the combining process.
---
## Raw Review
COMBINE_HEADER
{
cat /tmp/raw_review.txt
cat << 'COMBINE_SEPARATOR'
---
## Agentic Review
COMBINE_SEPARATOR
cat /tmp/agentic_review.txt
} >> /tmp/combine_prompt.txt
jq -n \
--arg model "$REVIEW_MODEL" \
--rawfile prompt /tmp/combine_prompt.txt \
'{
model: $model,
messages: [{role: "user", content: $prompt}]
}' > /tmp/combine_payload.json
HTTP_CODE=$(curl -s -w '%{http_code}' \
"$PROVIDER_API_URL" \
-H "Authorization: Bearer $PROVIDER_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/combine_payload.json \
-o /tmp/combine_response.json)
if [ "$HTTP_CODE" -ne 200 ]; then
echo "::warning::Combine step failed with HTTP $HTTP_CODE, falling back to raw review"
cp /tmp/raw_review.txt /tmp/final_review.txt
if [ "$RAW_REVIEW_OK" = "true" ]; then
echo "REVIEW_TYPE=Raw" >> "$GITHUB_ENV"
else
echo "REVIEW_TYPE=Error" >> "$GITHUB_ENV"
fi
elif jq -e '.choices[0].message.content' /tmp/combine_response.json > /dev/null 2>&1; then
jq -r '.choices[0].message.content' /tmp/combine_response.json > /tmp/final_review.txt
echo "REVIEW_TYPE=Combined" >> "$GITHUB_ENV"
else
echo "::warning::Combine step returned unexpected response, falling back to raw review"
cp /tmp/raw_review.txt /tmp/final_review.txt
if [ "$RAW_REVIEW_OK" = "true" ]; then
echo "REVIEW_TYPE=Raw" >> "$GITHUB_ENV"
else
echo "REVIEW_TYPE=Error" >> "$GITHUB_ENV"
fi
fi
- name: Use agentic review as final (fast mode)
if: steps.size-check.outputs.too_large == 'false' && env.FAST_MODE == 'true'
run: |
cp /tmp/agentic_review.txt /tmp/final_review.txt
echo "REVIEW_TYPE=Agentic" >> "$GITHUB_ENV"
- name: Post review comment
if: steps.size-check.outputs.too_large == 'false'
run: |
{
echo "## Advanced AI Review"
echo ""
echo "- Type: ${REVIEW_TYPE} (opencode)"
echo "- Model: ${REVIEW_MODEL}"
echo ""
echo "<details>"
echo "<summary>Click to expand review</summary>"
echo ""
cat /tmp/final_review.txt
echo ""
echo "</details>"
echo ""
echo "---"
echo "*Review [generated](${RUN_URL}) using \`${REVIEW_MODEL}\` via ${PROVIDER_DISPLAY}. Comment \`${REVIEW_COMMAND}\` to re-run.*"
} > /tmp/review_comment.txt
gh pr comment "${{ steps.pr-info.outputs.number }}" \
--repo "${{ github.repository }}" \
--body-file /tmp/review_comment.txt
- name: Post failure comment
if: failure() && steps.pr-info.outputs.number
run: |
gh pr comment "${{ steps.pr-info.outputs.number }}" \
--repo "${{ github.repository }}" \
--body "## Advanced AI Review
- Type: Error
- Model: ${REVIEW_MODEL}
The review workflow failed. Check the [workflow run](${RUN_URL}) for details. Comment \`${REVIEW_COMMAND}\` to re-run."