From ddb2d6d629abfc5b2736218134087eaa6d241653 Mon Sep 17 00:00:00 2001 From: satya-blend360 Date: Fri, 24 Apr 2026 14:46:24 +0530 Subject: [PATCH 1/4] Add fallback AI provider support to code review workflow If the primary provider (Claude or OpenAI) fails due to an API error, quota limit, or outage, the workflow automatically retries with the other provider and labels the review comment accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ai-review.yml | 109 ++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 5ab4a16..20629c4 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: ai_provider: - description: 'AI provider to use for this run' + description: 'Primary AI provider for this run' required: false default: 'claude' type: choice @@ -46,61 +46,86 @@ jobs: open('prompt.txt', 'w').write(template.format(rules=rules, pr_body=pr_body, diff=diff)) " - - name: Call Claude API - id: claude - if: env.AI_PROVIDER == 'claude' + # Tries the configured primary provider. continue-on-error allows the + # fallback step to run if this fails (bad key, quota, outage, etc.) + - name: Call primary provider + id: primary + continue-on-error: true env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | - PAYLOAD=$(jq -n --rawfile prompt prompt.txt '{ - model: "claude-sonnet-4-6", - max_tokens: 1024, - messages: [{role: "user", content: $prompt}] - }') - RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ - -H "x-api-key: $ANTHROPIC_API_KEY" \ - -H "anthropic-version: 2023-06-01" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") || { echo "::error::Claude API call failed"; exit 1; } - REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // ("Claude error: " + (.error.message // "unknown"))') + if [ "$AI_PROVIDER" = "claude" ]; then + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + REVIEW=$(echo "$RESPONSE" | jq -re '.content[0].text') + USED="Claude Sonnet 4.6 (Anthropic)" + else + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + REVIEW=$(echo "$RESPONSE" | jq -re '.choices[0].message.content') + USED="GPT-4o-mini (OpenAI)" + fi echo "REVIEW<> "$GITHUB_OUTPUT" printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + echo "USED=$USED" >> "$GITHUB_OUTPUT" - - name: Call OpenAI API - id: openai - if: env.AI_PROVIDER == 'openai' + # Only runs if the primary provider failed — automatically uses the other provider. + - name: Call fallback provider + id: fallback + if: steps.primary.outcome == 'failure' env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} run: | - PAYLOAD=$(jq -n --rawfile prompt prompt.txt '{ - model: "gpt-4o-mini", - temperature: 0.3, - messages: [{role: "user", content: $prompt}] - }') - RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") || { echo "::error::OpenAI API call failed"; exit 1; } - REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ("OpenAI error: " + (.error.message // "unknown"))') + echo "::warning::Primary provider ($AI_PROVIDER) failed — switching to fallback." + if [ "$AI_PROVIDER" = "claude" ]; then + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") || { echo "::error::Both Claude and OpenAI failed."; exit 1; } + REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ("OpenAI error: " + (.error.message // "unknown"))') + USED="GPT-4o-mini (OpenAI) ⚠️ fallback from Claude" + else + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") || { echo "::error::Both OpenAI and Claude failed."; exit 1; } + REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // ("Claude error: " + (.error.message // "unknown"))') + USED="Claude Sonnet 4.6 (Anthropic) ⚠️ fallback from OpenAI" + fi echo "REVIEW<> "$GITHUB_OUTPUT" printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + echo "USED=$USED" >> "$GITHUB_OUTPUT" - name: Post review comment if: github.event_name == 'pull_request' uses: actions/github-script@v7 env: - CLAUDE_REVIEW: ${{ steps.claude.outputs.REVIEW }} - OPENAI_REVIEW: ${{ steps.openai.outputs.REVIEW }} - AI_PROVIDER: ${{ env.AI_PROVIDER }} + PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }} + PRIMARY_USED: ${{ steps.primary.outputs.USED }} + FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }} + FALLBACK_USED: ${{ steps.fallback.outputs.USED }} with: script: | - const provider = process.env.AI_PROVIDER; - const review = process.env.CLAUDE_REVIEW || process.env.OPENAI_REVIEW; - const label = provider === 'claude' - ? 'Claude Sonnet 4.6 (Anthropic)' - : 'GPT-4o-mini (OpenAI)'; + const review = process.env.PRIMARY_REVIEW || process.env.FALLBACK_REVIEW; + const label = process.env.PRIMARY_USED || process.env.FALLBACK_USED; await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, @@ -111,8 +136,12 @@ jobs: - name: Print review (manual run) if: github.event_name == 'workflow_dispatch' env: - CLAUDE_REVIEW: ${{ steps.claude.outputs.REVIEW }} - OPENAI_REVIEW: ${{ steps.openai.outputs.REVIEW }} + PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }} + PRIMARY_USED: ${{ steps.primary.outputs.USED }} + FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }} + FALLBACK_USED: ${{ steps.fallback.outputs.USED }} run: | - echo "=== AI Review Output (provider: $AI_PROVIDER) ===" - printf '%s\n' "${CLAUDE_REVIEW:-$OPENAI_REVIEW}" + LABEL="${PRIMARY_USED:-$FALLBACK_USED}" + REVIEW="${PRIMARY_REVIEW:-$FALLBACK_REVIEW}" + echo "=== AI Review — $LABEL ===" + printf '%s\n' "$REVIEW" From 2a5287092ec3befb285d077d23ed48e371e94f52 Mon Sep 17 00:00:00 2001 From: satya-blend360 Date: Fri, 24 Apr 2026 15:08:45 +0530 Subject: [PATCH 2/4] Fix 403 by granting pull-requests:write permission to Actions token GitHub org repos restrict the default GITHUB_TOKEN. Explicitly granting pull-requests:write lets the workflow post review comments on PRs. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ai-review.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 20629c4..1184929 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -17,6 +17,8 @@ on: jobs: review: runs-on: ubuntu-latest + permissions: + pull-requests: write # Priority: manual dispatch input > repo variable (vars.AI_PROVIDER) > default 'claude' env: AI_PROVIDER: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || vars.AI_PROVIDER || 'claude' }} From 8ec5fba1c806c4da5c0bfc2ff3f82fe3e8df6284 Mon Sep 17 00:00:00 2001 From: satya-blend360 Date: Fri, 24 Apr 2026 15:50:44 +0530 Subject: [PATCH 3/4] Set OpenAI as default provider (Claude on standby) Claude API key is present but not used as primary until verified. Fallback still attempts Claude if OpenAI fails. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ai-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 1184929..10c6313 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -8,7 +8,7 @@ on: ai_provider: description: 'Primary AI provider for this run' required: false - default: 'claude' + default: 'openai' type: choice options: - claude @@ -21,7 +21,7 @@ jobs: pull-requests: write # Priority: manual dispatch input > repo variable (vars.AI_PROVIDER) > default 'claude' env: - AI_PROVIDER: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || vars.AI_PROVIDER || 'claude' }} + AI_PROVIDER: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || vars.AI_PROVIDER || 'openai' }} steps: - name: Checkout repo From cbdbe45f67632340952f128e4fb7a8431142ae1a Mon Sep 17 00:00:00 2001 From: satya-blend360 Date: Tue, 5 May 2026 15:43:29 +0530 Subject: [PATCH 4/4] Add reusable workflow for org-wide deployment - ai-review-reusable.yml: Central workflow other repos call - templates/ai-review-starter.yml: 10-line file teams copy - templates/review-guidelines.md: Sample rules to customize - SETUP.md: Step-by-step guide for teams - Updated ai-review.yml to use reusable workflow (dogfooding) Teams can now add AI code review to their repo by: 1. Copy the starter template to .github/workflows/ai-review.yml 2. Add OPENAI_API_KEY secret 3. Open a PR Co-Authored-By: Claude --- .github/workflows/ai-review-reusable.yml | 183 +++++++++++++++++++++++ .github/workflows/ai-review.yml | 143 ++---------------- SETUP.md | 90 +++++++++++ templates/ai-review-starter.yml | 32 ++++ templates/review-guidelines.md | 28 ++++ 5 files changed, 344 insertions(+), 132 deletions(-) create mode 100644 .github/workflows/ai-review-reusable.yml create mode 100644 SETUP.md create mode 100644 templates/ai-review-starter.yml create mode 100644 templates/review-guidelines.md diff --git a/.github/workflows/ai-review-reusable.yml b/.github/workflows/ai-review-reusable.yml new file mode 100644 index 0000000..b097194 --- /dev/null +++ b/.github/workflows/ai-review-reusable.yml @@ -0,0 +1,183 @@ +# Reusable AI Code Review Workflow +# Called by other repos via: uses: BLEND360/code-sage-code-review/.github/workflows/ai-review-reusable.yml@main + +name: AI Code Review (Reusable) + +on: + workflow_call: + inputs: + ai_provider: + description: 'Primary AI provider (openai or claude)' + required: false + default: 'openai' + type: string + review_guidelines_path: + description: 'Path to review guidelines file in your repo' + required: false + default: '.github/review-guidelines.md' + type: string + enable_fallback: + description: 'If primary fails, try the other provider' + required: false + default: true + type: boolean + secrets: + OPENAI_API_KEY: + required: false + ANTHROPIC_API_KEY: + required: false + +jobs: + review: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + env: + AI_PROVIDER: ${{ inputs.ai_provider }} + + steps: + - name: Checkout caller repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get PR diff + run: | + BASE_REF="${{ github.base_ref || 'main' }}" + git fetch origin "$BASE_REF" + git diff "origin/$BASE_REF" > diff.txt + + - name: Build prompt + env: + PR_BODY: ${{ github.event.pull_request.body }} + GUIDELINES_PATH: ${{ inputs.review_guidelines_path }} + run: | + printf '%s\n' "$PR_BODY" > pr_body.txt + + # Use repo's guidelines if exists, otherwise use default + if [ -f "$GUIDELINES_PATH" ]; then + RULES=$(cat "$GUIDELINES_PATH") + else + RULES="- Follow language best practices + - Avoid console.log in production code + - All API calls must have error handling + - Validate inputs before processing + - Use async/await over raw promises + - Avoid duplicate logic + - Ensure proper null/undefined checks" + fi + + python3 -c " + import sys + rules = sys.argv[1] + pr_body = open('pr_body.txt').read() + diff = open('diff.txt').read() + prompt = f'''You are a senior code reviewer. + + GLOBAL RULES: + {rules} + + PR CONTEXT: + {pr_body} + + CODE CHANGES: + {diff} + + Review the changes above for: + - Bugs and logic errors + - Security vulnerabilities (OWASP Top 10) + - Code quality and style violations + - Missing input validation or error handling + - Violations of the global rules listed above + + Give concise, actionable feedback. Reference specific line numbers where possible.''' + open('prompt.txt', 'w').write(prompt) + " "$RULES" + + - name: Call primary provider + id: primary + continue-on-error: true + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + if [ "$AI_PROVIDER" = "claude" ]; then + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + REVIEW=$(echo "$RESPONSE" | jq -re '.content[0].text') + USED="Claude Sonnet 4.6 (Anthropic)" + else + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + REVIEW=$(echo "$RESPONSE" | jq -re '.choices[0].message.content') + USED="GPT-4o-mini (OpenAI)" + fi + echo "REVIEW<> "$GITHUB_OUTPUT" + printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + echo "USED=$USED" >> "$GITHUB_OUTPUT" + + - name: Call fallback provider + id: fallback + if: steps.primary.outcome == 'failure' && inputs.enable_fallback + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: | + echo "::warning::Primary provider ($AI_PROVIDER) failed — switching to fallback." + if [ "$AI_PROVIDER" = "claude" ]; then + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ + -H "Authorization: Bearer $OPENAI_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") || { echo "::error::Both Claude and OpenAI failed."; exit 1; } + REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ("OpenAI error: " + (.error.message // "unknown"))') + USED="GPT-4o-mini (OpenAI) [fallback]" + else + PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ + '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') + RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ + -H "x-api-key: $ANTHROPIC_API_KEY" \ + -H "anthropic-version: 2023-06-01" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") || { echo "::error::Both OpenAI and Claude failed."; exit 1; } + REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // ("Claude error: " + (.error.message // "unknown"))') + USED="Claude Sonnet 4.6 (Anthropic) [fallback]" + fi + echo "REVIEW<> "$GITHUB_OUTPUT" + printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + echo "USED=$USED" >> "$GITHUB_OUTPUT" + + - name: Post review comment + uses: actions/github-script@v7 + env: + PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }} + PRIMARY_USED: ${{ steps.primary.outputs.USED }} + FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }} + FALLBACK_USED: ${{ steps.fallback.outputs.USED }} + with: + script: | + const review = process.env.PRIMARY_REVIEW || process.env.FALLBACK_REVIEW; + const label = process.env.PRIMARY_USED || process.env.FALLBACK_USED; + if (!review) { + core.setFailed('No review generated — both providers failed.'); + return; + } + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## 🤖 AI Code Review — ${label}\n\n${review}\n\n---\nPowered by [CodeSage](https://github.com/BLEND360/code-sage-code-review)` + }); diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 10c6313..46b84db 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -1,3 +1,5 @@ +# CodeSage's own AI review — uses the reusable workflow we provide to others + name: AI Code Review on: @@ -11,139 +13,16 @@ on: default: 'openai' type: choice options: - - claude - openai + - claude jobs: review: - runs-on: ubuntu-latest - permissions: - pull-requests: write - # Priority: manual dispatch input > repo variable (vars.AI_PROVIDER) > default 'claude' - env: - AI_PROVIDER: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || vars.AI_PROVIDER || 'openai' }} - - steps: - - name: Checkout repo - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Get PR diff - run: | - BASE_REF="${{ github.base_ref || 'main' }}" - git fetch origin "$BASE_REF" - git diff "origin/$BASE_REF" > diff.txt - - - name: Build prompt - env: - PR_BODY: ${{ github.event.pull_request.body }} - run: | - printf '%s\n' "$PR_BODY" > pr_body.txt - python3 -c " - template = open('.github/prompt-template.txt').read() - rules = open('.github/review-guidelines.md').read() - pr_body = open('pr_body.txt').read() - diff = open('diff.txt').read() - open('prompt.txt', 'w').write(template.format(rules=rules, pr_body=pr_body, diff=diff)) - " - - # Tries the configured primary provider. continue-on-error allows the - # fallback step to run if this fails (bad key, quota, outage, etc.) - - name: Call primary provider - id: primary - continue-on-error: true - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: | - if [ "$AI_PROVIDER" = "claude" ]; then - PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ - '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') - RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ - -H "x-api-key: $ANTHROPIC_API_KEY" \ - -H "anthropic-version: 2023-06-01" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") - REVIEW=$(echo "$RESPONSE" | jq -re '.content[0].text') - USED="Claude Sonnet 4.6 (Anthropic)" - else - PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ - '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') - RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") - REVIEW=$(echo "$RESPONSE" | jq -re '.choices[0].message.content') - USED="GPT-4o-mini (OpenAI)" - fi - echo "REVIEW<> "$GITHUB_OUTPUT" - printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - echo "USED=$USED" >> "$GITHUB_OUTPUT" - - # Only runs if the primary provider failed — automatically uses the other provider. - - name: Call fallback provider - id: fallback - if: steps.primary.outcome == 'failure' - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - run: | - echo "::warning::Primary provider ($AI_PROVIDER) failed — switching to fallback." - if [ "$AI_PROVIDER" = "claude" ]; then - PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ - '{model:"gpt-4o-mini",temperature:0.3,messages:[{role:"user",content:$prompt}]}') - RESPONSE=$(curl -sf https://api.openai.com/v1/chat/completions \ - -H "Authorization: Bearer $OPENAI_API_KEY" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") || { echo "::error::Both Claude and OpenAI failed."; exit 1; } - REVIEW=$(echo "$RESPONSE" | jq -r '.choices[0].message.content // ("OpenAI error: " + (.error.message // "unknown"))') - USED="GPT-4o-mini (OpenAI) ⚠️ fallback from Claude" - else - PAYLOAD=$(jq -n --rawfile prompt prompt.txt \ - '{model:"claude-sonnet-4-6",max_tokens:1024,messages:[{role:"user",content:$prompt}]}') - RESPONSE=$(curl -sf https://api.anthropic.com/v1/messages \ - -H "x-api-key: $ANTHROPIC_API_KEY" \ - -H "anthropic-version: 2023-06-01" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD") || { echo "::error::Both OpenAI and Claude failed."; exit 1; } - REVIEW=$(echo "$RESPONSE" | jq -r '.content[0].text // ("Claude error: " + (.error.message // "unknown"))') - USED="Claude Sonnet 4.6 (Anthropic) ⚠️ fallback from OpenAI" - fi - echo "REVIEW<> "$GITHUB_OUTPUT" - printf '%s\n' "$REVIEW" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - echo "USED=$USED" >> "$GITHUB_OUTPUT" - - - name: Post review comment - if: github.event_name == 'pull_request' - uses: actions/github-script@v7 - env: - PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }} - PRIMARY_USED: ${{ steps.primary.outputs.USED }} - FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }} - FALLBACK_USED: ${{ steps.fallback.outputs.USED }} - with: - script: | - const review = process.env.PRIMARY_REVIEW || process.env.FALLBACK_REVIEW; - const label = process.env.PRIMARY_USED || process.env.FALLBACK_USED; - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `## 🤖 AI Code Review — ${label}\n\n${review}` - }); - - - name: Print review (manual run) - if: github.event_name == 'workflow_dispatch' - env: - PRIMARY_REVIEW: ${{ steps.primary.outputs.REVIEW }} - PRIMARY_USED: ${{ steps.primary.outputs.USED }} - FALLBACK_REVIEW: ${{ steps.fallback.outputs.REVIEW }} - FALLBACK_USED: ${{ steps.fallback.outputs.USED }} - run: | - LABEL="${PRIMARY_USED:-$FALLBACK_USED}" - REVIEW="${PRIMARY_REVIEW:-$FALLBACK_REVIEW}" - echo "=== AI Review — $LABEL ===" - printf '%s\n' "$REVIEW" + uses: ./.github/workflows/ai-review-reusable.yml + with: + ai_provider: ${{ github.event_name == 'workflow_dispatch' && inputs.ai_provider || 'openai' }} + review_guidelines_path: '.github/review-guidelines.md' + enable_fallback: true + secrets: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..7cf00f6 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,90 @@ +# CodeSage Setup Guide + +Add AI-powered code reviews to your repo in 3 steps. + +--- + +## Step 1: Add the Workflow + +Copy [`templates/ai-review-starter.yml`](templates/ai-review-starter.yml) to your repo: + +``` +your-repo/ +└── .github/ + └── workflows/ + └── ai-review.yml ← paste it here +``` + +Or use this one-liner in your repo: + +```bash +mkdir -p .github/workflows && curl -o .github/workflows/ai-review.yml \ + https://raw.githubusercontent.com/BLEND360/code-sage-code-review/main/templates/ai-review-starter.yml +``` + +--- + +## Step 2: Add API Keys + +Go to your repo: **Settings → Secrets and variables → Actions → New repository secret** + +| Secret Name | Required | Get it from | +|-------------|----------|-------------| +| `OPENAI_API_KEY` | If using OpenAI | [platform.openai.com/api-keys](https://platform.openai.com/api-keys) | +| `ANTHROPIC_API_KEY` | If using Claude | [console.anthropic.com](https://console.anthropic.com/) | + +> **Org-wide secrets**: Ask your org admin to add these at the org level so all repos can use them. + +--- + +## Step 3: (Optional) Customize Review Rules + +Create `.github/review-guidelines.md` in your repo with your team's rules. + +See [`templates/review-guidelines.md`](templates/review-guidelines.md) for an example. + +If you don't add this file, the reviewer uses sensible defaults. + +--- + +## Configuration Options + +Edit your `.github/workflows/ai-review.yml`: + +```yaml +with: + # Choose provider: 'openai' (default) or 'claude' + ai_provider: 'openai' + + # Path to your team's review guidelines + review_guidelines_path: '.github/review-guidelines.md' + + # Fallback to other provider if primary fails (default: true) + enable_fallback: true +``` + +--- + +## Test It + +1. Create a branch with any code change +2. Open a Pull Request +3. Wait ~30 seconds +4. See the 🤖 AI Code Review comment appear! + +--- + +## Troubleshooting + +| Issue | Fix | +|-------|-----| +| No comment appears | Check Actions tab for errors | +| 403 error | Add `pull-requests: write` permission (already in template) | +| API error | Verify your secret name matches exactly | +| Wrong provider | Check `ai_provider` in your workflow | + +--- + +## Questions? + +Open an issue at [BLEND360/code-sage-code-review](https://github.com/BLEND360/code-sage-code-review/issues) diff --git a/templates/ai-review-starter.yml b/templates/ai-review-starter.yml new file mode 100644 index 0000000..b5560cc --- /dev/null +++ b/templates/ai-review-starter.yml @@ -0,0 +1,32 @@ +# AI Code Review — Starter Template +# Copy this file to your repo: .github/workflows/ai-review.yml +# +# Setup: +# 1. Copy this file to .github/workflows/ai-review.yml +# 2. Add secrets: Settings → Secrets → Actions +# - OPENAI_API_KEY (required if using openai) +# - ANTHROPIC_API_KEY (required if using claude) +# 3. (Optional) Add .github/review-guidelines.md with your team's rules +# 4. Open a PR — review comment will appear automatically! + +name: AI Code Review + +on: + pull_request: + types: [opened, synchronize] + +jobs: + review: + uses: BLEND360/code-sage-code-review/.github/workflows/ai-review-reusable.yml@main + with: + # Choose your provider: 'openai' or 'claude' + ai_provider: 'openai' + + # Path to your review guidelines (optional) + # review_guidelines_path: '.github/review-guidelines.md' + + # Enable fallback to other provider if primary fails (default: true) + # enable_fallback: true + secrets: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} diff --git a/templates/review-guidelines.md b/templates/review-guidelines.md new file mode 100644 index 0000000..d188067 --- /dev/null +++ b/templates/review-guidelines.md @@ -0,0 +1,28 @@ +## Code Review Guidelines + + + +### General +- Follow ESLint and Prettier rules +- Avoid console.log in production code +- No hardcoded secrets or API keys + +### Error Handling +- All API calls must have error handling (try/catch) +- Validate inputs before processing +- Use async/await instead of raw promises + +### Code Quality +- Avoid duplicate logic — extract to functions +- Ensure proper null/undefined checks +- Keep functions under 50 lines +- Use meaningful variable names + +### Security +- Sanitize user inputs +- Use parameterized queries for databases +- No sensitive data in logs + +### Testing +- New features must include unit tests +- Bug fixes should add regression tests