Releases Plain packages with intelligent version suggestions and parallel release notes generation. Use when releasing packages to PyPI.
Release Plain packages with version bumping, changelog generation, and git tagging.
All mechanical operations are handled by scripts in this skill directory:
| Script | Purpose |
|---|---|
discover-changes | Find packages with unreleased commits (outputs JSON) |
bump-versions | Bump package versions (<package>:<type> ...) |
commit-and-push | Format, sync, commit, tag, and push (<package>:<version> ...) |
add-hunks |
| Stage specific uv.lock hunks by grep pattern (used internally) |
Run git status --porcelain to check for uncommitted changes.
Run discover-changes:
./.claude/skills/release/discover-changes
This outputs JSON with each package's name, current version, and commits since last release. If specific packages were requested, filter the results to only those packages.
For each package that has changes to release, check if any of its files appear in the git status output. If so, stop and warn the user — uncommitted changes in a package being released could mean the release misses work or includes an inconsistent state. Ask them to commit or discard before proceeding. Changes in other directories (e.g. work/, scripts/) are fine to ignore.
For any package with current_version of 0.0.0:
uv version <version> in the package directory to set the version directly (instead of bump)For each package with changes:
./.claude/skills/release/bump-versions <package>:<type> [<package>:<type> ...]
Example: ./.claude/skills/release/bump-versions plain-admin:patch plain-dev:minor
When a package is being released because it depends on changes in another package being released in the same batch, update its minimum version constraint in pyproject.toml.
For each sub-package being released, check if any of its dependencies (in [project.dependencies]) are also being released in this batch. If so, and the sub-package's changes are driven by the dependency's changes (e.g., adapting to a new API), update the constraint from <1.0.0 to >=<new_version>,<1.0.0.
Example: if plain-auth is being released because it adapted to plain 0.113.0's new Request API, update its pyproject.toml:
"plain<1.0.0""plain>=0.113.0,<1.0.0"Only update constraints when there's an actual compatibility requirement — don't add minimums for packages whose changes are independent. Use the commit analysis from Phase 3 to determine this.
For each package to release, sequentially:
Get the file changes since the last release:
git diff <last_tag>..HEAD -- <name> ":(exclude)<name>/tests"
Read the existing <changelog_path> file.
Prepend a new release entry to the changelog with this format:
## [<new_version>](https://github.com/dropseed/plain/releases/<name>@<new_version>) (<today's date>)
### What's changed
- Summarize user-facing changes based on the actual diff (not just commit messages)
- Include commit hash links: ([abc1234](https://github.com/dropseed/plain/commit/abc1234))
- Skip test changes, internal refactors that don't affect public API
### Upgrade instructions
- Specific steps if any API changed
- If no changes required: "- No changes required."
./.claude/skills/release/commit-and-push <package>:<version> [<package>:<version> ...]
This script handles everything: uv sync, ./scripts/fix, staging files, committing each package separately, tagging, and pushing. Sub-packages are committed first, core plain last.
Example: ./.claude/skills/release/commit-and-push plain-admin:0.65.1 plain:0.103.0
Consider the current version when suggesting release types:
Most Plain packages are pre-1.0. For these:
Follow semver strictly: