From 4663b34fe73b65f5948d5b1a42472ae6f5c2cdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?oliver=20k=C3=B6nig?= Date: Fri, 2 Oct 2026 23:01:54 +0000 Subject: [PATCH] refactor(review): migrate legacy reviews to /review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: oliver könig --- .agents/recipes/pr-review/recipe.md | 7 +- .github/workflows/agentic-ci-pr-review.yml | 349 +++++---------------- 2 files changed, 78 insertions(+), 278 deletions(-) diff --git a/.agents/recipes/pr-review/recipe.md b/.agents/recipes/pr-review/recipe.md index cad34f052..321806de5 100644 --- a/.agents/recipes/pr-review/recipe.md +++ b/.agents/recipes/pr-review/recipe.md @@ -13,6 +13,10 @@ permissions: # PR Review +This is the legacy local Agentic CI recipe. The GitHub workflow now posts +`/review` guidance; the review runner loads the existing `review-code` skill +and owns review publication. The procedure below applies only to local use. + Review pull request #{{pr_number}} using the `review-code` skill. ## Instructions @@ -34,8 +38,7 @@ Review pull request #{{pr_number}} using the `review-code` skill. ## Constraints -- Do NOT post the review to GitHub yourself. The workflow handles posting via - `gh pr comment --body-file`. +- Do NOT post the review to GitHub yourself. Save the review locally; this recipe no longer has a GitHub publisher. - Do NOT approve or request changes on the PR. - If the diff is extremely large (>100 changed files), focus on the most critical files and note that a full review was not feasible in a single pass. diff --git a/.github/workflows/agentic-ci-pr-review.yml b/.github/workflows/agentic-ci-pr-review.yml index 3b64ea12b..383599e6c 100644 --- a/.github/workflows/agentic-ci-pr-review.yml +++ b/.github/workflows/agentic-ci-pr-review.yml @@ -1,298 +1,95 @@ -name: "Agentic CI: PR Review" - -on: +name: 'Agentic CI: Review guidance' +'on': pull_request_target: - types: [opened, ready_for_review, labeled] - branches: [main] + types: + - opened + - ready_for_review + - labeled + branches: + - main workflow_dispatch: inputs: pr_number: - description: "PR number to review" + description: PR number to receive /review guidance required: true - timeout_minutes: - description: "Review timeout in minutes. Defaults to AGENTIC_CI_TIMEOUT_MINUTES or 30." - required: false - permissions: - checks: write - contents: read pull-requests: write - concurrency: group: agentic-ci-pr-review-${{ github.event.pull_request.number || github.event.inputs.pr_number }} cancel-in-progress: true - jobs: gate: - # Decide whether the review job should run. Uses the collaborator API - # instead of author_association (which is unreliable when org membership - # is private). runs-on: ubuntu-latest outputs: allowed: ${{ steps.check.outputs.allowed }} - timeout_minutes: ${{ steps.timeout.outputs.minutes }} steps: - - name: Check permissions - id: check - env: - GH_TOKEN: ${{ github.token }} - EVENT_NAME: ${{ github.event_name }} - EVENT_ACTION: ${{ github.event.action }} - LABEL_NAME: ${{ github.event.label.name }} - IS_DRAFT: ${{ github.event.pull_request.draft }} - SENDER_LOGIN: ${{ github.event.sender.login }} - PR_AUTHOR: ${{ github.event.pull_request.user.login }} - REPO: ${{ github.repository }} - run: | - # workflow_dispatch callers already have write access. - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - echo "allowed=true" >> "$GITHUB_OUTPUT" - exit 0 - fi - - # Only the agent-review label should trigger a run. - if [ "$EVENT_ACTION" = "labeled" ] && [ "$LABEL_NAME" != "agent-review" ]; then - echo "Skipping: labeled event but not agent-review" + - name: Check permissions + id: check + env: + GH_TOKEN: ${{ github.token }} + EVENT_NAME: ${{ github.event_name }} + EVENT_ACTION: ${{ github.event.action }} + LABEL_NAME: ${{ github.event.label.name }} + IS_DRAFT: ${{ github.event.pull_request.draft }} + SENDER_LOGIN: ${{ github.event.sender.login }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + REPO: ${{ github.repository }} + run: | + # workflow_dispatch callers already have write access. + if [ "$EVENT_NAME" = "workflow_dispatch" ]; then + echo "allowed=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Only the agent-review label should trigger a run. + if [ "$EVENT_ACTION" = "labeled" ] && [ "$LABEL_NAME" != "agent-review" ]; then + echo "Skipping: labeled event but not agent-review" + echo "allowed=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + # Skip drafts unless agent-review label is being added. + if [ "$IS_DRAFT" = "true" ]; then + if [ "$EVENT_ACTION" != "labeled" ] || [ "$LABEL_NAME" != "agent-review" ]; then + echo "Skipping: draft PR" echo "allowed=false" >> "$GITHUB_OUTPUT" exit 0 fi - - # Skip drafts unless agent-review label is being added. - if [ "$IS_DRAFT" = "true" ]; then - if [ "$EVENT_ACTION" != "labeled" ] || [ "$LABEL_NAME" != "agent-review" ]; then - echo "Skipping: draft PR" - echo "allowed=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - fi - - # For labeled events, check the sender (who added the label) so - # maintainers can authorize reviews on external PRs. - # For other events, check the PR author. - if [ "$EVENT_ACTION" = "labeled" ]; then - USER="$SENDER_LOGIN" - echo "Checking sender (labeler): ${USER}" - else - USER="$PR_AUTHOR" - echo "Checking PR author: ${USER}" - fi - - PERMISSION=$(gh api "repos/${REPO}/collaborators/${USER}/permission" --jq '.permission' 2>/dev/null || echo "none") - echo "permission=${PERMISSION}" - - if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "write" ]; then - echo "allowed=true" >> "$GITHUB_OUTPUT" - else - echo "Skipping: ${USER} does not have write access (permission=${PERMISSION})" - echo "allowed=false" >> "$GITHUB_OUTPUT" - fi - - - name: Resolve review timeout - id: timeout - if: steps.check.outputs.allowed == 'true' - env: - INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }} - CONFIG_TIMEOUT_MINUTES: ${{ vars.AGENTIC_CI_TIMEOUT_MINUTES }} - run: | - TIMEOUT_MINUTES="${INPUT_TIMEOUT_MINUTES:-${CONFIG_TIMEOUT_MINUTES:-30}}" - if ! [[ "$TIMEOUT_MINUTES" =~ ^[0-9]+$ ]] || - (( TIMEOUT_MINUTES < 1 || TIMEOUT_MINUTES > 45 )); then - echo "::error::Review timeout must be an integer from 1 to 45 minutes." - exit 1 - fi - echo "minutes=${TIMEOUT_MINUTES}" >> "$GITHUB_OUTPUT" - - review: - name: Agentic review (advisory) + fi + + # For labeled events, check the sender (who added the label) so + # maintainers can authorize reviews on external PRs. + # For other events, check the PR author. + if [ "$EVENT_ACTION" = "labeled" ]; then + USER="$SENDER_LOGIN" + echo "Checking sender (labeler): ${USER}" + else + USER="$PR_AUTHOR" + echo "Checking PR author: ${USER}" + fi + + PERMISSION=$(gh api "repos/${REPO}/collaborators/${USER}/permission" --jq '.permission') + echo "permission=${PERMISSION}" + + if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "write" ]; then + echo "allowed=true" >> "$GITHUB_OUTPUT" + else + echo "Skipping: ${USER} does not have write access (permission=${PERMISSION})" + echo "allowed=false" >> "$GITHUB_OUTPUT" + fi + timeout-minutes: 5 + review-guidance: + name: Post /review guidance needs: gate if: needs.gate.outputs.allowed == 'true' - runs-on: [self-hosted, agentic-ci] - environment: agentic-ci - timeout-minutes: 60 + runs-on: ubuntu-latest + timeout-minutes: 5 + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }} steps: - - name: Determine PR number - id: pr - env: - EVENT_NAME: ${{ github.event_name }} - INPUT_PR_NUMBER: ${{ github.event.inputs.pr_number }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - echo "number=${INPUT_PR_NUMBER}" >> "$GITHUB_OUTPUT" - else - echo "number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" - fi - - - name: Validate PR number - env: - PR_NUMBER: ${{ steps.pr.outputs.number }} - run: | - if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then - echo "::error::Invalid PR number: ${PR_NUMBER}" - exit 1 - fi - - - name: Check required config - env: - AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} - run: | - if [ -z "$AGENTIC_CI_MODEL" ]; then - echo "::error::AGENTIC_CI_MODEL variable is not set. Configure it in repo settings." - exit 1 - fi - - - name: Resolve head SHA - id: head - env: - GH_TOKEN: ${{ github.token }} - EVENT_NAME: ${{ github.event_name }} - PR_NUMBER: ${{ steps.pr.outputs.number }} - PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: | - if [ "$EVENT_NAME" = "workflow_dispatch" ]; then - SHA=$(gh pr view "$PR_NUMBER" --json headRefOid -q '.headRefOid') - else - SHA="$PR_HEAD_SHA" - fi - echo "sha=$SHA" >> "$GITHUB_OUTPUT" - - - name: Checkout PR branch - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - # Preserve maintainer-approved fork reviews while #804 hardens isolation. - allow-unsafe-pr-checkout: true - ref: ${{ steps.head.outputs.sha }} - fetch-depth: 0 - - # SECURITY: Recipe and tool files define the agent's prompt and run - # with secrets in scope. Always read them from the base branch so a - # fork PR cannot inject malicious instructions or code. - - name: Checkout base branch agent files - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.event.pull_request.base.sha || 'main' }} - sparse-checkout: | - .agents/recipes - .agents/tools - path: base-agents - - - name: List changed Python files - id: changed-py - env: - PR_NUMBER: ${{ steps.pr.outputs.number }} - GH_TOKEN: ${{ github.token }} - run: | - gh pr diff "$PR_NUMBER" --name-only | grep '\.py$' > /tmp/changed-py.txt || true - echo "count=$(wc -l < /tmp/changed-py.txt | tr -d ' ')" >> "$GITHUB_OUTPUT" - - - name: Structural impact analysis - if: steps.changed-py.outputs.count != '0' - run: | - rm -f "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" - mapfile -t CHANGED_PY < /tmp/changed-py.txt - python -m venv /tmp/graphify-venv - /tmp/graphify-venv/bin/python -m pip install graphifyy==0.4.23 --quiet 2>&1 | tail -3 - /tmp/graphify-venv/bin/python base-agents/.agents/tools/structural_impact.py \ - --repo-root "${{ github.workspace }}" \ - --changed-files "${CHANGED_PY[@]}" \ - --output "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" - echo "Structural impact analysis complete:" - cat "/tmp/structural-impact-${{ steps.pr.outputs.number }}.md" - continue-on-error: true - - - name: Pre-flight checks - env: - ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }} - ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }} - AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} - run: | - if ! command -v claude &> /dev/null; then - echo "::error::claude CLI not found in PATH" - exit 1 - fi - echo "Claude CLI version: $(claude --version 2>&1 || true)" - - # Quick API check (custom endpoint only) - if [ -n "$ANTHROPIC_BASE_URL" ] && [ -n "$ANTHROPIC_API_KEY" ]; then - HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \ - --max-time 30 \ - -X POST "${ANTHROPIC_BASE_URL}/v1/messages" \ - -H "Content-Type: application/json" \ - -H "x-api-key: ${ANTHROPIC_API_KEY}" \ - -H "anthropic-version: 2023-06-01" \ - -d "{\"model\":\"${AGENTIC_CI_MODEL}\",\"max_tokens\":5,\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}]}") - if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then - echo "::error::API pre-flight failed with HTTP ${HTTP_CODE}" - exit 1 - fi - echo "API pre-flight passed (HTTP ${HTTP_CODE})" - fi - - - name: Run PR review recipe - id: review - timeout-minutes: ${{ fromJSON(needs.gate.outputs.timeout_minutes) }} - env: - ANTHROPIC_BASE_URL: ${{ secrets.AGENTIC_CI_API_BASE_URL }} - ANTHROPIC_API_KEY: ${{ secrets.AGENTIC_CI_API_KEY }} - AGENTIC_CI_MODEL: ${{ vars.AGENTIC_CI_MODEL }} - DISABLE_PROMPT_CACHING: "1" - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.pr.outputs.number }} - run: | - set -o pipefail - - # Build the prompt from _runner.md + recipe, substituting template vars. - # Read from base-agents/ (checked out from the base branch) so fork - # PRs cannot tamper with the agent prompt. - RUNNER_CTX=$(cat base-agents/.agents/recipes/_runner.md) - RECIPE_BODY=$(cat base-agents/.agents/recipes/pr-review/recipe.md \ - | sed '1,/^---$/{ /^---$/,/^---$/d }') - - PROMPT=$(printf '%s\n\n%s\n' "${RUNNER_CTX}" "${RECIPE_BODY}" \ - | sed "s/{{pr_number}}/${PR_NUMBER}/g") - - claude \ - --model "$AGENTIC_CI_MODEL" \ - -p "$PROMPT" \ - --max-turns 50 \ - --output-format text \ - --verbose \ - 2>&1 | tee /tmp/claude-review-log.txt - continue-on-error: true - - - name: Report incomplete review - if: steps.review.outcome != 'success' - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.pr.outputs.number }} - RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - TIMEOUT_MINUTES: ${{ needs.gate.outputs.timeout_minutes }} - run: | - echo "::warning::Agentic review failed or exceeded the ${TIMEOUT_MINUTES}-minute limit." - { - echo "## Agentic review incomplete" - echo - echo "The advisory review failed or timed out. This does not block merging." - } >> "$GITHUB_STEP_SUMMARY" - gh pr comment "$PR_NUMBER" --body "Agentic review did not complete. [View the workflow run]($RUN_URL) for details." || \ - echo "::warning::Could not post incomplete review comment." - - - name: Post review comment - if: steps.review.outcome == 'success' - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.pr.outputs.number }} - run: | - if [ -s "/tmp/review-${PR_NUMBER}.md" ]; then - gh pr comment "$PR_NUMBER" --body-file "/tmp/review-${PR_NUMBER}.md" - else - echo "::warning::Review file not created by agent." - fi - - - name: Remove agent-review label - if: always() && github.event.action == 'labeled' && github.event.label.name == 'agent-review' - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ steps.pr.outputs.number }} - run: | - gh pr edit "$PR_NUMBER" --remove-label "agent-review" + - name: Explain the replacement command + env: + NOTICE: Automatic Agentic CI reviews have been retired. Request a review by commenting `/review` on this pull request. Use `/review mode=strict` for a deeper review, or `/review model=claude` to select Claude. Comment `/review help` for all options. + run: gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file - <<< "$NOTICE"