release
Use when cutting a new DisMech GitHub release (a `v0.1.x` tag) — the action that rebuilds every bulk export and refreshes the Downloads page. Covers the mandatory origin/main sync, the version convent
SKILL.md.claude/skills/release/SKILL.md
GitHubrelease
Cut a DisMech release. A release is a GitHub release on a v0.1.x tag. Publishing
it is what rebuilds the bulk exports and makes the Downloads page
(https://dismech.monarchinitiative.org/downloads/index.html) point at the new
files. There is no local build step and no version string to edit — the tag is
the version (uv-dynamic-versioning, pyproject.toml).
When to use
- The user asks for a "fresh release", a version bump, or to refresh the Downloads page / bulk exports.
- New release artifacts have been added and need to ship.
Not for: NDEx publishing alone (that is a separate manual workflow, below), the PyPI package in isolation (a release publishes it as a side effect), or the website page build (the page is static and self-updating — see below).
The one trap: never release from a stale checkout
The primary checkout drifts tens or hundreds of commits behind origin/main
between releases. A release tags a commit, so always sync first and tag
origin/main's tip, never the local tip.
git fetch origin main
git rev-list --left-right --count main...origin/main # expect "0 N": 0 local-ahead, N behind
If there are 0 local-ahead commits, fast-forward the primary checkout (it
must stay on main; the reflog makes this reversible):
# First confirm no untracked file collides with an incoming added file:
comm -12 <(git ls-files --others --exclude-standard | sort) \
<(git diff --name-only --diff-filter=A main origin/main | sort)
# If that prints nothing, fast-forward:
git merge --ff-only origin/main
If the count shows local-ahead commits, stop and ask — do not force anything.
Either way, you can sidestep drift entirely by pinning the tag to an explicit
SHA with --target <sha> below.
Pick the version
Next in sequence after the latest tag. These are plain increments, v0.1.N.
gh release list --limit 3 # read the latest tag, add 1 to the patch number
gh release view vX.Y.Z # confirm the chosen tag does NOT already exist ("release not found")
Draft, review, publish — in that order
Draft first. A draft creates no git tag and fires no workflow, so it is
fully reversible. --target main ties the (future) tag to main's tip; pass a
SHA instead if main may move before you publish.
gh release create vX.Y.Z --target main --draft --generate-notes --title "vX.Y.Z"
The URL comes back as .../releases/tag/untagged-... — that untagged- prefix
confirms no tag exists yet. Review the generated notes:
gh release view vX.Y.Z --json isDraft,tagName,targetCommitish,body
Publish only on the user's explicit say-so. Publishing is irreversible and
public: it creates the tag, publishes to PyPI, and triggers every asset
workflow. Mark it latest (not prerelease) or the Downloads page will not pick it
up — the page reads releases/latest, which the GitHub API resolves to the most
recent published, non-prerelease release.
gh release edit vX.Y.Z --draft=false --latest
To abandon a draft instead: gh release delete vX.Y.Z.
What publishing fires (all on release: published)
Four independent workflows attach assets; a failure in one does not hold back the others. Watch them:
gh run list --event release --limit 12
| Workflow | Assets attached |
|---|---|
pypi-publish.yaml |
the package to PyPI (a bare tag push with no release does TestPyPI only) |
kgx-release.yaml |
dismech_nodes.jsonl, dismech_edges.jsonl, dismech_nodes.tsv, dismech_edges.tsv, dismech_sepio.jsonl, dismech_kgx_metadata.json |
mondo-emc-release.yaml |
mondo_emc.tsv |
release-assets.yaml |
kb.zip, pathographs.zip, pathographs_cx2.zip, dismech_models_omex.zip, release_manifest.json, and the experimental dismech_maximal_nodes.jsonl / dismech_maximal_edges.jsonl / dismech_maximal_metadata.json |
The asset builders are python -m dismech.export.release_assets <subcommand> and
python -m dismech.export.maximal_kgx_export; the KGX pipeline lives in
kgx-release.yaml. When a new artifact is added, it is added to one of these
workflows and to the Downloads page table (downloads/index.html) — both, or
the page will show it as "not in this release".
NDEx is separate and manual. publish-ndex.yml is a workflow_dispatch; it
does not fire on release. Run it only when asked, and only after the CX2 archive
exists.
The Downloads page needs no edit
downloads/index.html is static and self-updating. Every asset link already
points at /releases/latest/download/<name>, and a small script fetches
api.github.com/repos/monarch-initiative/dismech/releases/latest on load to fill
in the tag, date, and each file's size. So:
- Cutting a release requires no change to the page.
- A draft does not change the page (a draft is not "latest").
- After publishing, the page flips to the new release, but the size cells read "not in this release" and the direct links 404 until each asset workflow has finished uploading. Verify by loading the page, or:
gh release view vX.Y.Z --json assets -q '.assets[].name' # all expected files present?
Verify when done
- All four workflows green (
gh run list --event release). - Every expected asset name attached to the release.
- The Downloads page shows the new tag, a date, and real sizes (not em-dashes).
pip install dismech==X.Y.Zresolves on PyPI (package publish can lag a few minutes).