Skip to content
26 changes: 26 additions & 0 deletions .github/workflows/auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Auto-merge on Approval

on:
pull_request_review:
types: [submitted]

jobs:
auto-merge:
# Only run when a review is approved and PR is not a draft
if: >
github.event.review.state == 'approved' &&
github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Enable auto-merge
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
echo "Enabling auto-merge for PR: $PR_URL"
gh pr merge "$PR_URL" --auto --squash
echo "Auto-merge enabled. PR will merge when all required checks pass."
139 changes: 116 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,94 @@ env:
REGION: us-central1

jobs:
# ============================================
# Stage 0a: AI Code Review (PRs only)
# ============================================
ai-review:
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout base branch
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
- name: Fallback to PR branch if reviewer script missing
if: ${{ hashFiles('scripts/ai_reviewer.py') == '' }}
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install requests
- name: Run AI Reviewer
id: review
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
if [ -z "$GEMINI_API_KEY" ]; then
echo "::error::GEMINI_API_KEY secret is not configured. AI review cannot run."
exit 1
fi
OUTPUT=$(python scripts/ai_reviewer.py \
--repo "$REPO" \
--pr "$PR_NUMBER" \
--github-token "$GITHUB_TOKEN" 2>&1) || {
echo "$OUTPUT"
exit 1
}
echo "$OUTPUT"
if echo "$OUTPUT" | grep -q "Submitted review: REQUEST_CHANGES"; then
echo "::error::AI reviewer requested changes - see review comments"
exit 1
fi

# ============================================
# Stage 0b: Security Review (PRs only)
# ============================================
security-review:
if: github.event_name == 'pull_request' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
issues: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install requests anthropic
- name: Run Security Reviewer
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
OUTPUT=$(python scripts/security_reviewer.py \
--repo "$REPO" \
--pr "$PR_NUMBER" \
--github-token "$GITHUB_TOKEN" 2>&1) || {
echo "$OUTPUT"
exit 1
}
echo "$OUTPUT"
if echo "$OUTPUT" | grep -qi "submitted.*REQUEST_CHANGES"; then
echo "::error::Security reviewer requested changes - see review comments"
exit 1
fi

# ============================================
# Stage 1: Unit Tests (runs on every PR/push)
# ============================================
Expand Down Expand Up @@ -81,12 +169,35 @@ jobs:
# Stage 2c: Build Gate (for branch protection)
# ============================================
build:
# Gate job that passes when all build jobs complete
# This allows branch protection to require just "build" instead of listing each build job
needs: [build-slack-bot, build-jobs]
# Gate job: requires builds + reviewer approvals (on PRs) before staging deploy
needs: [build-slack-bot, build-jobs, ai-review, security-review]
if: always()
runs-on: ubuntu-latest
steps:
- run: echo "All build jobs completed successfully"
- name: Verify all gates passed
env:
EVENT_NAME: ${{ github.event_name }}
BUILD_BOT: ${{ needs.build-slack-bot.result }}
BUILD_JOBS: ${{ needs.build-jobs.result }}
AI_REVIEW: ${{ needs.ai-review.result }}
SEC_REVIEW: ${{ needs.security-review.result }}
run: |
echo "Build Slack Bot: $BUILD_BOT"
echo "Build Jobs: $BUILD_JOBS"
echo "AI Review: $AI_REVIEW"
echo "Security Review: $SEC_REVIEW"

# Build jobs must succeed
[[ "$BUILD_BOT" == "success" ]] || { echo "::error::build-slack-bot failed"; exit 1; }
[[ "$BUILD_JOBS" == "success" ]] || { echo "::error::build-jobs failed"; exit 1; }

# For PRs: reviewers must succeed
if [[ "$EVENT_NAME" == "pull_request" ]]; then
[[ "$AI_REVIEW" == "success" ]] || { echo "::error::ai-review failed or not approved"; exit 1; }
[[ "$SEC_REVIEW" == "success" ]] || { echo "::error::security-review failed"; exit 1; }
fi

echo "All gates passed"

# ============================================
# Stage 3: Deploy to Staging
Expand Down Expand Up @@ -180,25 +291,7 @@ jobs:
timeout-minutes: 15

# ============================================
# Stage 5: Auto-merge PR on success (PRs only)
# ============================================
auto-merge:
needs: e2e-tests-staging
if: github.event_name == 'pull_request' && github.actor != 'dependabot[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Enable auto-merge for PR
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
pull-request-number: ${{ github.event.pull_request.number }}
merge-method: squash

# ============================================
# Stage 6: Deploy to Production (main only)
# Stage 5: Deploy to Production (main only)
# ============================================
deploy-production:
needs: e2e-tests-staging
Expand Down
Loading