Release Workflow¶
This document describes the automated release process for TNH Scholar, designed to support biweekly (or faster) releases during rapid prototyping with minimal manual effort.
Quick Reference¶
Standard patch release workflow (e.g., x.x.y β 0.x.y+1):
make release-patch # Bump version + update TODO.md
make changelog-draft # Generate CHANGELOG entry
# Edit CHANGELOG.md with generated content
make release-commit # Commit version changes
make release-tag # Tag and push to remote
make release-publish # Strip frontmatter, build, publish to PyPI, restore README (automated)
Dry-run mode: Add DRY_RUN=1 to any command to preview without making changes:
make release-patch DRY_RUN=1 # Preview version bump
make release-commit DRY_RUN=1 # Preview commit
make release-tag DRY_RUN=1 # Preview tag and push
make release-publish DRY_RUN=1 # Preview PyPI publish
Prerequisites¶
One-Time Setup¶
Before first release, complete these configuration steps:
- PyPI API Token: Configure Poetry with your PyPI credentials
Get a token from: https://pypi.org/manage/account/token/
- Git Configuration: Ensure git is configured with your name and email
- Quality Checks Pass: Verify the project builds cleanly
Pre-Release Verification¶
Before starting any release, ensure all quality checks pass:
This runs:
make test- Full test suite with pytestmake type-check- MyPy type checkingmake lint- Ruff lintingmake docs-verify- Documentation build, link check, and spell check
Expected output: β
All quality checks passed - ready to release
If checks fail, fix the issues before proceeding with the release.
Release Types¶
Choose the appropriate version bump based on the changes:
Patch Release (0.x.Y β 0.x.Y+1)¶
Use for:
- Bug fixes
- Documentation improvements
- Minor refactoring without API changes
- Dependency updates
- Code cleanup and formatting
Minor Release (0.X.y β 0.X+1.0)¶
Use for:
- New features that maintain backward compatibility
- Significant documentation reorganization
- New CLI commands or subcommands
- Major improvements to existing features
Major Release (X.y.z β X+1.0.0)¶
Use for:
- Breaking API changes
- Incompatible CLI changes
- Major architectural shifts
Note: Major releases are rare during alpha/beta phases.
Step-by-Step Workflow¶
Step 1: Bump Version¶
Choose the appropriate version bump:
make release-patch # For 0.1.4 β 0.1.5
# OR
make release-minor # For 0.1.4 β 0.2.0
# OR
make release-major # For 0.1.4 β 1.0.0
What happens:
- Updates
pyproject.tomlversion field - Updates
TODO.mdversion header to match - Displays next steps
Example output:
π Bumping patch version (0.x.Y -> 0.x.Y+1)...
Bumping version from 0.1.4 to 0.1.5
π Updating version to 0.1.5 in TODO.md...
β
Version updated to 0.1.5
Next steps:
1. Run 'make changelog-draft' to generate CHANGELOG entry
2. Edit CHANGELOG.md with the generated content
3. Run 'make release-commit' to commit changes
4. Run 'make release-tag' to tag and push
5. Run 'make release-publish' to publish to PyPI
Step 2: Generate CHANGELOG Entry¶
What happens:
- Analyzes git commits since the last tag
- Categorizes commits by type (Added, Changed, Fixed, Documentation, etc.)
- Generates a formatted CHANGELOG entry
Example output:
π Generating CHANGELOG entry from git history...
## [0.1.5] - 2025-12-06
### Added
- Version sync pre-commit hook to prevent version mismatches
- Python-based link checker (md-dead-link-check) for documentation
### Changed
- Replaced lychee with Python-native link checking tool
- Improved developer onboarding with fewer external dependencies
### Documentation
- Created comprehensive release workflow documentation
============================================================
π Draft CHANGELOG entry for v0.1.5
Based on 8 commits since v0.1.4
============================================================
π Copy this to CHANGELOG.md and edit as needed
Step 3: Edit CHANGELOG.md¶
- Open
CHANGELOG.md(in project root) in your editor - Copy the generated entry from your terminal
- Paste at the top of the file (after the header, before previous versions)
- Edit the entry for clarity and completeness:
- Remove noise commits (merge commits, trivial fixes)
- Rewrite commit messages for user-facing clarity
- Group related changes together
- Add context or references where needed
- Add "Notes" or "Breaking Changes" sections if applicable
- Save the file
Example polished entry:
# Changelog
All notable changes to TNH Scholar will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
## [0.1.5] - 2025-12-06
### Added
- **Version Sync Pre-commit Hook**: Automatically validates that pyproject.toml and TODO.md versions match before allowing commits
- **Python-based Link Checker**: Replaced lychee (Rust tool) with md-dead-link-check for pure Python toolchain
### Changed
- Simplified developer setup by removing non-Python tooling requirements
- Restored full documentation verification pipeline with Python-native tools
### Documentation
- Created comprehensive release workflow documentation in docs/development/
### Notes
- This release focuses on release automation improvements (Phase 2)
- No code functionality changes - purely tooling and documentation infrastructure
- External link checking now uses pure Python stack for better Poetry integration
## [0.1.4] - 2025-12-05
...
Step 4: Commit Version Changes¶
What happens:
- Stages
pyproject.toml,TODO.md,CHANGELOG.md, andpoetry.lock - Creates a formatted commit with a standard message
- Shows next steps
Commit message format:
chore: Bump version to 0.1.5
- Update version in pyproject.toml
- Update TODO.md version header
- Add 0.1.5 release notes to CHANGELOG.md
π€ Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Step 5: Tag and Push¶
What happens:
- Creates an annotated git tag (e.g.,
v0.1.5) - Pushes the current branch to the remote
- Pushes the tag to the remote
Tag message format:
Release v0.1.5
See CHANGELOG.md for full details.
π€ Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Example output:
π·οΈ Tagging version v0.1.5...
π€ Pushing branch and tag...
β
Tagged and pushed v0.1.5
Next: Run 'make release-publish' to publish to PyPI
Step 6: Publish to PyPI¶
What happens (all automated):
- Prepares README for PyPI: Strips YAML frontmatter to ensure clean rendering
- Creates backup at
README.md.bak -
Removes frontmatter that would display as plain text on PyPI
-
Builds distributions: Creates wheel and source distribution using Poetry
-
Publishes to PyPI: Uploads both distributions
-
Restores README: Automatically restores original README with frontmatter
Why frontmatter stripping is needed: PyPI doesn't process YAML frontmatter and will display it as plain text, making your project description look unprofessional. The automation handles this for you.
Example output:
π Preparing README for PyPI (stripping YAML frontmatter)...
β Backed up README.md to /path/to/README.md.bak
β Stripped 443 bytes of frontmatter from README.md
π¦ README.md is ready for PyPI build
π‘ Run 'python scripts/prepare_pypi_readme.py --restore' to restore original
π¦ Building package...
Building tnh-scholar (0.1.5)
- Building sdist
- Built tnh_scholar-0.1.5.tar.gz
- Building wheel
- Built tnh_scholar-0.1.5-py3-none-any.whl
π€ Publishing to PyPI...
Publishing tnh-scholar (0.1.5) to PyPI
- Uploading tnh_scholar-0.1.5-py3-none-any.whl 100%
- Uploading tnh_scholar-0.1.5.tar.gz 100%
π Restoring original README...
β Restored README.md from backup
β
Published v0.1.5 to PyPI
π Release complete! Check https://pypi.org/project/tnh-scholar/
Step 7: Verify Release¶
After publishing, verify the release was successful:
- Check PyPI: Visit https://pypi.org/project/tnh-scholar/
- Verify the new version is listed
- Important: Check that README renders correctly without YAML frontmatter
-
Confirm the project description starts with "# TNH Scholar README" and not with "---"
-
Test Installation:
pip install --upgrade tnh-scholar==0.1.5
python -c "import tnh_scholar; print(tnh_scholar.__version__)"
Expected output: 0.1.5
- Check GitHub:
- Tag appears in releases: https://github.com/aaronksolomon/tnh-scholar/tags
- Verify the commit and tag messages are correct
Advanced Usage¶
All-in-One Release¶
If you've already edited CHANGELOG.md and are confident in your changes:
This runs: release-commit β release-tag β release-publish in sequence.
β οΈ Use with caution: This skips intermediate verification steps. Only use this after you've verified CHANGELOG.md is correct.
Dry Run Mode¶
Preview any release command without making changes by adding DRY_RUN=1:
# Preview version bump
make release-patch DRY_RUN=1
# Preview commit
make release-commit DRY_RUN=1
# Preview tag and push
make release-tag DRY_RUN=1
# Preview PyPI publish
make release-publish DRY_RUN=1
Example dry-run output for version bump:
π DRY RUN MODE - No changes will be made
π Would bump patch version: 0.1.4 β 0.1.5
Commands that would run:
poetry version patch
sed -i.bak 's/> \*\*Version\*\*:.*/> **Version**: 0.1.5 (Alpha)/' TODO.md
To execute: make release-patch
Benefits:
- See exact commands that will run
- Verify commit messages and tag messages before creating them
- Catch errors early (wrong version bump, missing files, etc.)
- Safe way to learn the release workflow
- Useful for documenting the process
When to use dry-run:
- First time using a release target
- Testing a new release workflow
- Verifying what will be committed/tagged/published
- Training new contributors
- Before major releases
Test version bumps without dry-run:
# Check current version
poetry version -s
# Test version bump calculation (doesn't modify files)
poetry version --dry-run patch # Shows: 0.1.4 β 0.1.5
poetry version --dry-run minor # Shows: 0.1.4 β 0.2.0
# Actually bump (modifies pyproject.toml)
make release-patch
# Undo if needed (before committing)
git checkout pyproject.toml TODO.md poetry.lock
Skip Steps¶
You can run individual targets if you need to customize the workflow:
# Just generate changelog (no version bump)
make changelog-draft
# Just commit (if you bumped version manually)
make release-commit
# Just tag (if you committed manually)
make release-tag
# Just publish (if you tagged manually)
make release-publish
Automation Features¶
Version Sync Pre-commit Hook¶
A pre-commit hook validates that pyproject.toml and TODO.md versions match before allowing commits. This prevents version drift bugs.
What it checks:
- Reads version from
pyproject.tomlviapoetry version -s - Extracts version from
TODO.mdusing pattern matching - Fails commit if versions don't match
How to fix a mismatch:
# Use one of the release targets to sync versions
make release-patch # Bump patch version
make release-minor # Bump minor version
make release-major # Bump major version
The hook runs automatically on every commit. You can also run it manually:
Documentation Verification¶
The make docs-verify target runs a comprehensive documentation quality check:
- Drift Check: Ensures README.md and docs/index.md are in sync
- Build Check: Runs MkDocs in strict mode (fails on warnings)
- Link Check: Validates external links using md-dead-link-check (Python-native)
- Spell Check: Runs codespell on README and docs
All documentation tools are Python-based and managed by Poetry for consistent developer experience.
Troubleshooting¶
"No previous tag found"¶
Problem: First release, no git tags exist yet.
Solution: Write the CHANGELOG entry manually, skip make changelog-draft.
"Version mismatch in TODO.md"¶
Problem: TODO.md version marker not found or malformed.
Solution: Ensure TODO.md contains a line matching the pattern:
The version sync hook validates this format.
"PyPI authentication failed"¶
Problem: No PyPI API token configured.
Solution:
Get a token from: https://pypi.org/manage/account/token/
"Tests failing in release-check"¶
Problem: Code has failing tests, type errors, or lint violations.
Solution: Fix issues before releasing:
make test # Run pytest test suite
make type-check # Run mypy type checks
make lint # Run ruff linting
make docs-verify # Build and validate docs
Address all failures before proceeding with the release.
"Git push rejected"¶
Problem: Remote has changes you don't have locally.
Solution:
git pull --rebase origin $(git branch --show-current)
# Resolve conflicts if any
make release-tag # Try again
"Link check failing"¶
Problem: External links are broken or timing out.
Solution:
- Review the failing links in the output
- Update or remove broken links
- For transient failures, retry after a few minutes
- For sites that block automated requests, add to the exclude list in
pyproject.toml:
Tips for Efficient Releases¶
Use Conventional Commits¶
Format commits to make CHANGELOG generation more accurate:
git commit -m "feat: add new feature X"
git commit -m "fix: resolve bug in Y"
git commit -m "docs: update getting started guide"
git commit -m "chore: bump dependencies"
Commit prefixes:
feat:β Added sectionfix:β Fixed sectiondocs:β Documentation sectionchore:,build:,ci:β Infrastructure sectionrefactor:,perf:β Changed sectiontest:β Testing section
Batch Related Changes¶
For rapid iteration, you can release after any logical batch of commits:
# Week 1: Development
git commit -m "feat: implement feature A"
git commit -m "feat: implement feature B"
git commit -m "fix: resolve edge case in feature A"
git commit -m "docs: document features A and B"
# Week 2: Release day
make release-patch
make changelog-draft
# Edit CHANGELOG.md
make release-commit
make release-tag
make release-publish
# Total time: 10-15 minutes
Schedule Regular Releases¶
Consider a biweekly release cadence during active development:
- Every other Friday: Release day
- Morning: Run
make release-checkto catch issues early - Afternoon: Execute release workflow
- Result: Consistent, predictable releases that build user trust
Parallel Development¶
You can work on multiple features in parallel using branches:
# Create feature branches from stable release
git checkout -b feature-a v0.1.4
# ... work on feature A ...
git checkout -b feature-b v0.1.4
# ... work on feature B ...
# When ready, merge to main and release
git checkout main
git merge feature-a
make release-patch # Release 0.1.5
This allows rapid iteration without blocking work on new features.
Release Checklist¶
Use this checklist to ensure complete releases:
- All quality checks pass (
make release-check) - Version bumped with appropriate type (
make release-patch/minor/major) - CHANGELOG.md updated with clear, user-friendly descriptions
- Version changes committed (
make release-commit) - Tag created and pushed (
make release-tag) - Package published to PyPI (
make release-publish- automatically strips/restores README frontmatter) - PyPI listing verified at https://pypi.org/project/tnh-scholar/
- Installation test from PyPI successful (
pip install tnh-scholar==X.Y.Z) - GitHub tag appears in repository tags
- GitHub Release created with release notes
Related Documentation¶
Makefile(project root) - Release automation implementationCHANGELOG.md(project root) - Version history- Contributing Guide - Contribution guidelines
- Markdown Standards - Documentation standards