src/osprey/templates/skills/osprey-release/SKILL.md
Guides a maintainer through cutting an OSPREY release on the GitHub Flow workflow: open a version-bump PR, merge it to main, tag the merge commit, push the tag, verify the automated PyPI publish. Use when someone says "create a release", "bump the version", "cut v2026.X.Y", "publish to PyPI", "tag a release", or asks about the release process. Composes with `osprey-contribute` for the bump PR. Versions follow CalVer (vYYYY.M.P) and the source of truth is `src/osprey/__init__.py` — Hatch derives the pyproject.toml version dynamically.
npx skillsauth add als-apg/osprey osprey-releaseInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill cuts a properly versioned OSPREY release. Releases are CalVer tags
(vYYYY.M.P) on main; the PyPI publish runs automatically when the tag is
pushed.
The shape is:
main — branch protection
rejects it).main.For the PR mechanics in step 2, defer to the osprey-contribute skill.
OSPREY uses CalVer: YYYY.M.P where:
YYYY — four-digit year of the releaseM — calendar month, no zero-padding (e.g., 5, not 05)P — patch counter within the month, starting at 0Examples: 2026.5.0, 2026.5.1 (patch within May 2026), 2026.6.0 (next
month). When the year or month rolls over, P resets to 0.
src/osprey/__init__.py holds __version__. Everything else either reads
from there (Hatch, the GitHub Actions verify step) or is a doc string that
also needs updating.
| File | Purpose | Updated by |
| --- | --- | --- |
| src/osprey/__init__.py | Source of truth — Hatch reads this for the package version | This skill |
| src/osprey/cli/main.py | Fallback version printed by osprey --version when not installed | This skill |
| RELEASE_NOTES.md | First-line title with the release version | This skill |
| CHANGELOG.md | Add ## [vYYYY.M.P] - YYYY-MM-DD heading; rotate ## [Unreleased] content | This skill |
| README.md | "Latest Release" line with version + theme | This skill |
| pyproject.toml | Uses dynamic = ["version"]; Hatch reads from __init__.py | Do not edit |
The release.yml verify step greps __version__ = out of src/osprey/__init__.py
and compares it to the pushed tag — if these disagree, the publish fails.
Open CHANGELOG.md, read the ## [Unreleased] section, and answer three
questions before doing anything else:
### Changed and ### Removed
sections. If user-facing API changed, the release should call it out
prominently and (if it would surprise users) include a migration note.Confirm theme + version + breaking-changes status with the maintainer before proceeding.
Your working venv may have packages that aren't declared in pyproject.toml.
A clean venv catches missing dependencies before users do:
python -m venv .venv-release-test
source .venv-release-test/bin/activate
pip install -e ".[dev]"
# Unit tests (fast, free)
pytest tests/ --ignore=tests/e2e -v
# E2E tests (~10-12 min, ~$1-2 in API calls — must use path, not marker)
pytest tests/e2e/ -v
deactivate && rm -rf .venv-release-test
Any failures stop the release. Fix forward, then re-run.
The version-bump commit cannot be pushed directly to main — branch
protection rejects it. Open a PR instead.
git checkout main && git pull --ff-only origin main
git checkout -b release/vYYYY.M.P
Update each file with the new version. Show the maintainer each diff before applying:
| File | Change |
| --- | --- |
| src/osprey/__init__.py | __version__ = "YYYY.M.P" |
| src/osprey/cli/main.py | The fallback __version__ = "YYYY.M.P" line |
| RELEASE_NOTES.md | First line: # Osprey Framework - Latest Release (vYYYY.M.P) followed by the theme tagline |
| CHANGELOG.md | Convert ## [Unreleased] to ## [YYYY.M.P] - YYYY-MM-DD; insert a fresh empty ## [Unreleased] above it |
| README.md | Update the "Latest Release" line with version + theme |
Then run a consistency check — every line should mention the same version:
echo "=== VERSION CONSISTENCY CHECK ==="
echo "__init__.py: $(grep '__version__ = ' src/osprey/__init__.py)"
echo "cli/main.py: $(grep '__version__ = ' src/osprey/cli/main.py)"
echo "RELEASE_NOTES: $(head -1 RELEASE_NOTES.md)"
echo "README.md: $(grep 'Latest Release:' README.md)"
echo "CHANGELOG.md: $(grep -m1 '^## \[' CHANGELOG.md)"
Now hand off to osprey-contribute for the rest of the PR mechanics:
quick_check.sh → commit (release: bump version to YYYY.M.P) →
ci_check.sh → push → premerge_check.sh main → gh pr create.
The PR title should be release: vYYYY.M.P — <theme>. The PR body should
include the CHANGELOG entries verbatim so reviewers see exactly what's being
released.
After CI passes (all 8 required checks green):
gh pr merge --rebase --delete-branch
Linear history is required, so --rebase. After merge:
git checkout main && git pull --ff-only origin main
Verify the latest commit on main is the version bump.
Tags can be pushed directly — branch protection covers branches, not tags:
git tag vYYYY.M.P
git push origin vYYYY.M.P
The tag must point at the merge commit on main. The release.yml workflow
triggers on v*.*.* and:
__version__ in src/osprey/__init__.py.If step 1 fails, the publish aborts before any PyPI write — safe.
gh run watch # follow the release.yml run
gh release view vYYYY.M.P # confirm GitHub Release exists
pip install --upgrade osprey-framework # in a fresh shell
python -c "import osprey; print(osprey.__version__)"
Three success signals:
release.yml finished green.https://pypi.org/project/osprey-framework/YYYY.M.P/ exists.https://github.com/als-apg/osprey/releases/tag/vYYYY.M.P has the CHANGELOG
entries as the body.If any fail, stop and investigate before announcing the release.
If release.yml is broken and the release is time-sensitive:
rm -rf dist/ build/ src/*.egg-info/
uv build
uvx twine check dist/*
uvx twine upload dist/* # requires PyPI credentials in env
Then manually create the GitHub Release: gh release create vYYYY.M.P --notes-file <(awk '/^## \[YYYY.M.P\]/,/^## \[/' CHANGELOG.md | head -n -1).
This is a fallback. The default path is the automated workflow.
| Symptom | Cause | Fix |
| --- | --- | --- |
| release.yml "Verify version matches tag" fails | __version__ in __init__.py doesn't match the pushed tag | Tag the wrong commit, or the version-bump PR didn't actually update __init__.py. Delete the tag locally and on origin, fix, retag |
| PyPI rejects the upload as a duplicate | This version was already published | CalVer means version numbers are unique; you cannot republish. Bump the patch counter and try again |
| gh pr merge --rebase fails with "not mergeable" | Stale checks because main moved | git rebase origin/main on the release branch, force-push with lease, wait for CI to re-run |
| GitHub Release body is empty or wrong | CHANGELOG section heading didn't match the regex release.yml uses | Make sure the CHANGELOG heading is exactly ## [YYYY.M.P] - YYYY-MM-DD |
fix/<short-kebab> branch off main, PR'd back; then
this skill cuts a follow-up release.release.yml, which triggers on v*.*.* only. If you need an RC channel,
the workflow needs changes first.docs.yml; no manual
step needed in the release flow.development
Interactive interview to create a custom OSPREY build profile for a new accelerator, detector, or beamline application. Use when someone says "interview me", "create a build profile", "set up my agent", "configure my detector", "onboard me", or needs to create an OSPREY project tailored to their specific control system. Also handles migration from existing OSPREY projects (including LangGraph-era projects) — trigger on "migrate my project", "I have an existing project", "upgrade from old OSPREY", "upgrade from langgraph", "legacy migration", "bring my project forward", "convert my project", "extract profile from existing project", "reverse-engineer build profile". Also use for /osprey-build-interview feedback to collect post-use feedback. Also trigger when onboarding a new colleague or when anyone needs help figuring out what their OSPREY agent should look like.
development
Validates code before committing using OSPREY's three-tier check scripts. Runs linting, formatting, and tests to catch issues early. Use when ready to commit, before pushing, before opening a PR, or when the user asks to run checks, validate, or verify their changes. For the full contribution journey (branching, commits, push, PR, merge), use `osprey-contribute` instead — this skill is the focused validation step.
development
Guides a contributor through the OSPREY GitHub Flow contribution journey from a working-tree change to a merged PR on main. Use when someone says "I want to contribute X to osprey", "help me commit/push this", "prep this branch for a PR", "open a PR", "my CI is failing on this branch", "rebase onto main", or wants help following the contributing workflow. Auto-detects whether they have push access to als-apg/osprey or are contributing from a fork. Composes with the osprey-pre-commit, commit-organize, and osprey-release skills — invoke this whenever someone is contributing code to OSPREY, even if they haven't named the workflow explicitly.
tools
Deployment control plane for an OSPREY-based facility profile repository. Owns the deploy lifecycle end-to-end: scaffolds the deploy infrastructure (docker-compose, .gitlab-ci.yml, scripts/deploy.sh, .env.template) from facility-config.yml, drives the GitLab CI/CD container pipeline (push → CI → manual release tag → server deploy), brings containers up on the deploy server with `scripts/deploy.sh`, and runs post-deploy health checks. Modular opt-in support for OLOG/logbook integration, multi-user web terminals, Ollama embeddings, shared-disk mounts, EPICS-driven event dispatcher, ARIEL database, wiki search, custom MCP servers, e2e benchmarks, and EPICS test IOC management. Use this skill whenever the user is asking about deploying, releasing, pushing to GitLab, building containers, modifying docker-compose files, scripts/deploy.sh, .gitlab-ci.yml, the CI pipeline, the container registry, the deploy server, the .env, the facility-config.yml, webhook triggers, the event dispatcher, web terminals, OLOG, Ollama, ARIEL, integration tests, test IOC, OSPREY:TEST PVs, agent benchmarks, or anything CI/CD or on-server-deploy related — even if they don't explicitly say "deploy". Mandatory entry point for any work touching the project's deploy pipeline; if the user says "let's set this up", "ship this", "release", "promote to prod", "rebuild on the server", or anything similar, invoke this skill before doing anything else. On first invocation, runs an interactive deploy interview to capture facility-specific values (GitLab host, deploy server, container runtime, proxy, ports, optional modules) and writes them to `facility-config.yml` at the repo root; subsequent invocations read that config. Does NOT author build profile YAMLs — that is the job of the separate `osprey-build-interview` skill (`/osprey-build-interview`). If the user wants to create or edit a profile, hand off to `/osprey-build-interview` and stop.