diff --git a/.github/workflows/label-pr-review-state.yml b/.github/workflows/label-pr-review-state.yml index d2c24ce3c0..46a9080a07 100644 --- a/.github/workflows/label-pr-review-state.yml +++ b/.github/workflows/label-pr-review-state.yml @@ -87,6 +87,7 @@ jobs: const codeRabbitLogin = 'coderabbitai[bot]'; const codeRabbitLogins = new Set([codeRabbitLogin, 'coderabbitai']); const codeRabbitActiveLabel = 'coderabbit-review-active'; + const codeRabbitEligibleBotLogins = new Set(['zoomote[bot]']); const reviewGateName = 'Zoo Code / PR review gate'; const reconciliationCheckName = 'Zoo Code / reconcile PR review state'; @@ -660,6 +661,8 @@ jobs: review => review.state === 'CHANGES_REQUESTED' ); const automatedAuthor = pr.user?.type === 'Bot'; + const codeRabbitEligibleAuthor = !automatedAuthor || + codeRabbitEligibleBotLogins.has(pr.user?.login.toLowerCase()); const codeRabbitReviewComplete = freshCodeRabbitReview?.state === 'APPROVED'; const codeRabbitChangesRequested = freshCodeRabbitReview?.state === 'CHANGES_REQUESTED'; const maintainerApproval = freshMaintainerReviews @@ -676,7 +679,7 @@ jobs: if (codeRabbitChangesRequested || maintainerChangeRequest) { desiredLabel = 'awaiting-author'; phase = codeRabbitChangesRequested ? 'coderabbit-changes' : 'maintainer-changes'; - } else if (automatedAuthor) { + } else if (!codeRabbitEligibleAuthor) { if (pr.draft) { desiredLabel = null; phase = 'draft'; @@ -728,7 +731,7 @@ jobs: core.info( `PR #${pr.number}: CI passing, reviews=${latest.size}, ` + - `coderabbit=${freshCodeRabbitReview?.state ?? (automatedAuthor ? 'optional' : 'pending')}, ` + + `coderabbit=${freshCodeRabbitReview?.state ?? (codeRabbitEligibleAuthor ? 'pending' : 'optional')}, ` + `maintainer=${maintainerApproval?.state ?? 'pending'} → ${desiredLabel ?? '(none)'}` ); diff --git a/src/services/__tests__/pr-review-state-workflow.test.ts b/src/services/__tests__/pr-review-state-workflow.test.ts index abc77eed2a..239f22ee5a 100644 --- a/src/services/__tests__/pr-review-state-workflow.test.ts +++ b/src/services/__tests__/pr-review-state-workflow.test.ts @@ -396,6 +396,7 @@ async function runWorkflow(options: HarnessOptions = {}) { createCommitStatus, createLabel, setFailed, + info: core.info, warning: core.warning, getPullRequest, listPullRequests: github.rest.pulls.list, @@ -537,22 +538,59 @@ describe("PR review-state workflow", () => { expect(latestGuide(result)).toContain("Review-state labels are managed by this workflow") }) - it("routes bot-authored PRs directly to maintainer review", async () => { + it("starts CodeRabbit for zoomote-authored PRs after required CI passes", async () => { const result = await runWorkflow({ prAuthor: { login: "zoomote[bot]", type: "Bot" }, }) + expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["coderabbit-review-active"] })) + expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["awaiting-coderabbit"] })) + expect(latestGateStatus(result)?.state).toBe("pending") + expect(latestGateStatus(result)?.description).toContain("Waiting for automated review") + expect(result.info).toHaveBeenCalledWith(expect.stringContaining("coderabbit=pending")) + }) + + it("does not start CodeRabbit for draft zoomote-authored PRs", async () => { + const result = await runWorkflow({ + draft: true, + prAuthor: { login: "zoomote[bot]", type: "Bot" }, + }) + + expect(result.addLabels).not.toHaveBeenCalledWith( + expect.objectContaining({ labels: ["coderabbit-review-active"] }), + ) + expect(latestGuide(result)).toContain("Mark the PR ready") + }) + + it("does not start CodeRabbit for zoomote-authored PRs while required CI fails", async () => { + const result = await runWorkflow({ + prAuthor: { login: "zoomote[bot]", type: "Bot" }, + requiredConclusion: "failure", + }) + + expect(result.addLabels).not.toHaveBeenCalledWith( + expect.objectContaining({ labels: ["coderabbit-review-active"] }), + ) + expect(latestGateStatus(result)?.description).toContain("Fix the failing required CI checks") + }) + + it("routes other bot-authored PRs directly to maintainer review", async () => { + const result = await runWorkflow({ + prAuthor: { login: "dependabot[bot]", type: "Bot" }, + }) + expect(result.addLabels).toHaveBeenCalledWith(expect.objectContaining({ labels: ["awaiting-maintainer"] })) expect(result.addLabels).not.toHaveBeenCalledWith( expect.objectContaining({ labels: ["coderabbit-review-active"] }), ) expect(latestGateStatus(result)?.state).toBe("success") expect(latestGateStatus(result)?.description).toContain("Awaiting fresh human maintainer") + expect(result.info).toHaveBeenCalledWith(expect.stringContaining("coderabbit=optional")) }) - it("completes bot-authored PR review after human maintainer approval", async () => { + it("completes other bot-authored PR review after human maintainer approval", async () => { const result = await runWorkflow({ - prAuthor: { login: "zoomote[bot]", type: "Bot" }, + prAuthor: { login: "dependabot[bot]", type: "Bot" }, permissions: { maintainer: "write" }, reviews: [ { @@ -569,7 +607,7 @@ describe("PR review-state workflow", () => { it("honors manually requested CodeRabbit changes on bot-authored PRs", async () => { const result = await runWorkflow({ - prAuthor: { login: "zoomote[bot]", type: "Bot" }, + prAuthor: { login: "dependabot[bot]", type: "Bot" }, reviews: [ { login: "coderabbitai[bot]",