Claude PR Review #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Claude PR Review" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| pr_number: | |
| description: "Pull request number" | |
| required: true | |
| type: number | |
| review_id: | |
| description: "Pull request review ID" | |
| required: true | |
| type: number | |
| workflow_call: | |
| inputs: | |
| pr_number: | |
| description: "Pull request number" | |
| required: true | |
| type: number | |
| review_id: | |
| description: "Pull request review ID" | |
| required: true | |
| type: number | |
| concurrency: | |
| group: claude-pr-review-${{ inputs.pr_number }}-${{ inputs.review_id }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| respond: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Harden the runner (Audit all outbound calls) | |
| uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 | |
| with: | |
| egress-policy: audit | |
| - name: Fetch review details | |
| id: review | |
| env: | |
| GH_TOKEN: ${{ secrets.PHPSTAN_BOT_PR_TOKEN }} | |
| run: | | |
| gh api "repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}" > /tmp/pr.json | |
| gh api "repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}/reviews/${{ inputs.review_id }}" > /tmp/review.json | |
| gh api "repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}/reviews/${{ inputs.review_id }}/comments" --paginate > /tmp/review-comments.json | |
| echo "pr_head_ref=$(jq -r '.head.ref' /tmp/pr.json)" >> "$GITHUB_OUTPUT" | |
| - name: "Checkout" | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| ref: ${{ steps.review.outputs.pr_head_ref }} | |
| token: ${{ secrets.PHPSTAN_BOT_FORK_TOKEN }} | |
| fetch-depth: 0 | |
| - name: "Install PHP" | |
| uses: "shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1" # v2 | |
| with: | |
| coverage: "none" | |
| php-version: "8.4" | |
| extensions: mbstring | |
| ini-file: development | |
| ini-values: memory_limit=-1 | |
| - name: "Install dependencies" | |
| uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # v3 | |
| - name: "Install test dependencies" | |
| run: composer install --working-dir tests | |
| - name: Build prompt | |
| id: prompt | |
| shell: bash | |
| run: | | |
| review_body=$(jq -r '.body // ""' /tmp/review.json) | |
| review_state=$(jq -r '.state' /tmp/review.json) | |
| reviewer=$(jq -r '.user.login' /tmp/review.json) | |
| num_comments=$(jq 'length' /tmp/review-comments.json) | |
| prompt="A reviewer ($reviewer) submitted a review on PR #${{ inputs.pr_number }} with state: $review_state." | |
| prompt+=$'\n' | |
| if [ -n "$review_body" ] && [ "$review_body" != "null" ]; then | |
| prompt+=$'\n' | |
| prompt+="## Review body" | |
| prompt+=$'\n\n' | |
| prompt+="$review_body" | |
| prompt+=$'\n' | |
| fi | |
| if [ "$num_comments" -gt 0 ]; then | |
| prompt+=$'\n' | |
| prompt+="## Review comments" | |
| prompt+=$'\n' | |
| for i in $(seq 0 $((num_comments - 1))); do | |
| file_path=$(jq -r ".[$i].path" /tmp/review-comments.json) | |
| line=$(jq -r ".[$i].line // .[$i].original_line // \"?\"" /tmp/review-comments.json) | |
| body=$(jq -r ".[$i].body" /tmp/review-comments.json) | |
| diff_hunk=$(jq -r ".[$i].diff_hunk // \"\"" /tmp/review-comments.json) | |
| prompt+=$'\n' | |
| prompt+="### $file_path (line $line)" | |
| prompt+=$'\n\n' | |
| if [ -n "$diff_hunk" ] && [ "$diff_hunk" != "null" ]; then | |
| prompt+='```diff' | |
| prompt+=$'\n' | |
| prompt+="$diff_hunk" | |
| prompt+=$'\n' | |
| prompt+='```' | |
| prompt+=$'\n\n' | |
| fi | |
| prompt+="$body" | |
| prompt+=$'\n' | |
| done | |
| fi | |
| prompt+=$'\n' | |
| prompt+="## Instructions" | |
| prompt+=$'\n\n' | |
| prompt+="Please address this review. Make the requested code changes, then run tests with \`make tests\` and static analysis with \`make phpstan\` to verify." | |
| prompt+=$'\n' | |
| prompt+="Commit each logical change separately with a descriptive message." | |
| { | |
| echo 'PROMPT<<PROMPT_DELIMITER' | |
| echo "$prompt" | |
| echo 'PROMPT_DELIMITER' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Install Claude Code | |
| run: npm install -g @anthropic-ai/claude-code | |
| - name: Run Claude | |
| env: | |
| CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| GH_TOKEN: ${{ secrets.PHPSTAN_BOT_FORK_TOKEN }} | |
| PROMPT: ${{ steps.prompt.outputs.PROMPT }} | |
| run: | | |
| claude -p "$PROMPT" \ | |
| --output-format text \ | |
| > /tmp/claude-response.txt | |
| - name: Commit and push changes | |
| id: push | |
| run: | | |
| if [ -z "$(git status --porcelain)" ]; then | |
| echo "pushed=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| git config user.name "phpstan-bot" | |
| git config user.email "ondrej+phpstanbot@mirtes.cz" | |
| git add -A | |
| git commit -m "Address review feedback on PR #${{ inputs.pr_number }}" | |
| git push | |
| echo "pushed=true" >> "$GITHUB_OUTPUT" | |
| - name: Reply to review | |
| if: always() && steps.claude.outcome != 'skipped' | |
| env: | |
| GH_TOKEN: ${{ secrets.PHPSTAN_BOT_PR_TOKEN }} | |
| PUSHED: ${{ steps.push.outputs.pushed }} | |
| run: | | |
| body="" | |
| if [ -f /tmp/claude-response.txt ]; then | |
| body=$(cat /tmp/claude-response.txt) | |
| fi | |
| if [ "$PUSHED" = "true" ]; then | |
| body+=$'\n\n---\n_I pushed a commit addressing this review._' | |
| fi | |
| if [ -z "$body" ]; then | |
| body="I processed this review but have nothing to report." | |
| fi | |
| gh api \ | |
| "repos/${{ github.repository }}/pulls/${{ inputs.pr_number }}/comments" \ | |
| -f body="$body" \ | |
| -F in_reply_to="$(jq -r '.[0].id // empty' /tmp/review-comments.json)" \ | |
| 2>/dev/null \ | |
| || gh pr comment "${{ inputs.pr_number }}" \ | |
| --repo "${{ github.repository }}" \ | |
| --body "$body" |