From 3fe60476cc96978b0079bb771411f43f8c7e3eee Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 18:43:57 +0000 Subject: [PATCH 1/2] ci: a breaking change bumps the minor, and the version stays below 1.0.0 Releases leave the `0.0.0-alpha.N` stream. The config already carried `bump-minor-pre-major` and `bump-patch-for-minor-pre-major`, which the three prerelease keys made inert; dropping those keys is what turns the pair on. So a breaking marker gives a minor, a feature and a fix give a patch, and nothing reaches 1.0.0 without removing two more keys. The manifest names `0.0.0` as the version to bump from, so the next release PR cuts `0.0.1`, or `0.1.0` if a breaking marker lands first. The base-version guard in `pr-title.yml` refused `!` because it would have moved the base version off the stream. There is no stream to move off, so the guard goes, and with it the manifest checkout and the PR-body read it needed. release-please's auto-merge step goes too: it was fenced as temporary and only ever fired for a prerelease, so from here it would have done nothing on every release. AGENTS.md, CONTRIBUTING.md, RELEASING.md, the README, the installation page and the PR template all said a breaking marker is refused. They now say what it does. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FmFn3b7j5QifYazTFufExd --- .github/pull_request_template.md | 7 ++- .github/workflows/pr-title.yml | 74 +++----------------------- .github/workflows/release.yml | 57 -------------------- .release-please-config.json | 3 -- .release-please-manifest.json | 2 +- AGENTS.md | 16 +++--- CONTRIBUTING.md | 13 +++-- README.md | 3 +- RELEASING.md | 89 +++++++++++++------------------- docs/howto/installation.md | 2 +- 10 files changed, 64 insertions(+), 202 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9584cb54..a1ac31db 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,10 +3,9 @@ silently missing from CHANGELOG.md. The `PR title` check enforces this. See RELEASING.md. - The version is pinned to the alpha stream. While it is, a breaking marker - is refused. That means a `!` in the subject, or a `BREAKING CHANGE:` - footer. Such a marker moves the base version instead of the alpha - counter. Describe the break here instead. --> + A breaking marker — a `!` in the subject, or a `BREAKING CHANGE:` footer — + bumps the minor, and the version stays below 1.0.0. Use one where a + consumer has to change something, and say what here. --> ## What this changes diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index cde0f5fb..e716d559 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -5,7 +5,7 @@ name: PR title # The squash subject becomes a commit on main, and release-please parses it to -# decide the changelog section (and, once we leave the alpha stream, the version). +# decide the changelog section and the version. # A subject it cannot parse is silently dropped from the changelog — nothing fails, # the entry just never appears. This check is the only thing standing between a # sloppy title and a hole in CHANGELOG.md. See RELEASING.md. @@ -45,23 +45,12 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - # Only the manifest, because only the manifest is read: the base-version - # guard below needs it, and a full checkout would fetch the tree to run a - # regex over one line of JSON. - - name: Fetch the version manifest - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - sparse-checkout: .release-please-manifest.json - sparse-checkout-cone-mode: false - persist-credentials: false - - name: Validate the subject that will land on main env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} NUMBER: ${{ github.event.pull_request.number }} PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} COMMIT_COUNT: ${{ github.event.pull_request.commits }} run: | set -euo pipefail @@ -86,66 +75,20 @@ jobs: echo "ok: ${what} — ${subject}" } - # Every subject GitHub could squash with, and every message a footer - # can hide in. A one-commit PR is squashed with that commit's own - # title, so both must reach both checks below — and the commit's whole - # message is kept, not just its first line, because a 'BREAKING - # CHANGE:' footer lives in the body. - # - # Checking only $PR_TITLE and $PR_BODY is how lpspec shipped - # 0.1.0-alpha.226 and .227 and had to withdraw them: the '!' was in - # the single commit's title, which the guard never read. + # Every subject GitHub could squash with. A one-commit PR is squashed + # with that commit's own title, so $PR_TITLE alone is not what lands + # on main: specsolve withdrew 0.1.0-alpha.226 and .227 over a marker + # in a single commit's title that no check had read. titles=("PR title|${PR_TITLE}") - messages=("the PR body|${PR_BODY:-}") if [[ "$COMMIT_COUNT" == "1" ]]; then only=$(gh api "repos/${REPO}/pulls/${NUMBER}/commits" --jq '.[0].commit.message') titles+=("the single commit's title|$(head -1 <<<"$only")") - messages+=("the single commit's message|${only}") fi for entry in "${titles[@]}"; do check "${entry%%|*}" "${entry#*|}" done - # A breaking marker moves the *base* version, not the alpha counter. - # While the base is 0.0.0 that is harmless — under `versioning: - # prerelease` a zero patch is an absorbing state, so every bump only - # increments the counter — but the immunity goes away the moment the - # stream leaves 0.0.0, and then `feat!:` on 0.0.1-alpha.12 yields - # 0.1.0-alpha.12 and the project has jumped a minor by accident. The - # marker is refused here rather than discovered in a release PR. - # - # Both ways this can go wrong are named, because either one leaves the - # guard below not running: a missing file aborts the step under `set - # -e` with only sed's own message, and a manifest whose shape changed - # yields an empty $base, which fails *open* — every '!' would sail - # through and the version would move off the stream unannounced. - manifest=.release-please-manifest.json - if [[ ! -f "$manifest" ]]; then - echo "::error::${manifest} not found — the base-version guard cannot run." - exit 1 - fi - base=$(sed -n 's/.*"\.": *"\([^"]*\)".*/\1/p' "$manifest") - if [[ -z "$base" ]]; then - echo "::error::no \".\" version in ${manifest} — the base-version guard cannot run." - exit 1 - fi - - if [[ "$base" == 0.* ]]; then - for entry in "${titles[@]}"; do - if [[ "${entry#*|}" =~ ^[a-z]+(\([a-z0-9._/-]+\))?!: ]]; then - echo "::error::'!' in ${entry%%|*} bumps the base version off the pinned alpha stream (currently ${base})." - fail=1 - fi - done - for entry in "${messages[@]}"; do - if grep -qE '^BREAKING[ -]CHANGE:' <<<"${entry#*|}"; then - echo "::error::a 'BREAKING CHANGE:' footer in ${entry%%|*} bumps the base version off the pinned alpha stream (currently ${base})." - fail=1 - fi - done - fi - if (( fail )); then cat <<'MSG' @@ -156,10 +99,9 @@ jobs: fix(parser): where clauses with a trailing comma docs: describe the two expression tiers - A '!' (or a 'BREAKING CHANGE:' footer) is refused while the version is - pinned to an alpha stream: it moves the *base* version, not the counter. - Describe the break in the PR body instead — the alpha stream carries no - compatibility promise, so there is nothing for the version to announce. + A '!' (or a 'BREAKING CHANGE:' footer) bumps the minor, and the version + stays below 1.0.0. Use one where a consumer has to change something, + and say what in the PR body. Fix the PR title — no need to rewrite the branch. Edits re-run this check. MSG diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51a31d7a..39c9571d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,10 +8,6 @@ name: Release # Merging it tags the release, and that tag is what build.yml already reacts to # — release-please never edits pyproject.toml, so the two halves compose without # either knowing about the other. See RELEASING.md. -# -# TEMPORARY, WHILE ON THE ALPHA STREAM: the last step merges the release PR for -# you, so every merge to main cuts a 0.0.0-alpha.N. It expires by itself at the -# first official version — see the step's own comment. on: push: @@ -68,56 +64,3 @@ jobs: config-file: .release-please-config.json manifest-file: .release-please-manifest.json target-branch: ${{ inputs.target-branch || 'main' }} - - # ---------------------------------------------------------------------- - # TEMPORARY — alpha stream only. Delete this step and the project is back - # to release PRs merged by hand, which is what RELEASING.md documents. - # ---------------------------------------------------------------------- - # It exists so an early user always has a version number to quote in a bug - # report: every merge to main cuts a 0.0.0-alpha.N rather than being - # reachable only as a commit sha. - # - # Expiry is enforced here, not remembered: the version comes off the PR - # title and anything that is not a prerelease is left for a human. So the - # first official release ends this by itself. - # - # Auto-merge rather than a direct merge: the required checks have not - # reported on the just-updated PR yet, so a direct merge would be refused. - # GitHub merges it when they go green; if they fail, it sits there. This - # needs "Allow auto-merge" enabled on the repository. - # - # Set the repo variable AUTO_RELEASE to "false" to pause it early and go - # back to merging by hand. - - name: Merge the release PR when CI passes (temporary, alpha only) - if: vars.AUTO_RELEASE != 'false' - env: - GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} - # this job never checks out, so gh has no remote to infer the repo from - GH_REPO: ${{ github.repository }} - BASE: ${{ inputs.target-branch || 'main' }} - run: | - set -euo pipefail - # resolve the PR from the API rather than the action's outputs: those - # distinguish created from updated, and we want it merged either way - read -r pr version < <( - gh pr list --state open --base "$BASE" --json number,title,headRefName \ - --jq '[.[] | select(.headRefName | startswith("release-please--"))] - | first - | select(. != null) - | "\(.number) \(.title | split(" ") | last)"' - ) || true - - if [[ -z "${pr:-}" ]]; then - echo "no open release PR against $BASE — nothing to release" - exit 0 - fi - - # 0.0.0-alpha.15 auto-merges; 0.1.0 does not. Dashed semver is the same - # signal RELEASING.md uses to describe what is and is not a prerelease. - if [[ "$version" != *-* ]]; then - echo "::notice::#$pr releases $version — the first official version is not automatic. Merge it yourself (RELEASING.md)." - exit 0 - fi - - echo "enabling auto-merge on #$pr ($version) — temporary alpha-stream policy, see RELEASING.md" - gh pr merge "$pr" --squash --auto diff --git a/.release-please-config.json b/.release-please-config.json index 3ec06212..2306c2c8 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -9,9 +9,6 @@ "changelog-path": "CHANGELOG.md", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, - "versioning": "prerelease", - "prerelease": true, - "prerelease-type": "alpha", "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0a4483c7..e18ee077 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.0-alpha.96" + ".": "0.0.0" } diff --git a/AGENTS.md b/AGENTS.md index f09dc0aa..18774ca9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,8 +110,7 @@ answer here is the mistake. ## Renaming and deleting -The project is on the `0.0.0-alphaN` stream, and it holds no compatibility -promise. +The project is below 1.0.0, and it holds no compatibility promise. So when you are asked to change something, change it. Rename it, move it, or delete it. Add no alias, no deprecation cycle, and no `legacy_` path. Write **no @@ -121,11 +120,10 @@ the valid keys, and that is the whole migration story. **A test that asserts the old behaviour is not a blocker.** Say in the PR what coverage moved where. -There is one place where this costs something. A breaking marker in the PR title -is **refused** by the `Conventional commit subject` check. A breaking marker is -a `!`, or a `BREAKING CHANGE:` footer. It is refused because it would move the -base version rather than the alpha counter. Describe the break in the PR body -instead. +This costs one thing. A breaking marker in the PR title bumps the minor, and +the minor is what says a consumer has to change something. A breaking marker is +a `!`, or a `BREAKING CHANGE:` footer. Use one where the break is real, and say +what broke in the PR body. ## Numbers and claims @@ -375,8 +373,8 @@ Then write the subject: - **Write a subject the changelog reader can name.** Not `a pass` or `a walk`, and not `dim`, `coord` or `AST`. -Use lower case, no full stop, and conventional-commit form. The breaking marker -is refused. See [CONTRIBUTING.md](CONTRIBUTING.md#commit-messages). The 72 +Use lower case, no full stop, and conventional-commit form. A breaking marker +bumps the minor. See [CONTRIBUTING.md](CONTRIBUTING.md#commit-messages). The 72 character warning in `pr-title.yml` is about `git log --oneline`. The changelog does not truncate. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68092c85..cd5ca0ca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,9 +93,9 @@ hidden. A subject the parser cannot read is not an error — the entry simply never appears — so the `Conventional commit subject` check enforces the format on every pull request. -While the version is pinned to the alpha stream, a breaking marker (`!`, or a -`BREAKING CHANGE:` footer) is refused, because it moves the base version rather -than the alpha counter. Describe the break in the PR body instead. See +A breaking marker (`!`, or a `BREAKING CHANGE:` footer) bumps the minor, and +the version stays below 1.0.0. Use one where a consumer has to change +something, and say what in the PR body. See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md). Beyond the subject line, write whatever body the change deserves — a paragraph @@ -120,15 +120,14 @@ When adding docstrings, we request you use the [Google docstring style](https:// Nothing here is done by hand. release-please opens a release PR from the conventional-commit subjects on `main`; merging it tags the release, and the tag -is what builds and publishes the package. While the project is on the alpha -stream that release PR is merged automatically, so every merge to `main` cuts a -version. +is what builds and publishes the package. Merging that release PR is yours to +decide, so a release is a decision rather than a side effect of merging. The version is never written down in the source tree — it comes from the git tag at build time, and `math_spec.__version__` reads it back from the installed package metadata. -See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md) for the full pipeline, the alpha-stream rules, +See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md) for the full pipeline, the version scheme, and the one-time repository setup it still needs. diff --git a/README.md b/README.md index c8a34e3a..d998e59e 100644 --- a/README.md +++ b/README.md @@ -368,7 +368,8 @@ pixi run test -Releases are on the alpha stream, and **nothing is on PyPI yet**. `build.yml` +The version is below 1.0.0, where a breaking change bumps the minor, and +**nothing is on PyPI yet**. `build.yml` publishes every tag, so the first upload is the first tag cut after the project's trusted publisher is registered; see [RELEASING.md](RELEASING.md). Until it appears there, install from a checkout or a git reference. diff --git a/RELEASING.md b/RELEASING.md index 8dc04619..03607a5d 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,8 +7,7 @@ SPDX-License-Identifier: CC-BY-4.0 [release-please](https://github.com/googleapis/release-please) cuts the releases. It works from the conventional-commit subjects that land on `main`. -While the project is on the alpha stream, no part of a release is done by -hand. +Merging the release PR is the one part done by hand. ## The pipeline @@ -17,10 +16,10 @@ PR title (conventional) ──► squash onto main │ release.yml │ release-please opens/updates a release PR ▼ - "chore(main): release 0.0.0-alpha.N" - │ auto-merged while on the alpha stream + "chore(main): release 0.0.1" + │ you merge it ▼ - tag v0.0.0-alpha.N + GitHub release + tag v0.0.1 + GitHub release │ build.yml ▼ builds the wheel, checks it against the tag and publishes it to PyPI @@ -28,11 +27,11 @@ PR title (conventional) ──► squash onto main Three files own it: -| File | Role | -| ------------------------------- | -------------------------------------------------------------- | -| `.release-please-config.json` | the release type, the changelog sections, and the alpha stream | -| `.release-please-manifest.json` | the last released version. release-please rewrites this file | -| `.github/workflows/release.yml` | runs release-please on every push to `main` | +| File | Role | +| ------------------------------- | --------------------------------------------------------------- | +| `.release-please-config.json` | the release type, the changelog sections and the version scheme | +| `.release-please-manifest.json` | the last released version. release-please rewrites this file | +| `.github/workflows/release.yml` | runs release-please on every push to `main` | `.github/workflows/pr-title.yml` guards the input. `.github/workflows/build.yml` consumes the output. @@ -52,54 +51,38 @@ Note that `simple` also declares a `version.txt` updater, but with `createIfMissing: false`. There is no `version.txt` in this repository, and none will be created. -## The alpha stream +## The version scheme -The manifest is seeded at `0.0.0-alpha.0`, and the config is in sticky -`prerelease` mode. So every release is `0.0.0-alpha.N`, which is the -distribution version `0.0.0aN`. +The version is below 1.0.0, and two keys in the config keep it there: -The seed is what pins the `0.0.0`. release-please increments the counter only -when the version it starts from already carries a prerelease. From a plain -`0.0.0` it would bump the patch first, and the stream would be -`0.0.1-alpha.N`. +- `bump-minor-pre-major` — a breaking change bumps the **minor**, so a `feat!:` + on `0.1.4` gives `0.2.0` and not `1.0.0`. +- `bump-patch-for-minor-pre-major` — a feature bumps the **patch**, so a + feature and a fix both give `0.1.5`. -None of these versions carries a semantic promise. The point of them is that an -early user always has a number to quote in a bug report, instead of a commit -SHA. +So below 1.0.0 the minor means _a consumer has to change something_, and the +patch means everything else. A breaking marker is how you ask for it: a `!` in +the subject, or a `BREAKING CHANGE:` footer. + +The releases before this scheme are the `0.0.0-alpha.N` stream, which the +config pinned with `versioning: prerelease`. Those numbers promised nothing at +all, and they stay in the changelog as they are. The manifest names `0.0.0` as +the version to bump from, so the first release under the scheme is `0.0.1`, or +`0.1.0` if a breaking marker lands first. **Nothing is on PyPI yet.** The publish job in `build.yml` runs on every tag, -and waits on the trusted publisher in the PyPI note below. Until that exists, -the alpha stream produces tags, changelog entries and GitHub releases, and -nothing more. - -Two consequences worth knowing: - -- **`main` releases on every merge.** The last step of `release.yml` enables - auto-merge on the release PR. That step is explicitly temporary, and it - expires by itself. It reads the version off the PR title and refuses anything - that is not a prerelease. So the first official version stops the automation, - and nobody has to remember to do it. To pause it earlier, set the repository - variable `AUTO_RELEASE` to `false`, and merge the release PRs by hand. -- **Breaking markers are refused.** A `!` in the subject, or a - `BREAKING CHANGE:` footer, moves the _base_ version rather than the counter. - Under `versioning: prerelease`, a zero patch is an absorbing state. So at - `0.0.0` a breaking marker is currently harmless. But that immunity disappears - the moment the stream moves, and then one `feat!:` turns `0.0.1-alpha.12` - into `0.1.0-alpha.12`. So `pr-title.yml` refuses the marker. Describe the - break in the PR body instead. The alpha stream carries no compatibility - promise, so there is nothing for the version to announce. - -## Leaving the alpha stream - -When the project is ready for a real version: - -1. Delete the auto-merge step from `release.yml` (it is fenced by a comment - banner). -2. Remove `versioning`, `prerelease` and `prerelease-type` from - `.release-please-config.json`. -3. Set the manifest to the last version you want release-please to bump _from_. -4. Drop the base-version guard from `pr-title.yml`, so `!` works again. -5. Merge the next release PR by hand. +and waits on the trusted publisher in the PyPI note below. + +**`main` does not release on its own.** release-please opens the release PR and +it waits for you. The alpha stream auto-merged those PRs, and that step is gone +with the stream. + +## Reaching 1.0.0 + +Remove `bump-minor-pre-major` and `bump-patch-for-minor-pre-major` from +`.release-please-config.json`. A breaking change then bumps the major and a +feature the minor, which is ordinary semver. Do it in the pull request that +argues the API is stable, because the promise cannot be withdrawn afterwards. ## One-time setup diff --git a/docs/howto/installation.md b/docs/howto/installation.md index 571c15bc..3122f2d1 100644 --- a/docs/howto/installation.md +++ b/docs/howto/installation.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: CC-BY-4.0 !!! warning "Not on PyPI yet" - math-spec is on the alpha stream. Every tag is published from `build.yml`, + math-spec is below 1.0.0. Every tag is published from `build.yml`, so the first upload is the first tag cut after the project's trusted publisher is registered — see [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md). From 936ba26bc5d8dac5278d90f57cca50a51d0e6be3 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 20:08:56 +0000 Subject: [PATCH 2/2] ci: a breaking change bumps the minor while releases stay alpha MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit on this branch left the alpha stream, which is not what was asked. It is reverted here: `versioning: prerelease`, `prerelease` and `prerelease-type` stay, the auto-merge step stays, and every release is still `0.0.1-alpha.N`. What remains is the breaking marker. `pr-title.yml` refused a `!` and a `BREAKING CHANGE:` footer to keep the base pinned at `0.0.0`; the guard goes, so a marker moves the base to `0.1.0` and the counter carries on. With it goes the manifest checkout and the PR-body read that only the footer check needed. The manifest moves from `0.0.0-alpha.96` to `0.0.1-alpha.96`, because at a zero patch `versioning: prerelease` absorbs a minor bump into the counter — a marker on `0.0.0-alpha.96` would have changed nothing. At `0.0.1` it bites. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01FmFn3b7j5QifYazTFufExd --- .github/pull_request_template.md | 5 +- .github/workflows/pr-title.yml | 13 +++-- .github/workflows/release.yml | 57 ++++++++++++++++++++ .release-please-config.json | 3 ++ .release-please-manifest.json | 2 +- AGENTS.md | 11 ++-- CONTRIBUTING.md | 13 ++--- README.md | 3 +- RELEASING.md | 89 +++++++++++++++++++------------- docs/howto/installation.md | 2 +- 10 files changed, 138 insertions(+), 60 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a1ac31db..2e1e7302 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -4,8 +4,9 @@ See RELEASING.md. A breaking marker — a `!` in the subject, or a `BREAKING CHANGE:` footer — - bumps the minor, and the version stays below 1.0.0. Use one where a - consumer has to change something, and say what here. --> + moves the base version, so the alpha stream goes from 0.0.1-alpha.N to + 0.1.0-alpha.N. Use one where a consumer has to change something, and say + what here. --> ## What this changes diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index e716d559..01358878 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -5,7 +5,7 @@ name: PR title # The squash subject becomes a commit on main, and release-please parses it to -# decide the changelog section and the version. +# decide the changelog section (and, once we leave the alpha stream, the version). # A subject it cannot parse is silently dropped from the changelog — nothing fails, # the entry just never appears. This check is the only thing standing between a # sloppy title and a hole in CHANGELOG.md. See RELEASING.md. @@ -76,9 +76,8 @@ jobs: } # Every subject GitHub could squash with. A one-commit PR is squashed - # with that commit's own title, so $PR_TITLE alone is not what lands - # on main: specsolve withdrew 0.1.0-alpha.226 and .227 over a marker - # in a single commit's title that no check had read. + # with that commit's own title, so $PR_TITLE alone is not the subject + # that lands on main. titles=("PR title|${PR_TITLE}") if [[ "$COMMIT_COUNT" == "1" ]]; then only=$(gh api "repos/${REPO}/pulls/${NUMBER}/commits" --jq '.[0].commit.message') @@ -99,9 +98,9 @@ jobs: fix(parser): where clauses with a trailing comma docs: describe the two expression tiers - A '!' (or a 'BREAKING CHANGE:' footer) bumps the minor, and the version - stays below 1.0.0. Use one where a consumer has to change something, - and say what in the PR body. + A '!' (or a 'BREAKING CHANGE:' footer) moves the base version: the + stream goes from 0.0.1-alpha.N to 0.1.0-alpha.N. Use one where a + consumer has to change something, and say what in the PR body. Fix the PR title — no need to rewrite the branch. Edits re-run this check. MSG diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39c9571d..51a31d7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,10 @@ name: Release # Merging it tags the release, and that tag is what build.yml already reacts to # — release-please never edits pyproject.toml, so the two halves compose without # either knowing about the other. See RELEASING.md. +# +# TEMPORARY, WHILE ON THE ALPHA STREAM: the last step merges the release PR for +# you, so every merge to main cuts a 0.0.0-alpha.N. It expires by itself at the +# first official version — see the step's own comment. on: push: @@ -64,3 +68,56 @@ jobs: config-file: .release-please-config.json manifest-file: .release-please-manifest.json target-branch: ${{ inputs.target-branch || 'main' }} + + # ---------------------------------------------------------------------- + # TEMPORARY — alpha stream only. Delete this step and the project is back + # to release PRs merged by hand, which is what RELEASING.md documents. + # ---------------------------------------------------------------------- + # It exists so an early user always has a version number to quote in a bug + # report: every merge to main cuts a 0.0.0-alpha.N rather than being + # reachable only as a commit sha. + # + # Expiry is enforced here, not remembered: the version comes off the PR + # title and anything that is not a prerelease is left for a human. So the + # first official release ends this by itself. + # + # Auto-merge rather than a direct merge: the required checks have not + # reported on the just-updated PR yet, so a direct merge would be refused. + # GitHub merges it when they go green; if they fail, it sits there. This + # needs "Allow auto-merge" enabled on the repository. + # + # Set the repo variable AUTO_RELEASE to "false" to pause it early and go + # back to merging by hand. + - name: Merge the release PR when CI passes (temporary, alpha only) + if: vars.AUTO_RELEASE != 'false' + env: + GH_TOKEN: ${{ steps.app-token.outputs.token || secrets.GITHUB_TOKEN }} + # this job never checks out, so gh has no remote to infer the repo from + GH_REPO: ${{ github.repository }} + BASE: ${{ inputs.target-branch || 'main' }} + run: | + set -euo pipefail + # resolve the PR from the API rather than the action's outputs: those + # distinguish created from updated, and we want it merged either way + read -r pr version < <( + gh pr list --state open --base "$BASE" --json number,title,headRefName \ + --jq '[.[] | select(.headRefName | startswith("release-please--"))] + | first + | select(. != null) + | "\(.number) \(.title | split(" ") | last)"' + ) || true + + if [[ -z "${pr:-}" ]]; then + echo "no open release PR against $BASE — nothing to release" + exit 0 + fi + + # 0.0.0-alpha.15 auto-merges; 0.1.0 does not. Dashed semver is the same + # signal RELEASING.md uses to describe what is and is not a prerelease. + if [[ "$version" != *-* ]]; then + echo "::notice::#$pr releases $version — the first official version is not automatic. Merge it yourself (RELEASING.md)." + exit 0 + fi + + echo "enabling auto-merge on #$pr ($version) — temporary alpha-stream policy, see RELEASING.md" + gh pr merge "$pr" --squash --auto diff --git a/.release-please-config.json b/.release-please-config.json index 2306c2c8..3ec06212 100644 --- a/.release-please-config.json +++ b/.release-please-config.json @@ -9,6 +9,9 @@ "changelog-path": "CHANGELOG.md", "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, + "versioning": "prerelease", + "prerelease": true, + "prerelease-type": "alpha", "changelog-sections": [ { "type": "feat", "section": "Features" }, { "type": "fix", "section": "Bug Fixes" }, diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e18ee077..03663987 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.0" + ".": "0.0.1-alpha.96" } diff --git a/AGENTS.md b/AGENTS.md index 18774ca9..6ec9bde2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,7 +110,8 @@ answer here is the mistake. ## Renaming and deleting -The project is below 1.0.0, and it holds no compatibility promise. +The project is on the `0.0.1-alphaN` stream, and it holds no compatibility +promise. So when you are asked to change something, change it. Rename it, move it, or delete it. Add no alias, no deprecation cycle, and no `legacy_` path. Write **no @@ -120,10 +121,10 @@ the valid keys, and that is the whole migration story. **A test that asserts the old behaviour is not a blocker.** Say in the PR what coverage moved where. -This costs one thing. A breaking marker in the PR title bumps the minor, and -the minor is what says a consumer has to change something. A breaking marker is -a `!`, or a `BREAKING CHANGE:` footer. Use one where the break is real, and say -what broke in the PR body. +This costs one thing. A breaking marker in the PR title moves the minor, so +the stream goes from `0.0.1-alphaN` to `0.1.0-alphaN`. A breaking marker is a +`!`, or a `BREAKING CHANGE:` footer. Use one where a consumer has to change +something, and say what broke in the PR body. ## Numbers and claims diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cd5ca0ca..dcd03106 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,9 +93,9 @@ hidden. A subject the parser cannot read is not an error — the entry simply never appears — so the `Conventional commit subject` check enforces the format on every pull request. -A breaking marker (`!`, or a `BREAKING CHANGE:` footer) bumps the minor, and -the version stays below 1.0.0. Use one where a consumer has to change -something, and say what in the PR body. See +A breaking marker (`!`, or a `BREAKING CHANGE:` footer) moves the base version, +so the alpha stream goes from `0.0.1-alpha.N` to `0.1.0-alpha.N`. Use one where +a consumer has to change something, and say what in the PR body. See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md). Beyond the subject line, write whatever body the change deserves — a paragraph @@ -120,14 +120,15 @@ When adding docstrings, we request you use the [Google docstring style](https:// Nothing here is done by hand. release-please opens a release PR from the conventional-commit subjects on `main`; merging it tags the release, and the tag -is what builds and publishes the package. Merging that release PR is yours to -decide, so a release is a decision rather than a side effect of merging. +is what builds and publishes the package. While the project is on the alpha +stream that release PR is merged automatically, so every merge to `main` cuts a +version. The version is never written down in the source tree — it comes from the git tag at build time, and `math_spec.__version__` reads it back from the installed package metadata. -See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md) for the full pipeline, the version scheme, +See [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md) for the full pipeline, the alpha-stream rules, and the one-time repository setup it still needs. diff --git a/README.md b/README.md index d998e59e..c8a34e3a 100644 --- a/README.md +++ b/README.md @@ -368,8 +368,7 @@ pixi run test -The version is below 1.0.0, where a breaking change bumps the minor, and -**nothing is on PyPI yet**. `build.yml` +Releases are on the alpha stream, and **nothing is on PyPI yet**. `build.yml` publishes every tag, so the first upload is the first tag cut after the project's trusted publisher is registered; see [RELEASING.md](RELEASING.md). Until it appears there, install from a checkout or a git reference. diff --git a/RELEASING.md b/RELEASING.md index 03607a5d..117ef19f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,7 +7,8 @@ SPDX-License-Identifier: CC-BY-4.0 [release-please](https://github.com/googleapis/release-please) cuts the releases. It works from the conventional-commit subjects that land on `main`. -Merging the release PR is the one part done by hand. +While the project is on the alpha stream, no part of a release is done by +hand. ## The pipeline @@ -16,10 +17,10 @@ PR title (conventional) ──► squash onto main │ release.yml │ release-please opens/updates a release PR ▼ - "chore(main): release 0.0.1" - │ you merge it + "chore(main): release 0.0.0-alpha.N" + │ auto-merged while on the alpha stream ▼ - tag v0.0.1 + GitHub release + tag v0.0.0-alpha.N + GitHub release │ build.yml ▼ builds the wheel, checks it against the tag and publishes it to PyPI @@ -27,11 +28,11 @@ PR title (conventional) ──► squash onto main Three files own it: -| File | Role | -| ------------------------------- | --------------------------------------------------------------- | -| `.release-please-config.json` | the release type, the changelog sections and the version scheme | -| `.release-please-manifest.json` | the last released version. release-please rewrites this file | -| `.github/workflows/release.yml` | runs release-please on every push to `main` | +| File | Role | +| ------------------------------- | -------------------------------------------------------------- | +| `.release-please-config.json` | the release type, the changelog sections, and the alpha stream | +| `.release-please-manifest.json` | the last released version. release-please rewrites this file | +| `.github/workflows/release.yml` | runs release-please on every push to `main` | `.github/workflows/pr-title.yml` guards the input. `.github/workflows/build.yml` consumes the output. @@ -51,38 +52,54 @@ Note that `simple` also declares a `version.txt` updater, but with `createIfMissing: false`. There is no `version.txt` in this repository, and none will be created. -## The version scheme +## The alpha stream -The version is below 1.0.0, and two keys in the config keep it there: +The config is in sticky `prerelease` mode, so every release is +`0.0.1-alpha.N`, which is the distribution version `0.0.1aN`. The manifest +carries the base, and one thing moves it: a breaking change. -- `bump-minor-pre-major` — a breaking change bumps the **minor**, so a `feat!:` - on `0.1.4` gives `0.2.0` and not `1.0.0`. -- `bump-patch-for-minor-pre-major` — a feature bumps the **patch**, so a - feature and a fix both give `0.1.5`. +The base was `0.0.0` while the stream was pinned there. Under +`versioning: prerelease` a bump lands on the counter whenever the digits below +it are already zero, so at `0.0.0` a minor bump was absorbed and a breaking +marker changed nothing at all. At `0.0.1` the patch is not zero, so the minor +bump bites and one `feat!:` gives `0.1.0-alpha.N`. -So below 1.0.0 the minor means _a consumer has to change something_, and the -patch means everything else. A breaking marker is how you ask for it: a `!` in -the subject, or a `BREAKING CHANGE:` footer. - -The releases before this scheme are the `0.0.0-alpha.N` stream, which the -config pinned with `versioning: prerelease`. Those numbers promised nothing at -all, and they stay in the changelog as they are. The manifest names `0.0.0` as -the version to bump from, so the first release under the scheme is `0.0.1`, or -`0.1.0` if a breaking marker lands first. +None of these versions carries a semantic promise. The point of them is that an +early user always has a number to quote in a bug report, instead of a commit +SHA. **Nothing is on PyPI yet.** The publish job in `build.yml` runs on every tag, -and waits on the trusted publisher in the PyPI note below. - -**`main` does not release on its own.** release-please opens the release PR and -it waits for you. The alpha stream auto-merged those PRs, and that step is gone -with the stream. - -## Reaching 1.0.0 - -Remove `bump-minor-pre-major` and `bump-patch-for-minor-pre-major` from -`.release-please-config.json`. A breaking change then bumps the major and a -feature the minor, which is ordinary semver. Do it in the pull request that -argues the API is stable, because the promise cannot be withdrawn afterwards. +and waits on the trusted publisher in the PyPI note below. Until that exists, +the alpha stream produces tags, changelog entries and GitHub releases, and +nothing more. + +Two consequences worth knowing: + +- **`main` releases on every merge.** The last step of `release.yml` enables + auto-merge on the release PR. That step is explicitly temporary, and it + expires by itself. It reads the version off the PR title and refuses anything + that is not a prerelease. So the first official version stops the automation, + and nobody has to remember to do it. To pause it earlier, set the repository + variable `AUTO_RELEASE` to `false`, and merge the release PRs by hand. +- **A breaking marker bumps the minor.** A `!` in the subject, or a + `BREAKING CHANGE:` footer, moves the base from `0.0.1` to `0.1.0`, and the + counter carries on rather than restarting. That is the one compatibility + signal the stream has: the minor says a consumer has to change something, and + the counter says nothing at all. `pr-title.yml` used to refuse the marker, + because the base was pinned to `0.0.0` and a marker would have moved it off + the stream unannounced. The base is no longer pinned, so the check no longer + looks. + +## Leaving the alpha stream + +When the project is ready for a real version: + +1. Delete the auto-merge step from `release.yml` (it is fenced by a comment + banner). +2. Remove `versioning`, `prerelease` and `prerelease-type` from + `.release-please-config.json`. +3. Set the manifest to the last version you want release-please to bump _from_. +4. Merge the next release PR by hand. ## One-time setup diff --git a/docs/howto/installation.md b/docs/howto/installation.md index 3122f2d1..571c15bc 100644 --- a/docs/howto/installation.md +++ b/docs/howto/installation.md @@ -9,7 +9,7 @@ SPDX-License-Identifier: CC-BY-4.0 !!! warning "Not on PyPI yet" - math-spec is below 1.0.0. Every tag is published from `build.yml`, + math-spec is on the alpha stream. Every tag is published from `build.yml`, so the first upload is the first tag cut after the project's trusted publisher is registered — see [RELEASING.md](https://github.com/energy-models/math-spec/blob/main/RELEASING.md).