Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/workflows/pr-describe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
# Only run if comment contains /describe and is on a PR
if: ${{ (github.event.issue.pull_request && contains(github.event.comment.body, '/describe')) }}
runs-on: ubuntu-latest
env:
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
permissions:
contents: read
pull-requests: write
Expand All @@ -20,10 +22,20 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0

# Generate GitHub App token so actions appear as the custom app (optional - falls back to github.token)
- name: Get GitHub App token
id: app-token
if: env.HAS_APP_SECRETS == 'true'
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
with:
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}

- name: Validate PR and add reaction
id: validate_pr
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const prNumber = context.issue.number;
const commentId = context.payload.comment.id;
Expand Down Expand Up @@ -54,6 +66,7 @@ jobs:
id: pr_details
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const fs = require('fs');
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
Expand Down Expand Up @@ -144,12 +157,14 @@ jobs:
**Diff:**
$(cat pr.diff)
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
github-token: ${{ steps.app-token.outputs.token || github.token }}
timeout: 300000 # 5 minutes

- name: Update PR description
if: ${{ steps.generate.conclusion == 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const fs = require('fs');
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
Expand All @@ -172,6 +187,7 @@ jobs:
if: ${{ steps.generate.conclusion == 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};

Expand All @@ -186,6 +202,7 @@ jobs:
if: ${{ failure() && steps.generate.conclusion != 'success' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};

Expand All @@ -200,6 +217,7 @@ jobs:
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
script: |
const prNumber = ${{ steps.validate_pr.outputs.pr_number }};
const title = '${{ steps.pr_details.outputs.title }}';
Expand Down
264 changes: 264 additions & 0 deletions .github/workflows/review-pr.yml
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost entirely copied and pasted from the review workflow in cagent.

Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
# Reusable workflow for AI-powered PR reviews
# Usage:
# name: PR Review
# on:
# issue_comment:
# types: [created]
# pull_request_review_comment:
# types: [created]
# pull_request_target:
# types: [ready_for_review, opened]
#
# permissions:
# contents: read
# pull-requests: write
# issues: write
#
# jobs:
# review:
# uses: docker/cagent-action/.github/workflows/review-pr.yml@latest
# secrets: inherit

name: PR Review

on:
workflow_call:
inputs:
pr-number:
description: "Pull request number (auto-detected if not provided)"
required: false
type: string
default: ""
comment-id:
description: "Comment ID for reactions (auto-detected if not provided)"
required: false
type: string
default: ""
additional-prompt:
description: "Additional instructions for the review"
required: false
type: string
default: ""
model:
description: "Model to use (e.g., anthropic/claude-sonnet-4-5)"
required: false
type: string
default: ""
cagent-version:
description: "Version of cagent to use"
required: false
type: string
default: "v1.19.7"
auto-review-org:
description: "Organization to check membership for auto-reviews"
required: false
type: string
default: "docker"
secrets:
ORG_MEMBERSHIP_TOKEN:
description: "PAT with read:org scope to check org membership for auto-reviews"
required: false
ANTHROPIC_API_KEY:
description: "Anthropic API key (at least one API key required)"
required: false
OPENAI_API_KEY:
description: "OpenAI API key (at least one API key required)"
required: false
GOOGLE_API_KEY:
description: "Google API key (at least one API key required)"
required: false
AWS_BEARER_TOKEN_BEDROCK:
description: "AWS Bearer token for Bedrock (at least one API key required)"
required: false
XAI_API_KEY:
description: "xAI API key for Grok (at least one API key required)"
required: false
NEBIUS_API_KEY:
description: "Nebius API key (at least one API key required)"
required: false
MISTRAL_API_KEY:
description: "Mistral API key (at least one API key required)"
required: false
CAGENT_REVIEWER_APP_ID:
description: "GitHub App ID for reviewer identity"
required: false
CAGENT_REVIEWER_APP_PRIVATE_KEY:
description: "GitHub App private key"
required: false
outputs:
exit-code:
description: "Exit code from the review"
value: ${{ jobs.auto-review.outputs.exit-code || jobs.manual-review.outputs.exit-code }}

permissions:
contents: read
pull-requests: write
issues: write

jobs:
# ==========================================================================
# AUTOMATIC REVIEW FOR ORG MEMBERS
# Triggers when a PR is marked ready for review or opened (non-draft)
# Only runs for members of the configured org (supports fork-based workflow)
# ==========================================================================
auto-review:
if: |
github.event_name == 'pull_request_target' &&
!github.event.pull_request.draft
runs-on: ubuntu-latest
env:
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
outputs:
exit-code: ${{ steps.run-review.outputs.exit-code }}

steps:
- name: Check if PR author is org member
id: membership
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
github-token: ${{ secrets.ORG_MEMBERSHIP_TOKEN }}
script: |
const org = '${{ inputs.auto-review-org }}';
const username = context.payload.pull_request.user.login;

try {
await github.rest.orgs.checkMembershipForUser({
org: org,
username: username
});
core.setOutput('is_member', 'true');
console.log(`✅ ${username} is a ${org} org member - proceeding with auto-review`);
} catch (error) {
if (error.status === 404 || error.status === 302) {
core.setOutput('is_member', 'false');
console.log(`⏭️ ${username} is not a ${org} org member - skipping auto-review`);
} else if (error.status === 401) {
core.setFailed(
'❌ ORG_MEMBERSHIP_TOKEN secret is missing or invalid.\n\n' +
`This secret is required to check ${org} org membership for auto-reviews.\n\n` +
'To fix this:\n' +
'1. Create a classic PAT with read:org scope at https://github.com/settings/tokens/new\n' +
'2. Add it as an org secret named ORG_MEMBERSHIP_TOKEN'
);
} else {
core.setFailed(`Failed to check org membership: ${error.message}`);
}
}

# Safe to checkout PR head because review-pr only READS files (no code execution)
- name: Checkout PR head
if: steps.membership.outputs.is_member == 'true'
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.pull_request.number }}/head

# Generate GitHub App token for custom app identity (optional - falls back to github.token)
- name: Generate GitHub App token
if: steps.membership.outputs.is_member == 'true' && env.HAS_APP_SECRETS == 'true'
id: app-token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
with:
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}

- name: Run PR Review
if: steps.membership.outputs.is_member == 'true'
id: run-review
uses: docker/cagent-action/review-pr@latest
with:
pr-number: ${{ inputs.pr-number || github.event.pull_request.number }}
additional-prompt: ${{ inputs.additional-prompt }}
model: ${{ inputs.model }}
cagent-version: ${{ inputs.cagent-version }}
github-token: ${{ steps.app-token.outputs.token || github.token }}
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
xai-api-key: ${{ secrets.XAI_API_KEY }}
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}

# ==========================================================================
# MANUAL REVIEW PIPELINE
# Triggers when someone comments /review on a PR
# ==========================================================================
manual-review:
if: github.event.issue.pull_request && contains(github.event.comment.body, '/review')
runs-on: ubuntu-latest
env:
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
outputs:
exit-code: ${{ steps.run-review.outputs.exit-code }}

steps:
# Checkout PR head (not default branch)
# Note: Authorization is handled by the composite action's built-in check
- name: Checkout PR head
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
ref: refs/pull/${{ github.event.issue.number }}/head

# Generate GitHub App token for custom app identity (optional - falls back to github.token)
- name: Generate GitHub App token
if: env.HAS_APP_SECRETS == 'true'
id: app-token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
with:
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}

- name: Run PR Review
id: run-review
uses: docker/cagent-action/review-pr@latest
with:
pr-number: ${{ inputs.pr-number || github.event.issue.number }}
comment-id: ${{ inputs.comment-id || github.event.comment.id }}
additional-prompt: ${{ inputs.additional-prompt }}
model: ${{ inputs.model }}
cagent-version: ${{ inputs.cagent-version }}
github-token: ${{ steps.app-token.outputs.token || github.token }}
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
xai-api-key: ${{ secrets.XAI_API_KEY }}
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}

# ==========================================================================
# LEARN FROM FEEDBACK
# Processes replies to agent review comments for continuous improvement
# ==========================================================================
learn-from-feedback:
if: github.event_name == 'pull_request_review_comment' && github.event.comment.in_reply_to_id
runs-on: ubuntu-latest
env:
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

# Generate GitHub App token for custom app identity (optional - falls back to github.token)
- name: Generate GitHub App token
if: env.HAS_APP_SECRETS == 'true'
id: app-token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
with:
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}

- name: Learn from user feedback
uses: docker/cagent-action/review-pr/learn@latest
with:
github-token: ${{ steps.app-token.outputs.token || github.token }}
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
google-api-key: ${{ secrets.GOOGLE_API_KEY }}
aws-bearer-token-bedrock: ${{ secrets.AWS_BEARER_TOKEN_BEDROCK }}
xai-api-key: ${{ secrets.XAI_API_KEY }}
nebius-api-key: ${{ secrets.NEBIUS_API_KEY }}
mistral-api-key: ${{ secrets.MISTRAL_API_KEY }}
13 changes: 12 additions & 1 deletion .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
security-scan:
name: Security Scan with cagent
runs-on: ubuntu-latest
env:
HAS_APP_SECRETS: ${{ secrets.CAGENT_REVIEWER_APP_ID != '' }}
permissions:
contents: read
issues: write
Expand All @@ -28,6 +30,15 @@ jobs:
with:
fetch-depth: 0 # Need full history to get commits from past week

# Generate GitHub App token so issues appear as the custom app (optional - falls back to github.token)
- name: Get GitHub App token
id: app-token
if: env.HAS_APP_SECRETS == 'true'
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2
with:
app_id: ${{ secrets.CAGENT_REVIEWER_APP_ID }}
private_key: ${{ secrets.CAGENT_REVIEWER_APP_PRIVATE_KEY }}

- name: Get commits from past week
id: commits
env:
Expand Down Expand Up @@ -234,7 +245,7 @@ jobs:
- name: Create security issue
if: steps.check-issues.outputs.has_issues == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }}
OUTPUT_FILE: ${{ steps.scan.outputs.output-file }}
COMMIT_COUNT: ${{ steps.commits.outputs.commit_count }}
DAYS_BACK: ${{ inputs.days_back || '7' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ jobs:
fi

- name: Checkout code
if: steps.fork-check.outputs.is_fork != 'true'
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0

- name: Run test
Expand Down Expand Up @@ -163,7 +164,6 @@ jobs:
echo "✅ Found agent response content"
echo "Response preview: $(echo "$CONTENT" | head -n 1)"


test-invalid-agent:
name: Invalid Agent Test
runs-on: ubuntu-latest
Expand Down
Loading
Loading