Skip to content

Release Guide

Two Release Methods:

  1. Automated Release (Recommended) - One-click via GitHub Actions
  2. 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-issues opens/updates a single issue listing every outdated tool.
  • Layer 3 — auto-PR + soak-window auto-merge. The auto-update-tools job 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 --classify JSON 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, add needs-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:

python3 scripts/dev/auto_merge_tool_bumps.py --min-age-hours 0 --dry-run

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:

  1. GitHub UI → Settings → Branches → main branch protection rule
  2. Under "Require status checks to pass before merging," search for and add: tool-contract-tests (exact string — pinned via both jobs.tool-contract-tests: key and name: tool-contract-tests in ci.yml).
  3. Leave the other required checks (quick-checks, test-sharded (*), lint-quick, coverage-aggregate) in place.
  4. Save.

If tool-contract-tests does not appear in the required-checks dropdown, open and close one PR that touches versions.yaml first. 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.


Use the automated-release workflow for one-click releases with guaranteed tool updates.

Quick Start

  1. Navigate to GitHub Actions → Automated ReleaseRun workflow
  2. Fill in parameters:
  3. Version bump: patch (0.7.1→0.7.2) / minor (0.7.1→0.8.0) / major (0.7.1→1.0.0)
  4. Changelog entry: Brief summary (e.g., "Add AWS scanning support and fix CLI argument parsing")
  5. Click Run workflow
  6. Wait for PR creation (~3-5 minutes)
  7. Review PR, verify CI passes
  8. Merge PR → release workflow triggers automatically

What It Does Automatically

  1. ✅ Updates ALL security tools to latest versions
  2. ✅ Bumps version in pyproject.toml
  3. ✅ Updates CHANGELOG.md with your entry
  4. ✅ Syncs Dockerfiles with new tool versions
  5. ✅ Creates release PR with detailed summary
  6. ✅ 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.md with 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

  1. 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
  1. Verify linting and formatting:
make lint
make fmt
make pre-commit-run
  1. Review documentation:
  2. CHANGELOG.md updated with "Unreleased" changes moved to new version section
  3. README.md reflects latest features
  4. QUICKSTART.md is up-to-date
  5. docs/USER_GUIDE.md includes new features

  6. Validate README consistency (PyPI + Docker Hub sync check):

make validate-readme

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.

  1. Verify CI is green:
  2. Check GitHub Actions: all tests passing on ubuntu-latest and macos-latest
  3. 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

  1. WSL2 Installation:
# Windows PowerShell (Administrator)
wsl --install Ubuntu-22.04
wsl --set-default-version 2
  1. Docker Desktop Integration:
  2. Install Docker Desktop for Windows
  3. Enable "Use WSL 2 based engine" in settings
  4. Enable "Ubuntu-22.04" in Resources → WSL Integration

  5. Git Configuration:

# Inside WSL
git config --global core.autocrlf input  # Prevent CRLF issues

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 check shows 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.html opens 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 (/repo accessible 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 ^M errors

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

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:

git config --global core.autocrlf input
git checkout --force
dos2unix scripts/**/*.sh

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:

cp results/summaries/dashboard.html /mnt/c/Users/$(whoami)/Desktop/

macOS Docker Validation

Frequency: Every minor release (vX.Y.0)

Environment: macOS 12+ with Docker Desktop

macOS Prerequisites

  1. Docker Desktop Installation:
# Install via Homebrew
brew install --cask docker

# Or download from https://www.docker.com/products/docker-desktop
  1. 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 (/repo accessible 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, /tmp may 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:

docker run --rm --platform linux/amd64 jmogaming/jmo-security:latest --help

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

  1. Bump version in pyproject.toml (for example, 0.4.0):
[project]
name = "jmo-security"
version = "0.4.0"  # Update this line
  1. Update CHANGELOG.md:
  2. Move "Unreleased" section content to a new version section:

    ## 0.4.0 (2025-10-15)
    
    Highlights:
    
    - [Content from Unreleased section]
    
    ## Unreleased
    
    [Empty for now]
    
  3. Commit the version bump and changelog:

git add pyproject.toml CHANGELOG.md
git commit -m "release: v1.0.1"
  1. Create and push the tag:
git tag v1.0.1
git push origin main
git push origin v1.0.1
  1. Monitor the Release workflow:
  2. Go to Actions tab in GitHub
  3. The Release (PyPI) workflow will automatically:

    • Build the package
    • Publish to PyPI using Trusted Publishers (OIDC)
    • No token/secret required!
  4. 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"

Problem: Tests fail in CI but pass locally

  • Solution: Check matrix differences (Ubuntu vs macOS, Python 3.12 vs 3.13)
  • Check: Run make test with different Python versions locally

Problem: Coverage below 85%

  • Solution: Add tests for uncovered code paths
  • Check: Run pytest --cov=. --cov-report=term-missing to see gaps

Notes

  • The package exposes the jmo console script.
  • Coverage reports are uploaded to Codecov as part of the tests workflow (tokenless OIDC).
  • License is defined via SPDX string in pyproject.toml and the LICENSE file is included in the distribution.
  • CI enforces: tests passing, coverage ≥85%, pre-commit checks, reproducible dev deps.

Post-Release

  1. Announce the release:
  2. Update project homepage (jmotools.com) if applicable
  3. Post to relevant communities/channels
  4. Tweet/share on social media

  5. Monitor for issues:

  6. Watch GitHub issues for bug reports
  7. Check PyPI download stats
  8. Monitor Codecov for coverage trends

Documentation Hub: docs/index.md | Project Home: README.md

Last Updated: February 2026