81207143f3
Signed-off-by: Peter Siegmund <developer@mars3142.org>
154 lines
5.3 KiB
YAML
154 lines
5.3 KiB
YAML
name: Claude Code Review
|
|
description: >
|
|
Reviews pull requests using Claude AI. Reads review instructions from a
|
|
prompt file in the target branch and posts the result as a PR comment.
|
|
author: mars3142
|
|
|
|
inputs:
|
|
api_key:
|
|
description: Anthropic API key
|
|
required: true
|
|
gitea_token:
|
|
description: Gitea token for posting PR comments
|
|
required: true
|
|
prompt_file:
|
|
description: Path to the review prompt file inside the target branch
|
|
required: false
|
|
default: .claude/review-prompt.md
|
|
model:
|
|
description: Claude model ID
|
|
required: false
|
|
default: claude-sonnet-4-6
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Fetch PR branches
|
|
shell: bash
|
|
run: |
|
|
git fetch origin "${{ github.base_ref }}"
|
|
git fetch origin "${{ github.head_ref }}" 2>/dev/null || \
|
|
git fetch origin \
|
|
"refs/pull/${{ github.event.pull_request.number }}/head:pr/${{ github.event.pull_request.number }}"
|
|
|
|
- name: Read prompt file from target branch
|
|
shell: bash
|
|
run: |
|
|
PROMPT_FILE="${{ inputs.prompt_file }}"
|
|
|
|
if ! git show "origin/${{ github.base_ref }}:${PROMPT_FILE}" \
|
|
> /tmp/claude-review-prompt.txt 2>/dev/null; then
|
|
echo "::error::Prompt file '${PROMPT_FILE}' not found in branch '${{ github.base_ref }}'"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Prompt loaded from '${PROMPT_FILE}' ($(wc -c < /tmp/claude-review-prompt.txt) bytes)"
|
|
|
|
- name: Generate PR diff
|
|
shell: bash
|
|
run: |
|
|
HEAD_REF="${{ github.head_ref }}"
|
|
BASE_REF="origin/${{ github.base_ref }}"
|
|
|
|
if git diff "${BASE_REF}...origin/${HEAD_REF}" \
|
|
> /tmp/claude-review-diff.txt 2>/dev/null; then
|
|
true
|
|
else
|
|
git diff "${BASE_REF}" \
|
|
"pr/${{ github.event.pull_request.number }}" \
|
|
> /tmp/claude-review-diff.txt
|
|
fi
|
|
|
|
DIFF_SIZE=$(wc -c < /tmp/claude-review-diff.txt)
|
|
|
|
if [ "$DIFF_SIZE" -eq 0 ]; then
|
|
echo "::notice::Empty diff — skipping review"
|
|
echo "CLAUDE_SKIP=true" >> "$GITHUB_ENV"
|
|
exit 0
|
|
fi
|
|
|
|
# Truncate at 80 KB to stay within API limits
|
|
if [ "$DIFF_SIZE" -gt 81920 ]; then
|
|
head -c 81920 /tmp/claude-review-diff.txt > /tmp/claude-review-diff-trunc.txt
|
|
printf '\n\n[... diff truncated at 80 KB ...]\n' >> /tmp/claude-review-diff-trunc.txt
|
|
mv /tmp/claude-review-diff-trunc.txt /tmp/claude-review-diff.txt
|
|
echo "::warning::Diff truncated to 80 KB"
|
|
fi
|
|
|
|
echo "Diff ready ($(wc -c < /tmp/claude-review-diff.txt) bytes)"
|
|
|
|
- name: Call Claude API
|
|
if: env.CLAUDE_SKIP != 'true'
|
|
shell: bash
|
|
env:
|
|
CLAUDE_API_KEY: ${{ inputs.api_key }}
|
|
run: |
|
|
jq -n \
|
|
--arg model "${{ inputs.model }}" \
|
|
--arg prompt "$(cat /tmp/claude-review-prompt.txt)" \
|
|
--arg diff "$(cat /tmp/claude-review-diff.txt)" \
|
|
'{
|
|
model: $model,
|
|
max_tokens: 4096,
|
|
messages: [{
|
|
role: "user",
|
|
content: ($prompt + "\n\n---\n\n## Pull Request Diff\n\n```diff\n" + $diff + "\n```")
|
|
}]
|
|
}' > /tmp/claude-review-request.json
|
|
|
|
HTTP_STATUS=$(curl -s -w "%{http_code}" -o /tmp/claude-review-response.json \
|
|
-X POST "https://api.anthropic.com/v1/messages" \
|
|
-H "x-api-key: ${CLAUDE_API_KEY}" \
|
|
-H "anthropic-version: 2023-06-01" \
|
|
-H "content-type: application/json" \
|
|
-d @/tmp/claude-review-request.json)
|
|
|
|
if [ "$HTTP_STATUS" -ne 200 ]; then
|
|
echo "::error::Claude API returned HTTP ${HTTP_STATUS}"
|
|
jq -r '.error.message // .' /tmp/claude-review-response.json
|
|
exit 1
|
|
fi
|
|
|
|
REVIEW=$(jq -r '.content[0].text' /tmp/claude-review-response.json)
|
|
|
|
if [ -z "$REVIEW" ] || [ "$REVIEW" = "null" ]; then
|
|
echo "::error::Empty response from Claude API"
|
|
cat /tmp/claude-review-response.json
|
|
exit 1
|
|
fi
|
|
|
|
printf '%s' "$REVIEW" > /tmp/claude-review-result.txt
|
|
echo "Review ready ($(wc -c < /tmp/claude-review-result.txt) bytes)"
|
|
|
|
- name: Post review comment
|
|
if: env.CLAUDE_SKIP != 'true'
|
|
shell: bash
|
|
env:
|
|
GITEA_TOKEN: ${{ inputs.gitea_token }}
|
|
run: |
|
|
jq -n \
|
|
--arg review "$(cat /tmp/claude-review-result.txt)" \
|
|
--arg model "${{ inputs.model }}" \
|
|
'{body: ("## Claude Code Review\n\n" + $review + "\n\n---\n*Review generated by `" + $model + "`*")}' \
|
|
> /tmp/claude-review-comment.json
|
|
|
|
HTTP_STATUS=$(curl -s -w "%{http_code}" -o /tmp/claude-review-comment-response.json \
|
|
-X POST \
|
|
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d @/tmp/claude-review-comment.json)
|
|
|
|
if [ "$HTTP_STATUS" -ne 201 ]; then
|
|
echo "::error::Failed to post comment (HTTP ${HTTP_STATUS})"
|
|
cat /tmp/claude-review-comment-response.json
|
|
exit 1
|
|
fi
|
|
|
|
echo "Review posted to PR #${{ github.event.pull_request.number }}"
|