Skip to content

Commit 09ce7ca

Browse files
authored
feat: implement new github app (#27)
1 parent cd526a9 commit 09ce7ca

File tree

8 files changed

+570
-84
lines changed

8 files changed

+570
-84
lines changed

.github/workflows/pr-describe.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ jobs:
1212
# Only run if comment contains /describe and is on a PR
1313
if: ${{ (github.event.issue.pull_request && contains(github.event.comment.body, '/describe')) }}
1414
runs-on: ubuntu-latest
15+
env:
16+
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
1517
permissions:
1618
contents: read
1719
pull-requests: write
@@ -20,10 +22,20 @@ jobs:
2022
- name: Check out Git repository
2123
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
2224

25+
# Generate GitHub App token so actions appear as the custom app (optional - falls back to github.token)
26+
- name: Get GitHub App token
27+
id: app-token
28+
if: env.HAS_APP_SECRETS == 'true'
29+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
30+
with:
31+
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
32+
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
33+
2334
- name: Validate PR and add reaction
2435
id: validate_pr
2536
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
2637
with:
38+
github-token: ${{ steps.app-token.outputs.token || github.token }}
2739
script: |
2840
const prNumber = context.issue.number;
2941
const commentId = context.payload.comment.id;
@@ -54,6 +66,7 @@ jobs:
5466
id: pr_details
5567
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
5668
with:
69+
github-token: ${{ steps.app-token.outputs.token || github.token }}
5770
script: |
5871
const fs = require('fs');
5972
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
@@ -144,12 +157,14 @@ jobs:
144157
**Diff:**
145158
$(cat pr.diff)
146159
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
160+
github-token: ${{ steps.app-token.outputs.token || github.token }}
147161
timeout: 300000 # 5 minutes
148162

149163
- name: Update PR description
150164
if: ${{ steps.generate.conclusion == 'success' }}
151165
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
152166
with:
167+
github-token: ${{ steps.app-token.outputs.token || github.token }}
153168
script: |
154169
const fs = require('fs');
155170
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
@@ -172,6 +187,7 @@ jobs:
172187
if: ${{ steps.generate.conclusion == 'success' }}
173188
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
174189
with:
190+
github-token: ${{ steps.app-token.outputs.token || github.token }}
175191
script: |
176192
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
177193
@@ -186,6 +202,7 @@ jobs:
186202
if: ${{ failure() && steps.generate.conclusion != 'success' }}
187203
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
188204
with:
205+
github-token: ${{ steps.app-token.outputs.token || github.token }}
189206
script: |
190207
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
191208
@@ -200,6 +217,7 @@ jobs:
200217
if: always()
201218
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
202219
with:
220+
github-token: ${{ steps.app-token.outputs.token || github.token }}
203221
script: |
204222
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
205223
const title = '${{ steps.pr_details.outputs.title }}';

.github/workflows/review-pr.yml

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
# Reusable workflow for AI-powered PR reviews
2+
# Usage:
3+
# name: PR Review
4+
# on:
5+
# issue_comment:
6+
# types: [created]
7+
# pull_request_review_comment:
8+
# types: [created]
9+
# pull_request_target:
10+
# types: [ready_for_review, opened]
11+
#
12+
# permissions:
13+
# contents: read
14+
# pull-requests: write
15+
# issues: write
16+
#
17+
# jobs:
18+
# review:
19+
# uses: docker/cagent-action/.github/workflows/review-pr.yml@latest
20+
# secrets: inherit
21+
22+
name: PR Review
23+
24+
on:
25+
workflow_call:
26+
inputs:
27+
pr-number:
28+
description: "Pull request number (auto-detected if not provided)"
29+
required: false
30+
type: string
31+
default: ""
32+
comment-id:
33+
description: "Comment ID for reactions (auto-detected if not provided)"
34+
required: false
35+
type: string
36+
default: ""
37+
additional-prompt:
38+
description: "Additional instructions for the review"
39+
required: false
40+
type: string
41+
default: ""
42+
model:
43+
description: "Model to use (e.g., anthropic/claude-sonnet-4-5)"
44+
required: false
45+
type: string
46+
default: ""
47+
cagent-version:
48+
description: "Version of cagent to use"
49+
required: false
50+
type: string
51+
default: "v1.19.7"
52+
auto-review-org:
53+
description: "Organization to check membership for auto-reviews"
54+
required: false
55+
type: string
56+
default: "docker"
57+
secrets:
58+
ORG_MEMBERSHIP_TOKEN:
59+
description: "PAT with read:org scope to check org membership for auto-reviews"
60+
required: false
61+
ANTHROPIC_API_KEY:
62+
description: "Anthropic API key (at least one API key required)"
63+
required: false
64+
OPENAI_API_KEY:
65+
description: "OpenAI API key (at least one API key required)"
66+
required: false
67+
GOOGLE_API_KEY:
68+
description: "Google API key (at least one API key required)"
69+
required: false
70+
AWS_BEARER_TOKEN_BEDROCK:
71+
description: "AWS Bearer token for Bedrock (at least one API key required)"
72+
required: false
73+
XAI_API_KEY:
74+
description: "xAI API key for Grok (at least one API key required)"
75+
required: false
76+
NEBIUS_API_KEY:
77+
description: "Nebius API key (at least one API key required)"
78+
required: false
79+
MISTRAL_API_KEY:
80+
description: "Mistral API key (at least one API key required)"
81+
required: false
82+
CAGENT_REVIEWER_APP_ID:
83+
description: "GitHub App ID for reviewer identity"
84+
required: false
85+
CAGENT_REVIEWER_APP_PRIVATE_KEY:
86+
description: "GitHub App private key"
87+
required: false
88+
outputs:
89+
exit-code:
90+
description: "Exit code from the review"
91+
value: ${{ jobs.auto-review.outputs.exit-code || jobs.manual-review.outputs.exit-code }}
92+
93+
permissions:
94+
contents: read
95+
pull-requests: write
96+
issues: write
97+
98+
jobs:
99+
# ==========================================================================
100+
# AUTOMATIC REVIEW FOR ORG MEMBERS
101+
# Triggers when a PR is marked ready for review or opened (non-draft)
102+
# Only runs for members of the configured org (supports fork-based workflow)
103+
# ==========================================================================
104+
auto-review:
105+
if: |
106+
github.event_name == 'pull_request_target' &&
107+
!github.event.pull_request.draft
108+
runs-on: ubuntu-latest
109+
env:
110+
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
111+
outputs:
112+
exit-code: ${{ steps.run-review.outputs.exit-code }}
113+
114+
steps:
115+
- name: Check if PR author is org member
116+
id: membership
117+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
118+
with:
119+
github-token: ${{ secrets.ORG_MEMBERSHIP_TOKEN }}
120+
script: |
121+
const org = '${{ inputs.auto-review-org }}';
122+
const username = context.payload.pull_request.user.login;
123+
124+
try {
125+
await github.rest.orgs.checkMembershipForUser({
126+
org: org,
127+
username: username
128+
});
129+
core.setOutput('is_member', 'true');
130+
console.log(`✅ ${username} is a ${org} org member - proceeding with auto-review`);
131+
} catch (error) {
132+
if (error.status === 404 || error.status === 302) {
133+
core.setOutput('is_member', 'false');
134+
console.log(`⏭️ ${username} is not a ${org} org member - skipping auto-review`);
135+
} else if (error.status === 401) {
136+
core.setFailed(
137+
'❌ ORG_MEMBERSHIP_TOKEN secret is missing or invalid.\n\n' +
138+
`This secret is required to check ${org} org membership for auto-reviews.\n\n` +
139+
'To fix this:\n' +
140+
'1. Create a classic PAT with read:org scope at https://github.com/settings/tokens/new\n' +
141+
'2. Add it as an org secret named ORG_MEMBERSHIP_TOKEN'
142+
);
143+
} else {
144+
core.setFailed(`Failed to check org membership: ${error.message}`);
145+
}
146+
}
147+
148+
# Safe to checkout PR head because review-pr only READS files (no code execution)
149+
- name: Checkout PR head
150+
if: steps.membership.outputs.is_member == 'true'
151+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
152+
with:
153+
fetch-depth: 0
154+
ref: refs/pull/${{ github.event.pull_request.number }}/head
155+
156+
# Generate GitHub App token for custom app identity (optional - falls back to github.token)
157+
- name: Generate GitHub App token
158+
if: steps.membership.outputs.is_member == 'true' && env.HAS_APP_SECRETS == 'true'
159+
id: app-token
160+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
161+
with:
162+
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
163+
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
164+
165+
- name: Run PR Review
166+
if: steps.membership.outputs.is_member == 'true'
167+
id: run-review
168+
uses: docker/cagent-action/review-pr@latest
169+
with:
170+
pr-number: ${{ inputs.pr-number || github.event.pull_request.number }}
171+
additional-prompt: ${{ inputs.additional-prompt }}
172+
model: ${{ inputs.model }}
173+
cagent-version: ${{ inputs.cagent-version }}
174+
github-token: ${{ steps.app-token.outputs.token || github.token }}
175+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
176+
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
177+
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
178+
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
179+
xai-api-key: ${{ secrets.XAI_API_KEY }}
180+
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
181+
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
182+
183+
# ==========================================================================
184+
# MANUAL REVIEW PIPELINE
185+
# Triggers when someone comments /review on a PR
186+
# ==========================================================================
187+
manual-review:
188+
if: github.event.issue.pull_request && contains(github.event.comment.body, '/review')
189+
runs-on: ubuntu-latest
190+
env:
191+
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
192+
outputs:
193+
exit-code: ${{ steps.run-review.outputs.exit-code }}
194+
195+
steps:
196+
# Checkout PR head (not default branch)
197+
# Note: Authorization is handled by the composite action's built-in check
198+
- name: Checkout PR head
199+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
200+
with:
201+
fetch-depth: 0
202+
ref: refs/pull/${{ github.event.issue.number }}/head
203+
204+
# Generate GitHub App token for custom app identity (optional - falls back to github.token)
205+
- name: Generate GitHub App token
206+
if: env.HAS_APP_SECRETS == 'true'
207+
id: app-token
208+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
209+
with:
210+
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
211+
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
212+
213+
- name: Run PR Review
214+
id: run-review
215+
uses: docker/cagent-action/review-pr@latest
216+
with:
217+
pr-number: ${{ inputs.pr-number || github.event.issue.number }}
218+
comment-id: ${{ inputs.comment-id || github.event.comment.id }}
219+
additional-prompt: ${{ inputs.additional-prompt }}
220+
model: ${{ inputs.model }}
221+
cagent-version: ${{ inputs.cagent-version }}
222+
github-token: ${{ steps.app-token.outputs.token || github.token }}
223+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
224+
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
225+
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
226+
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
227+
xai-api-key: ${{ secrets.XAI_API_KEY }}
228+
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
229+
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
230+
231+
# ==========================================================================
232+
# LEARN FROM FEEDBACK
233+
# Processes replies to agent review comments for continuous improvement
234+
# ==========================================================================
235+
learn-from-feedback:
236+
if: github.event_name == 'pull_request_review_comment' && github.event.comment.in_reply_to_id
237+
runs-on: ubuntu-latest
238+
env:
239+
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
240+
241+
steps:
242+
- name: Checkout repository
243+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
244+
245+
# Generate GitHub App token for custom app identity (optional - falls back to github.token)
246+
- name: Generate GitHub App token
247+
if: env.HAS_APP_SECRETS == 'true'
248+
id: app-token
249+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
250+
with:
251+
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
252+
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
253+
254+
- name: Learn from user feedback
255+
uses: docker/cagent-action/review-pr/learn@latest
256+
with:
257+
github-token: ${{ steps.app-token.outputs.token || github.token }}
258+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
259+
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
260+
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
261+
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
262+
xai-api-key: ${{ secrets.XAI_API_KEY }}
263+
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
264+
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}

.github/workflows/security-scan.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
security-scan:
2020
name: Security Scan with cagent
2121
runs-on: ubuntu-latest
22+
env:
23+
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
2224
permissions:
2325
contents: read
2426
issues: write
@@ -28,6 +30,15 @@ jobs:
2830
with:
2931
fetch-depth: 0 # Need full history to get commits from past week
3032

33+
# Generate GitHub App token so issues appear as the custom app (optional - falls back to github.token)
34+
- name: Get GitHub App token
35+
id: app-token
36+
if: env.HAS_APP_SECRETS == 'true'
37+
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
38+
with:
39+
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
40+
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}
41+
3142
- name: Get commits from past week
3243
id: commits
3344
env:
@@ -234,7 +245,7 @@ jobs:
234245
- name: Create security issue
235246
if: steps.check-issues.outputs.has_issues == 'true'
236247
env:
237-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
248+
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
238249
OUTPUT_FILE: ${{ steps.scan.outputs.output-file }}
239250
COMMIT_COUNT: ${{ steps.commits.outputs.commit_count }}
240251
DAYS_BACK: ${{ inputs.days_back || '7' }}

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ jobs:
105105
fi
106106
107107
- name: Checkout code
108+
if: steps.fork-check.outputs.is_fork != 'true'
108109
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
109110

110111
- name: Run test
@@ -163,7 +164,6 @@ jobs:
163164
echo "✅ Found agent response content"
164165
echo "Response preview: $(echo "$CONTENT" | head -n 1)"
165166
166-
167167
test-invalid-agent:
168168
name: Invalid Agent Test
169169
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)