chore: replace per-PR changelog edits with generated release notes (#4578)
## TL;DR
Every code PR currently competes to edit the top of `CHANGELOG.md` and
advance the development version. Those shared lines cause avoidable
conflicts and rebases, especially for external contributors. This draft
proposes using PR titles and GitHub generated release notes instead,
while keeping the existing version format and human release process.
This PR adds no changelog entry and does not bump `0.27.34-dev3`: it is
the first PR under the proposed convention. The only changelog edit is a
freeze notice; all history remains intact.
## Proposed contributor convention
- Ordinary PRs do not edit the changelog or bump the package version.
Multiple commits on `main` may share one `-devN` version.
- A PR title written for users as a conventional commit (for example,
`fix: …` or `feat: …`) becomes the release-note entry. Apply
`enhancement`, `bug`, or another appropriate existing category label.
External contributors can ask a maintainer to label their PR.
- An optional **Release note** section supplies detail for the releaser
to paste into the release, particularly breaking-change impact and
migration instructions.
- `.github/release.yml` groups entries into Breaking changes,
Enhancements, Fixes, and Other changes. It excludes the existing
`dependencies` label; the releaser must restore significant
security/dependency updates during curation.
The repository's labels were checked with `gh label list`.
`breaking-change` does not exist, and this PR creates no labels. The
`release.yml` file reserves that category for later use. Until then,
breaking PRs use `!` in the title, an existing category label, and
manual placement during curation.
## Release day, step by step
1. Open a release PR that changes `unstructured/__version__.py` from
`-devN` to the final version, for example `0.27.34-dev3` to `0.27.34`.
Leave the frozen changelog alone.
2. Verify that no tag or existing release already represents that final
version. Use `packaging.version.Version` to normalize versions when
comparing: `0.27.34-dev3` and `0.27.34.dev3` are the same version. Skip
tags that are not package versions, such as `opencv-*`. Squash and merge
the release PR: only that commit may introduce the final version. Before
merging the release PR, empty the merge queue. Queue no ordinary PRs
until the next development bump lands.
3. Create the GitHub Release and tag from that release commit using the
existing process. Click **Generate release notes** and explicitly select
the previous package release tag. Curate labels and categories, paste
useful **Release note** sections, and add significant excluded
security/dependency updates. For this first release, also carry forward
relevant entries from the frozen development section.
4. Publish using the existing process. No publication, PyPI, Docker, or
release workflows change.
5. A human immediately opens and merges the next development-version PR,
for example `0.27.35-dev1`. Resume ordinary merges only after that bump,
so no later commit retains the released final version.
## Removed enforcement and automatic edits
- Removed the CI `changelog` job and changelog-enforcer action.
- Removed `check-version` from `make check`, and removed the
`check-version` and `version-sync` targets. They compared against the
latest changelog heading, which no longer changes.
- Retired the version-sync and Renovate security-bump script
implementations. They now fail with a pointer to the new instructions;
they cannot edit the changelog or version file. Removed Renovate's
PR-time post-upgrade hook so dependency PRs follow the same convention.
The shared security configuration remains inherited.
- Added a PR template and updated the contributor checklist and release
instructions.
**Merge prerequisite:** `changelog` is currently required by `main`
branch protection (verified through the GitHub API). A repo admin must
remove `changelog` from the required checks before this PR can merge,
because this PR itself no longer produces that check. Do this when the
proposal is accepted, just before merging; until then other PRs still
run the existing job. The active rulesets were checked as well. This PR
does not change repository settings.
The removed `check-version` command also blocked ordinary PRs while
`main` held a final version. That guard becomes a manual step in this
round: empty the merge queue before merging the release PR, then queue
no ordinary PRs until the next development bump lands. A replacement CI
guard is deferred.
The unchanged Docker workflow publishes a version tag on every push to
`main`. Repeated development versions therefore make tags such as
`:0.27.34-dev3` move across commits. Consumers who need a fixed image
should use the existing short-commit-SHA image tag. Renovate security
PRs also stop preparing final versions automatically; a maintainer must
decide when to prepare their release.
An organization code search for `renovate-security-bump` found no
executable configuration outside this repository calling
`renovate-security-bump.sh`. Shared configuration has no script hook;
related documentation describes copying a local script. No
cross-repository changes are included. Any unindexed consumer that
downloads `renovate-security-bump.sh` from `main` must retire its hook
or keep a pinned historical copy.
## Deferred work and limits
The optional duplicate-final-version CI guard is deferred: it needs
normalized comparisons across all tags and existing releases, plus a
policy for releases created after a PR check runs. It would not by
itself enforce merge ordering. The manual check and short release-only
merge window above remain necessary in round 1.
Round 2 can automate the post-release development bump. Automated
tagging/publishing and tag-derived versions are optional later
proposals; this draft changes none of those mechanisms and does not
introduce setuptools-scm or hatch-vcs.
## Validation
Targeted YAML/JSON5 parsing and preservation checks passed: changelog
history is intact, the package version and publication workflows are
unchanged, and retired hooks are absent. Shell syntax and ShellCheck
passed for both retired commands. `make -n check` invokes only the
existing Ruff checks; `git diff --check` passed. Exact-head CI will
provide broader coverage; long-running jobs will not block this draft
handoff.
### Make check command selection
The normal check target no longer reads the frozen changelog.
```console
$ make -n check
uv run --no-sync ruff check .
uv run --no-sync ruff format --check .
```
### Retired command validation
Both former entry points have valid shell syntax and pass ShellCheck.
```console
$ bash -n scripts/version-sync.sh scripts/renovate-security-bump.sh
$ shellcheck scripts/version-sync.sh scripts/renovate-security-bump.sh
```
## Review
Independent Claude Opus code review at
`3c36862bd199355ce75e930fca46e62cfda39881`: **APPROVED, no blocking
findings**.
The first round requested explicit merge-queue ordering, inspection of
required checks and external script callers, and documentation of
Docker-tag and security-release effects. Those findings were verified
and addressed above and in the contributor instructions. The final round
approved the source and suggested description clarifications; the merge
prerequisite and normalization wording were clarified.
Exact-spelling-only tag commands were not adopted because they miss
normalized aliases. The optional README Docker-tag clarification is
deferred; the operational effect is documented above. Opus 4.6 also
reviewed the description for clear language, and valid suggestions were
applied.
Exact-head CI at handoff: Python 3.11–3.13 lint, ShellCheck, shfmt,
CodeQL, and Socket security checks passed. Broader test and Docker jobs
were still running; no long-running CI wait was required.
(authored by codex)