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
45 changes: 35 additions & 10 deletions .github/workflows/automated_pr_review.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,29 @@ jobs:
- name: Workflow trigger check
run: echo "Workflow triggered successfully."

review:
# Job 1: Runs without secrets to extract the git diff from untrusted PR code.
# Keeps GEMINI_API_KEY away from any environment that checks out untrusted PR files.
prepare_diff:
runs-on: ubuntu-latest
# Trigger only if it is a regular comment on a pull request, the comment body has a line
# starting with "/review", and the commenter is a maintainer (OWNER, MEMBER, or COLLABORATOR).
if: >
github.event_name == 'issue_comment' && github.event.issue.pull_request != null &&
(startsWith(github.event.comment.body, '/review') ||
contains(github.event.comment.body, '\n/review') ||
contains(github.event.comment.body, '\r\n/review')) &&
contains(fromJson('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
steps:
- name: Checkout PR Branch
- name: Checkout PR Branch (Data Only)
uses: actions/checkout@v7
with:
# Note: In GHA, during an issue_comment event on a PR, github.event.pull_request is null
# and the PR number is placed inside github.event.issue.number (because every PR is an issue).
# Therefore, github.event.issue.number is the PR number when checking out refs/pull/<number>/head.
ref: refs/pull/${{ github.event.pull_request.number || github.event.issue.number }}/head
path: untrusted_pr_head
persist-credentials: false

- name: Fetch Base Branch and Negotiate Minimal Diff History
env:
PR_COMMITS: ${{ github.event.pull_request.commits }}
run: |
cd untrusted_pr_head
# 1. Fetch the tip of main
git fetch origin main:refs/remotes/origin/main --depth=1

Expand All @@ -59,8 +58,27 @@ jobs:
git fetch --unshallow || git fetch --deepen=50
fi
fi
git diff origin/main...HEAD > ../pr_diff.txt

# Upload extracted diff as artifact to pass to Job 2 safely as text data.
- name: Upload Diff Artifact
uses: actions/upload-artifact@v4
with:
name: pr_diff
path: pr_diff.txt

# Job 2: Runs with secrets in base branch context.
review:
needs: prepare_diff
runs-on: ubuntu-latest
steps:
- name: Download Diff Artifact
uses: actions/download-artifact@v4
with:
name: pr_diff

- name: Checkout Reviewbot (Base Branch)
# Check out reviewbot into a separate directory to isolate base branch tool code.
- name: Checkout Reviewbot (Base Branch Only)
uses: actions/checkout@v7
with:
sparse-checkout: |
Expand All @@ -71,9 +89,16 @@ jobs:
uses: astral-sh/setup-uv@v8.3.2

- name: Run Antigravity Review
# Run inside reviewbot directory so execution context is the trusted base branch.
# This also helps prevent uv from looking for config files in locations it shouldn't,
# i.e. by default, uv will look in $PWD for a pyproject file to build.
working-directory: reviewbot
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
uv run reviewbot/tools/private/reviewbot/antigravity_review.py \
--prompt reviewbot/tools/private/reviewbot/prompt.txt
# Use --no-project to prevent uv from discovering or building pyproject.toml/setup.py
# in the workspace, ensuring only standalone script dependencies are resolved.
uv run --no-project --directory . tools/private/reviewbot/antigravity_review.py \
--prompt tools/private/reviewbot/prompt.txt \
--diff-file ../pr_diff.txt
7 changes: 5 additions & 2 deletions tools/private/reviewbot/antigravity_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("--prompt", required=True, help="Path to prompt file")
parser.add_argument("--diff-file", help="Path to pre-computed diff file")
return parser.parse_args()


def get_pr_diff() -> str:
def get_pr_diff(diff_file: str | None = None) -> str:
"""Fetches the git diff for the current pull request against origin/main."""
if diff_file and Path(diff_file).exists():
return Path(diff_file).read_text()
try:
return subprocess.check_output(
["git", "diff", "origin/main...HEAD"], text=True, stderr=subprocess.DEVNULL
Expand All @@ -35,7 +38,7 @@ async def main():

# Read prompt file and pre-hydrate with the exact PR code diff
base_prompt = Path(args.prompt).read_text()
diff_text = get_pr_diff()
diff_text = get_pr_diff(args.diff_file)
prompt = (
f"{base_prompt}\n\n## Pull Request Git Diff\n"
f"Here is the exact code diff for this pull request:\n```diff\n{diff_text}\n```"
Expand Down