From b18df56665f848a38a44e491d04c47e28b85b791 Mon Sep 17 00:00:00 2001 From: Vincent De Smet Date: Mon, 1 Jun 2026 15:58:01 +0800 Subject: [PATCH] ci: enforce conventional commit PR titles --- .github/workflows/pr-title.yml | 49 ++++++++++++++++++++++++++++++++++ CLAUDE.md | 2 ++ 2 files changed, 51 insertions(+) create mode 100644 .github/workflows/pr-title.yml diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml new file mode 100644 index 0000000..8a3a11c --- /dev/null +++ b/.github/workflows/pr-title.yml @@ -0,0 +1,49 @@ +# Validate that every PR title is a Conventional Commit. +# +# We squash-merge with the PR title as the commit subject (repo setting +# squash_merge_commit_title=PR_TITLE), and release-please derives the version +# bump + changelog from that subject. So a non-conventional PR title would +# silently break versioning — this check is the guard. +# +# pull_request_target is used (not pull_request) so the check still runs for +# fork PRs; it only reads the title via the API and never checks out PR code, +# so there is no untrusted-code execution risk. +name: pr-title + +on: + pull_request_target: + types: [opened, edited, reopened, synchronize] + +permissions: + pull-requests: read + +concurrency: + group: pr-title-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + validate: + name: conventional PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v6.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + # Keep in sync with release-please's understanding of types. + types: | + feat + fix + docs + chore + ci + refactor + perf + test + build + revert + # Subject must not be empty and not start with an uppercase letter. + subjectPattern: ^(?![A-Z]).+$ + subjectPatternError: | + The subject "{subject}" must start with a lowercase letter, e.g. + "fix: correct the origin resolver", not "fix: Correct ...". diff --git a/CLAUDE.md b/CLAUDE.md index 1910e2c..5b74ee6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,8 @@ Features follow SpecLedger: **Specify → Clarify → Plan → Tasks → Review **Commit & PR conventions.** Conventional prefixes (`feat:`, `fix:`, `chore:`, `docs:`), imperative subjects ≤72 chars, scoped to the feature (e.g. `docs(002): …`). Reference related issues in the body; call out migrations / new binaries explicitly. PRs carry a concise summary + testing evidence (`make test-unit`, `make test-integration`) and a CLI transcript when behavior changes. +**PR titles are load-bearing.** The repo is **squash-merge only**, and the squash commit subject is the **PR title** (GitHub setting `squash_merge_commit_title=PR_TITLE`). `release-please` derives the version bump + changelog from that subject, so **every PR title must be a Conventional Commit** — enforced by the `pr-title` workflow (`.github/workflows/pr-title.yml`). Only `fix:` (→ patch), `feat:` (→ minor), and `!`/`BREAKING CHANGE` (→ major while ≥1.0.0) cut a release; `docs:`/`chore:`/`ci:`/`refactor:`/`test:`/`build:` land without a release. Don't dress a non-functional change as `fix:`/`feat:` to force a release. + **Work-item tracking.** The durable, team-visible record lives in the SpecLedger issue tracker — `sl issue`, stored per-spec in `specledger//issues.jsonl` (committed to git). The agent's in-session task list (the `Task*` tools) is an ephemeral execution aid, not a substitute for that committed record.