Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions .github/workflows/ai-code-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
name: AI Code Review

on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:

permissions:
pull-requests: write
contents: read

jobs:
code-review:
runs-on: ubuntu-latest
steps:
- name: Checkout PR code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Get PR diff
id: diff
run: |
git diff origin/${{ github.base_ref }}...HEAD > pr.diff
MAX_SIZE=50000
if [ $(wc -c < pr.diff) -gt $MAX_SIZE ]; then
head -c $MAX_SIZE pr.diff > pr_truncated.diff
mv pr_truncated.diff pr.diff
echo "warning=Diff truncated to ${MAX_SIZE} bytes" >> $GITHUB_OUTPUT
fi
FILES_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | wc -l)
echo "files_changed=$FILES_CHANGED" >> $GITHUB_OUTPUT

- name: Code Review
id: review
env:
API_KEY: ${{ secrets.AI_PROVIDER_API_KEY }}
API_ENDPOINT: ${{ secrets.AI_PROVIDER_API_ENDPOINT }}
MODEL: ${{ secrets.AI_PROVIDER_MODEL }}
run: |
set +x # Disable verbose mode to prevent secret leakage in logs
DIFF=$(cat pr.diff | jq -Rs .)

# Retry logic with exponential backoff
MAX_RETRIES=3
RETRY_COUNT=0
HTTP_CODE=0

while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
HTTP_CODE=$(curl -s -o response.json -w "%{http_code}" $API_ENDPOINT \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Accept-Language: en-US,en" \
-d "{
\"model\": \"$MODEL\",
\"messages\": [
{
\"role\": \"system\",
\"content\": \"You are an expert code reviewer. Analyze the PR diff and provide:\\n\\n1. **Summary**: Brief overview\\n2. **Issues**: Bugs or problems (if any)\\n3. **Security**: Security concerns (if any)\\n4. **Performance**: Performance considerations (if any)\\n5. **Best Practices**: Swift 6, concurrency, architecture (if any)\\n6. **Suggestions**: Optional improvements\\n\\nBe concise but thorough. Use markdown formatting.\"
},
{
\"role\": \"user\",
\"content\": ${DIFF}
}
],
\"temperature\": 0.3,
\"max_tokens\": 4096
}")

if [ "$HTTP_CODE" -eq 200 ]; then
break
fi

RETRY_COUNT=$((RETRY_COUNT + 1))
if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then
WAIT_TIME=$((2 ** RETRY_COUNT))
echo "API returned $HTTP_CODE. Retrying in ${WAIT_TIME}s... (Attempt $RETRY_COUNT/$MAX_RETRIES)"
sleep $WAIT_TIME
fi
done

if [ "$HTTP_CODE" -ne 200 ]; then
echo "Error: API returned $HTTP_CODE after $MAX_RETRIES attempts"
cat response.json
exit 1
fi

RESPONSE=$(cat response.json)
REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content')

# Check if review was extracted successfully
if [ -z "$REVIEW" ] || [ "$REVIEW" == "null" ]; then
echo "Error: Failed to extract review content from API response"
echo "Response: $RESPONSE"
exit 1
fi

echo "$REVIEW" > review.md

cat > review_with_header.md << EOF
### 🧠 AI Code Review

$REVIEW
EOF

if [ -n "${{ steps.diff.outputs.warning }}" ]; then
echo "" >> review_with_header.md
echo "⚠️ **Note**: ${{ steps.diff.outputs.warning }}" >> review_with_header.md
fi

- name: Post review as PR comment
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const review = fs.readFileSync('review_with_header.md', 'utf8');

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('AI Code Review')
);

if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: review
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: review
});
}
Loading