+18
@@ -0,0 +1,18 @@
|
||||
### macOS
|
||||
# Finder metadata
|
||||
.DS_Store
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Custom folder icons
|
||||
Icon
|
||||
|
||||
|
||||
# Volume root files
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
@@ -0,0 +1,102 @@
|
||||
# claude-review
|
||||
|
||||
A Gitea composite action that reviews pull requests using Claude AI. It reads
|
||||
review instructions from a prompt file in your target branch, sends the PR diff
|
||||
to the Claude API, and posts the result as a PR comment.
|
||||
|
||||
## Usage
|
||||
|
||||
Create `.gitea/workflows/claude-review.yml` in any repository you want to
|
||||
enable reviews for:
|
||||
|
||||
```yaml
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mars3142/claude-review@v1
|
||||
with:
|
||||
api_key: ${{ secrets.CLAUDE_API_KEY }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Default | Description |
|
||||
|-------|----------|---------|-------------|
|
||||
| `api_key` | yes | — | Anthropic API key |
|
||||
| `gitea_token` | yes | — | Gitea token for posting comments (`GITEA_TOKEN` is set automatically by Gitea Actions) |
|
||||
| `prompt_file` | no | `.claude/review-prompt.md` | Path to the review instructions file, resolved from the target branch |
|
||||
| `model` | no | `claude-sonnet-4-6` | Claude model ID to use |
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Add the API key secret
|
||||
|
||||
In the target repository go to **Settings → Secrets** and add:
|
||||
|
||||
- `CLAUDE_API_KEY` — your [Anthropic API key](https://console.anthropic.com/)
|
||||
|
||||
`GITEA_TOKEN` is injected automatically by Gitea Actions and requires no manual setup.
|
||||
|
||||
### 2. Add the prompt file to your target branch
|
||||
|
||||
The action reads the review instructions from a file in your **target branch**
|
||||
(e.g. `main`). This ensures that only instructions committed to the main line
|
||||
are used, regardless of what a PR author puts in their branch.
|
||||
|
||||
Copy the example and adjust it to your needs:
|
||||
|
||||
```bash
|
||||
cp review-prompt.example.md .claude/review-prompt.md
|
||||
git add .claude/review-prompt.md
|
||||
git commit -m "Add Claude review prompt"
|
||||
git push
|
||||
```
|
||||
|
||||
If you prefer a different path, pass it via the `prompt_file` input:
|
||||
|
||||
```yaml
|
||||
- uses: mars3142/claude-review@v1
|
||||
with:
|
||||
api_key: ${{ secrets.CLAUDE_API_KEY }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
prompt_file: docs/review-instructions.md
|
||||
```
|
||||
|
||||
### 3. Open a pull request
|
||||
|
||||
The action triggers automatically on every PR that is opened, updated, or
|
||||
reopened. It will post a comment with the review once it completes.
|
||||
|
||||
## How it works
|
||||
|
||||
1. **Fetch branches** — both the target branch and the PR head are fetched,
|
||||
with a fallback to `refs/pull/<number>/head` for cross-fork PRs.
|
||||
2. **Read prompt** — the instructions file is read directly from the target
|
||||
branch via `git show`, so the caller's working tree is not affected.
|
||||
3. **Generate diff** — a three-dot diff (`base...head`) is computed, capturing
|
||||
only the changes introduced by the PR. Diffs larger than 80 KB are
|
||||
truncated with a notice.
|
||||
4. **Call Claude** — the prompt and diff are sent to the Claude API as a single
|
||||
user message.
|
||||
5. **Post comment** — the response is posted to the PR via the Gitea REST API.
|
||||
|
||||
## Prompt file
|
||||
|
||||
The prompt file is plain text or Markdown. It becomes the user message sent to
|
||||
Claude, with the diff appended at the end. See
|
||||
[`review-prompt.example.md`](review-prompt.example.md) for a ready-to-use
|
||||
starting point.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Gitea with Actions enabled
|
||||
- An Anthropic API key with access to the chosen model
|
||||
- `jq` and `curl` available on the runner (both are present on `ubuntu-latest`)
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
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 }}"
|
||||
@@ -0,0 +1,22 @@
|
||||
# Place this file in your repo under .gitea/workflows/claude-review.yml
|
||||
#
|
||||
# Required secrets: CLAUDE_API_KEY (Anthropic API key)
|
||||
# GITEA_TOKEN (set automatically by Gitea)
|
||||
# Optional variable: CLAUDE_PROMPT_FILE (default: .claude/review-prompt.md)
|
||||
|
||||
name: Claude Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: mars3142/claude-review@v1
|
||||
with:
|
||||
api_key: ${{ secrets.CLAUDE_API_KEY }}
|
||||
gitea_token: ${{ secrets.GITEA_TOKEN }}
|
||||
# prompt_file: .claude/review-prompt.md # optional, this is the default
|
||||
# model: claude-sonnet-4-6 # optional, this is the default
|
||||
@@ -0,0 +1,34 @@
|
||||
# Claude Code Review Instructions
|
||||
|
||||
You are a thorough and constructive code reviewer. Analyze the pull request diff below and provide a structured review.
|
||||
|
||||
## Focus areas
|
||||
|
||||
1. **Correctness** — logic errors, off-by-one, unhandled edge cases, wrong assumptions
|
||||
2. **Security** — injection vulnerabilities, improper auth/authz, sensitive data exposure, insecure defaults
|
||||
3. **Code quality** — readability, naming, duplication, overly complex logic
|
||||
4. **Error handling** — missing error checks, swallowed exceptions, unhelpful error messages
|
||||
5. **Tests** — missing tests for new behaviour, tests that don't actually verify the change
|
||||
6. **Performance** — obvious inefficiencies, unnecessary allocations or queries, N+1 problems
|
||||
|
||||
## Response format
|
||||
|
||||
### Summary
|
||||
One short paragraph describing what the PR does.
|
||||
|
||||
### Issues
|
||||
List each issue with:
|
||||
- **Severity**: `critical` | `major` | `minor`
|
||||
- **File & location** (if determinable from the diff)
|
||||
- **Description** and suggested fix
|
||||
|
||||
If no issues are found, state that explicitly.
|
||||
|
||||
### Suggestions
|
||||
Non-blocking improvements worth considering (refactoring, better naming, etc.).
|
||||
|
||||
### Verdict
|
||||
End with exactly one of:
|
||||
- ✅ **Approve**
|
||||
- ⚠️ **Approve with suggestions**
|
||||
- ❌ **Request changes**
|
||||
Reference in New Issue
Block a user