From e136407f02b3f7f04ce081f7afcc709ffec82632 Mon Sep 17 00:00:00 2001 From: Zijian Zhou Date: Sun, 27 Sep 2026 10:59:26 +0800 Subject: [PATCH] ci(review): replace the Merge Oncall with CODEOWNERS review routing Every pull request was assigned to one Merge Oncall, who also owned the default CODEOWNERS rule, so one maintainer merged most pull requests. - Remove merge-oncall.json, the assignment workflow, and its validation. - Give each CODEOWNERS area a primary and a backup owner; one approval from either satisfies the area. - Authors with write access merge their own pull request after one approval; an approving code owner merges external contributions. - Add a reminder, posted every three days, that lists ready pull requests idle for three days and mentions whoever the next step waits on. Refs #656 Co-Authored-By: Claude Opus 5.5 --- .github/CODEOWNERS | 89 ++++++------- .github/MAINTAINER.md | 105 ++++++++-------- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/merge-oncall.json | 3 - .github/scripts/check_community_files.py | 20 +-- .github/workflows/assign-merge-oncall.yml | 82 ------------ .github/workflows/review-reminders.yml | 147 ++++++++++++++++++++++ CONTRIBUTING.md | 6 +- 8 files changed, 256 insertions(+), 198 deletions(-) delete mode 100644 .github/merge-oncall.json delete mode 100644 .github/workflows/assign-merge-oncall.yml create mode 100644 .github/workflows/review-reminders.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 958a3e99e..554749321 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,58 +1,63 @@ -# Default owner for files without a more specific area rule. -* @BobbyZhouZijian +# Each area lists a primary owner followed by a backup owner. GitHub requests +# review from every listed owner, and one approval from any of them is enough. +# The last matching rule wins, so the more specific area rules below override +# the default. + +# Default owners for files without a more specific area rule. +* @BobbyZhouZijian @quao627 # Community process, repository automation, packaging, and deployment. -/.github/ @BobbyZhouZijian -/docker/ @BobbyZhouZijian -/pyproject.toml @BobbyZhouZijian -/README.md @BobbyZhouZijian -/README.zh.md @BobbyZhouZijian -/README.i18n.yaml @BobbyZhouZijian +/.github/ @BobbyZhouZijian @Benjamin-eecs +/docker/ @BobbyZhouZijian @MikeZheng777 +/pyproject.toml @BobbyZhouZijian @Benjamin-eecs +/README.md @BobbyZhouZijian @Benjamin-eecs +/README.zh.md @BobbyZhouZijian @Benjamin-eecs +/README.i18n.yaml @BobbyZhouZijian @Benjamin-eecs # Core product areas. Keep these entries separate so ownership can be # delegated one subsystem at a time as the maintainer group grows. -/reef/service/ @BobbyZhouZijian -/reef/scenario/ @BobbyZhouZijian -/reef/core/ @BobbyZhouZijian -/recipes/basic/ @BobbyZhouZijian -/third_party/ @BobbyZhouZijian +/reef/service/ @BobbyZhouZijian @quao627 +/reef/scenario/ @BobbyZhouZijian @quao627 +/reef/core/ @BobbyZhouZijian @quao627 +/recipes/basic/ @BobbyZhouZijian @quao627 +/third_party/ @BobbyZhouZijian @Benjamin-eecs # Harness-facing product layer. -/reef/harness/ @Benjamin-eecs -/recipes/sao/ @Benjamin-eecs -/recipes/tttd/ @Benjamin-eecs -/recipes/openclawrl/ @Benjamin-eecs -/reef/train/cordis_backend/ @Benjamin-eecs -/reef/surface/ @Benjamin-eecs -/third_party/cordis/ @Benjamin-eecs -/tutorials/evolve-your-harness/ @Benjamin-eecs -/recipes/skillclaw/ @Benjamin-eecs -/recipes/gepa/ @simonucl +/reef/harness/ @Benjamin-eecs @quao627 +/recipes/sao/ @Benjamin-eecs @MikeZheng777 +/recipes/tttd/ @Benjamin-eecs @MikeZheng777 +/recipes/openclawrl/ @Benjamin-eecs @MikeZheng777 +/reef/train/cordis_backend/ @Benjamin-eecs @hanfeiyu +/reef/surface/ @Benjamin-eecs @quao627 +/third_party/cordis/ @Benjamin-eecs @quao627 +/tutorials/evolve-your-harness/ @Benjamin-eecs @quao627 +/recipes/skillclaw/ @Benjamin-eecs @quao627 +/recipes/gepa/ @simonucl @Benjamin-eecs # Runnable examples of the weight methods (the method packages above stay # with the product layer). -/recipes/sao/examples/ @BobbyZhouZijian -/recipes/tttd/examples/ @BobbyZhouZijian -/recipes/openclawrl/examples/ @BobbyZhouZijian +/recipes/sao/examples/ @BobbyZhouZijian @MikeZheng777 +/recipes/tttd/examples/ @BobbyZhouZijian @MikeZheng777 +/recipes/openclawrl/examples/ @BobbyZhouZijian @MikeZheng777 # Training execution, artifact handling, and pipeline reliability. -/reef/train/ @hanfeiyu -/reef/runtime/ @hanfeiyu -/reef/artifact/ @hanfeiyu -/reef/dispatcher.py @hanfeiyu -/recipes/openclawrl/examples/openclawrl/serve.yaml @hanfeiyu +/reef/train/ @hanfeiyu @MikeZheng777 +/reef/runtime/ @hanfeiyu @MikeZheng777 +/reef/artifact/ @hanfeiyu @MikeZheng777 +/reef/dispatcher.py @hanfeiyu @MikeZheng777 +/recipes/openclawrl/examples/openclawrl/serve.yaml @hanfeiyu @MikeZheng777 -# Documentation defaults to the Merge Oncall. Domain references override it. -/docs/ @BobbyZhouZijian -/docs/user-guide/evolve-your-harness.rst @Benjamin-eecs -/docs/developer-guide/harness-adapters.rst @Benjamin-eecs -/docs/developer-guide/write-a-harness-method.rst @Benjamin-eecs -/docs/developer-guide/write-a-recipe.rst @Benjamin-eecs -/docs/user-guide/recipes.rst @Benjamin-eecs -/docs/user-guide/recipes/ @Benjamin-eecs -/docs/user-guide/evolve-your-model.rst @hanfeiyu -/docs/developer-guide/loss-families.rst @hanfeiyu -/docs/reference/cli.rst @BobbyZhouZijian +# Documentation defaults to these owners. Domain references override it. +/docs/ @BobbyZhouZijian @Benjamin-eecs +/docs/user-guide/evolve-your-harness.rst @Benjamin-eecs @quao627 +/docs/developer-guide/harness-adapters.rst @Benjamin-eecs @quao627 +/docs/developer-guide/write-a-harness-method.rst @Benjamin-eecs @quao627 +/docs/developer-guide/write-a-recipe.rst @Benjamin-eecs @quao627 +/docs/user-guide/recipes.rst @Benjamin-eecs @quao627 +/docs/user-guide/recipes/ @Benjamin-eecs @quao627 +/docs/user-guide/evolve-your-model.rst @hanfeiyu @MikeZheng777 +/docs/developer-guide/loss-families.rst @hanfeiyu @MikeZheng777 +/docs/reference/cli.rst @BobbyZhouZijian @quao627 # python-api.rst merges the former recipe/surface and batch/processor/preparer # pages, so it keeps both prior owners. /docs/reference/python-api.rst @Benjamin-eecs @hanfeiyu diff --git a/.github/MAINTAINER.md b/.github/MAINTAINER.md index 39acac75b..e0270f579 100644 --- a/.github/MAINTAINER.md +++ b/.github/MAINTAINER.md @@ -8,16 +8,27 @@ of the project as it grows. Roles describe work, not status. One person may hold more than one role for a pull request. -## Current Merge Oncall +## Review routing -The current Merge Oncall is configured in -[`merge-oncall.json`](merge-oncall.json): **@BobbyZhouZijian**. +Reef has no single on-call maintainer. [`CODEOWNERS`](CODEOWNERS) routes each +pull request: GitHub requests review from the code owners of every changed +path when the pull request is ready for review. Each area lists a primary owner +and a backup owner, and an approval from either one satisfies that area. +Changes outside a specific area fall back to the default owners listed at the +top of that file. -When a pull request is opened, reopened, or marked ready for review, the -`assign merge oncall` workflow assigns the configured account and posts or -updates a single on-call comment. The workflow uses `pull_request_target` -without checking out or executing pull-request code, so assignment also works -for contributions from forks without exposing a write token to their code. +Who merges depends on the author: + +- An author with repository write access merges their own pull request after + receiving at least one approval and meeting the acceptance conditions below. +- A pull request from an external contributor is merged by one of the code + owners who approved it. + +The `remind reviewers of idle pull requests` workflow runs every three days. It +posts one comment on a fixed tracking issue listing ready pull requests without +activity for three days and mentions whoever the next step waits on: the +requested reviewers, the author when changes are requested or no reviewer is +requested, or the merging person after approval. ## Roles @@ -29,7 +40,8 @@ whole. They can merge and close pull requests and are responsible for: - deciding whether a contribution fits Reef's direction and maintenance budget; - identifying when an RFC is required; -- selecting reviewers for affected areas; +- requesting additional reviewers when path rules do not cover an affected + area; - resolving cross-area or project-level disagreements; - merging changes when the acceptance conditions are met; and - reverting or coordinating a fix when a merged change causes a regression. @@ -37,21 +49,18 @@ whole. They can merge and close pull requests and are responsible for: An approval from a maintainer accepts responsibility for the change, not only its local implementation. -### Merge Oncall +### Code owner -The Merge Oncall is the maintainer actively responsible for moving one pull -request toward a clear outcome. The on-call: +Code owners are the maintainers listed for an area in +[`CODEOWNERS`](CODEOWNERS). For pull requests in their area, a code owner: - performs the initial scope and RFC check; -- identifies affected areas and requests the right reviews; +- reviews the change or hands the review to the backup owner; - distinguishes blocking feedback from optional follow-up work; -- keeps the pull request state clear when review stalls or changes direction; -- confirms that acceptance conditions are met; and -- merges, closes, or hands the pull request to another maintainer. +- requests review from other areas the change materially affects; and +- merges or closes external contributions they approved. -The Merge Oncall is an active coordination role. An area reviewer may protect -a specific subsystem without being responsible for driving the entire pull -request. +Code owners must have repository write permission. ### Area reviewer @@ -60,10 +69,9 @@ operational concern. They are responsible for reviewing changes in that area for correctness, compatibility, tests, documentation, and maintainability. Area reviewers do not need merge permission. Their approval is a technical -signal to the Merge Oncall; the final repository decision remains with a -maintainer. An area reviewer must have repository write permission before they -can be listed in [`CODEOWNERS`](CODEOWNERS), but qualified reviewers may still -be requested manually without being code owners. +signal to the code owners; the final repository decision remains with a +maintainer. Qualified reviewers may be requested manually without being code +owners. Current primary area ownership is: @@ -72,7 +80,9 @@ Current primary area ownership is: - **@hanfeiyu** — training execution, runtime integration, artifact handling, pipeline reliability, and their focused documentation; and - **@BobbyZhouZijian** — service and core boundaries, scenario lifecycle, - repository automation, packaging, and cross-cutting changes. + repository automation, and packaging. + +[`CODEOWNERS`](CODEOWNERS) lists the backup owner for each area. ## Issue process @@ -166,19 +176,15 @@ The maintainer may ask the author to narrow the change, move design discussion to the appropriate issue form, or close work that does not fit project direction. The reason should be stated clearly. -### 3. On-call assignment and review routing +### 3. Review routing -The assignment workflow makes the configured Merge Oncall the pull request -assignee. The Merge Oncall requests reviews for every materially affected area. -GitHub also requests reviewers using [`CODEOWNERS`](CODEOWNERS). Its current -area-specific entries route harness-facing work to @Benjamin-eecs and training -and reliability work to @hanfeiyu, while cross-cutting work stays with the -Merge Oncall. The pull-request labeler applies the corresponding `area:*`, -`dependencies`, and `type: tests` labels from +GitHub requests reviewers using [`CODEOWNERS`](CODEOWNERS), as described in +[Review routing](#review-routing). The pull-request labeler applies the +corresponding `area:*`, `dependencies`, and `type: tests` labels from [`labeler.yml`](labeler.yml), including when the changed-file set is updated. -Labels are routing hints rather than approvals. The on-call remains responsible -for checking that every affected area has a qualified reviewer and requesting -additional reviews when path rules are insufficient. +Labels are routing hints rather than approvals. The author and the requested +code owners request additional reviews when path rules do not cover an +affected area. ### 4. Technical review @@ -231,8 +237,8 @@ run the full matrix. Manual dispatches are diagnostic runs; use the original PR workflow's full rerun to satisfy merge checks for that PR. Required checks must pass on the reviewed revision. CI is a gate, not a -substitute for review. The Merge Oncall may request focused or environment- -specific validation when the standard checks do not cover the affected path. +substitute for review. Reviewers may request focused or environment-specific +validation when the standard checks do not cover the affected path. Dependabot groups weekly GitHub Actions, Python, documentation-site, and Docker updates into bounded pull-request queues. Dependency pull requests follow the same ownership, review, and required-check rules as author-submitted changes; @@ -243,9 +249,8 @@ they are not merged automatically. A pull request is ready to merge when: - an RFC is accepted if the change requires one; -- at least one maintainer approves the pull request; -- every materially affected area has an approval from a maintainer or an area - reviewer selected by the Merge Oncall; +- at least one code owner of an affected area or another maintainer approves + the pull request; - all required checks pass; - no blocking review or design discussion remains; - tests cover the behavior change; and @@ -258,8 +263,9 @@ and rationale should be recorded on the pull request. ### 7. Merge and follow-up -A maintainer merges the accepted revision and ensures linked issues are closed -or updated. Deferred work should be captured in explicit follow-up issues +An author with repository write access merges their own accepted pull +request; for an external contribution, an approving code owner merges it. The +person who merges ensures linked issues are closed or updated. Deferred work should be captured in explicit follow-up issues rather than left only in review comments. If a merge causes a serious regression, restoring a known-good state takes @@ -270,16 +276,17 @@ design or debugging in a follow-up pull request. Small documentation fixes, clear test-only changes, reversions, release blockers, and fixes for active regressions may receive an expedited review. The -Merge Oncall must state why the change is being fast-tracked and which review -or validation remains required. Any exception is still subject to repository +approving reviewer must state why the change is being fast-tracked and which +review or validation remains required. Any exception is still subject to repository permissions and branch protections. ## Stalled pull requests -When a pull request is waiting on the author, the Merge Oncall should identify -the remaining work and leave a clear prompt before closing it. When it is -waiting on review, the on-call should request another qualified reviewer or -hand the role to another maintainer. +When a pull request is waiting on the author, the reviewer should identify the +remaining work and leave a clear prompt before closing it. When it is waiting on +review, the requested code owner should review it or ask the area's backup owner +to take over. The reminder described in [Review routing](#review-routing) +lists pull requests in either state. The stale workflow marks a non-draft pull request after 60 days without activity and closes it after another 21 days. Drafts and work labeled `KIV`, @@ -304,7 +311,7 @@ does not delete labels that are absent from the catalog. ## Disagreements and escalation Reviewers should first seek a decision through concrete technical discussion -on the issue, RFC, or pull request. The Merge Oncall may request additional +on the issue, RFC, or pull request. Any maintainer may request additional area review when expertise or ownership is disputed. Maintainers make the final repository-level decision and record the rationale when consensus is not possible. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c1f7d18a3..89b9ec9c2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -41,6 +41,6 @@ remains responsible for every submitted line, claim, and test result. ## Review and merge -The Merge Oncall is assigned automatically. See the +Reviewers are requested automatically from `CODEOWNERS`. See the [maintenance model](https://github.com/Human-Agent-Society/reef/blob/main/.github/MAINTAINER.md) for review and merge requirements. diff --git a/.github/merge-oncall.json b/.github/merge-oncall.json deleted file mode 100644 index 48b3aae2d..000000000 --- a/.github/merge-oncall.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "login": "BobbyZhouZijian" -} diff --git a/.github/scripts/check_community_files.py b/.github/scripts/check_community_files.py index 4048c54da..642223f4f 100644 --- a/.github/scripts/check_community_files.py +++ b/.github/scripts/check_community_files.py @@ -58,9 +58,8 @@ def pattern_matches(pattern: str, paths: list[str]) -> bool: return any(fnmatch.fnmatchcase(path, normalized) for path in paths) -def validate_codeowners(paths: list[str]) -> set[str]: +def validate_codeowners(paths: list[str]) -> None: codeowners = ROOT / ".github" / "CODEOWNERS" - owners: set[str] = set() errors = 0 for number, raw_line in enumerate(codeowners.read_text().splitlines(), 1): line = raw_line.strip() @@ -82,22 +81,8 @@ def validate_codeowners(paths: list[str]) -> set[str]: if not owner.startswith("@") or not all(GITHUB_LOGIN.fullmatch(part) for part in owner[1:].split("/", 1)): fail(f"CODEOWNERS:{number}: invalid owner: {owner}") errors += 1 - owners.add(owner.removeprefix("@").split("/", 1)[-1]) if errors: raise ValueError("invalid CODEOWNERS") - return owners - - -def validate_oncall(owners: set[str]) -> None: - path = ROOT / ".github" / "merge-oncall.json" - config = json.loads(path.read_text()) - if set(config) != {"login"}: - raise ValueError("merge-oncall.json must contain exactly the 'login' key") - login = config["login"] - if not isinstance(login, str) or not GITHUB_LOGIN.fullmatch(login): - raise ValueError("merge-oncall.json contains an invalid GitHub login") - if login not in owners: - raise ValueError("the merge oncall must also appear in CODEOWNERS") def validate_labels() -> set[str]: @@ -205,8 +190,7 @@ def validate_workflows() -> None: def main() -> int: try: paths = tracked_files() - owners = validate_codeowners(paths) - validate_oncall(owners) + validate_codeowners(paths) labels = validate_labels() validate_issue_templates(labels) validate_workflows() diff --git a/.github/workflows/assign-merge-oncall.yml b/.github/workflows/assign-merge-oncall.yml deleted file mode 100644 index e312873d6..000000000 --- a/.github/workflows/assign-merge-oncall.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: assign merge oncall - -on: - pull_request_target: - types: [opened, reopened, ready_for_review] - -permissions: - contents: read - issues: write - pull-requests: write - -jobs: - assign: - if: github.event.pull_request.state == 'open' - runs-on: blacksmith-4vcpu-ubuntu-2404 - timeout-minutes: 2 - steps: - - name: Assign and announce the merge oncall - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const marker = ""; - const configPath = ".github/merge-oncall.json"; - const { owner, repo } = context.repo; - const issue_number = context.issue.number; - const ref = context.payload.pull_request.base.ref; - - const response = await github.rest.repos.getContent({ - owner, - repo, - path: configPath, - ref, - }); - if (Array.isArray(response.data) || response.data.type !== "file") { - core.setFailed(`${configPath} is not a file`); - return; - } - - const config = JSON.parse( - Buffer.from(response.data.content, "base64").toString("utf8"), - ); - const oncall = config.login; - if (typeof oncall !== "string" || !/^[A-Za-z0-9-]+$/.test(oncall)) { - core.setFailed(`${configPath} must contain a valid GitHub login`); - return; - } - - await github.rest.issues.addAssignees({ - owner, - repo, - issue_number, - assignees: [oncall], - }); - - const body = `${marker}\nMerge Oncall: @${oncall}\n\n` + - "The merge oncall will route reviews and keep this pull request " + - "moving toward a clear decision."; - const comments = await github.paginate( - github.rest.issues.listComments, - { owner, repo, issue_number, per_page: 100 }, - ); - const existing = comments.find((comment) => - comment.body?.includes(marker), - ); - - if (existing) { - if (existing.body !== body) { - await github.rest.issues.updateComment({ - owner, - repo, - comment_id: existing.id, - body, - }); - } - } else { - await github.rest.issues.createComment({ - owner, - repo, - issue_number, - body, - }); - } diff --git a/.github/workflows/review-reminders.yml b/.github/workflows/review-reminders.yml new file mode 100644 index 000000000..08cfa26aa --- /dev/null +++ b/.github/workflows/review-reminders.yml @@ -0,0 +1,147 @@ +name: remind reviewers of idle pull requests + +on: + schedule: + - cron: "0 1 * * *" + workflow_dispatch: + +permissions: + contents: read + issues: write + pull-requests: read + +jobs: + remind: + if: github.repository == 'Human-Agent-Society/reef' + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 5 + steps: + - name: Post the list of idle pull requests every three days + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const marker = ""; + const idleDays = 3; + const writeAssociations = new Set(["OWNER", "MEMBER", "COLLABORATOR"]); + const { owner, repo } = context.repo; + const now = Date.now(); + // Cron cannot express an exact three-day interval across month + // boundaries, so the daily schedule posts on every third UTC day. + const reminderIntervalDays = 3; + if (context.eventName === "schedule" && Math.floor(now / 86400000) % reminderIntervalDays !== 0) { + core.info("Not a reminder day"); + return; + } + const cutoffDate = new Date(now - idleDays * 86400000).toISOString().slice(0, 10); + + const query = `query($search: String!, $cursor: String) { + search(query: $search, type: ISSUE, first: 50, after: $cursor) { + pageInfo { hasNextPage endCursor } + nodes { + ... on PullRequest { + number + title + updatedAt + authorAssociation + author { login } + reviewRequests(first: 20) { + nodes { + requestedReviewer { + ... on User { login } + ... on Team { combinedSlug } + } + } + } + latestReviews(first: 20) { + nodes { state author { login } } + } + } + } + } + }`; + const search = `repo:${owner}/${repo} is:pr is:open draft:false updated:<${cutoffDate}`; + const pullRequests = []; + let cursor = null; + do { + const result = await github.graphql(query, { search, cursor }); + pullRequests.push(...result.search.nodes); + cursor = result.search.pageInfo.hasNextPage ? result.search.pageInfo.endCursor : null; + } while (cursor); + + // Waiting on the author after requested changes; waiting on the + // author (with write access) or an approving code owner (for + // external contributions) to merge after approval; otherwise + // waiting on the requested reviewers, or on the author to request + // one when nobody is requested. + const sections = { + review: { title: "Waiting for review", lines: [] }, + author: { title: "Waiting for the author", lines: [] }, + merge: { title: "Approved, waiting to merge", lines: [] }, + }; + for (const pullRequest of pullRequests.sort((a, b) => a.number - b.number)) { + const author = pullRequest.author ? `@${pullRequest.author.login}` : "the author"; + const reviews = pullRequest.latestReviews.nodes; + const approvers = reviews + .filter((review) => review.state === "APPROVED" && review.author) + .map((review) => `@${review.author.login}`); + let section; + let mentions; + if (reviews.some((review) => review.state === "CHANGES_REQUESTED")) { + section = sections.author; + mentions = [author]; + } else if (approvers.length > 0) { + section = sections.merge; + mentions = writeAssociations.has(pullRequest.authorAssociation) ? [author] : approvers; + } else { + section = sections.review; + const requested = pullRequest.reviewRequests.nodes + .map((request) => request.requestedReviewer) + .filter((reviewer) => reviewer) + .map((reviewer) => `@${reviewer.login ?? reviewer.combinedSlug}`); + mentions = requested.length > 0 ? requested : [author]; + } + const idleDayCount = Math.floor((now - Date.parse(pullRequest.updatedAt)) / 86400000); + section.lines.push( + `- #${pullRequest.number} ${pullRequest.title} — ${mentions.join(" ")} ` + + `(idle ${idleDayCount} days)`, + ); + } + + const lines = Object.values(sections).flatMap((section) => + section.lines.length > 0 ? [`**${section.title}**`, "", ...section.lines, ""] : [], + ); + if (lines.length === 0) { + core.info(`No ready pull request has been idle for ${idleDays} days`); + return; + } + + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner, + repo, + state: "open", + creator: "github-actions[bot]", + per_page: 100, + }); + let issue = issues.find((candidate) => !candidate.pull_request && candidate.body?.includes(marker)); + if (!issue) { + const created = await github.rest.issues.create({ + owner, + repo, + title: "[Task] Pull request review reminders", + body: + `${marker}\nEvery ${reminderIntervalDays} days, automation lists ready pull requests without ` + + `activity for ${idleDays} days and mentions whoever the next step is ` + + "waiting on. See the " + + "[maintenance model](https://github.com/Human-Agent-Society/reef/blob/main/.github/MAINTAINER.md).", + labels: ["type: task", "status: keep-open"], + }); + issue = created.data; + } + + const today = new Date(now).toISOString().slice(0, 10); + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issue.number, + body: [`### Pull requests idle for ${idleDays}+ days (${today})`, "", ...lines].join("\n"), + }); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2d0384533..4d9cab3a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -358,9 +358,9 @@ use the PR's `ci` rerun to satisfy its merge checks. ## Review and acceptance -Maintainers route reviews according to the affected areas. The -[maintenance model](.github/MAINTAINER.md) defines Maintainer, Merge Oncall, -and Area Reviewer responsibilities and the merge process. Reviewers may ask for +GitHub requests reviews from the code owners of the affected areas. The +[maintenance model](.github/MAINTAINER.md) defines Maintainer, Code Owner, and +Area Reviewer responsibilities and the merge process. Reviewers may ask for changes to correctness, interfaces, tests, documentation, compatibility, operability, or scope. Authors are expected to respond to substantive comments and to say when a request is unclear or when they disagree.