From b2499d83424ecb567204d34d6bb45b8b4702ed9b Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Sun, 4 Oct 2026 13:51:51 +0400 Subject: [PATCH] ci: run CI and the PR title check for the merge queue (#867) * ci: run CI and the PR title check for the merge queue A merge queue tests each queued change on top of the ones ahead of it and waits for the required checks, CI result and PR title, on that merge group. Both workflows now run on merge_group, reading the base and head from either event. The title was checked on the pull request, so the queue run reports success without a title to read. * docs: explain stacked pull requests and the merge queue Agents had to be told what a stacked pull request is each time. CONTRIBUTING.md now covers stacks with gh stack, keeping them linear, and landing them through the merge queue; AGENTS.md links to it, and tools/AGENTS.md records that required checks must run on merge_group. * docs: shorten the stacked pull request and merge queue guidance The AGENTS.md line loads into every agent context, so keep it to the rules; CONTRIBUTING.md keeps the commands. --- .github/workflows/ci.yml | 10 ++++++---- .github/workflows/pr-title.yml | 5 ++++- AGENTS.md | 1 + CONTRIBUTING.md | 8 ++++++++ tools/AGENTS.md | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2274e3987..28752e7a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ name: CI on: # Every base, so stacked pull requests are checked against the PR below them. pull_request: + # The merge queue checks each queued change on top of the ones ahead of it. + merge_group: permissions: contents: read @@ -28,13 +30,13 @@ jobs: - name: Fetch comparison base env: - CI_BASE_SHA: ${{ github.event.pull_request.base.sha }} + CI_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} run: git fetch --no-tags --depth=1 origin "$CI_BASE_SHA" - name: Select validation scope id: classify env: - CI_BASE_SHA: ${{ github.event.pull_request.base.sha }} + CI_BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} run: bun tools/ci/policy/src/classify.ts commit-messages: @@ -49,8 +51,8 @@ jobs: - uses: ./.github/actions/setup-bun - name: Validate PR commit messages env: - BASE_SHA: ${{ github.event.pull_request.base.sha }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} + BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }} run: | if ! bun run check:commits --from "$BASE_SHA" --to "$HEAD_SHA" --verbose; then echo '::error title=Commit messages::Check the messages listed above. Use type(scope): description, for example fix: preserve selection. See CONTRIBUTING.md#commit-messages.' diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 8c893a608..8538074b9 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -4,12 +4,14 @@ on: # Every base, so stacked pull requests are checked against the PR below them. pull_request: types: [opened, synchronize, reopened, edited] + # A queued change was checked as a pull request; the queue still needs the result. + merge_group: permissions: contents: read concurrency: - group: pr-title-${{ github.event.pull_request.number }} + group: pr-title-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: @@ -23,6 +25,7 @@ jobs: persist-credentials: false - uses: ./.github/actions/setup-bun - name: Validate PR title + if: github.event_name == 'pull_request' env: PR_TITLE: ${{ github.event.pull_request.title }} COMMITLINT_PR_TITLE: '1' diff --git a/AGENTS.md b/AGENTS.md index e253446eb..1e1a9b11e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -100,6 +100,7 @@ Follow `packages/docs/development/testing.md` and `tests/AGENTS.md`. Package-loc - Conventional Commits (`feat`, `fix`, `refactor`, `perf`, `docs`, `test`, `build`, `ci`, `chore`); short imperative subjects, rationale in the body; release commits are exactly `Release vX.Y.Z`. Preserve product casing: DOM/CSS, HTML, JSX, Tailwind, Kiwi, `.fig`, MCP, CLI, AI, ACP, i18n. Validate with `bun run check:commits --last` or `--from`/`--to`. - PR titles are Conventional Commits because GitHub uses them as merge subjects; branch-update merges get explicit subjects such as `chore: merge master into `. Do not rewrite published history solely to normalize messages. - Disclose AI assistance in the PR's AI assistance section, never as commit authorship, `Co-authored-by` trailers, promotional signatures, or session links. Preserve human co-author credits and third-party notices. +- Stacked pull requests: `gh stack`, rebase never merge, land from the top layer via the merge queue (`CONTRIBUTING.md#stacked-pull-requests`). - Issues, PR descriptions, and public comments use concise concrete technical prose: lead with the problem and outcome, add a short example when needed, avoid filler, promotional claims, decorative emojis, unnecessary tables, and file-by-file inventories. Link long logs or design notes. ## Code review diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4271c6694..736eddebc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,14 @@ Pull requests must be reviewable without guessing the author's intent. - Complete the AI assistance section. If an LLM materially helped create or modify the PR, list the model names you know. Write `None` otherwise. This is review context, not authorship attribution; prompts and transcripts are not required. - Keep the body primarily in English. Code identifiers, file paths, logs, error messages, and short quoted examples may use their original language. +### Stacked pull requests + +A stack is a chain of pull requests: the bottom one targets `master`, each one above targets the branch below. CI and branch protection treat every layer as targeting `master`. + +- Manage stacks with [`gh stack`](https://github.com/github/gh-stack) (`init`, `add`, `submit`, `sync`; add `--remote origin` with several remotes); a stacked pull request's base cannot be edited. +- Keep it linear: rebase the layers when `master` or a lower branch moves; never merge `master` in. +- Merge from the top layer through the merge queue; it lands every layer below too. Auto-merge is unavailable for stacks. + ### Reviewability Do not submit placeholder PRs. Remove template comments before opening a PR. Do not leave dangling issue references such as `Fixes #`, `TODO`, `TBD`, empty headings, unfilled sections, or similar unfinished text. diff --git a/tools/AGENTS.md b/tools/AGENTS.md index 03451932d..3299e9114 100644 --- a/tools/AGENTS.md +++ b/tools/AGENTS.md @@ -20,6 +20,7 @@ Private tooling lives under `tools///{src,tests}`; Steiger enforce - `.github/workflows/ci.yml` and `heavy-tests.yml` define validation gates. PR CI always classifies changed paths through `tools/ci/policy/src/policy.ts`: root docs, package READMEs, every `AGENTS.md`, `packages/docs` Markdown and assets, and skill Markdown are docs-only; runtime prompt Markdown, executable examples, configuration, and unknown paths require code validation. - Docs-only changes run documentation integrity and the docs build; everything else runs the full suites. The aggregate `CI result` gate requires successful classification and every applicable job; failures, cancellations, and unexpected skips cannot pass. Do not restore workflow-level path filtering on required CI. - `commitlint.config.ts` enforces commit structure in the **Commit messages** job; the separate **PR title** workflow validates titles. Preserve the `Release vX.Y.Z` exception and product casing when changing rules; the known AI co-author check does not rewrite base history. Gate policy lives in `tools/ci/policy/src/policy.ts`. +- Required checks must also run on `merge_group`, the merge queue's event; read base and head from `merge_group.base_sha`/`head_sha` there (`ci.yml`, `pr-title.yml`). - App and docs production workflows run on `v*` tags or `workflow_dispatch`, not ordinary `master` pushes. `build.yml` checks the tooling out under `.pipeline/` and runs `tools/release/release-packages` from there. ## Releases