arch: Fix 3 key architectural gaps for DRY, protocol-driven, multi-agent safety #1837
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Auto PR Comment | |
| # Review chain: CodeRabbit/Qodo/Gemini review first β Copilot β Claude (final) | |
| # For human PRs: CodeRabbit/Qodo auto-review β this workflow triggers Copilot β then Claude | |
| # For bot PRs: CodeRabbit/Qodo skip bots, so we trigger them explicitly first | |
| # | |
| # IMPORTANT: @copilot ignores bot comments. All @copilot mentions must use | |
| # PAT_TOKEN so the comment posts as MervinPraison (a real user). | |
| # | |
| # Claude trigger runs IN-BAND (same workflow) to avoid GitHub Actions | |
| # blocking bot-triggered workflows with action_required approval gates. | |
| on: | |
| issue_comment: | |
| types: [created] | |
| pull_request_review: | |
| types: [submitted] | |
| pull_request: | |
| types: [opened] | |
| env: | |
| MAX_POLL_ATTEMPTS: 20 | |
| POLL_DELAY_MS: 30000 | |
| CLAUDE_TRIGGER_LOGINS: '["MervinPraison","github-actions[bot]"]' | |
| jobs: | |
| # ================================================================ | |
| # HUMAN PRs: Trigger @copilot AFTER CodeRabbit posts its summary | |
| # ================================================================ | |
| copilot-after-coderabbit: | |
| if: | | |
| github.event_name == 'issue_comment' && | |
| github.event.issue.pull_request && | |
| github.event.comment.user.login == 'coderabbitai[bot]' && | |
| contains(github.event.comment.body, 'summarize by coderabbit') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| outputs: | |
| pr_number: ${{ steps.trigger.outputs.pr_number }} | |
| triggered: ${{ steps.trigger.outputs.triggered }} | |
| steps: | |
| - name: Post Copilot review request (as user via PAT) | |
| id: trigger | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| // Check for duplicates β posted by MervinPraison (PAT) or github-actions[bot] (legacy) | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@copilot') && | |
| (c.user.login === 'MervinPraison' || c.user.login === 'github-actions[bot]') | |
| ); | |
| if (alreadyPosted) { | |
| console.log('Copilot already triggered, skipping'); | |
| core.setOutput('triggered', 'false'); | |
| core.setOutput('pr_number', context.issue.number.toString()); | |
| return; | |
| } | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first β incorporate their findings.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused? Any unnecessary code or scope creep?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. **Address reviewer feedback**: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review\n8. Suggest specific improvements with code examples where possible' | |
| }); | |
| core.setOutput('triggered', 'true'); | |
| core.setOutput('pr_number', context.issue.number.toString()); | |
| # Fallback: Trigger @copilot after Qodo review (if Coderabbit doesn't fire) | |
| copilot-after-qodo: | |
| if: | | |
| github.event_name == 'pull_request_review' && | |
| github.event.review.user.login == 'qodo-code-review[bot]' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| outputs: | |
| pr_number: ${{ steps.trigger.outputs.pr_number }} | |
| triggered: ${{ steps.trigger.outputs.triggered }} | |
| steps: | |
| - name: Post Copilot review request (as user via PAT) | |
| id: trigger | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@copilot') && | |
| (c.user.login === 'MervinPraison' || c.user.login === 'github-actions[bot]') | |
| ); | |
| if (alreadyPosted) { | |
| console.log('Copilot already triggered, skipping'); | |
| core.setOutput('triggered', 'false'); | |
| core.setOutput('pr_number', context.issue.number.toString()); | |
| return; | |
| } | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above first.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. Suggest specific improvements with code examples where possible' | |
| }); | |
| core.setOutput('triggered', 'true'); | |
| core.setOutput('pr_number', context.issue.number.toString()); | |
| # ================================================================ | |
| # FALLBACK: Claude trigger for cases where Copilot chain fails/skips | |
| # Ensures Claude always runs after sufficient review time | |
| # ================================================================ | |
| claude-fallback-timeout: | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'opened' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Wait for review window (15 min) | |
| run: sleep 900 | |
| - name: Check if Claude already triggered | |
| id: check_claude | |
| uses: actions/github-script@v7 | |
| env: | |
| TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@claude') && triggerLogins.includes(c.user.login) | |
| ); | |
| core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false'); | |
| core.setOutput('comments_count', comments.data.length.toString()); | |
| // Collect all bot reviewer comments for context | |
| const botReviews = comments.data.filter(c => | |
| ['coderabbitai[bot]', 'qodo-code-review[bot]', 'gemini-code-assist[bot]', | |
| 'copilot-swe-agent', 'Copilot', 'greptile-apps[bot]'].some(bot => | |
| c.user.login.toLowerCase().includes(bot.toLowerCase()) | |
| ) | |
| ); | |
| core.setOutput('review_count', botReviews.length.toString()); | |
| - name: Aggregate reviews and trigger Claude | |
| if: steps.check_claude.outputs.already_triggered != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const comments = await github.rest.issues.listComments({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, per_page: 100}); | |
| const reviews = {coderabbit: [], qodo: [], gemini: [], copilot: [], greptile: [], others: []}; | |
| comments.data.forEach(c => { | |
| const login = c.user.login.toLowerCase(); | |
| const body = c.body.substring(0, 500); | |
| if (login.includes('coderabbit')) reviews.coderabbit.push(body); | |
| else if (login.includes('qodo')) reviews.qodo.push(body); | |
| else if (login.includes('gemini')) reviews.gemini.push(body); | |
| else if (login.includes('copilot')) reviews.copilot.push(body); | |
| else if (login.includes('greptile')) reviews.greptile.push(body); | |
| else if (c.user.type === 'Bot') reviews.others.push(body); | |
| }); | |
| const summaryParts = []; | |
| if (reviews.coderabbit.length) summaryParts.push('CodeRabbit: ' + reviews.coderabbit.length + ' comments'); | |
| if (reviews.qodo.length) summaryParts.push('Qodo: ' + reviews.qodo.length + ' comments'); | |
| if (reviews.gemini.length) summaryParts.push('Gemini: ' + reviews.gemini.length + ' comments'); | |
| if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments'); | |
| if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments'); | |
| const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected'; | |
| const claudeBody = '@claude You are the FINAL architecture reviewer. ' + reviewSummary + '.\n\n' + | |
| 'Review Context: ' + comments.data.length + ' total comments, ' + | |
| (reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' + | |
| 'Phase 1: Review per AGENTS.md principles (protocol-driven, backward compatible, no perf impact).\n' + | |
| 'Phase 2: FIX valid issues found by prior reviewers.\n' + | |
| 'Phase 3: Provide final verdict (approve or request changes).'; | |
| await github.rest.issues.createComment({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody}); | |
| console.log('Claude fallback triggered on PR #' + context.issue.number + ' with ' + reviewSummary); | |
| # ================================================================ | |
| # Claude FINAL review β polls for Copilot's response, then triggers | |
| # This runs IN-BAND under the PAT context (MervinPraison), avoiding | |
| # the action_required approval gate that blocks bot-triggered workflows. | |
| # ================================================================ | |
| claude-after-copilot: | |
| needs: [copilot-after-coderabbit] | |
| if: | | |
| always() && | |
| needs.copilot-after-coderabbit.result == 'success' && | |
| needs.copilot-after-coderabbit.outputs.triggered == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Wait for Copilot to respond (poll) | |
| id: poll | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }} | |
| MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }} | |
| POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| // Poll for Copilot's response β check every 30s for up to 10 minutes | |
| const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10); | |
| const delayMs = parseInt(process.env.POLL_DELAY_MS, 10); | |
| for (let i = 0; i < maxAttempts; i++) { | |
| console.log(`Poll attempt ${i + 1}/${maxAttempts}...`); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: pr, | |
| owner, | |
| repo, | |
| per_page: 50, | |
| sort: 'created', | |
| direction: 'desc' | |
| }); | |
| // Look for Copilot's response (copilot-swe-agent or Copilot) | |
| const copilotResponse = comments.data.find(c => | |
| (c.user.login === 'copilot-swe-agent' || | |
| c.user.login === 'Copilot' || | |
| c.user.login.toLowerCase().includes('copilot')) && | |
| c.user.type === 'Bot' | |
| ); | |
| if (copilotResponse) { | |
| console.log(`Copilot responded at ${copilotResponse.created_at}`); | |
| core.setOutput('copilot_responded', 'true'); | |
| return; | |
| } | |
| // Also check PR reviews for Copilot | |
| const reviews = await github.rest.pulls.listReviews({ | |
| pull_number: pr, | |
| owner, | |
| repo | |
| }); | |
| const copilotReview = reviews.data.find(r => | |
| r.user.login.toLowerCase().includes('copilot') | |
| ); | |
| if (copilotReview) { | |
| console.log(`Copilot review submitted at ${copilotReview.submitted_at}`); | |
| core.setOutput('copilot_responded', 'true'); | |
| return; | |
| } | |
| if (i < maxAttempts - 1) { | |
| console.log(`Copilot hasn't responded yet, waiting ${delayMs/1000}s...`); | |
| await new Promise(resolve => setTimeout(resolve, delayMs)); | |
| } | |
| } | |
| console.log('Copilot did not respond within timeout, triggering Claude anyway'); | |
| core.setOutput('copilot_responded', 'timeout'); | |
| - name: Check if Claude already triggered | |
| id: check_claude | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }} | |
| TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: pr, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@claude') && triggerLogins.includes(c.user.login) | |
| ); | |
| core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false'); | |
| - name: Trigger Claude final review | |
| if: steps.check_claude.outputs.already_triggered != 'true' | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| await github.rest.issues.createComment({ | |
| issue_number: pr, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '@claude You are the FINAL architecture reviewer. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items' | |
| }); | |
| console.log(`Claude triggered on PR #${pr}`); | |
| # ================================================================ | |
| # Claude after Qodo fallback path (same polling approach) | |
| # ================================================================ | |
| claude-after-copilot-qodo: | |
| needs: [copilot-after-qodo] | |
| if: | | |
| always() && | |
| needs.copilot-after-qodo.result == 'success' && | |
| needs.copilot-after-qodo.outputs.triggered == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Wait for Copilot to respond (poll) | |
| id: poll | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }} | |
| MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }} | |
| POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10); | |
| const delayMs = parseInt(process.env.POLL_DELAY_MS, 10); | |
| for (let i = 0; i < maxAttempts; i++) { | |
| console.log(`Poll attempt ${i + 1}/${maxAttempts}...`); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: pr, owner, repo, | |
| per_page: 50, sort: 'created', direction: 'desc' | |
| }); | |
| const copilotResponse = comments.data.find(c => | |
| (c.user.login === 'copilot-swe-agent' || | |
| c.user.login === 'Copilot' || | |
| c.user.login.toLowerCase().includes('copilot')) && | |
| c.user.type === 'Bot' | |
| ); | |
| if (copilotResponse) { | |
| console.log(`Copilot responded at ${copilotResponse.created_at}`); | |
| core.setOutput('copilot_responded', 'true'); | |
| return; | |
| } | |
| const reviews = await github.rest.pulls.listReviews({ | |
| pull_number: pr, owner, repo | |
| }); | |
| const copilotReview = reviews.data.find(r => | |
| r.user.login.toLowerCase().includes('copilot') | |
| ); | |
| if (copilotReview) { | |
| console.log(`Copilot review submitted at ${copilotReview.submitted_at}`); | |
| core.setOutput('copilot_responded', 'true'); | |
| return; | |
| } | |
| if (i < maxAttempts - 1) { | |
| await new Promise(resolve => setTimeout(resolve, delayMs)); | |
| } | |
| } | |
| console.log('Copilot did not respond within timeout, triggering Claude anyway'); | |
| core.setOutput('copilot_responded', 'timeout'); | |
| - name: Check if Claude already triggered | |
| id: check_claude | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }} | |
| TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: pr, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@claude') && triggerLogins.includes(c.user.login) | |
| ); | |
| core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false'); | |
| - name: Trigger Claude final review | |
| if: steps.check_claude.outputs.already_triggered != 'true' | |
| uses: actions/github-script@v7 | |
| env: | |
| PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = parseInt(process.env.PR_NUMBER, 10); | |
| await github.rest.issues.createComment({ | |
| issue_number: pr, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '@claude You are the FINAL architecture reviewer. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items' | |
| }); | |
| console.log(`Claude triggered on PR #${pr}`); | |
| # ================================================================ | |
| # Claude after Gemini β restores the trigger lost from the deleted | |
| # chain-claude-after-copilot.yml workflow. | |
| # Fires when Gemini posts a review comment on a PR or issue. | |
| # ================================================================ | |
| claude-after-gemini: | |
| if: | | |
| github.event_name == 'issue_comment' && | |
| ( | |
| github.event.comment.user.login == 'gemini-code-assist[bot]' || | |
| contains(github.event.comment.body, 'Review completed by Gemini CLI') || | |
| contains(github.event.comment.body, 'The fix is ready for review. Please test') | |
| ) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Check if Claude already triggered | |
| id: check_claude | |
| uses: actions/github-script@v7 | |
| env: | |
| TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }} | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS); | |
| const comments = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const alreadyPosted = comments.data.some(c => | |
| c.body.includes('@claude') && triggerLogins.includes(c.user.login) | |
| ); | |
| core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false'); | |
| - name: Post Claude analysis request (Lead Engineer) | |
| if: steps.check_claude.outputs.already_triggered != 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: '@claude You are the Lead Engineer. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items' | |
| }); | |
| # ================================================================ | |
| # BOT PRs: CodeRabbit/Qodo skip bot-authored PRs by default. | |
| # Trigger them explicitly. Do NOT trigger Copilot here β it will | |
| # be triggered by copilot-after-coderabbit/qodo when they finish. | |
| # ================================================================ | |
| bot-pr-trigger-reviews: | |
| if: | | |
| github.event_name == 'pull_request' && | |
| github.event.action == 'opened' && | |
| ( | |
| github.event.pull_request.user.login == 'github-actions[bot]' || | |
| github.event.pull_request.user.login == 'praisonai-triage-agent[bot]' || | |
| github.event.pull_request.user.type == 'Bot' | |
| ) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| pull-requests: write | |
| steps: | |
| - name: Trigger CodeRabbit and Qodo reviews | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GH_TOKEN }} | |
| script: | | |
| const pr = context.payload.pull_request.number; | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| // 1. Trigger CodeRabbit review | |
| await github.rest.issues.createComment({ | |
| issue_number: pr, owner, repo, | |
| body: '@coderabbitai review' | |
| }); | |
| console.log(`Triggered CodeRabbit for bot PR #${pr}`); | |
| // 2. Trigger Qodo review | |
| await github.rest.issues.createComment({ | |
| issue_number: pr, owner, repo, | |
| body: '/review' | |
| }); | |
| console.log(`Triggered Qodo for bot PR #${pr}`); | |
| // 3. Trigger Gemini review (commented out) | |
| // await github.rest.issues.createComment({ | |
| // issue_number: pr, owner, repo, | |
| // body: '@gemini review this PR' | |
| // }); | |
| // console.log(`Triggered Gemini for bot PR #${pr}`); | |
| // NOTE: @copilot is NOT triggered here. | |
| // It will be triggered by copilot-after-coderabbit or copilot-after-qodo | |
| // once those bots finish, ensuring Copilot always reviews LAST. | |
| console.log('Copilot will be triggered after CodeRabbit/Qodo complete.'); |