diff --git a/.claude/skills/create-release-pr/SKILL.md b/.claude/skills/create-release-pr/SKILL.md new file mode 100644 index 0000000..dcd5f9b --- /dev/null +++ b/.claude/skills/create-release-pr/SKILL.md @@ -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 ..origin/main + +# The actual code changes since that tag — this is the source of truth for the bump +git diff --stat ..origin/main +git diff ..origin/main -- src/ +``` + +### 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`: . Suggested next version is **`A.B.C`** (). Which version should this release + use?" +- Options (first = recommended): + - `A.B.C` — " (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) +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 "" +``` + +### Phase 7: Report + +Print a short summary: + +``` +# Release PR + +- Previous version: X.Y.Z +- New version: NEW_VERSION () +- Branch: release/bump-vNEW_VERSION (based on origin/main @ ) +- Checks: make check passed +- PR: +``` diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c13472c..bfe5dcd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,6 +9,7 @@ - [ ] Breaking change - [ ] Documentation update - [ ] Refactoring +- [ ] Release / version bump ## Checklist diff --git a/.gitignore b/.gitignore index 1a7518f..b9375a3 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ !.claude/skills/ .claude/skills/* !.claude/skills/sync-to-ts-sdk/ +!.claude/skills/create-release-pr/ # Python __pycache__/