Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions .github/workflows/auto-assign-reviewer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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=""
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/validate-review-map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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[])
Expand Down
33 changes: 31 additions & 2 deletions docs/auto-reviewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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[<repo>]`
(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]`.
Expand Down Expand Up @@ -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[<repo>]` 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)
Expand Down
14 changes: 13 additions & 1 deletion review-map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down
Loading