diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a47654668..2014d8dc8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -54,7 +54,7 @@ Closes # - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes -- [ ] I have updated the CHANGELOG.md if applicable +- [ ] I did **not** edit `CHANGELOG.md` — it is generated by release-please from my Conventional Commit PR title (a CI guard enforces this) ## Screenshots (if applicable) diff --git a/.github/workflows/changelog-guard.yml b/.github/workflows/changelog-guard.yml new file mode 100644 index 000000000..bde07e1c5 --- /dev/null +++ b/.github/workflows/changelog-guard.yml @@ -0,0 +1,38 @@ +name: Changelog Guard + +# CHANGELOG.md is generated by release-please from Conventional Commit titles +# (see .release-please-config.json). Hand-editing it makes every concurrent PR +# conflict on the same `## Unreleased` lines — the "changelog cascade" where one +# merge turns the rest DIRTY. `.gitattributes merge=union` does not help because +# GitHub squash-merge ignores merge drivers. So the fix is to stop hand-edits at +# the source: this guard fails any PR that touches CHANGELOG.md, except +# release-please's own release PR (the one place it is meant to change). +# +# ponytail: uses the preinstalled gh CLI, no third-party action to pin. If a +# rare PR legitimately must edit CHANGELOG.md, a maintainer can merge past this +# non-required check; add a label-based exemption only if that ever recurs. + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + no-manual-changelog: + # release-please's release PR is the sole author of CHANGELOG.md. + if: ${{ !startsWith(github.head_ref, 'release-please--') }} + runs-on: ubuntu-latest + steps: + - name: Reject manual CHANGELOG.md edits + env: + GH_TOKEN: ${{ github.token }} + run: | + if gh pr view "${{ github.event.pull_request.number }}" \ + --repo "${{ github.repository }}" \ + --json files --jq '.files[].path' | grep -qx 'CHANGELOG.md'; then + echo "::error::Do not edit CHANGELOG.md by hand. release-please generates it from your Conventional Commit PR title (e.g. 'fix(proxy): ...'). Remove the CHANGELOG.md change — your entry appears automatically in the next release PR." + exit 1 + fi + echo "OK — CHANGELOG.md not modified." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cb3692a08..1b6e7b6d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,7 +77,7 @@ A human maintainer reviews every dep change. PRs that add or bump a package must 3. One logical change per PR. 4. Add tests. 5. `uv run pytest` · `uv run ruff check .` · `uv run ruff format .` -6. Update `CHANGELOG.md` for user-facing changes. +6. Do **not** edit `CHANGELOG.md` — release-please generates it from your Conventional Commit PR title, so a clear `fix(...)`/`feat(...)` title *is* your changelog entry. A CI guard rejects manual edits. 7. Open the PR with a clear description + `Real behavior proof` + any spec/justification required, and keep the PR in draft until the `Review Readiness` boxes are complete. **Title format** (conventional commits): `feat:`, `fix:`, `docs:`, `test:`, `refactor:`.