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
232 changes: 232 additions & 0 deletions .claude/skills/create-release-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
---
name: create-release-pr
description: Create a version-bump release PR for the Python SDK based on latest remote main, with a semver suggestion derived from changes since the last release.
argument-hint: "[VERSION] [--no-pr]"
disable-model-invocation: true
allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
---

# Create a Release PR (Python SDK)

This skill creates a version-bump PR for `twilio-agent-connect` (the Python SDK).
It branches off the **latest remote `main`**, bumps the version in `pyproject.toml`,
runs `uv sync` to update `uv.lock`, and opens a PR.

The version follows [semantic versioning](https://semver.org/). The skill inspects
commits since the last release tag, **suggests** a bump, and asks the user to confirm
or override it.

## What gets changed

A version bump touches exactly two files (the in-code `__version__` is dynamic via
`importlib.metadata`, so it does not need editing):

- `pyproject.toml` — `[project].version`
- `uv.lock` — the `twilio-agent-connect` package entry (regenerated by `uv sync`)

## Arguments

`$ARGUMENTS` (the full argument string):

1. **Explicit version** (optional): a semver string like `1.1.0`. If present, skip the
suggestion logic and use this version (still confirm with the user).
2. **`--no-pr`** (optional): commit and push the change on a branch, but don't open the PR —
leave it ready for the user to open manually.

## Workflow

### Phase 0: Preflight

Verify `gh` is installed and authenticated against this repo, and that `uv` is available:

```bash
gh repo view twilio/twilio-agent-connect-python --json name --jq '.name'
uv --version
```

If `gh` fails for any reason (not installed, not authenticated, no access, network), **STOP**
and tell the user to run `gh auth status` / `gh auth login`. If `uv` is missing, **STOP** and
tell the user this skill requires `uv` (see the repo's `make sync`).

### Phase 1: Sync to latest remote main and check the working tree

The release branch **must** be based on the latest remote `main`. Do not rebase or reset the
user's current branch — the branch is cut fresh from `origin/main` in Phase 3.

```bash
cd "$(git rev-parse --show-toplevel)"
git fetch origin
git status --porcelain
```

**If `git status --porcelain` is non-empty** (uncommitted changes exist), warn the user — the
release branch is cut from `origin/main`, so their uncommitted work won't be included but will
remain on their current branch. Use `AskUserQuestion` ("Continue?" / "Abort"); on abort, STOP
and confirm nothing was changed.

> Exception: if any uncommitted change touches `pyproject.toml` or `uv.lock`, **STOP
> regardless** and ask the user to commit or stash those two files first — the skill needs a
> clean base for them.

### Phase 2: Determine current version and changes since last release

```bash
# Current version
grep -E '^version = ' pyproject.toml | head -1

# Latest release tag (vMAJOR.MINOR.PATCH)
git tag --sort=-creatordate | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1

# Commits since that tag (use the tag from above; if no tag exists, use the full history)
git log --oneline <LAST_TAG>..origin/main

# The actual code changes since that tag — this is the source of truth for the bump
git diff --stat <LAST_TAG>..origin/main
git diff <LAST_TAG>..origin/main -- src/
Comment on lines +80 to +85
```

### Phase 3: Suggest a semver bump and confirm

Decide the bump from **what the code actually changed**, not from commit-message prefixes
(this repo doesn't enforce Conventional Commits, so subjects are an unreliable signal). Read
the diff from Phase 2 — focusing on the public API surface under `src/tac/` (exported classes,
functions, model fields, function signatures, defaults) — and pick the level by semver intent:

- **MAJOR** (`X+1.0.0`): a backward-incompatible change to the public API — removed/renamed
exports, changed signatures or required params, removed/renamed model fields, changed
defaults or behavior existing callers depend on.
- **MINOR** (`X.Y+1.0`): backward-compatible new capability — new exported API, new optional
params/fields, new channels/adapters/tools.
- **PATCH** (`X.Y.Z+1`): no public-API surface change — bug fixes, internal refactors, docs,
examples, tests, CI/build only.

Commit subjects are a useful hint to skim first, but when a subject and the diff disagree,
**trust the diff**. If the diff is large, read the parts touching exported symbols rather than
implementation internals.

Pre-1.0 caveat: not applicable here (current series is ≥1.0.0), so use standard semver.

**If an explicit version was passed in `$ARGUMENTS`**, skip this analysis and use it as the
suggestion (but still confirm).

Present the decision with `AskUserQuestion`. State the suggested level and a one-line
rationale grounded in the code change (e.g. "added `TwiMLOptions` + new optional voice config
fields, no breaking changes → MINOR"):

- Question: "Current version is `X.Y.Z`. Since `vX.Y.Z`: <one-line rationale grounded in the
diff>. Suggested next version is **`A.B.C`** (<level>). Which version should this release
use?"
- Options (first = recommended):
- `A.B.C` — "<suggested level> (Recommended)"
- the other two semver candidates (e.g. the patch and major alternatives)

The user can pick one, or choose "Other" to type any custom semver string.

After selection, validate the chosen version:

- It must match `^[0-9]+\.[0-9]+\.[0-9]+$` (allow a pre-release/build suffix only if the
user explicitly typed one).
- It must be **strictly greater** than the current version. If not, show the conflict and
re-ask rather than proceeding.

Let `NEW_VERSION` be the confirmed value. Create the release branch from latest remote main:

```bash
git checkout -b "release/bump-v${NEW_VERSION}" origin/main
```

### Phase 4: Apply the bump

1. Edit `pyproject.toml` — change the single `[project]` `version = "..."` line (line ~3) to
`NEW_VERSION`. Use `Edit` to replace only that line; do not touch `ruff`/`mypy`/`pytest`
version-like fields elsewhere in the file.

2. Regenerate the lockfile:

```bash
uv sync
```

3. Confirm only the two expected files changed and the lock updated correctly:

```bash
git status --porcelain # expect only pyproject.toml and uv.lock
git diff --unified=0 pyproject.toml uv.lock # sanity-check the version strings
```

If anything other than `pyproject.toml` and `uv.lock` is modified, STOP and show the user
the diff before continuing.

### Phase 5: Verify

Run the repo's checks to make sure the bump didn't break anything:

```bash
make check
```

If `make check` fails, STOP, show the failure, and ask the user how to proceed. Do not open
a PR on a failing tree.

### Phase 6: Commit, push, and open the PR

**Stage explicitly by path** (never `git add -A/./-u`):

```bash
git add pyproject.toml uv.lock
git status # confirm only these two are staged
```

Commit (match the established subject style — see `git log` for `chore: bump version to X`):

```bash
git commit -m "$(cat <<'EOF'
chore: bump version to NEW_VERSION

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"
```

(Substitute the real `NEW_VERSION` before running.)

```bash
git push -u origin "release/bump-v${NEW_VERSION}"
```

**If `--no-pr` was passed:** STOP here. Tell the user the branch is pushed and ready, and
print the `gh pr create` command they can run, plus a compare URL.

**Otherwise**, read the PR template and open the PR:

1. Read `.github/PULL_REQUEST_TEMPLATE.md` and fill it in:
- **Summary**: "Bump version `X.Y.Z` → `NEW_VERSION`." Include a short bullet list of the
notable changes since the last release (grouped: Features / Fixes / Other), derived from
the Phase 2 commit log.
- **Type of Change**: check **Release / version bump**.
- **Checklist**: leave as appropriate (tests pass via `make check`).
- **SDK Parity**: check "Change is Python-specific (no TypeScript update needed)" — a
version bump is Python-specific.

2. Create the PR:

```bash
gh pr create \
--base main \
--title "chore: bump version to ${NEW_VERSION}" \
--body "<filled-in PR template>"
```

### Phase 7: Report

Print a short summary:

```
# Release PR

- Previous version: X.Y.Z
- New version: NEW_VERSION (<bump level>)
- Branch: release/bump-vNEW_VERSION (based on origin/main @ <short sha>)
- Checks: make check passed
- PR: <url, or "not created (--no-pr)">
```
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [ ] Breaking change
- [ ] Documentation update
- [ ] Refactoring
- [ ] Release / version bump

## Checklist

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
!.claude/skills/
.claude/skills/*
!.claude/skills/sync-to-ts-sdk/
!.claude/skills/create-release-pr/

# Python
__pycache__/
Expand Down
Loading