diff --git a/.github/workflows/auto_assign_reviewer.yml b/.github/workflows/auto_assign_reviewer.yml new file mode 100644 index 00000000000..37b1693d0cf --- /dev/null +++ b/.github/workflows/auto_assign_reviewer.yml @@ -0,0 +1,54 @@ +name: Auto-assign Code Review + +on: + pull_request: + types: [opened, ready_for_review] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: auto-assign-review-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + assign: + runs-on: ubuntu-latest + # Self-authored PRs (opened by openlibrary-bot itself) are excluded here -- + # GitHub rejects requesting a PR's own author as a reviewer (422). Those are + # handled directly by code_review_action.yml's own opened/ready_for_review + # trigger instead, bypassing the reviewer-request mechanism entirely. + if: > + !github.event.pull_request.draft && + github.event.pull_request.user.login != 'openlibrary-bot' + steps: + - name: Request openlibrary-bot as reviewer if not already present + uses: actions/github-script@v9 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.payload.pull_request.number; + + const { data: requested } = await github.rest.pulls.listRequestedReviewers({ + owner, repo, pull_number: prNumber, + }); + if (requested.users.some((u) => u.login === 'openlibrary-bot')) { + core.info(`PR #${prNumber} already has openlibrary-bot requested -- skipping.`); + return; + } + + const reviews = await github.paginate(github.rest.pulls.listReviews, { + owner, repo, pull_number: prNumber, per_page: 100, + }); + if (reviews.some((r) => r.user.login === 'openlibrary-bot')) { + core.info(`PR #${prNumber} already has an openlibrary-bot review -- skipping.`); + return; + } + + await github.rest.pulls.requestReviewers({ + owner, repo, pull_number: prNumber, + reviewers: ['openlibrary-bot'], + }); + core.info(`Requested openlibrary-bot as reviewer on PR #${prNumber}.`); diff --git a/.github/workflows/code_review_action.yml b/.github/workflows/code_review_action.yml new file mode 100644 index 00000000000..a667b223569 --- /dev/null +++ b/.github/workflows/code_review_action.yml @@ -0,0 +1,117 @@ +name: Code Review Action + +on: + pull_request: + types: [review_requested, opened, ready_for_review] + issue_comment: + types: [created] + workflow_dispatch: + inputs: + pr_number: + description: "Debug: manually run the code review trigger on a specific existing PR" + required: true + type: string + +permissions: + contents: read + pull-requests: write + issues: write + +concurrency: + group: code-review-${{ github.event.pull_request.number || github.event.issue.number || inputs.pr_number }} + cancel-in-progress: false + +jobs: + respond: + runs-on: ubuntu-latest + # Placeholder scaffold for #13163 -- trigger wiring only, no review logic yet. + # + # Three trigger paths: + # 1. review_requested with openlibrary-bot as the reviewer -- covers both a + # human manually requesting it, and auto_assign_reviewer.yml's automated + # request. No staff-login check here: requesting *any* reviewer already + # requires write/triage access on GitHub's side, so this isn't an open door. + # 2. opened / ready_for_review where the PR's own author is openlibrary-bot -- + # GitHub rejects requesting a PR's own author as a reviewer (422), so a + # self-authored PR can never reach this workflow via path 1. This is the + # direct substitute for "add openlibrary-bot as a reviewer on its own PR", + # which is not literally possible. + # 3. issue_comment @-mention on a PR -- open to any commenter, including + # external contributors with only read access, so this path keeps an + # explicit staff allowlist (mirrors pr_autoresponder.yml / issue_enrichment.md). + # + # Draft PRs never trigger a review, on any path -- enforced again defensively + # inside the job itself (see Step 1), since issue_comment's payload has no + # PR draft field to check at the `if:` level. + if: > + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + !github.event.pull_request.draft && + ( + github.event.requested_reviewer.login == 'openlibrary-bot' || + github.event.pull_request.user.login == 'openlibrary-bot' + ) + ) || ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request != null && + contains(github.event.comment.body, '@openlibrary-bot') && + github.event.comment.user.login != 'openlibrary-bot' && + contains(fromJSON('["mekarpeles","cdrini","jimchamp","hornc","scottbarnes","seabelis","RayBB","lokesh"]'), github.event.comment.user.login) + ) + steps: + - name: Acknowledge trigger (placeholder -- no review logic yet) + uses: actions/github-script@v9 + with: + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + const prNumber = context.eventName === 'workflow_dispatch' + ? Number(context.payload.inputs.pr_number) + : (context.payload.pull_request + ? context.payload.pull_request.number + : context.payload.issue.number); + + // Defensive draft check -- issue_comment's payload has no PR draft + // field, and this also re-confirms the pull_request-path checks + // above in case GitHub's payload state lagged the actual PR state. + const { data: pr } = await github.rest.pulls.get({ + owner, repo, pull_number: prNumber, + }); + if (pr.draft) { + core.info(`PR #${prNumber} is a draft -- skipping.`); + return; + } + + // Idempotency: this workflow can be reached by multiple overlapping + // paths (e.g. opened -> auto-assign -> review_requested), so guard + // against posting more than one placeholder. + const comments = await github.paginate(github.rest.issues.listComments, { + owner, repo, issue_number: prNumber, per_page: 100, + }); + if (comments.some((c) => (c.body || '').includes(''))) { + core.info(`PR #${prNumber} already has a placeholder comment -- skipping.`); + return; + } + + let trigger; + if (context.eventName === 'workflow_dispatch') { + trigger = 'a manual debug dispatch'; + } else if (context.eventName === 'issue_comment') { + trigger = 'an @-mention'; + } else if (pr.user.login === 'openlibrary-bot') { + trigger = 'its own PR being opened (self-authored, cannot request itself as reviewer)'; + } else { + trigger = 'a reviewer request'; + } + + await github.rest.issues.createComment({ + owner, repo, issue_number: prNumber, + body: [ + `👀 Code review was requested via ${trigger}, but the automated reviewer isn't built yet — tracked in #13163.`, + '', + 'This comment only confirms the trigger fired correctly. No review was performed.', + '', + '', + ].join('\n'), + });