Outcome Engineering releases are tag-driven. Pushing a vX.Y.Z tag triggers the
GitHub Actions publish pipeline, which runs tests, builds the package, publishes
to PyPI through trusted publishing, and creates a GitHub Release.
Prepare the release so the tag pipeline succeeds on the first try.
Preconditions
Verify these before editing release files. If any fail, stop and tell the user.
- Clean working tree:
git status --short - On
main:git branch --show-current - Up to date with remote:
git fetch originand comparemainwithorigin/main
If the user has not specified a release type, ask whether this is:
patch: bug fixes or small safe changesminor: backwards-compatible featuresmajor: breaking changesbeta: pre-release testing build
Step 1: Inspect Changes
Understand what is being released before touching files.
git describe --tags --abbrev=0
git log $(git describe --tags --abbrev=0)..HEAD --oneline
If there are no tags yet, inspect the relevant history manually:
git log --oneline
Cross-check the commits with the [Unreleased] section in CHANGELOG.md.
Add missing user-facing changes under standard categories:
AddedChangedFixedRemovedDocs
Step 2: Run Local Checks
Run the checks that mirror the release pipeline.
cd api
uv run ruff check .
uv run ty check
uv run pytest
uv build
If a check fails, fix it before continuing. Do not tag a release from a failing local state.
Step 3: Check Release-Sensitive Docs
Update docs only when the release changes user-facing behavior:
- CLI commands or flags changed
- Outcome Graph structure or validation changed
- Installation or skill behavior changed
- Existing examples no longer match current behavior
For CLI surface changes, update the bundled oe-cli skill:
api/src/oe_cli/skills/oe-cli/SKILL.md
Step 4: Bump Version
The package version lives in api/pyproject.toml:
[project]
version = "X.Y.Z"
Calculate the next version from the chosen release type.
For beta releases:
0.1.0->0.1.1b10.1.1b1->0.1.1b20.1.1b2->0.1.1when promoting to stable
Step 5: Update Changelog
In CHANGELOG.md:
- Replace
## [Unreleased]with a new empty## [Unreleased]section followed by## [X.Y.Z] - YYYY-MM-DD. - Move release entries under the version heading.
- Keep entries concise and useful to a user scanning release notes.
The GitHub Release job extracts the matching version section from
CHANGELOG.md, so the heading must be exactly:
## [X.Y.Z] - YYYY-MM-DD
Step 6: Commit, Tag, and Push
Stage the release files:
git add api/pyproject.toml api/uv.lock CHANGELOG.md
git add README.md skills api/src/oe_cli/skills api/tests .github/workflows
git commit -m "release: vX.Y.Z"
git tag vX.Y.Z
Before pushing, show the user:
- version being released
- changelog section
- files changed
- tag to push
Wait for explicit user confirmation before pushing:
git push origin main
git push origin vX.Y.Z
Step 7: Monitor Pipeline
After pushing the tag, monitor the publish workflow:
gh run list --workflow=publish.yml --limit=1
gh run watch $(gh run list --workflow=publish.yml --limit=1 --json databaseId -q '.[0].databaseId')
The pipeline stages are:
- Quality Checks:
uv run ruff check .,uv run ty check, anduv run pytestfromapi/ - Build Package:
uv buildand tag/version verification - Publish to PyPI: trusted publishing through OIDC
- Create GitHub Release: release notes from
CHANGELOG.md
If the pipeline fails, inspect logs:
gh run view <run-id> --log-failed
Step 8: Verify Release
After the pipeline succeeds:
pip index versions outcome-engineering
gh release view vX.Y.Z
Report the PyPI and GitHub Release links:
https://pypi.org/project/outcome-engineering/X.Y.Z/- the URL from
gh release view
Recovery
If a pushed tag must be retried:
- Fix the issue.
- Ask the user before deleting any tag.
- Delete local and remote tags only after confirmation:
git tag -d vX.Y.Z
git push origin :refs/tags/vX.Y.Z
- Amend the release commit or create a fix commit.
- Re-tag and push again.
Deleting tags is destructive. Never do it without explicit confirmation.