diff --git a/.github/reviewers.yml b/.github/reviewers.yml new file mode 100644 index 0000000..79a7a01 --- /dev/null +++ b/.github/reviewers.yml @@ -0,0 +1,131 @@ +# Expertise map consumed by assign-reviewers.yml at PR time (see +# .github/workflows/ci-assign-reviewers.yml, this repo's caller). +# +# Despite the workflow's name it writes the ASSIGNEE field, not reviewer +# requests — "this org routes/alerts people via the ASSIGNEE field" +# (assign-reviewers.yml's header). So a `reviewers:` entry below means "gets +# assigned", which is what actually gets a PR looked at here. +# +# HOW THIS MAP WAS SEEDED, and why it is not derived from git history: nearly +# every commit on main in this repo has one author, so authorship carries no +# routing signal at all. The people below are instead the ones who have actually +# APPROVED merged PRs in the matching area. Approval counts at seed time: +# wei-hai 9, huntcsg 3, deepme987 3, sundar-svg 2, synap5e 1, +# luke-mino-altherr 1, bigcat88 1. +# +# ORDER OF OPERATIONS with refresh-reviewers (#63, BE-4116): that generator +# treats the committed `rules:` globs as its BUCKET DEFINITIONS and only +# re-scores who belongs in each — it cannot invent buckets, so this file has to +# exist first. Two things to know when its first drift PR arrives: +# 1. It scores recency-decayed COMMIT authorship, so on this repo it will want +# to collapse most buckets to the single dominant committer. That is a +# history artifact, not a routing improvement — read that PR, don't +# rubber-stamp it. +# 2. The bucket globs below are therefore the durable part of this file. Edit +# them deliberately; they outlive the names. +# +# Selection at PR time: changed paths -> union of matching rules -> drop the PR +# author -> rank ascending by open assignee load -> assign `num_reviewers`. +# NOTE the author drop: a bucket whose only member is the author empties, so +# `default_pool` is the real safety net and must never be just one person (and +# must not be the person who opens most PRs here). + +default_pool: + - wei-hai + - deepme987 + - huntcsg + +rules: + # The shared caller-bump machinery + every per-fleet entrypoint. wei-hai owns + # this area by approval history (the CURSOR_REVIEW_CALLERS variable migration, + # the trailing-newline fix, the caller-list additions); deepme987 approved the + # wire-bot identity injection. + - paths: + - .github/bump-callers/** + - .github/workflows/bump-*.yml + # `test-bump-callers.yml` does not match `bump-*.yml` — name it, the same + # way every other bucket names its own `test-*.yml`. + - .github/workflows/test-bump-callers.yml + reviewers: + - wei-hai + - deepme987 + + # groom: briefs, ledger, sandbox/broker, and the reusable + its own caller. + # synap5e approved the builder PR-body convention and holds the open sandbox + # PRs; luke-mino-altherr approved the direct-CLI agent phases; bigcat88 + # approved the caller-permissions correction. + - paths: + - .github/groom/** + - .github/workflows/groom.yml + - .github/workflows/ci-groom.yml + - .github/workflows/test-groom-scripts.yml + reviewers: + - synap5e + - luke-mino-altherr + - bigcat88 + + # cursor-review: the review panel, judge, prompts and auto-label. huntcsg + # approved the blocking gate and the judge-parsing recovery; sundar-svg + # approved the runs_on/self-hosted parameterization. + - paths: + - .github/cursor-review/** + - .github/workflows/cursor-review.yml + - .github/workflows/cursor-review-auto-label.yml + - .github/workflows/ci-cursor-review.yml + - .github/workflows/test-cursor-review-scripts.yml + reviewers: + - huntcsg + - sundar-svg + - deepme987 + + # PR-size gate + its Go classifier. guill carries the open pr-size token work; + # wei-hai approved it. + - paths: + - scripts/check-pr-size/** + - .github/workflows/pr-size.yml + - .github/workflows/test-pr-size.yml + reviewers: + - guill + - wei-hai + + # The assignee-routing machinery itself, including the generator that will + # later rewrite this very file. wei-hai approved both the expertise-aware + # assign-reviewers workflow and assign-prs-to-author; deepme987 approved the + # non-author-assignee skip. + - paths: + - .github/refresh-reviewers/** + - .github/workflows/assign-*.yml + # The `ci-` prefixed self-enrollment caller does NOT match `assign-*.yml`, + # so it needs naming outright — otherwise a change to this repo's own + # routing caller falls through to default_pool. + - .github/workflows/ci-assign-reviewers.yml + - .github/workflows/refresh-reviewers.yml + - .github/reviewers.yml + reviewers: + - wei-hai + - deepme987 + + # AGENTS.md standard checker + housekeeping workflows. + - paths: + - .github/agents-md-integrity/** + - .github/workflows/agents-md-integrity.yml + - .github/workflows/test-agents-md-integrity.yml + - .github/workflows/stale.yml + - .github/workflows/detect-unreviewed-merge.yml + # Its `ci-` prefixed self-enrollment caller, named outright for the same + # reason ci-assign-reviewers.yml is below: no glob here reaches it. + - .github/workflows/ci-detect-unreviewed-merge.yml + reviewers: + - wei-hai + - deepme987 + + # Repo-level docs and standards. Deliberately the broad pool, since an + # AGENTS.md or README change can come from any of the areas above. + - paths: + - AGENTS.md + - CLAUDE.md + - README.md + reviewers: + - wei-hai + - huntcsg + - deepme987 diff --git a/.github/workflows/ci-assign-reviewers.yml b/.github/workflows/ci-assign-reviewers.yml new file mode 100644 index 0000000..8709141 --- /dev/null +++ b/.github/workflows/ci-assign-reviewers.yml @@ -0,0 +1,76 @@ +name: CI - Assign Reviewers + +# Thin caller enrolling THIS repo in its own expertise-aware assignee routing. +# The logic lives in assign-reviewers.yml in this same repo; the caller pins it +# by full commit SHA like every external consumer — a local `uses: ./...` path +# would run the PR's OWN copy of the router, letting a PR rewrite who gets +# assigned to review it. Named ci-assign-reviewers.yml because the canonical +# caller path (assign-reviewers.yml) is occupied here by the reusable itself, +# same as ci-cursor-review.yml / ci-detect-unreviewed-merge.yml / ci-groom.yml. +# +# WHAT IT WRITES: the ASSIGNEE field, not reviewer requests — despite the +# workflow's name. "This org routes/alerts people via the ASSIGNEE field" +# (assign-reviewers.yml's header). That is the point of enrolling: PRs here were +# sitting with no assignee, so nothing routed them to anyone who could approve. +# +# The expertise map is .github/reviewers.yml in this repo (seeded from who has +# actually APPROVED merged PRs per area — see that file's header for why git +# history was NOT the source). It is read from the PR HEAD sha, so a PR's own +# edit to the map takes effect on that same PR. +# +# PREREQUISITES, both already provisioned here for cursor-review / the bumpers: +# vars.APP_ID + secrets.CLOUD_CODE_BOT_PRIVATE_KEY. The App token (not +# GITHUB_TOKEN) does the assigning — the reusable needs `Pull requests: write` +# to set assignees and this job therefore needs only `contents: read` itself. +# +# FORK PRs ARE NOT ROUTED, and that is deliberate. `pull_request` withholds +# repository secrets from fork-originated runs, so CLOUD_CODE_BOT_PRIVATE_KEY +# arrives empty and the App-token mint hard-fails — a red X and no routing +# either way. The job is guarded to skip cleanly on forks instead (see `if:` +# below). Routing them would mean `pull_request_target`, which runs privileged +# against untrusted HEAD and would turn the head-sha map read described above +# into a real escalation; not a trade worth making for assignee routing. +# +# PIN FRESHNESS (operator): the pin below only tracks assign-reviewers.yml once +# this repo is listed in vars.ASSIGN_REVIEWERS_CALLERS, which +# bump-assign-reviewers-callers.yml fans bumps out to. Until then the pin is +# updated by hand, same as any other consumer that isn't enrolled. + +on: + pull_request: + # `opened` covers ready PRs; `ready_for_review` catches a draft being + # promoted (the reusable skips drafts, so a PR opened as a draft would + # otherwise never get routed); `reopened` gives a second chance to a PR that + # was closed still unrouted (e.g. its `opened` run failed). Deliberately NOT + # `synchronize` — re-running on every push would fight a human who + # reassigned the PR on purpose. Re-firing is safe regardless: the reusable + # skips any PR that already has a non-author assignee. + types: [opened, ready_for_review, reopened] + +# Never let routing fight itself: two events for the same PR (opened, then +# ready_for_review moments later) would both read open-assignee load and could +# both assign, overshooting num_reviewers. +concurrency: + group: assign-reviewers-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + assign: + # Same-repo branches only. On a fork PR the secret below is empty (see + # "FORK PRs" in the header), so without this the App-token step fails and + # every external contribution carries a red check for a routing decision + # that could never have been made anyway. A skipped job reports as neutral. + if: github.event.pull_request.head.repo.full_name == github.repository + permissions: + # Only `contents: read` — the reusable reads .github/reviewers.yml from the + # head sha and does every write with the CLOUD_CODE_BOT App token, so no + # pull-requests/issues GITHUB_TOKEN scope is needed here. + contents: read + uses: Comfy-Org/github-workflows/.github/workflows/assign-reviewers.yml@29a81cab43766adc52a6f2adadcab63f0db7f6c5 # main @ 29a81ca — assign-reviewers.yml not on the v1 tag + with: + # ONE assignee per PR, matching how this repo's PRs are actually tracked + # (a single named owner, not a pair). Raise to 2 if single-owner PRs start + # stalling — the reusable's own default is 2. + num_reviewers: 1 + secrets: + CLOUD_CODE_BOT_PRIVATE_KEY: ${{ secrets.CLOUD_CODE_BOT_PRIVATE_KEY }} diff --git a/AGENTS.md b/AGENTS.md index 134ae61..4a41c18 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -95,9 +95,9 @@ tests — run the matching command above for whatever you touched. - **Scripts are the single source of truth**, loaded at run time from a pinned ref of THIS repo — never from the caller's checkout. That's what makes the reviewer/checker tamper-proof: a PR can't rewrite the logic judging it. The - self-enrollment callers (`ci-cursor-review.yml`, `ci-detect-unreviewed-merge.yml`) - deliberately pin a merged-main SHA instead of a local `./` path for the same - reason — do not "simplify" them to a local path. + self-enrollment callers (`ci-cursor-review.yml`, `ci-assign-reviewers.yml`, + `ci-detect-unreviewed-merge.yml`) deliberately pin a merged-main SHA instead + of a local `./` path for the same reason — do not "simplify" them to a path. - **One bumper, not several.** `bump-callers.sh` backs every fleet; the `bump-*-callers.yml` files are thin per-fleet wrappers (they stay separate so a `cursor-review.yml` change doesn't spuriously bump agents-md or pr-size