PR lifecycle helpers for a main + release/X.Y + feat|fix|hotfix/slug branching model.
Project description
pr-bot
PR lifecycle helpers for a main + release/X.Y + {feat,fix,hotfix}/<slug>
branching model. Linear history, squash merges to main, fast-forward-only
main, semver (X.Y.Z) tags on release branches.
Ships as:
- a single Python CLI (
pr-bot) — one install, works in every repo - a set of Claude Code slash commands (
/pr-start,/release-cut, …) that are thin wrappers over the CLI
The model
main ─────●──●──●──●──●──────●──●──●──────────●────────▶ (FF-only, linear)
\ \ \
feat/login fix/off-by-one release/1.4 ──●──●──▶
│ │
│ └── tag 1.4.1
│
hotfix/npe-on-logout
- Branches off
main:feat/<slug>,fix/<slug>. Squash-merged back intomain; branch is deleted on merge. - Release branch:
release/X.Yis cut from a commit onmain. Long-lived. Receives only hotfixes. - Tags:
X.Y.Z(annotated) placed on a commit on the matchingrelease/X.Ybranch. - Hotfixes:
hotfix/<slug>offrelease/X.Y, PR'd back torelease/X.Y, then forward-ported tomainas afix/<slug>PR.
Prerequisites
| Tool | Minimum | Why |
|---|---|---|
uv |
0.4+ | Runs the CLI hermetically via uvx; bootstraps Python 3.14 |
git |
2.30+ | --force-with-lease, symbolic-ref --short |
gh |
2.40+ | pr create/merge, repo create, release create |
gh must be authenticated (gh auth status). For git pushes, run
gh auth setup-git once — pr-bot relies on gh's credential helper.
Install
Recommended: no install, hermetic per invocation
Every slash command invokes the CLI through uvx. Each call runs in a
cached, isolated environment with deps pinned by pyproject.toml — nothing
is installed into any shared venv, and upgrades are atomic (bump the
version, invalidate the cache).
Point uvx at a source by setting one env var:
# a local clone (dev loop)
export PR_BOT_SOURCE=/path/to/pr-bot
# or a git ref (pinned release — most hermetic)
export PR_BOT_SOURCE='git+https://github.com/arothste/pr-bot@1.0.0'
The slash commands default to PR_BOT_SOURCE=pr-bot, which resolves from
PyPI once the package is published there.
Convenience: plain CLI on PATH (not hermetic)
If you want pr-bot on your PATH instead of going through uvx each time:
uv tool install /path/to/pr-bot
This creates a shared user venv — fine for a workstation, but it's one global install, not per-invocation isolation.
Slash commands
Claude Code discovers slash commands from ~/.claude/commands/ (user) or
.claude/commands/ (project). Symlink the directory so updates propagate:
ln -s /path/to/pr-bot/.claude/commands ~/.claude/commands/pr-bot
# or, for a project-local install:
ln -s /path/to/pr-bot/.claude/commands .claude/commands/pr-bot
(Plain cp also works if you prefer a detached copy.)
Commands
Everything is pr-bot <group> <subcommand>. Each has a slash-command twin.
Starting and landing a PR
| Command | Slash | What it does |
|---|---|---|
pr-bot pr start feat <slug> |
/pr-start |
Fetch + FF main, create feat/<slug> off it. |
pr-bot pr start fix <slug> |
/pr-start |
Same, but fix/<slug>. |
pr-bot pr open [--draft] |
/pr-open |
Push current branch, gh pr create against main. |
pr-bot pr sync |
/pr-sync |
Rebase onto origin/main, force-push with lease. |
pr-bot pr ready |
/pr-ready |
Mark draft PR ready for review. |
pr-bot pr merge [--auto] |
/pr-merge |
Squash-merge, delete remote branch, fetch + FF main, prune local. |
pr-bot pr status |
/pr-status |
Ahead/behind main + open PR state. |
Releases
| Command | Slash | What it does |
|---|---|---|
pr-bot release cut X.Y |
/release-cut |
Create release/X.Y off main (or --from <ref>), push. |
pr-bot release prepare X.Y.Z |
/release-prepare |
Bump pyproject.toml on the matching release branch + push. |
pr-bot release tag X.Y.Z |
/release-tag |
Tag X.Y.Z on release/X.Y, push, create GitHub release. Accepts --notes-file. |
pr-bot release list |
/release-list |
List release branches + their highest tag. |
pr-bot release next-patch |
— | Print the next unused patch version for the current series. |
Hotfixes
| Command | Slash | What it does |
|---|---|---|
pr-bot hotfix start <slug> --on X.Y |
/hotfix-start |
hotfix/<slug> off release/X.Y. |
pr-bot hotfix open --on X.Y |
/hotfix-open |
Push + gh pr create against release/X.Y. |
pr-bot hotfix forward-port <sha> |
/hotfix-forward |
New fix/<slug> off main, cherry-picks the hotfix commit. |
Sanity
| Command | Slash | What it does |
|---|---|---|
pr-bot doctor |
/pr-doctor |
Check main is linear, branches/tags match the model, remote wired. |
Typical workflows
Feature PR
pr-bot pr start feat add-login-button
# …hack hack hack; commit as usual…
pr-bot pr open --draft
# …more commits…
pr-bot pr sync # keep up with main
pr-bot pr ready
pr-bot pr merge # squashes, deletes branch, fast-forwards main
Cut and ship 1.4.0
pr-bot release cut 1.4 # creates release/1.4
pr-bot release tag 1.4.0 # annotated tag + gh release
pr-bot release list # confirm
Hotfix for a released version
pr-bot hotfix start fix-npe-on-logout --on 1.4
# …fix, commit…
pr-bot hotfix open --on 1.4 # PR into release/1.4
# …merge via GitHub UI or gh pr merge, whichever your repo uses…
pr-bot release tag 1.4.1 # ship it
pr-bot hotfix forward-port <sha> # carry the fix to main as fix/<slug>
pr-bot pr open
pr-bot pr merge
Why these rules
- Linear main makes
git bisect,git log, and blame actually useful. - Squash merges give one commit per PR, keeping PR == unit of change.
- FF-only main means main never absorbs stale state; rebasing is the only way to integrate.
release/X.Y+X.Y.Ztags cleanly separate "what's next" from "what shipped", and let hotfixes target exactly one released line.
toolbar — workspace-admin helpers
Ships alongside pr-bot from this same repo. Scope: things you do around
a repo (create it, wire up a remote), as opposed to what pr-bot does
inside one once it exists.
| Command | Slash | What it does |
|---|---|---|
toolbar repo info |
/toolbar-repo-info |
Dry-run: show the resolved target (org/name, URL, host). |
toolbar repo create [--public] [--push] |
/toolbar-repo-create |
gh repo create (private by default) + set origin; --push optional. |
toolbar repo push-main |
— | Push local main/master after a clean-tree sanity check. |
Honors two env vars for defaults (flags override):
GH_MIRROR (default: https://github.com)
GH_ORG (default: andrewrothstein)
GH_HOST (from the gh CLI) takes precedence over the host parsed from
GH_MIRROR — useful when the browser URL differs from the API host.
Bootstrap a brand-new repo
cd /path/to/newproj
git init -b main && git commit --allow-empty -m "initial"
toolbar repo info # confirm target
toolbar repo create --push # private repo + push main
Troubleshooting
git push fails with "Repository not found" right after toolbar repo create.
Run gh auth setup-git once. gh wires itself in as a git credential helper
for github.com (and any GH_HOST you've set). Without it, git push has
no token and gets a 401, which GitHub surfaces as "not found".
pr-bot pr sync stops on a rebase conflict.
That's the expected behavior — pr-bot does not auto-resolve conflicts.
Resolve with the usual git add / git rebase --continue flow, then
git push --force-with-lease origin <branch>.
uvx --from pr-bot pr-bot ... fails with "package not found".
pr-bot isn't on PyPI yet. Set PR_BOT_SOURCE to a local clone or a git
ref:
export PR_BOT_SOURCE='git+https://github.com/andrewrothstein/pr-bot@0.1.0'
# or
export PR_BOT_SOURCE=/path/to/your/clone
Contributing
This repo dogfoods its own model. For every change:
pr-bot pr start feat <slug>
# …edit, commit…
uv run pytest -q && uv run ruff check src tests
pr-bot pr open --draft
pr-bot pr ready
pr-bot pr merge
Pure logic goes in src/pr_bot/names.py or src/toolbar/env.py — unit
tests cover any changes there. Subprocess wrappers live in pr_bot.git
(integration-tested in tests/test_git_integration.py against a real
git repo in tmp_path) and pr_bot.gh (tested manually — they talk
to the GitHub API).
Error messages should start with pr-bot: or toolbar: so callers (and
agents) can grep them.
Versioning
SemVer on the matching release line.
- 0.1.x — initial preview. APIs and command names may change.
- 1.0 — ships after a couple weeks of dogfooding and a PyPI publish.
Publishing
Releases to PyPI happen automatically on tag push (X.Y.Z) via a
Trusted Publisher — no API tokens in repo secrets. See
docs/publishing.md for the one-time PyPI setup
and the per-release flow.
See also
AGENTS.md— how AI agents should drivepr-botin any repo.CLAUDE.md— project-specific instructions for Claude Code agents working on this repo.docs/releases/— release notes.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pr_bot-0.1.4.tar.gz.
File metadata
- Download URL: pr_bot-0.1.4.tar.gz
- Upload date:
- Size: 40.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd75ee5ae07a12658545e29aa9c2e2c5777514ca7c121b43f53bdbb343f043b7
|
|
| MD5 |
0b627c23a56f99f686c8b15107a84e8b
|
|
| BLAKE2b-256 |
c6758e973e2c758cd031718941a0118a351ec95c99d4a7312e65317c2a1f1a89
|
Provenance
The following attestation bundles were made for pr_bot-0.1.4.tar.gz:
Publisher:
publish.yml on andrewrothstein/pr-bot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pr_bot-0.1.4.tar.gz -
Subject digest:
bd75ee5ae07a12658545e29aa9c2e2c5777514ca7c121b43f53bdbb343f043b7 - Sigstore transparency entry: 1376554270
- Sigstore integration time:
-
Permalink:
andrewrothstein/pr-bot@f5d6221b7c4f0bd33c89987f70e5a82a53acc23b -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/andrewrothstein
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f5d6221b7c4f0bd33c89987f70e5a82a53acc23b -
Trigger Event:
push
-
Statement type:
File details
Details for the file pr_bot-0.1.4-py3-none-any.whl.
File metadata
- Download URL: pr_bot-0.1.4-py3-none-any.whl
- Upload date:
- Size: 23.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
75c853e36ff93ae196b6fd5578d6de84d176a87c8903b543f8190122992ef600
|
|
| MD5 |
0166cf1ed02574f451a4b12b6832ad94
|
|
| BLAKE2b-256 |
1d27f8d11fd1663ea980b2f7b90364d83c30606d10fa1d1507806f49ffd9fb8a
|
Provenance
The following attestation bundles were made for pr_bot-0.1.4-py3-none-any.whl:
Publisher:
publish.yml on andrewrothstein/pr-bot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pr_bot-0.1.4-py3-none-any.whl -
Subject digest:
75c853e36ff93ae196b6fd5578d6de84d176a87c8903b543f8190122992ef600 - Sigstore transparency entry: 1376554325
- Sigstore integration time:
-
Permalink:
andrewrothstein/pr-bot@f5d6221b7c4f0bd33c89987f70e5a82a53acc23b -
Branch / Tag:
refs/tags/0.1.4 - Owner: https://github.com/andrewrothstein
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f5d6221b7c4f0bd33c89987f70e5a82a53acc23b -
Trigger Event:
push
-
Statement type: