release-and-changelog
verified8c37d9b9-4deb-4e36-ba95-b54d7e0ef1e6
Use when cutting a release — semver decision table (breaking/feature/fix → major/minor/patch), Keep a Changelog format, and the full release checklist from version bump to announce.
Metadata
Skill file
# Release and Changelog
**Use when** cutting a release — deciding the version number, writing the changelog, tagging, building, and announcing. A release without a changelog is an untracked deployment.
## Semver Decision Table
```
MAJOR.MINOR.PATCH
| | └─ PATCH: backward-compatible bug fixes
| └─────── MINOR: backward-compatible new features
└───────────── MAJOR: breaking changes
```
| Change type | Bump | Example |
|-------------|------|---------|
| Breaking API change | **MAJOR** | Removed param, renamed endpoint, dropped Python 3.8 |
| New feature (backward-compatible) | **MINOR** | New endpoint, new optional param |
| Bug fix (backward-compatible) | **PATCH** | Fixed null crash, corrected rounding |
| Docs only | (usually) **PATCH** | README typo, comment fix |
| Deprecation (not removal) | **MINOR** | Added `@deprecated` warning |
### Pre-1.0 Pragmatics
```text
0.x.y is special: anything may break, so:
- 0.1.0 → 0.2.0 can have breaking changes (MINOR acts like MAJOR)
- 0.2.0 → 0.2.1 is a bug fix (PATCH behaves normally)
Rule: before 1.0, don't promise stability.
```
### Quick decision: what changed?
```bash
# Inspect commits since the last tag
git log --oneline v1.2.0..HEAD
# Categorize:
# - "feat:" -> MINOR
# - "fix:" -> PATCH
# - "refactor:" -> PATCH (usually)
# - "BREAKING" -> MAJOR
```
## Changelog Format (Keep a Changelog)
Follow https://keepachangelog.com — sections by version, newest first:
```markdown
# Changelog
All notable changes to this project are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com).
## [Unreleased]
## [1.3.0] - 2026-08-15
### Added
- Bulk order cancellation endpoint (`POST /v1/orders/bulk-cancel`)
### Changed
- Rate limiting now returns 429 with a `Retry-After` header
### Fixed
- Null `user_id` no longer crashes the refresh flow (#1234)
### Deprecated
- `GET /v1/orders` will be removed in 2.0; use `GET /v2/orders`
## [1.2.0] - 2026-07-20
### Added
- CSV export for order history
```
### Categories
```text
Added — new features
Changed — changes to existing functionality
Deprecated — soon-to-be-removed features
Removed — removed features
Fixed — bug fixes
Security — vulnerability fixes
```
## Deriving Changelog from Conventional Commits
```bash
# If you use Conventional Commits, you can auto-generate a changelog draft
# Using git-cliff (https://github.com/orhun/git-cliff)
git cliff --tag v1.3.0
# Or generate a simple log grouped by type
git log --oneline v1.2.0..HEAD | grep "^[a-f0-9]* feat" # features
git log --oneline v1.2.0..HEAD | grep "^[a-f0-9]* fix" # fixes
```
## The Release Checklist
```bash
# 1. Version bump (in pyproject.toml, package.json, etc.)
# pyproject.toml: version = "1.3.0"
# package.json: "version": "1.3.0"
# 2. Update the changelog (move [Unreleased] -> [1.3.0])
# CHANGELOG.md
# 3. Commit the bump + changelog
git add pyproject.toml CHANGELOG.md
git commit -m "chore: release 1.3.0"
# 4. Tag it
git tag -a v1.3.0 -m "Release 1.3.0"
# 5. Push the tag
git push origin main --tags
# 6. Build the artifact
python -m build # or: make build, npm run build
# 7. Publish (PyPI example)
python -m twine upload dist/*
# 8. Announce (GitHub release + notes)
gh release create v1.3.0 --title "1.3.0" --notes "$(cat CHANGELOG.md | sed -n '/## \[1.3.0\]/,/## \[1.2.0\]/p')"
```
## Git Tag Conventions
```bash
# Annotated tag (recommended — stores metadata)
git tag -a v1.3.0 -m "Release 1.3.0"
# Lightweight tag (not recommended for releases)
git tag v1.3.0
# Tag naming: v-prefix is conventional for semver
# v1.3.0 (yes) 1.3.0 (also fine, but v-prefix is clearer)
```
## Guardrails
- **Never bump without a changelog.** A version with no changelog is an untracked deployment.
- **Never mix breaking changes into a patch bump.** That's how you break downstream consumers silently.
- **Don't leave `[Unreleased]` with released changes.** Every merged change belongs in a versioned section.
- **Tag BEFORE building**, so the build embeds the correct version.
- **Untagged releases don't exist.** If it's not tagged, you can't roll back to it.
## Pitfalls
| Pitfall | Fix |
|---------|-----|
| Breaking change in a patch bump | Categorize commits; breaking → MAJOR |
| Changelog not updated | Update `[Unreleased]` → versioned section EVERY release |
| Untagged release | Always `git tag -a vX.Y.Z` |
| Changelog generated but not human-reviewed | Auto-gen is a draft; clean it up and add context |
| Version bump in only some places | Grep for the old version everywhere (`grep -rn "1.2.0" .`) |
## Verify / Checklist
- [ ] Semver decision matches the changes (breaking/feature/fix → major/minor/patch)
- [ ] Changelog has an entry for this version with correct categories
- [ ] Version bumped in ALL locations (`grep -rn "<old>" .` finds nothing stale)
- [ ] Annotated git tag created (`git tag -a vX.Y.Z`)
- [ ] Tag pushed (`git push --tags`)
- [ ] Artifact built and published
- [ ] GitHub release / announcement created with notes
Attached files
No attached files.