From 411720a1f432a61811573c7088368496bc7207c1 Mon Sep 17 00:00:00 2001 From: Yuriy Kirillov Date: Wed, 12 Aug 2026 04:13:57 +0200 Subject: [PATCH] feat: validate conventional pull request titles --- .github/dependabot.yml | 12 +++++++ .github/workflows/commit-shared.yml | 12 +++++++ .github/workflows/commit.yml | 9 +++++ AGENTS.md | 4 ++- README.md | 51 +++++++++++++++++++++++++---- config/embedder.yml | 14 +++++++- config/github.yml | 5 +++ test/test_github_config.py | 14 +++++++- 8 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/commit-shared.yml create mode 100644 .github/workflows/commit.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 724c02b..39edbc6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,6 +4,9 @@ updates: - package-ecosystem: github-actions directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -11,6 +14,9 @@ updates: - package-ecosystem: pre-commit directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -18,6 +24,9 @@ updates: - package-ecosystem: pip directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -25,6 +34,9 @@ updates: - package-ecosystem: bundler directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" diff --git a/.github/workflows/commit-shared.yml b/.github/workflows/commit-shared.yml new file mode 100644 index 0000000..8e6d782 --- /dev/null +++ b/.github/workflows/commit-shared.yml @@ -0,0 +1,12 @@ +name: Commit (shared) +on: + workflow_call: +jobs: + pull-request-title: + runs-on: ubuntu-slim + permissions: + pull-requests: read + steps: + - uses: amannn/action-semantic-pull-request@v6 + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/commit.yml b/.github/workflows/commit.yml new file mode 100644 index 0000000..301235e --- /dev/null +++ b/.github/workflows/commit.yml @@ -0,0 +1,9 @@ +name: Commit +on: + # Baseline uses pull_request so changes to the shared workflow validate themselves. + # Consumers should use pull_request_target to load trusted workflow code from main. + pull_request: + types: [opened, reopened, edited, synchronize] +jobs: + pull-request-title: + uses: ./.github/workflows/commit-shared.yml diff --git a/AGENTS.md b/AGENTS.md index 35dd2a1..bf80aeb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ Use the emoji to identify the agent: - `🤖` Codex - `🧠` Claude Code -- `🖊️` Cursor +- `🖱️` Cursor - `🥽` GitHub Copilot - `🧩` unknown or other agent @@ -70,7 +70,9 @@ tools to already be installed in the developer environment. - `.github/actions/check-precommit/` — composite action: verifies pre-commit hooks match detected CI linters - `.github/actions/setup-runtimes/` — installs Python packages, Ruby, and standalone binaries for requested linters; Python is provided by the runner - `.github/workflows/lint-shared.yml` — reusable workflow exported for consuming repos: setup + lint +- `.github/workflows/commit-shared.yml` — reusable workflow exported for Conventional Commit pull request titles - `.github/workflows/embedder-shared.yml` — reusable workflow exported for required content validation +- `.github/workflows/commit.yml` — baseline caller for its Conventional Commit pull request title check - `.github/workflows/lint.yml` — baseline self-lint (uses local `./` references, not `@vX`) - `.github/workflows/prepare-release.yml` — dispatch workflow: calls `rubykatzen/releaser` to prepare `release/vX.Y.Z` - `.github/workflows/publish-release.yml` — publishes merged `release/*` PRs via `rubykatzen/releaser` diff --git a/README.md b/README.md index bf1ffd6..f48aa4d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ repositories install runtimes and linter binaries only for local pre-commit use. Replace `VERSION` in all examples with the latest release tag from [github.com/rubykatzen/baseline/releases](https://github.com/rubykatzen/baseline/releases). -After initial setup, [Dependabot](#3-dependabot) keeps the pin current automatically. +After initial setup, [Dependabot](#6-dependabot) keeps the pin current automatically. ### 1. Lint workflow @@ -61,8 +61,9 @@ jobs: ``` The shared workflow checks repository settings against `config/github.yml`. -The initial policy requires the wiki to be disabled, auto-merge to be enabled, -and merged branches to be deleted automatically. +The policy requires the wiki to be disabled, auto-merge to be enabled, merged +branches to be deleted automatically, and pull requests to use squash-only +merging with the pull request title as the complete resulting commit message. Skip checks explicitly when a repository needs an exception: @@ -76,7 +77,34 @@ jobs: The `skip` input must be a JSON array. Unknown check names fail the workflow. -### 3. Embedded content +### 3. Commit workflow + +Create `.github/workflows/commit.yml`: + +```yaml +name: Commit +on: + # Load trusted workflow code from the default branch when validating fork PRs. + pull_request_target: + types: [opened, reopened, edited, synchronize] +jobs: + pull-request-title: + uses: rubykatzen/baseline/.github/workflows/commit-shared.yml@VERSION +``` + +The shared workflow requires pull request titles to follow Conventional Commits: + +```text +[optional scope][!]: +``` + +Only the pull request title is validated. Intermediate branch commits may use +any format because the GitHub repository policy squashes the pull request and +uses only its title for the single commit added to the default branch. Use `!` +for a breaking change, for example +`refactor!: remove legacy workflow inputs`. + +### 4. Embedded content Create `.github/workflows/embedder.yml`: @@ -110,7 +138,7 @@ jobs: The `skip` input must be a JSON array. Unknown fragment names fail the workflow. -### 4. Pre-commit hooks +### 5. Pre-commit hooks Copy `.pre-commit-config.yaml.example` to your repo or add to your existing config. Include only the hooks relevant to your stack: @@ -142,7 +170,7 @@ Ruby hooks use `bundle exec`; install Ruby and run `bundle install` in the consuming repository first. `rubocop` and `erb_lint` must be available through the [`rubykatzen-baseline`](#ruby-gem-rubocop--erb_lint) gem. -### 5. Dependabot +### 6. Dependabot Add `.github/dependabot.yml` to keep GitHub Actions and pre-commit pins current automatically: @@ -153,6 +181,9 @@ updates: - package-ecosystem: github-actions directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -160,13 +191,19 @@ updates: - package-ecosystem: pre-commit directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" timezone: "Europe/Berlin" ``` -Dependabot opens pull requests for version bumps. Pair with +The commit message configuration also prefixes pull request titles with +`chore(deps):`, so they pass the commit workflow without creating a release by +default. Rename a release-worthy dependency update to `fix(deps):` to request a +patch release. Pair Dependabot with `dependabot-automerge` if you want patch/minor updates merged automatically. --- diff --git a/config/embedder.yml b/config/embedder.yml index f2d8ad4..1efc5dc 100644 --- a/config/embedder.yml +++ b/config/embedder.yml @@ -16,7 +16,7 @@ fragments: - `🤖` Codex - `🧠` Claude Code - - `🖊️` Cursor + - `🖱️` Cursor - `🥽` GitHub Copilot - `🧩` unknown or other agent @@ -60,6 +60,9 @@ fragments: - package-ecosystem: github-actions directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -67,6 +70,9 @@ fragments: - package-ecosystem: pre-commit directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -74,6 +80,9 @@ fragments: - package-ecosystem: pip directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" @@ -81,6 +90,9 @@ fragments: - package-ecosystem: bundler directory: / labels: [] + commit-message: + prefix: chore + include: scope schedule: interval: daily time: "10:00" diff --git a/config/github.yml b/config/github.yml index 389a679..939da87 100644 --- a/config/github.yml +++ b/config/github.yml @@ -2,3 +2,8 @@ config: hasWikiEnabled: false autoMergeAllowed: true deleteBranchOnMerge: true + mergeCommitAllowed: false + rebaseMergeAllowed: false + squashMergeAllowed: true + squashMergeCommitTitle: PR_TITLE + squashMergeCommitMessage: BLANK diff --git a/test/test_github_config.py b/test/test_github_config.py index 2b80426..6f48163 100644 --- a/test/test_github_config.py +++ b/test/test_github_config.py @@ -22,7 +22,19 @@ def setUp(self): def test_loads_repository_config(self): checks = CHECK_GITHUB_CONFIG.load_config(BASELINE_ROOT / "config" / "github.yml") - self.assertEqual(set(checks), {"hasWikiEnabled", "autoMergeAllowed", "deleteBranchOnMerge"}) + self.assertEqual( + set(checks), + { + "hasWikiEnabled", + "autoMergeAllowed", + "deleteBranchOnMerge", + "mergeCommitAllowed", + "rebaseMergeAllowed", + "squashMergeAllowed", + "squashMergeCommitMessage", + "squashMergeCommitTitle", + }, + ) def test_rejects_invalid_repository_config(self): with tempfile.NamedTemporaryFile(mode="w", suffix=".yml") as config: