Release Guide
Two Release Methods:
- Automated Release (Recommended) - One-click via GitHub Actions
- Manual Release (Advanced) - Full control for experienced maintainers
This project publishes to PyPI via GitHub Actions on git tags of the form v* using Trusted Publishers (OIDC).
Tool-Version Auto-Merge (between releases)
Security tools bump constantly and cutting a release for every bump would
be exhausting. Instead, the repo runs a three-layer tool-version automation
stack that keeps versions.yaml + Dockerfiles fresh with minimal maintainer
attention:
- Layer 1 — exit 0 on outdated. The weekly version check reports outdated tools but does not fail CI (outdated ≠ broken).
- Layer 2 — tracking issue dashboard.
update_versions.py --check-outdated --create-issuesopens/updates a single issue listing every outdated tool. - Layer 3 — auto-PR + soak-window auto-merge. The
auto-update-toolsjob opens a PR with patch+minor bumps. A separate job runs every 6 hours and auto-merges the PR once it is ≥ 24 hours old with all required checks green.
What an auto-PR looks like
Every Sunday at 00:00 UTC the auto-update-tools job (in
.github/workflows/maintenance.yml) opens a PR like this:
- Title:
chore(deps): weekly auto-update 2026-04-19 (level=minor) - Labels:
dependencies,automated,auto-merge-ok - Body: Embeds the
update_versions.py --classifyJSON so you can see every bump and its semver classification (patch / minor / major / unknown) in one block. - Merge policy: Not set to
--auto. The soak-window job handles merging.
Major and unknown bumps are left outdated — they appear as items in the
weekly tracking issue (Layer 2) so you can investigate when you have time.
How auto-merge actually fires
The auto-merge-tool-bumps job (cron 0 */6 * * *) runs
scripts/dev/auto_merge_tool_bumps.py, which for each open PR labeled
auto-merge-ok decides:
- merge — PR is ≥ 24h old, all required checks green →
gh pr merge --squash - flip — A required check failed → remove
auto-merge-ok, addneeds-review, post an explanatory comment. - defer — Anything else (soak window still active, checks running, the
maintainer removed
auto-merge-ok).
The decision logic is unit-tested in
tests/unit/test_auto_merge_tool_bumps.py.
Cancel an auto-merge
Remove the auto-merge-ok label from the PR. The soak job will never merge
a PR without that label — full stop. Re-add it to resume.
Force immediate merge (verification / urgent)
# Dispatch the soak job manually with zero soak window
gh workflow run maintenance.yml --ref main \
-f task=auto-merge
For a truly zero-wait test of the machinery, run the script locally:
Manual dispatch with a specific bump level
# Just patches (safest)
gh workflow run maintenance.yml --ref main -f task=tool-update -f bump_level=patch
# Patches + minors + majors (skips only 'unknown')
gh workflow run maintenance.yml --ref main -f task=tool-update -f bump_level=major
# Everything including synthetic bumps (falco 0.0.0, akto mini-testing-X)
gh workflow run maintenance.yml --ref main -f task=tool-update -f bump_level=all
One-time branch-protection setup (required)
The contract-test gate only has teeth when tool-contract-tests is a
required status check:
- GitHub UI → Settings → Branches →
mainbranch protection rule - Under "Require status checks to pass before merging," search for and
add:
tool-contract-tests(exact string — pinned via bothjobs.tool-contract-tests:key andname: tool-contract-testsinci.yml). - Leave the other required checks (
quick-checks,test-sharded (*),lint-quick,coverage-aggregate) in place. - Save.
If
tool-contract-testsdoes not appear in the required-checks dropdown, open and close one PR that touchesversions.yamlfirst. GitHub only surfaces status checks in the UI after they have run at least once.
Known transient behavior
Weekly, a patch/minor bump opens both an auto-PR (Sun 00:00) and a
tracking issue (Sun 02:00). This overlap lasts ~24-26 hours until the PR
merges; the issue then auto-closes. Not a bug — the issue dashboard is a
complete picture of outstanding work, and the overlap window is the
trade-off for not filtering --create-issues by bump level.
Method 1: Automated Release (Recommended)
Use the automated-release workflow for one-click releases with guaranteed tool updates.
Quick Start
- Navigate to GitHub Actions → Automated Release → Run workflow
- Fill in parameters:
- Version bump: patch (0.7.1→0.7.2) / minor (0.7.1→0.8.0) / major (0.7.1→1.0.0)
- Changelog entry: Brief summary (e.g., "Add AWS scanning support and fix CLI argument parsing")
- Click Run workflow
- Wait for PR creation (~3-5 minutes)
- Review PR, verify CI passes
- Merge PR → release workflow triggers automatically
What It Does Automatically
- ✅ Updates ALL security tools to latest versions
- ✅ Bumps version in pyproject.toml
- ✅ Updates CHANGELOG.md with your entry
- ✅ Syncs Dockerfiles with new tool versions
- ✅ Creates release PR with detailed summary
- ✅ When merged → creates git tag and triggers full release
Benefits
- Zero manual steps - Just provide version bump type and changelog entry
- Guaranteed tool updates - Cannot accidentally skip tool updates
- Consistent structure - Commit messages, PR format, CHANGELOG format all standardized
- Audit trail - PR shows exactly what changed before release
- Fail-safe - CI blocks if tool updates fail or tests break
Example Workflow
# 1. Navigate to GitHub Actions
https://github.com/jimmy058910/jmo-security-repo/actions/workflows/automated-release.yml
# 2. Click "Run workflow"
# 3. Select:
# - Branch: main
# - Version bump: patch
# - Changelog entry: "Fix badge verification and add weekly tool updates"
# 4. Wait for PR creation
# PR title: "release: v1.0.1"
# PR body includes:
# - Summary of changes
# - Tool version updates (e.g. semgrep version bump)
# - Pre-release checklist
# 5. Review PR, merge when CI passes
# 6. Tag v1.0.1 created automatically
# 7. Release workflow publishes to PyPI and Docker Hub
Method 2: Manual Release (Advanced)
For experienced maintainers who want full control over the release process.
Remember to update CHANGELOG.md with user-facing changes.
Prerequisites
- No PyPI API token required! This repo uses Trusted Publishers (OIDC) for tokenless publishing.
- Ensure the repository is configured as a Trusted Publisher in PyPI settings (one-time setup).
- Ensure tests are green and coverage passes locally (≥85% required).
- Update
CHANGELOG.mdwith all user-facing changes from the "Unreleased" section.
Pre-Release Checklist
📋 Comprehensive Testing: For full manual verification across all platforms (Windows, WSL, Linux, macOS, Docker), see the Manual Testing Checklist.
🔴 CRITICAL FIRST STEP: Update ALL security tools to latest versions
- Update ALL security tools (MANDATORY - enforced by CI):
# Check for available updates
python3 scripts/dev/update_versions.py --check-latest
# If updates found, update all tools
python3 scripts/dev/update_versions.py --update-all # Update versions.yaml
python3 scripts/dev/update_versions.py --sync # Sync Dockerfiles
# Commit tool updates
git add versions.yaml Dockerfile* scripts/dev/install_tools.sh
git commit -m "deps(tools): update all to latest before vX.Y.Z
Updated all security tools before release vX.Y.Z:
- semgrep, checkov, trivy, trufflehog, syft, etc.
See: python3 scripts/dev/update_versions.py --report"
# Verify all tools current
python3 scripts/dev/update_versions.py --check-latest
# Expected output: [ok] All tools are up to date
Why this matters:
- Outdated tools miss security vulnerabilities (semgrep 41 versions behind = 200+ missing rules)
- Users expect latest security tools in fresh releases
-
CI will BLOCK release if tools are outdated (pre-release-check job)
-
Run full test suite locally:
make test
# or
pytest -q --maxfail=1 --disable-warnings --cov=. --cov-report=term-missing --cov-fail-under=85
- Verify linting and formatting:
- Review documentation:
- CHANGELOG.md updated with "Unreleased" changes moved to new version section
- README.md reflects latest features
- QUICKSTART.md is up-to-date
-
docs/USER_GUIDE.md includes new features
-
Validate README consistency (PyPI + Docker Hub sync check):
Why: PyPI and Docker Hub render READMEs from uploaded packages/API, not from GitHub. This check detects:
- PyPI: Missing/outdated badges, Docker namespace mismatches, stale content
- Docker Hub: Old namespace in DOCKER_HUB_README.md, outdated versions, sync configuration issues
- GHCR: Auto-syncs from GitHub (no check needed)
Fix: If inconsistencies found:
- PyPI issues: Resolved automatically when you publish the new release (uses current README.md)
-
Docker Hub issues: Update DOCKER_HUB_README.md before release, ensure DOCKERHUB_ENABLED=true
-
Run pre-release validation:
jmo validate # Quick tier (fixture-based, ~3 min)
jmo validate --tier full -v # Full tier with real tools (~10 min)
This runs 207 checks across CLI completeness, scan correctness, cross-platform behavior, and release artifacts. Expect a GO verdict before proceeding. Use --json for machine-readable output.
- Verify CI is green:
- Check GitHub Actions: all tests passing on ubuntu-latest and macos-latest
- Coverage uploaded to Codecov successfully
WSL (Windows Subsystem for Linux) Validation
Frequency: Every minor release (vX.Y.0)
Environment: WSL2 with Ubuntu 22.04 or later
WSL Prerequisites
- WSL2 Installation:
- Docker Desktop Integration:
- Install Docker Desktop for Windows
- Enable "Use WSL 2 based engine" in settings
-
Enable "Ubuntu-22.04" in Resources → WSL Integration
-
Git Configuration:
Test Cases
TC1: Installation
Goal: Verify JMo Security installs correctly in WSL
# Clone repo to WSL filesystem (NOT /mnt/c/)
cd ~
git clone https://github.com/jimmy058910/jmo-security-repo.git
cd jmo-security-repo
# Install dev dependencies
make dev-deps
# Install external tools
jmo tools install --profile balanced
# Verify tools installed
jmo tools check --profile balanced
Success Criteria:
- [ ] All dependencies install without errors
- [ ]
jmo tools checkshows all tools installed - [ ] No path-related errors (e.g., "cannot find /mnt/c/...")
Known Issues:
- WSL1 not supported (must use WSL2)
- File permissions may require
chmod +x scripts/cli/*.py - Windows paths (
C:\...) not supported; use WSL paths (/home/...)
TC2: Basic Scan
Goal: Verify native CLI scanning works
# Run fast profile scan
jmo fast --repo .
# Verify results generated
ls -lh results/summaries/
cat results/summaries/SUMMARY.md
Success Criteria:
- [ ] Scan completes without path errors
- [ ] Results written to
results/summaries/ - [ ]
dashboard.htmlopens in Windows browser
Troubleshooting:
# If path errors occur, check line endings
file scripts/cli/jmo.py # Should show "ASCII text" (NOT "with CRLF")
# Fix line endings if needed
find . -name "*.py" -exec dos2unix {} \;
TC3: Docker Mode
Goal: Verify Docker integration works
# Test Docker connectivity
docker run --rm hello-world
# Run scan in Docker
docker run --rm -v $(pwd):/repo jmo-security:latest scan --repo /repo --profile-name fast
Success Criteria:
- [ ] Docker daemon accessible from WSL
- [ ] Volume mount works (
/repoaccessible inside container) - [ ] Results written back to WSL filesystem
Known Issues:
- Docker Desktop must be running in Windows
- Volume mounts may be slow (WSL filesystem I/O)
- Use WSL paths, not Windows paths (
/home/...not/mnt/c/...)
TC4: Line Endings
Goal: Verify CRLF handling doesn't break scripts
# Check Git config
git config --get core.autocrlf # Should be "input" or "false" (NOT "true")
# Verify Python scripts have LF endings
file scripts/cli/jmo.py # Should show "ASCII text"
# Verify shell scripts have LF endings
file scripts/dev/install_tools.sh # Should show "Bourne-Again shell script"
Success Criteria:
- [ ] No CRLF line endings in Python or shell scripts
- [ ] Scripts execute without
^Merrors
Fix If Needed:
# Configure Git to not convert line endings
git config --global core.autocrlf input
# Re-checkout repository
git checkout --force
# Verify fixed
file scripts/cli/jmo.py
TC5: Results Accessibility
Goal: Verify results accessible from Windows
# Generate dashboard
jmo fast --repo .
# Open dashboard in Windows browser
# Option 1: Use WSL path in Windows browser
explorer.exe results/summaries/dashboard.html
# Option 2: Copy to Windows filesystem
cp results/summaries/dashboard.html /mnt/c/Users/$(whoami)/Desktop/
# Open from Desktop in Windows browser
Success Criteria:
- [ ] Dashboard opens in Windows browser
- [ ] Interactive features work (filtering, sorting)
- [ ] No path-related errors in browser console
TC6: Symlinks
Goal: Verify symlink handling works
# Create symlink to config
ln -s jmo.yml jmo-link.yml
# Run scan using symlinked config
jmo scan --config jmo-link.yml --repo . --results-dir ./results-symlink-test
# Verify results generated
ls results-symlink-test/
Success Criteria:
- [ ] Symlinks resolve correctly
- [ ] No "cannot find file" errors
- [ ] Results written to expected directory
TC7: Performance Comparison
Goal: Verify WSL performance comparable to native Linux
# Time a fast scan
time jmo fast --repo .
# Expected: Within 20% of native Linux performance
# Fast profile: 5-10 minutes (WSL should be 6-12 minutes)
Success Criteria:
- [ ] Scan duration within 20% of native Linux
- [ ] No excessive disk I/O delays
Known Issues:
- WSL2 I/O may be slower for large repositories
- Recommend cloning to WSL filesystem (
/home/...) not Windows (/mnt/c/...)
WSL Validation Summary
Checklist Completion:
- [ ] TC1: Installation (✅ / ❌)
- [ ] TC2: Basic Scan (✅ / ❌)
- [ ] TC3: Docker Mode (✅ / ❌)
- [ ] TC4: Line Endings (✅ / ❌)
- [ ] TC5: Results Accessibility (✅ / ❌)
- [ ] TC6: Symlinks (✅ / ❌)
- [ ] TC7: Performance (✅ / ❌)
Issues Found:
- Issue 1: [Description]
- Issue 2: [Description]
Resolution:
- All issues resolved: ✅ / ❌
- Blocked issues documented in ROADMAP.md: ✅ / ❌
Sign-Off:
- Tester: [Name]
- Date: [YYYY-MM-DD]
- WSL Version: [Output of
wsl --version] - Ubuntu Version: [Output of
lsb_release -a] - Docker Desktop Version: [Output of
docker --version]
WSL Troubleshooting Guide
Problem: "cannot find /mnt/c/..." errors
Solution: Clone repo to WSL filesystem (/home/...), not Windows filesystem
Problem: "^M: bad interpreter" errors
Solution: Fix line endings:
Problem: Docker volume mounts not working
Solution: Verify Docker Desktop WSL integration enabled for Ubuntu distribution
Problem: Slow I/O performance
Solution: Use WSL filesystem (/home/...), not Windows filesystem (/mnt/c/...)
Problem: explorer.exe not opening files
Solution: Use full Windows path or copy to Windows filesystem first:
macOS Docker Validation
Frequency: Every minor release (vX.Y.0)
Environment: macOS 12+ with Docker Desktop
macOS Prerequisites
- Docker Desktop Installation:
# Install via Homebrew
brew install --cask docker
# Or download from https://www.docker.com/products/docker-desktop
- Docker Daemon Running:
# Verify Docker is running
docker info
# Expected: Server version, OS/Arch: linux/arm64 or linux/amd64
Test Cases (macOS Docker)
TC1: Docker Image Availability
Goal: Verify Docker images work on macOS
# Pull jmo-security image
docker pull jmogaming/jmo-security:latest
# Test basic help command
docker run --rm jmogaming/jmo-security:latest --help
# Expected: jmo CLI help output
Success Criteria:
- [ ] Image pulls successfully
- [ ] Help command works
- [ ] No platform warnings (should work on both Intel and Apple Silicon)
TC2: Volume Mount Handling
Goal: Verify macOS volume mounts work correctly
# Create test repository
mkdir -p ~/tmp/jmo-test
cd ~/tmp/jmo-test
echo "# Test" > README.md
# Run scan with volume mount
docker run --rm \
-v $(pwd):/repo \
jmogaming/jmo-security:latest \
scan --repo /repo --profile-name fast --allow-missing-tools
# Verify results created
ls results/
Success Criteria:
- [ ] Volume mount works (
/repoaccessible inside container) - [ ] Results written back to macOS filesystem
- [ ] No permission errors
Known Issues:
- Docker Desktop on Apple Silicon may show platform warnings (safe to ignore)
- Volume mounts from
/Users/...work,/tmpmay have permission issues
TC3: Network Access
Goal: Verify container can access internet (for tool updates)
# Test network access
docker run --rm jimmy058910/jmo-security:latest bash -c "curl -I https://github.com"
# Expected: HTTP 200 response
Success Criteria:
- [ ] Container has internet access
- [ ] DNS resolution works
- [ ] No proxy configuration needed
TC4: Performance Comparison
Goal: Verify macOS Docker performance is acceptable
# Time a fast scan
time docker run --rm \
-v $(pwd):/repo \
jmogaming/jmo-security:latest \
scan --repo /repo --profile-name fast --allow-missing-tools
# Expected: Within 30% of Linux Docker performance
Success Criteria:
- [ ] Scan duration within 30% of Linux baseline
- [ ] No excessive disk I/O delays
Known Issues:
- Apple Silicon (M1/M2/M3) may be faster than Intel for some operations
- Docker Desktop may be slower than native Linux (expected)
TC5: Multi-Variant Testing
Goal: Verify all Docker image variants work on macOS
# Test deep variant (default/latest)
docker run --rm jmogaming/jmo-security:latest --help
# Test balanced variant
docker run --rm jmogaming/jmo-security:balanced --help
# Test slim variant
docker run --rm jmogaming/jmo-security:slim --help
# Test fast variant
docker run --rm jmogaming/jmo-security:fast --help
Success Criteria:
- [ ] All 4 variants work
- [ ] No platform-specific errors
- [ ] Help commands succeed for all variants
macOS Validation Summary
Checklist Completion:
- [ ] TC1: Image Availability (✅ / ❌)
- [ ] TC2: Volume Mounts (✅ / ❌)
- [ ] TC3: Network Access (✅ / ❌)
- [ ] TC4: Performance (✅ / ❌)
- [ ] TC5: Multi-Variant (✅ / ❌)
Issues Found:
- Issue 1: [Description]
- Issue 2: [Description]
Sign-Off:
- Tester: [Name]
- Date: [YYYY-MM-DD]
- macOS Version: [Output of
sw_vers] - Docker Desktop Version: [Output of
docker --version] - Architecture: [Intel / Apple Silicon]
macOS Troubleshooting
Problem: "no matching manifest for platform" error
Solution: Docker image may not support Apple Silicon, use Rosetta:
Problem: Permission denied on volume mounts
Solution: Grant Docker Desktop access to /Users in macOS Privacy settings
Problem: Slow performance
Solution: Ensure "Use Virtualization Framework" enabled in Docker Desktop settings
Step-by-Step Release Process
- Bump version in
pyproject.toml(for example,0.4.0):
- Update CHANGELOG.md:
-
Move "Unreleased" section content to a new version section:
-
Commit the version bump and changelog:
- Create and push the tag:
- Monitor the Release workflow:
- Go to Actions tab in GitHub
-
The
Release (PyPI)workflow will automatically:- Build the package
- Publish to PyPI using Trusted Publishers (OIDC)
- No token/secret required!
-
Verify the release:
# Wait a few minutes for PyPI to update, then (substitute the version you just tagged):
pip install --upgrade jmo-security==1.0.4
jmo --version
jmo --help
Troubleshooting
Problem: Release workflow fails with "Trusted Publisher authentication failed"
- Solution: Verify the repository is configured as a Trusted Publisher in PyPI settings
- Check: https://pypi.org/manage/account/publishing/ (must match org/repo/workflow)
Problem: Tests fail in CI but pass locally
- Solution: Check matrix differences (Ubuntu vs macOS, Python 3.12 vs 3.13)
- Check: Run
make testwith different Python versions locally
Problem: Coverage below 85%
- Solution: Add tests for uncovered code paths
- Check: Run
pytest --cov=. --cov-report=term-missingto see gaps
Notes
- The package exposes the
jmoconsole script. - Coverage reports are uploaded to Codecov as part of the tests workflow (tokenless OIDC).
- License is defined via SPDX string in
pyproject.tomland theLICENSEfile is included in the distribution. - CI enforces: tests passing, coverage ≥85%, pre-commit checks, reproducible dev deps.
Post-Release
- Announce the release:
- Update project homepage (jmotools.com) if applicable
- Post to relevant communities/channels
-
Tweet/share on social media
-
Monitor for issues:
- Watch GitHub issues for bug reports
- Check PyPI download stats
- Monitor Codecov for coverage trends
Documentation Hub: docs/index.md | Project Home: README.md
Last Updated: February 2026