name: Claude Code Review permissions: contents: read pull-requests: write on: pull_request: types: [opened, reopened, synchronize] workflow_dispatch: inputs: pr_number: description: 'Pull Request Number' required: true type: string jobs: code-review: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Get PR number id: pr-number run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "number=${{ inputs.pr_number }}" >> $GITHUB_OUTPUT else echo "number=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT fi - name: Get PR details id: pr-details uses: actions/github-script@v7 with: script: | try { const { owner, repo } = context.repo; const prNumber = ${{ steps.pr-number.outputs.number }}; console.log(`Getting PR #${prNumber} from ${owner}/${repo}`); const pr = await github.rest.pulls.get({ owner: owner, repo: repo, pull_number: prNumber }); return { base_sha: pr.data.base.sha, head_sha: pr.data.head.sha, base_ref: pr.data.base.ref, head_ref: pr.data.head.ref }; } catch (error) { core.setFailed(`Failed to get PR details: ${error.message}`); throw error; } - name: Generate diff and analyze id: analysis env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} run: | # Configure git to fetch PR refs git config --local --add remote.origin.fetch "+refs/pull/*/head:refs/remotes/origin/pr/*" git fetch origin BASE_SHA="${{ fromJSON(steps.pr-details.outputs.result).base_sha }}" HEAD_SHA="${{ fromJSON(steps.pr-details.outputs.result).head_sha }}" echo "Comparing $BASE_SHA..$HEAD_SHA" # Generate the full diff git diff -U10 "$BASE_SHA" "$HEAD_SHA" > full_diff.txt # Use awk to properly capture full context of relevant files awk ' BEGIN { found=0; buffer="" } /^diff --git/ { if (found) { print buffer } found=0; buffer="" if ($0 ~ /\.(js|ts|py|cpp|h|java|cs)$/ && $0 !~ /(package-lock\.json|yarn\.lock|\.md|\.json)/) { found=1 } } { if (found) buffer = buffer $0 "\n" } END { if (found) print buffer } ' full_diff.txt > filtered_diff.txt # Check if we have any relevant changes if [ ! -s filtered_diff.txt ]; then echo "No relevant changes found" echo "diff_size=0" >> $GITHUB_OUTPUT exit 0 fi # Get file size and content DIFF_SIZE=$(wc -c < filtered_diff.txt) echo "Found $DIFF_SIZE bytes of relevant changes" echo "diff_size=$DIFF_SIZE" >> $GITHUB_OUTPUT # Create prompt text in a file for better handling cat > prompt.txt << 'EOL' You are performing a code review. Please analyze this code diff and provide a thorough review that covers: 1. Potential conflicts with existing codebase 2. Code correctness and potential bugs 3. Security vulnerabilities or risks 4. Performance implications 5. Maintainability and readability issues 6. Adherence to best practices and coding standards 7. Suggestions for improvements For each issue found: - Explain the problem clearly - Rate the severity (Critical/High/Medium/Low) - Provide specific recommendations for fixes - Include code examples where helpful If no issues are found in a particular area, explicitly state that. Here is the code diff to review: ``` EOL cat filtered_diff.txt >> prompt.txt echo '```' >> prompt.txt # Create API request REQUEST_JSON=$(jq -n \ --arg content "$(cat prompt.txt)" \ '{ model: "claude-3-sonnet-20240229", max_tokens: 4096, temperature: 0.7, messages: [{ role: "user", content: $content }] }') # Call the API echo "Sending request to Claude API..." RESPONSE=$(curl -s https://api.anthropic.com/v1/messages \ -H "Content-Type: application/json" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -d "$REQUEST_JSON") # Process the response if echo "$RESPONSE" | jq -e '.content[0].text' > /dev/null; then { echo 'review<> $GITHUB_OUTPUT else echo "Error in Claude API response: $RESPONSE" echo "Request was: $REQUEST_JSON" exit 1 fi - name: Process review if: success() && steps.analysis.outputs.diff_size != '0' id: process-review uses: actions/github-script@v7 with: script: | const review = `${{ steps.analysis.outputs.review }}`; // Process review text to properly escape code blocks const processedReview = review .replace(/```/g, '\\`\\`\\`') .replace(/`([^`]+)`/g, '\\`$1\\`') .replace(/\${/g, '\\${'); return { text: processedReview }; - name: Post review comment if: success() && steps.analysis.outputs.diff_size != '0' uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { owner, repo } = context.repo; const prNumber = ${{ steps.pr-number.outputs.number }}; const review = ${{ steps.process-review.outputs.result }}.text; await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body: "# Claude Code Review\n\n" + review });