Skip to content
Closed
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
90 changes: 90 additions & 0 deletions .cursor/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: release
description: >-
Prepares a version bump in pyproject.toml, opens a PR from branch release/VERSION
toward main with auto-merge, and coordinates with CI that publishes a GitHub Release
when that branch merges. Use when the user invokes /release, /release VERSION, asks
for a release PR, version bump, or release automation.
---

# Release (`/release` and optional VERSION)

## When this applies

- User message starts with **`/release`** or **`/release VERSION`** (VERSION optional).
- User asks to cut a release, bump the package version, or open a release PR with auto-merge.

## Preconditions

- Working tree clean (`git status`); stash or commit unrelated work first.
- `gh` CLI authenticated (`gh auth status`).
- Remote `origin` is GitHub.
- Repository allows **auto-merge** (Settings → General → Pull Requests → Allow auto-merge). If auto-merge is unavailable, open the PR anyway and tell the user to merge manually after checks pass.

## Version selection

1. Read the current version from `pyproject.toml` under `[project]` → `version` (PEP 440 / semver `MAJOR.MINOR.PATCH`).
2. If **VERSION was provided**: set the new version to that string (must match `^\d+\.\d+\.\d+` unless the project already uses a different scheme—then follow existing `pyproject.toml` format).
3. If **VERSION was omitted**: bump the **patch** segment only (e.g. `0.2.1` → `0.2.2`). If the current value is not `x.y.z`, stop and ask the user for an explicit VERSION.

## Git identity (this repo)

Configure once if needed:

```bash
git config user.email "cursor@proxymesh.com"
git config user.name "Cursor"
```

## Steps

1. **Sync main**

```bash
git fetch origin main
```

2. **Compute** `NEW_VERSION` (per rules above). **Branch name** is `release/${NEW_VERSION}` (no `v` prefix in the branch name).

3. **Create branch from latest main**

```bash
git checkout -B "release/${NEW_VERSION}" origin/main
```

4. **Edit** `pyproject.toml`: set `version = "NEW_VERSION"` in `[project]`.

5. **Commit and push** (never push to `main`; push only the release branch)

```bash
git add pyproject.toml
git commit -m "chore: bump version to ${NEW_VERSION}"
git push -u origin "release/${NEW_VERSION}"
```

6. **Open PR** into `main` with a short body (no Cursor boilerplate). Example:

```bash
gh pr create --base main --head "release/${NEW_VERSION}" \
--title "Release ${NEW_VERSION}" \
--body "Bumps the package version to ${NEW_VERSION} for release."
```

7. **Enable auto-merge** after the PR exists (merge method: repository default—omit `--merge` / `--squash` / `--rebase` unless the user specified one).

```bash
gh pr merge <PR_NUMBER_OR_URL> --auto
```

If `--auto` fails (permissions, auto-merge disabled, or pending checks), leave the PR open and report the error; the user can merge manually after CI passes. You can poll with `gh pr checks <PR_NUMBER_OR_URL> --watch` then retry `gh pr merge ... --auto`, or merge manually.

## After merge

Merging the PR into `main` triggers `.github/workflows/github_release_on_release_branch_merge.yml`, which creates a **GitHub Release** for tag `v{version}` from the merge commit. That **published** release event runs the existing PyPI **publish** workflow.

## Quick reference

| Input | Result |
|--------------------|---------------------------------------------|
| `/release` | Patch bump, branch `release/x.y.(z+1)` |
| `/release 1.4.0` | Version `1.4.0`, branch `release/1.4.0` |
60 changes: 60 additions & 0 deletions .github/workflows/github_release_on_release_branch_merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# When a PR from release/* is merged into main, create a GitHub Release (tag vX.Y.Z).
# The existing publish.yml workflow runs on release: published and uploads to PyPI.

name: GitHub Release on release branch merge

on:
pull_request:
types: [closed]
branches:
- main

permissions:
contents: write

jobs:
create-release:
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'release/')
runs-on: ubuntu-latest
steps:
- name: Checkout merge commit
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Read version from pyproject.toml
id: meta
run: |
python3 <<'PY'
import os
import tomllib

with open("pyproject.toml", "rb") as f:
data = tomllib.load(f)
version = data["project"]["version"]
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as out:
out.write(f"version={version}\n")
PY

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
VERSION="${{ steps.meta.outputs.version }}"
TAG="v${VERSION}"
MERGE_SHA="${{ github.event.pull_request.merge_commit_sha }}"
if gh release view "${TAG}" --repo "${{ github.repository }}" >/dev/null 2>&1; then
echo "Release ${TAG} already exists; skipping."
exit 0
fi
gh release create "${TAG}" \
--repo "${{ github.repository }}" \
--target "${MERGE_SHA}" \
--title "${TAG}" \
--generate-notes
Loading