gabriel / musehub public
feat BREAKING feat/64-pep440-to-semver-tag #1 / 1
AI Agent gabriel · 14 hours ago · Oct 21, 2026 · Diff

feat: derive SemVer release tag from pyproject.toml's PEP 440 version automatically (muse#64)

pyproject.toml's version field must stay in canonical PEP 440 form (0.2.0rc15, no hyphen) -- a hyphenated form was tried once during the rc15 release and broke publish_muse_release.sh's tarball-filename lookup, since hatchling normalizes it away regardless of what's written in the file. Separately, `muse tag add` enforces strict SemVer 2.0, which *requires* a hyphen before a pre-release identifier (v0.2.0-rc15 is valid, v0.2.0rc15 is rejected). The two canonical forms are incompatible at the string level, so the SemVer tag must be derived from the PEP 440 version, not hand-typed as a second, independently-maintained string.

Added deploy/pep440_to_semver_tag.py: a small helper (using the already-available packaging library, following the exact pattern of the existing deploy/prune_releases.py) that converts a PEP 440 version string to its SemVer tag equivalent (e.g. "0.2.1rc12" -> "v0.2.1-rc12"). Usable as both a library function and a CLI (`python3 pep440_to_semver_tag.py <version>`).

Wired into publish_muse_release.sh as a new step 8, after the smoke test succeeds: computes $TAG via the helper and runs `muse -C "$MUSE_REPO" tag add "$TAG" --message "release $MUSE_VERSION"`. A tagging failure (e.g. the tag already exists from a prior run) is a warning, not fatal -- wrapped in an explicit if/else so it survives `set -euo pipefail` without aborting after the release is already live.

Updated .museagent.md's "Standard release flow" section to document the new automatic tagging step and why the tag is derived rather than hand-typed.

New tests: - tests/test_pep440_to_semver_tag.py: unit tests for the helper (rc/a/b pre-release forms, final releases, the documented dev-segment-dropped limitation, invalid-version rejection) plus CLI subprocess tests. - tests/test_publish_muse_release_tag.py: source-level assertions that the script correctly wires in the helper (matching this repo's existing convention in test_publish_muse_release_prune.py for validating deploy scripts without executing real AWS/muse calls) -- confirmed genuine RED against the pre-fix script (5 of 6 failed) before GREEN with the fix.

Per the ticket's explicit sequencing ("manual QA first, then tag"), this commit does NOT retroactively tag any existing release, and does not run publish_muse_release.sh for a real release -- that remains gated on muse#63 being signed off, same as the ticket's own deferred deliverable. The code/docs work (deliverables 1-2) is unblocked by that gate; only the one-time real-release QA (deliverable 3) is not.

Targeted regression suite -- test_pep440_to_semver_tag.py (11), test_publish_muse_release_tag.py (6), test_publish_muse_release_prune.py (8, unaffected sibling), test_deployment.py (44), test_prune_releases.py (9) -- all pass (76 passed), zero regressions. bash -n confirms the script still parses as valid bash.

Co-Authored-By: Claude Sonnet 5 <[email protected]>

sha256:8c52c8464b3c4abcb640dd432b8b85bf43fb0c6adf16066d1dfa8d646ff01701 sha
+33 ~3 symbols
5 changed · 1088 in snapshot files
sha256:3f31cd7580d98d6cba7070258f45d7aef93702de75007ea1dd97a6f3845d443a snapshot
+33
symbols added
~3
symbols modified
5
files changed
1088
files in snapshot
0
dead code introduced
Semantic Changes 36 symbols
~ deploy/pep440_to_semver_tag.py .py 4 symbols added
+ Version import import Version L29–29
+ annotations import import annotations L25–25
+ sys import import sys L27–27
+ pep440_to_semver_tag function function pep440_to_semver_tag L32–48
~ tests/test_pep440_to_semver_tag.py .py 14 symbols added
+ TestPep440ToSemverTag class class TestPep440ToSemverTag L18–45
+ test_dev_segment_is_dropped_not_tagged method method test_dev_segment_is_dropped_not_tagged L34–39
+ test_pep440_to_semver_tag method method test_pep440_to_semver_tag L31–32
+ test_rejects_invalid_pep440 method method test_rejects_invalid_pep440 L41–45
+ TestPep440ToSemverTagCli class class TestPep440ToSemverTagCli L48–65
+ test_cli_prints_tag method method test_cli_prints_tag L49–56
+ test_cli_requires_exactly_one_argument method method test_cli_requires_exactly_one_argument L58–65
+ _SCRIPT variable variable _SCRIPT L15–15
+ Path import import Path L9–9
+ annotations import import annotations L5–5
+ pep440_to_semver_tag import import pep440_to_semver_tag L13–13
+ pytest import import pytest L11–11
+ subprocess import import subprocess L7–7
+ sys import import sys L8–8
+ TestTaggingWiredIntoPublishScript class class TestTaggingWiredIntoPublishScript L32–70
+ test_muse_tag_add_called_with_derived_tag method method test_muse_tag_add_called_with_derived_tag L43–44
+ test_muse_tag_add_targets_muse_repo method method test_muse_tag_add_targets_muse_repo L46–52
+ test_pep440_to_semver_tag_referenced_via_script_dir method method test_pep440_to_semver_tag_referenced_via_script_dir L33–36
+ test_syntax_is_valid_bash method method test_syntax_is_valid_bash L64–70
+ test_tag_computed_from_muse_version_not_hand_typed method method test_tag_computed_from_muse_version_not_hand_typed L38–41
+ test_tagging_failure_is_a_warning_not_fatal method method test_tagging_failure_is_a_warning_not_fatal L54–62
+ _CODE variable variable _CODE L29–29
+ _PUBLISH_SH variable variable _PUBLISH_SH L18–18
+ _ROOT variable variable _ROOT L17–17
+ _SRC variable variable _SRC L19–19
+ _executable_lines function function _executable_lines L22–26
+ Path import import Path L15–15
+ annotations import import annotations L12–12
+ subprocess import import subprocess L14–14
Files Changed
+3 ~2
1088 in snapshot
← Older Oldest on feat/64-pep440-to-semver-tag
All commits
Newer → Latest on feat/64-pep440-to-semver-tag

0 comments

No comments yet. Be the first to start the discussion.

To add a comment, use the Muse CLI: muse hub commit comment sha256:8c52c8464b3c4abcb640dd432b8b85bf43fb0c6adf16066d1dfa8d646ff01701 --body "your comment"