diff --git a/.github/workflows/auto-assign-reviewer.yml b/.github/workflows/auto-assign-reviewer.yml index 8167919..43d92bb 100644 --- a/.github/workflows/auto-assign-reviewer.yml +++ b/.github/workflows/auto-assign-reviewer.yml @@ -96,9 +96,36 @@ jobs: exit 0 fi - # ── Bot-author skip ────────────────────────────────────────────── + # ── Already-requested reviewers (idempotency) ──────────────────── + # Fetched up-front so both the bot-author path and the squad + # cascade can short-circuit if the chosen reviewer is already + # requested. + EXISTING=$(gh api "repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" --jq '.users[].login' 2>/dev/null || true) + + # ── Bot-author / external-author routing ───────────────────────── + # Bots and listed external contributors bypass the squad cascade + # because they have no squad. Route them to the repo's bot_pr_owner + # if one is mapped; otherwise skip. if jq -r '.bot_authors[]' "$MAP_JSON" | grep -Fxq "$PR_AUTHOR"; then - log "auto-reviewer: skipping bot/external author '$PR_AUTHOR'." + BOT_OWNER=$(jq -r --arg r "$REPO_NAME" '.bot_pr_owners[$r] // ""' "$MAP_JSON") + if [ -z "$BOT_OWNER" ] || [ "$BOT_OWNER" = "null" ]; then + log "auto-reviewer: bot/external author '$PR_AUTHOR' on $REPO_NAME has no bot_pr_owners mapping. Skipping." + exit 0 + fi + if printf '%s\n' "$EXISTING" | grep -Fxq "$BOT_OWNER"; then + log "auto-reviewer: $BOT_OWNER already requested on PR #$PR_NUMBER (bot path). Skipping." + exit 0 + fi + log "auto-reviewer: routing bot/external author '$PR_AUTHOR' → $BOT_OWNER (repo owner for $REPO_NAME)" + if ! gh api -X POST "repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" \ + -f "reviewers[]=$BOT_OWNER" >/dev/null 2>err.log; then + ERR=$(cat err.log || true) + warn "auto-reviewer: failed to request $BOT_OWNER for bot PR: $ERR" + comment "🤖 **Auto-reviewer:** tried to request review from \`@$BOT_OWNER\` for bot/external author \`$PR_AUTHOR\` but the API rejected it (likely not a collaborator on this repo). Please assign a reviewer manually." + exit 0 + fi + comment "🤖 **Auto-reviewer:** requested review from @$BOT_OWNER — repo owner for bot/external author \`$PR_AUTHOR\`." + log "auto-reviewer: bot-path success." exit 0 fi @@ -115,9 +142,6 @@ jobs: log "auto-reviewer: author=$PR_AUTHOR squad=$AUTHOR_SQUAD domain=$DOMAIN repo=$REPO_NAME pr=$PR_NUMBER" - # ── Already-requested reviewers (idempotency) ──────────────────── - EXISTING=$(gh api "repos/$REPO/pulls/$PR_NUMBER/requested_reviewers" --jq '.users[].login' 2>/dev/null || true) - # ── Cascade resolution ─────────────────────────────────────────── SELECTED="" REASON="" diff --git a/.github/workflows/validate-review-map.yml b/.github/workflows/validate-review-map.yml index 22ca2f2..f64f806 100644 --- a/.github/workflows/validate-review-map.yml +++ b/.github/workflows/validate-review-map.yml @@ -7,6 +7,7 @@ name: Validate review-map.yml # - Every domain key in specialists is one of: backend|web|infra|mobile|contracts # - Every squad name in sibling_fallback_order exists under .squads # - Each squad's TL is also in members (when not null) +# - Every bot_pr_owners key matches an entry in enabled_repos on: pull_request: @@ -86,6 +87,14 @@ jobs: fi done < <(jq -r '.squads | to_entries[] | [.key, (.value.tl // "")] | @tsv' "$MAP_JSON") + # bot_pr_owners keys must be in enabled_repos + while IFS=$'\t' read -r repo owner; do + [ -z "$repo" ] && continue + if ! jq -r '.enabled_repos[]' "$MAP_JSON" | grep -Fxq "$repo"; then + err "bot_pr_owners key '$repo' is not in enabled_repos" + fi + done < <(jq -r '.bot_pr_owners // {} | to_entries[] | [.key, .value] | @tsv' "$MAP_JSON") + # fallback_to_squad_members_for entries must be valid domains while IFS=$'\t' read -r squad domain; do [ -z "$domain" ] && continue @@ -141,6 +150,7 @@ jobs: jq -r ' [ (.bot_authors // [])[], + (.bot_pr_owners // {} | to_entries[].value), (.squads[].members // [])[], (.squads[].tl // empty), (.squads[].specialists // {} | to_entries[].value[]) diff --git a/docs/auto-reviewer.md b/docs/auto-reviewer.md index 5a8205f..96afbfd 100644 --- a/docs/auto-reviewer.md +++ b/docs/auto-reviewer.md @@ -24,7 +24,13 @@ specialist." This workflow does that with one shared map. ## Cascade -Given PR author `A`, repo domain `D`, author squad `S`: +**Bot / external authors short-circuit before the cascade.** If the PR author +is in `bot_authors`, the workflow requests the repo's `bot_pr_owners[]` +(if mapped) and exits. Bots have no squad and don't need round-robin +distribution; a single per-repo owner is enough. If no owner is mapped for +the repo, the workflow exits 0 silently. + +Given a human PR author `A`, repo domain `D`, author squad `S`: 1. **Own-squad specialists.** If `S.specialists[D]` minus `A` is non-empty, pick `eligible[PR# % len]`. @@ -84,10 +90,33 @@ siblings. - **All assignment off:** set `enabled: false` in `review-map.yml`. - **One repo off:** remove it from `enabled_repos`. -- **Skip a specific author:** add login to `bot_authors`. +- **Bypass squad cascade for an author:** add login to `bot_authors`. They get + routed to `bot_pr_owners[]` if mapped, otherwise skipped. +- **Stop bot routing in a repo:** remove the repo from `bot_pr_owners` (bot + PRs in that repo will then be skipped). All take effect on the next PR open with no other action. +## Bot / external author routing + +`bot_pr_owners` maps repo name → single GitHub login. Designed for PRs where +squad-based routing doesn't apply: dependabot bumps, renovate bumps, claude +PR commits, copilot review bot, plus listed external contributors. + +Current mapping: + +| Repo | Owner | +|-----------------|-------------| +| `lisk-backend` | `ishantiw` | +| `lisk-web` | `mmarinovic`| +| `lisk-mobile` | `5heri` | +| `lisk-infra` | `Nazgolze` | +| `lisk-contracts`| `matjazv` | + +The owner is requested verbatim — no round-robin, no domain check, no +fallback. If they're unavailable, the comment trail surfaces who the API +rejected so a human can re-assign. + ## What does and doesn't fail the workflow The workflow is best-effort. It always exits 0 (so it never blocks merges) diff --git a/review-map.yml b/review-map.yml index 052b844..f61b29c 100644 --- a/review-map.yml +++ b/review-map.yml @@ -17,7 +17,8 @@ enabled_repos: - lisk-mobile - lisk-contracts -# Authors to skip entirely (no assignment, exit 0). +# Authors that bypass the squad cascade. They get routed to the repo's +# bot_pr_owner (see below) if one is set, otherwise the workflow exits 0. bot_authors: - dependabot[bot] - renovate[bot] @@ -26,6 +27,17 @@ bot_authors: - copilot-pull-request-reviewer[bot] - bmijac # external contributor on lisk-web +# Per-repo owner for bot-authored / external-author PRs. Keyed by repo +# name (must match an entry in enabled_repos). When a PR author is in +# bot_authors, the mapped owner is requested instead of running the +# squad cascade. If a repo has no entry here, bot PRs are skipped. +bot_pr_owners: + lisk-backend: ishantiw + lisk-web: mmarinovic + lisk-mobile: 5heri + lisk-infra: Nazgolze + lisk-contracts: matjazv + # Domain equivalence — within a squad, specialists for an equivalent domain # may review when no specialist exists for the requested domain. This stays # the review inside the squad. Equivalence does NOT apply to sibling cascade