python-package/skills/versioning-releases/SKILL.md
This skill should be used when the user is choosing a versioning scheme, writing a changelog, preparing a release, configuring Towncrier or python-semantic-release, planning deprecations, managing major version migrations, or setting up version single-source-of-truth. Covers SemVer, CalVer, PEP 440, Keep a Changelog, Towncrier, Conventional Commits, deprecation strategy, pre-release publishing, and release automation.
npx skillsauth add oborchers/fractional-cto versioning-releasesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A version number is a contract with every user who pins against your package. Breaking that contract -- shipping a behavioral change in a PATCH bump, removing a function without a deprecation cycle, or publishing a non-PEP-440 tag -- erodes trust and causes silent failures across the dependency graph. The best Python packages (Pydantic, pytest, Django) treat versioning as seriously as they treat their API surface.
Pydantic's v1-to-v2 migration is the most instructive case study: systematic renaming with a model_ prefix, a compatibility namespace (pydantic.v1), an automated codemod (bump-pydantic), and a 12-month support window for v1. That level of discipline is what users expect from a well-maintained package.
Use MAJOR.MINOR.PATCH for every Python library. Increment the correct segment based on what changed, not how much code was touched.
| Change Type | Bump | Example |
|-------------|------|---------|
| Remove public function/class | MAJOR | Delete Client.send() |
| Change function signature (required args) | MAJOR | def get(url) becomes def get(url, timeout) |
| Change return type or default behavior | MAJOR | Return dict instead of list |
| Rename public attribute/method | MAJOR | .json() becomes .model_dump() |
| Drop Python version support | MAJOR | Drop Python 3.9 |
| Add new public function/class | MINOR | New Client.stream() method |
| Add optional parameter with default | MINOR | def get(url, *, timeout=30) |
| Deprecate without removing | MINOR | Add deprecation warning |
| Fix incorrect behavior | PATCH | Fix off-by-one in pagination |
| Internal refactor (no API change) | PATCH | Restructure _internal/ |
The hardest calls are behavioral changes. If parse("2024-01-01") returned a date and now returns a datetime, the signature is identical but downstream code breaks. This is a MAJOR change.
SemVer allows anything to change at 0.y.z. In practice, treat MINOR as "breaking" and PATCH as "non-breaking" during 0.x development. Document this convention in your README. Graduate to 1.0 when the API is stable. FastAPI (0.115.x) and httpx (0.28.x) have stayed at 0.x for years -- if you have significant adoption, either go to 1.0 or explicitly document your 0.x contract.
Use CalVer only for tools where "backward compatibility" is meaningless or always maintained: formatters (Black 24.10.0), packaging tools (pip 24.3.1), platforms. For libraries with downstream dependents, always use SemVer.
PyPI and pip enforce PEP 440. Non-compliant version strings are rejected.
| Concept | SemVer Syntax | PEP 440 Syntax |
|---------|---------------|----------------|
| Pre-release alpha | 1.0.0-alpha.1 | 1.0.0a1 |
| Pre-release beta | 1.0.0-beta.2 | 1.0.0b2 |
| Release candidate | 1.0.0-rc.1 | 1.0.0rc1 |
| Post-release | Not defined | 1.0.0.post1 |
| Dev release | Not defined | 1.0.0.dev1 |
Ordering: 1.0.0.dev1 < 1.0.0a1 < 1.0.0b1 < 1.0.0rc1 < 1.0.0 < 1.0.0.post1
Pre-releases are never installed by default. Users must pass --pre or pin an exact pre-release version. Always use PEP 440 format for tags: v1.0.0a1, not v1.0.0-alpha.1.
Maintain a CHANGELOG.md following the Keep a Changelog format. Group entries by type, write from the user's perspective, and link each version to its diff.
| Section | When to Use | SemVer Signal | |---------|------------|---------------| | Added | New features | MINOR | | Changed | Changes to existing functionality | MINOR or MAJOR | | Deprecated | Features marked for future removal | MINOR | | Removed | Features removed | MAJOR | | Fixed | Bug fixes | PATCH | | Security | Vulnerability fixes | PATCH |
| Bad Entry | Good Entry | |-----------|------------| | "Refactored internal connection pooling module" | "Fixed connection pool not releasing connections on timeout" | | "Updated CI matrix" | (Do not include -- not user-facing) |
Use Towncrier for multi-contributor projects. Each PR adds a news fragment (changes/423.bugfix.md), CI enforces fragment presence, and towncrier build compiles them at release time. No merge conflicts on the changelog. Used by pip, pytest, attrs, Twisted.
Define the version in exactly one place. Prefer git-tag-derived versioning for open source.
| Scenario | Approach |
|----------|----------|
| Open source library | hatch-vcs or setuptools-scm (git tag as source, generates _version.py) |
| Internal/private package | Static version in pyproject.toml |
| Simple script/tool | __version__ in source |
For runtime access, import from the generated _version.py file (see project-structure skill for the full rationale on why _version.py is preferred over importlib.metadata.version()).
Never remove a public API without at least one release cycle of deprecation warnings. Include the removal version, the alternative, and a migration link in every warning message.
warnings.warn(
"old_function() is deprecated since v2.0 and will be removed in v3.0. "
"Use new_function() instead. "
"See https://mylib.dev/migration for details.",
DeprecationWarning,
stacklevel=2, # CRITICAL: points warning to the caller's code
)
Always use stacklevel=2. Without it, the warning points to the line inside your library, not the user's code that called the deprecated function. For wrapped or decorated functions, use stacklevel=3 or higher.
| User Base | Minimum Deprecation Period | |-----------|--------------------------| | Small (< 1K downloads/month) | 1 minor version (3+ months) | | Medium (1K-100K downloads/month) | 2 minor versions (6+ months) | | Large (100K+ downloads/month) | 1 full major version (12+ months) |
Test that deprecation warnings fire correctly with pytest.warns(DeprecationWarning, match=...) and that deprecated functions still work until removal.
Follow this end-to-end workflow for every release:
towncrier build --version 2.1.0git tag v2.1.0 && git push origin main --tagsrelease eventFor major versions, publish pre-releases first: v2.0.0a1 (alpha) then v2.0.0b1 (beta) then v2.0.0rc1 (release candidate) then v2.0.0 (stable). Allow 2-4 weeks between stages for ecosystem testing. Pydantic v2 used a 2-month pre-release window.
# pyproject.toml -- Towncrier configuration
[tool.towncrier]
package = "my_package"
directory = "changes"
filename = "CHANGELOG.md"
title_format = "## [{version}] - {project_date}"
issue_format = "[#{issue}](https://github.com/org/repo/issues/{issue})"
underlines = ["", "", ""]
[[tool.towncrier.type]]
directory = "feature"
name = "Added"
showcontent = true
[[tool.towncrier.type]]
directory = "bugfix"
name = "Fixed"
showcontent = true
[[tool.towncrier.type]]
directory = "deprecation"
name = "Deprecated"
showcontent = true
[[tool.towncrier.type]]
directory = "removal"
name = "Removed"
showcontent = true
[[tool.towncrier.type]]
directory = "change"
name = "Changed"
showcontent = true
[[tool.towncrier.type]]
directory = "security"
name = "Security"
showcontent = true
When reviewing code for versioning, releases, and changelogs:
MAJOR.MINOR.PATCH) with correct segment bumped for the change type1.0.0a1, not 1.0.0-alpha.1)_version.py, or static in pyproject.toml)CHANGELOG.md follows Keep a Changelog format with entries grouped by Added/Changed/Deprecated/Removed/Fixed/Securitywarnings.warn() with DeprecationWarning, stacklevel=2, the removal version, and the alternativev prefix (v2.1.0) and PEP 440 format for pre-releases (v2.0.0a1)tools
This skill should be used when the user invokes any /plan-* command from the planning-tools plugin (/plan-context, /plan-master, /plan-open-questions, /plan-verify, /plan-tick, /plan-progress, /plan-delete), asks how Claude Code's plan files work, asks where plans are stored, asks to author or audit a multi-phase master planning document, asks how to walk through a plan's Open Questions interactively, asks how to write progress entries, or mentions ~/.claude/plans/ or .claude/planning-tools.local.md. Provides the index of planning-tools commands, the master-plan workflow lifecycle, the v0.3.0+ list-shape mandate (phases and questions as headings + bulleted scope items, never tables), the v0.3.2+ plain-bullet shape (no `- [ ]` checkboxes — heading emoji is the sole tick signal), the progress-entry methodology, and the mechanics of Claude Code's plan-mode file storage.
testing
This skill should be used by the plan-verifier agent and the /plan-verify command to audit a drafted master plan against a fixed checklist. Covers universal-core completeness, the v0.3.0+ no-tables-for-phases-or-questions rule, trigger-based section-coverage gaps, phase actionability (heading + per-phase TL;DR + bulleted scope + exit criteria), the v0.3.1+ per-phase TL;DR requirement, the v0.3.2+ plain-bullet scope shape (legacy `- [ ]`/`- [x]` accepted silently), the v0.3.3+ context-block shape (plan-level `**TL;DR:**` + bulleted metadata, legacy `>` blockquote accepted silently), integer phase numbering enforcement, dependency traceability, citation resolution, callout/evidence convention compliance, Open Questions placement, and the one-PR-per-master-plan rule. Single-owner of the audit checklist.
tools
This skill should be used when authoring, reviewing, or modifying a multi-phase master planning document via the planning-tools plugin (especially the /plan-master and /plan-verify commands). Codifies the universal core sections, trigger-based optional sections, integer-only phase numbering, Open Questions placement, one-PR-per-plan rule, status conventions, evidence attribution, callouts, cross-reference formats, the v0.3.0 list-shape mandate (phases and questions are heading + bulleted list, never markdown tables), the v0.3.1 per-phase TL;DR requirement (1–3 sentence what/why summary under each phase heading for glance-ability), the v0.3.2 plain-bullet scope shape (`- <action>` items, no `- [ ]` checkboxes — the phase status emoji is the sole tick signal), and the v0.3.3 context-block shape (a plan-level `**TL;DR:**` + a bulleted metadata list instead of a `>` blockquote; legacy blockquote blocks accepted silently). Project-agnostic — no ticket-prefix or plan-type taxonomy.
testing
This skill should be used when the user is adjusting spacing, padding, margins, content density, section gaps, vertical rhythm, or separation between elements. Also applies when reviewing whether a design feels cramped or too sparse, choosing between borders and whitespace for separation, or defining a spacing system. Covers the 4px/8px spacing system, macro vs micro whitespace, content density spectrum, separation techniques (whitespace > background shifts > borders), and vertical rhythm.