From 8da41445d9451d0e77ae1d4eec690bf1f1b756eb Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Mon, 3 Aug 2026 07:22:12 -0400 Subject: [PATCH 1/2] docs: require PR title and body for squash merges --- docs/release.md | 15 ++++++++++----- docs/repo-layout.md | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docs/release.md b/docs/release.md index 1c4f6ed..b2321f1 100644 --- a/docs/release.md +++ b/docs/release.md @@ -39,11 +39,16 @@ not auto-bump the minor and `fix:` does not auto-bump the patch; both simply answer "does this merge ship?" The `MAJOR.MINOR` line is human-controlled in `version.txt` and the patch is the CI run number (§2). -Squash-merge (`repo-layout.md` §6) means the **PR title is the commit that lands -on `main`**, so the PR title MUST be a conventional commit. This is enforced by -a **CI check on pull requests** (`ci.md` §2) — a local `commit-msg` hook never -sees the squashed PR title, so the gate has to live in CI. Commit messages MUST -NOT mention AI tooling (`repo-layout.md` §7). +With the required squash settings (`squash_merge_commit_title=PR_TITLE` and +`squash_merge_commit_message=PR_BODY`; see `repo-layout.md` §6), the **PR title +is the commit subject and the PR body is the body of the commit that lands on +`main`**. `PR_TITLE` is load-bearing: GitHub's `COMMIT_OR_PR_TITLE` option can +choose the lone commit subject for a one-commit squash PR, so a conventional +commit subject can bypass validation of a non-conventional PR title. The PR +title MUST therefore be a conventional commit. This is enforced by a **CI +check on pull requests** (`ci.md` §2) — a local `commit-msg` hook never sees the +squashed PR title, so the gate has to live in CI. Commit messages MUST NOT +mention AI tooling (`repo-layout.md` §7). ### §1.1 The shared commit grammar diff --git a/docs/repo-layout.md b/docs/repo-layout.md index 2af2bdd..8c3372a 100644 --- a/docs/repo-layout.md +++ b/docs/repo-layout.md @@ -201,6 +201,11 @@ defaults (no config file) is non-conformant. ### Merge settings - **Squash merge only** (no merge commits, no rebase merge). This is why the PR title must be a conventional commit (`release.md` §1). +- Set `squash_merge_commit_title=PR_TITLE` and + `squash_merge_commit_message=PR_BODY`. `COMMIT_OR_PR_TITLE` is prohibited: + for a one-commit squash PR it can select the lone commit subject instead of + the validated PR title and bypass the `pr-title` check. `PR_BODY` keeps the + validated PR body as the squash commit body. - **Delete branch on merge.** --- From 5cb1ed06371508fafbd15727ca198d1cc7051891 Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Mon, 3 Aug 2026 08:04:19 -0400 Subject: [PATCH 2/2] docs: align commit hygiene with squash settings --- docs/repo-layout.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/repo-layout.md b/docs/repo-layout.md index 8c3372a..fe262c2 100644 --- a/docs/repo-layout.md +++ b/docs/repo-layout.md @@ -216,12 +216,10 @@ defaults (no config file) is non-conformant. - Commit messages MUST NOT mention AI tooling (Claude, Anthropic, ChatGPT, Copilot, etc.). Enforce with a `commit-msg` hook that greps a blocklist and rejects on match. The hook alone is insufficient under squash merge: the - landing commit is built from the PR title plus, depending on the - squash-message setting, either the PR description or the branch's commit - messages — and the local hook sees only the last of those. The CI - `pr-title` check therefore greps the PR title and body against the same - blocklist (`ci.md` §2); between the two enforcement points every - squash-message mode is covered. Reference implementation (track it as + required `PR_TITLE` / `PR_BODY` settings build the landing commit from the PR + title and body, neither of which the local hook sees. The CI `pr-title` check + therefore greps both against the same blocklist (`ci.md` §2). Reference + implementation (track it as `scripts/hooks/commit-msg` and wire via `git config core.hooksPath scripts/hooks`): ```sh