Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Guide for coding agents working in this repo. Read this before touching code.
## Golden Rules

1. **Never commit to `main`.** Always `git checkout -b <feature-branch>` before editing. Land via PR.
2. **Every PR bumps the version.** Even doc-only PRs — at minimum a patch bump. `deploy.sh <version>` handles the bump + commit + push.
2. **Every PR bumps the version.** Even doc-only PRs — at minimum a patch bump in `varcode/version.py` with release notes. Include the bump in the PR; `deploy.sh [version]` publishes the merged version and checks any supplied version matches.
3. **"Done" means merged AND deployed to PyPI** — never stop at merge. After a PR merges, run `./deploy.sh` from a clean main. Skipping deploy = task not done.
4. **File problems as issues, don't silently work around them.** If you hit a bug here or in a sibling openvax/pirl-unc repo, open a GitHub issue on the correct repo and link it from the PR.
5. **After a PR ships, look for the next block of work.** Read open issues across the relevant openvax repos, group by dependency + urgency. Prefer *foundational* changes that unblock multiple downstream improvements; otherwise chain the smallest independent improvements.
Expand All @@ -29,7 +29,7 @@ Before telling the user a change is "complete":
- `./develop.sh` — editable install (dev mode)
- `./lint.sh` — ruff check
- `./test.sh` — pytest (with coverage where configured)
- `./deploy.sh [version]` — lint → test → optional version bump → build → twine upload → tag → push
- `./deploy.sh [--dry-run] [version]` — clean synchronized main/master + unused-tag checks → lint → test → build → twine check → upload → tag → push. Dry runs stop before upload; version changes always go through a PR.

## Code Style

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Change Log

## [v10.11.3](https://github.com/openvax/varcode/tree/v10.11.3) (2026-10-01)

- Guard deployment with a clean main/master checkout synchronized with live
origin, matching version, unused tag, lint/tests and distribution checks
(#414). Publish the merged version and push its tag only after upload;
`--dry-run` validates and builds without publishing or changing Git history.
- Preserve each attempt's release artifacts for publication verification and
recovery. Keep version bumps in PRs and align the release instructions.

## [v10.11.2](https://github.com/openvax/varcode/tree/v10.11.2) (2026-10-01)

- Pin bare GRCh38 references during pytest to the existing Ensembl 81 fixture
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[rna,test-data]' pytest pytest-cov pytest-xdist ruff
python -m pip install mkdocs mkdocs-material 'mkdocstrings[python]'
python -m pip install build twine
```

`rna` installs the optional BAM reader; `test-data` installs the pinned
Expand Down
50 changes: 28 additions & 22 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,41 @@ with build/upload credentials configured for PyPI. Use a clean checkout:
git switch main
git pull --ff-only origin main
git status --porcelain
python -c 'from varcode.version import __version__; print(__version__)'
python -c 'import runpy; print(runpy.run_path("varcode/version.py")["__version__"])'
```

Confirm that the working tree is clean, the branch is `main`, and the imported
version is the merged release. Then run:
The script requires a clean `main` or `master` whose HEAD matches the live
branch on `origin`. The release tag must be absent both locally and remotely.
Then run:

```bash
./deploy.sh
```

The current script runs lint and tests, installs/upgrades `build` and `twine`,
recreates `dist/`, builds distributions, and uploads them to PyPI. It does
**not** check the branch or working tree, accept a version argument, bump the
version, create a tag, or push commits. Those missing safeguards are tracked
in [#414](https://github.com/openvax/varcode/issues/414); enforce the clean-main
requirement yourself until the script implements it. If a step fails, fix its
root cause and verify the publication state before retrying an upload.
Use `./deploy.sh X.Y.Z` to also assert the already-merged version.

After a successful upload, verify the exact version on
[PyPI](https://pypi.org/project/varcode/), compare its wheel/sdist SHA256 hashes
with the files in `dist/`, and smoke-test the published package in an isolated
environment. Tag the same merged commit with `v<version>` and push that tag:
`deploy.sh` runs lint and tests, builds a wheel and source distribution in a
new directory under `dist/`, and runs `twine check --strict` on that exact pair.
It rechecks the checkout and remote refs before uploading, then tags the
original merged commit with `v<version>` and pushes only that tag. Version
changes belong in the PR; a different version argument fails without editing
or committing files. Activate the development environment first; the script
uses `python` and the installed `build` and `twine` tools.

```bash
# Replace X.Y.Z with the verified published version.
git tag vX.Y.Z
git push origin vX.Y.Z
```
`./deploy.sh --dry-run [version]` performs the same guards, lint, tests, build,
and distribution checks, then stops before uploading or changing Git history.
It needs read access to `origin` and leaves its build artifacts under `dist/`.

If the tag already exists, verify its target instead of moving it. Check the
main-branch CI and documentation deployment, then review open issues for the
next dependency or correctness blocker.
After a successful upload, verify the exact version on
[PyPI](https://pypi.org/project/varcode/), compare its wheel/sdist SHA256 hashes
with the files in the printed artifact directory, and smoke-test the published
package in an isolated environment. Verify that the pushed tag targets the
merged commit. Check the main-branch CI and documentation deployment, then
review open issues for the next dependency or correctness blocker.

If publication fails, keep the printed artifact directory and inspect PyPI
before retrying. Upload only missing files from that original pair after
verifying the published hashes; rebuilding may produce different bytes.
Finish tagging the original commit and pushing the tag once both files are
verified. If only the tag push failed, verify the local tag's target and retry
`git push origin refs/tags/vX.Y.Z`. Never move an existing release tag.
79 changes: 72 additions & 7 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,72 @@
./lint.sh && \
./test.sh && \
python3 -m pip install --upgrade build && \
python3 -m pip install --upgrade twine && \
rm -rf dist && \
python3 -m build && \
python3 -m twine upload dist/*
#!/usr/bin/env bash
set -euo pipefail

usage() {
echo "Usage: ./deploy.sh [--dry-run] [version]"
echo "Publish the merged version; an optional version must match varcode/version.py."
}

fail() { echo "deploy.sh: $*" >&2; exit 1; }

dry_run=0
expected_version=""
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) dry_run=1 ;;
-h|--help) usage; exit 0 ;;
-*) fail "Unknown option: $1" ;;
*)
[[ -z "$expected_version" ]] || fail "Expected at most one version."
expected_version="${1#v}"
[[ -n "$expected_version" ]] || fail "Expected a release version."
;;
esac
shift
done

cd "$(dirname "$0")"
branch="$(git symbolic-ref --quiet --short HEAD)" || fail "Deploy from main or master, not a detached HEAD."
[[ "$branch" == main || "$branch" == master ]] || fail "Deploys require main or master (current: $branch)."
commit="$(git rev-parse HEAD)"
version="$(python -c 'import runpy; print(runpy.run_path("varcode/version.py")["__version__"])')"
[[ "$version" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || fail "Invalid release version: $version"
[[ -z "$expected_version" || "$expected_version" == "$version" ]] || fail "Requested $expected_version, but the merged version is $version. Bump versions in a PR."
tag="v$version"

# Check live origin refs, not a possibly stale remote-tracking branch.
check_release() {
local status remote_branch remote_tag
status="$(git status --porcelain --untracked-files=all)"
[[ -z "$status" ]] || fail "Working tree is not clean."
[[ "$(git symbolic-ref --quiet --short HEAD)" == "$branch" && "$(git rev-parse HEAD)" == "$commit" ]] || fail "Checkout changed during release."
remote_branch="$(git ls-remote --exit-code origin "refs/heads/$branch")" || fail "Cannot read origin/$branch."
[[ "${remote_branch%%$'\t'*}" == "$commit" ]] || fail "HEAD must match live origin/$branch; pull the merged release first."
if git show-ref --verify --quiet "refs/tags/$tag"; then
fail "Tag $tag already exists locally. Verify the publication state before retrying."
fi
remote_tag="$(git ls-remote origin "refs/tags/$tag")" || fail "Cannot check release tags on origin."
[[ -z "$remote_tag" ]] || fail "Tag $tag already exists on origin."
}

check_release
./lint.sh
./test.sh

# Keep each attempt's original bytes, including after a failed/partial upload.
mkdir -p dist
artifact_dir="$(mktemp -d "dist/$tag.XXXXXX")"
echo "Building $tag at $commit; artifacts: $artifact_dir"
python -m build --outdir "$artifact_dir"
distributions=("$artifact_dir/varcode-$version-py3-none-any.whl" "$artifact_dir/varcode-$version.tar.gz")
python -m twine check --strict "${distributions[@]}"
check_release

if [[ "$dry_run" -eq 1 ]]; then
echo "Dry run passed for $tag; no upload, commit, tag, or push. Artifacts: $artifact_dir"
exit 0
fi

python -m twine upload --non-interactive --disable-progress-bar "${distributions[@]}"
git tag "$tag" "$commit"
git push origin "refs/tags/$tag"
echo "Deployed varcode $version: https://pypi.org/project/varcode/$version/"
2 changes: 1 addition & 1 deletion docs/development_audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ domain-specific reviews and independent scientific oracles.
|---|---|---|
| High | VCF export derives header sample order from a set but writes row values in dictionary order. A two-row reproduction assigns tumor GT/DP to normal and emits `44:0/1` under `GT:DP`. Differing sample sets also discard sample data; headers and allele-indexed metadata lack a complete preservation contract. | Filed [#502](https://github.com/openvax/varcode/issues/502); resolved in 10.1.3 with explicit [export guarantees and limits](csv.md#vcf-export). |
| High | The phase cap returns an all-cis placeholder that the consumer classifies as a precise effect. The existing CFTR pair yields cis/trans alternatives at cap 8 but only `p.S159T` at cap 1, with no molecular evidence resolving phase. Four unphased germline alleles exceed the default cap. | Filed [#503](https://github.com/openvax/varcode/issues/503); documented in [germline annotation](germline.md). |
| High | `deploy.sh` lacks the branch/clean-tree guards, version handling, tagging, and pushing described in AGENTS.md. It can upload from the wrong checkout if invoked without independent checks. | Existing [#414](https://github.com/openvax/varcode/issues/414); release instructions now describe the actual script and required manual checks. |
| High | `deploy.sh` lacked the branch/clean-tree guards, version handling, tagging, and pushing described in AGENTS.md. | Resolved in 10.11.3 ([#414](https://github.com/openvax/varcode/issues/414)): guarded deployment of the merged version, dry runs, distribution checks and automatic tagging; version bumps remain in PRs. |
| Medium | The test cache probe accesses `EnsemblRelease.db`, which can download missing reference data while supposedly checking local installation. | Resolved [#493](https://github.com/openvax/varcode/issues/493) in 10.11.2 by pinning bare GRCh38 test references to the existing Ensembl 81 fixture and removing the cache probe. |
| Medium | Some tests cannot detect failures, including the fast-path self-comparison and protocol/attribute surveys. VCF round-trip checks omitted ALT and complete record counts. | Existing [#479](https://github.com/openvax/varcode/issues/479); this change strengthens the VCF checks. The broader test review remains open. |
| Medium | Reference mismatch fields contain transcript-oriented bases but the message labels them as genome bases; minus-strand errors can be read backwards. | Existing [#434](https://github.com/openvax/varcode/issues/434); troubleshooting now explains the current orientation. |
Expand Down
Loading
Loading