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) on PyPI — one install, works in every repo - a sibling CLI (
toolbar) for repo bootstrap + workspace ops - a Claude Code plugin shipping ~28 slash commands (
/pr-*,/release-*,/hotfix-*,/stack-*,/policy-*,/toolbar-*) that thinly wrap the CLI - a Textual terminal dashboard (
pr-bot ui)
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: PyPI via uvx
Every slash command invokes the CLI through uvx, which fetches pr-bot
from PyPI into a cached isolated env per invocation. No shared venv to
manage; upgrades are atomic (bump the version, invalidate the cache).
# verify
uvx pr-bot --version
Pin to a specific release for reproducibility:
export PR_BOT_SOURCE='pr-bot==0.2.0' # exact version
export PR_BOT_SOURCE='git+https://github.com/andrewrothstein/pr-bot@0.2.0'
export PR_BOT_SOURCE=/path/to/clone # dev loop
The slash commands default to PR_BOT_SOURCE=pr-bot (= latest on PyPI).
Convenience: plain CLI on PATH
If you want pr-bot on your PATH instead of going through uvx each time:
uv tool install pr-bot
This creates a shared user venv — fine for a workstation, but it's one global install, not per-invocation isolation.
Slash commands
Recommended: install as a Claude Code plugin
# inside Claude Code:
/plugin marketplace add andrewrothstein/pr-bot
/plugin install pr-bot@pr-bot
That's it. All /pr-*, /release-*, /hotfix-*, /stack-*,
/policy-*, /toolbar-* slash commands are available in any project
once the plugin is installed.
Alternative: symlink locally
If you'd rather use a clone of this repo directly:
ln -s /path/to/pr-bot/commands ~/.claude/commands/pr-bot
# or, for a project-local install:
ln -s /path/to/pr-bot/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 cleanup |
/pr-cleanup |
Finish the merge dance after a GitHub-UI merge (FF main + delete local branch). |
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, regenerate CHANGELOG.md, draft missing notes, commit, 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 notes-draft X.Y.Z |
/release-notes-draft |
Auto-draft docs/releases/X.Y.Z.md from PRs merged since the last tag, grouped by conventional-commit prefix. |
pr-bot release changelog |
/release-changelog |
Regenerate CHANGELOG.md from docs/releases/*.md (sorted desc). |
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. |
pr-bot policy apply |
/policy-apply |
Push .pr-bot.toml policy to GitHub branch protection. See docs/policy.md. |
pr-bot policy check |
/policy-check |
Diff GitHub's actual branch protection vs. declared policy. |
pr-bot ui |
— | Launch the Textual terminal dashboard. See docs/ui.md. |
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 prepare 1.4.0 # bumps pyproject + auto-drafts notes + commits + pushes
pr-bot release tag 1.4.0 --notes-file docs/releases/1.4.0.md
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.
Stacked PRs
For changes too large for one PR but logically a unit, build a chain of dependent PRs and land them in order:
| Command | Slash | What it does |
|---|---|---|
pr-bot stack new <slug-1> <slug-2> [...] |
/stack-new |
Create a chain of PR branches (each branched off the previous). |
pr-bot stack push |
/stack-push |
Push every branch + open/update PRs with cross-link <!-- pr-bot:stack --> block. |
pr-bot stack sync |
/stack-sync |
Rebase the chain onto origin/main; force-push each branch. |
pr-bot stack land [--all] |
/stack-land |
Squash-merge the bottom branch + pop from state. --all lands the whole stack. |
pr-bot stack status |
/stack-status |
Show branches, PR numbers, draft state. |
pr-bot stack abandon |
/stack-abandon |
Delete the stack state file (branches/PRs kept). |
State lives in .git/pr-bot/stack.json (per-clone, never committed). See
docs/design-pr-stack.md for the design.
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 template <name> |
/toolbar-repo-template |
Bootstrap a new project: render skeleton + git init + cut release/0.1 + push to GitHub. |
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
If you want the full pr-bot model wired up from scratch (pyproject + ruff
- ratchet-pinned CI + CLAUDE.md + AGENTS.md + LICENSE + a cut
release/0.1):
toolbar repo template my-thing --description "What it does"
# → creates ./my-thing/, git init, scaffolds, gh repo create --private --push
Or, if you already have a repo and just need it pushed up:
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>.
Want a pinned version of pr-bot instead of always-latest?
Override PR_BOT_SOURCE:
export PR_BOT_SOURCE='pr-bot==0.2.0' # PyPI
export PR_BOT_SOURCE='git+https://github.com/andrewrothstein/pr-bot@0.2.0'
export PR_BOT_SOURCE=/path/to/your/clone # dev loop
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 (src/pr_bot/names.py, pr_bot/policy.py, pr_bot/stack.py,
pr_bot/changelog.py, pr_bot/release_notes.py, pr_bot/pyproject.py,
toolbar/env.py, toolbar/template.py) is unit-tested in tests/.
Subprocess wrappers (pr_bot.git, pr_bot.gh) and the higher-level
command modules (pr_bot.commands.{pr,release,hotfix,stack}) are
integration-tested against a real git repo in tmp_path plus a gh
shim on PATH. Total: 148 tests.
Error messages should start with pr-bot: or toolbar: so callers (and
agents) can grep them.
Versioning
SemVer on the matching release line.
- 0.x.y — preview. CLI surface, file formats, and the importable Python API may change between minor releases.
- 1.0+ — stable surfaces locked. See
docs/stability.mdfor the full list of what's stable, what's experimental, and the deprecation policy.
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/stability.md— 1.0 stability commitment.docs/release-checklist.md— manual smoke-test steps run against the live GitHub API before tagging.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.3.0.tar.gz.
File metadata
- Download URL: pr_bot-0.3.0.tar.gz
- Upload date:
- Size: 93.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5268a0616397805028f5017221c409072f168e467719f12d038882b1911dd48e
|
|
| MD5 |
8ba1e79674605fbeab4dac501376f7c9
|
|
| BLAKE2b-256 |
ab37c73d50c8fbc63a556fe4a2da23f6374ff88e441063ee42b875ddd72ab755
|
Provenance
The following attestation bundles were made for pr_bot-0.3.0.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.3.0.tar.gz -
Subject digest:
5268a0616397805028f5017221c409072f168e467719f12d038882b1911dd48e - Sigstore transparency entry: 1384093518
- Sigstore integration time:
-
Permalink:
andrewrothstein/pr-bot@c87af7a3b93d9f0baef90237852499b8f78e7805 -
Branch / Tag:
refs/tags/0.3.0 - Owner: https://github.com/andrewrothstein
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c87af7a3b93d9f0baef90237852499b8f78e7805 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pr_bot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: pr_bot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 51.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 |
6d940610f0c9b5ff39647f2a3e871b55ef085c3d19fc39dbdbad0506c5d04a16
|
|
| MD5 |
d6c8caefa14178f0b6e82f97063d0307
|
|
| BLAKE2b-256 |
cc29cb8e0c2e3e9e1d4719916d1b234efaf0e11aa826b5f53e25185108603890
|
Provenance
The following attestation bundles were made for pr_bot-0.3.0-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.3.0-py3-none-any.whl -
Subject digest:
6d940610f0c9b5ff39647f2a3e871b55ef085c3d19fc39dbdbad0506c5d04a16 - Sigstore transparency entry: 1384093586
- Sigstore integration time:
-
Permalink:
andrewrothstein/pr-bot@c87af7a3b93d9f0baef90237852499b8f78e7805 -
Branch / Tag:
refs/tags/0.3.0 - Owner: https://github.com/andrewrothstein
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c87af7a3b93d9f0baef90237852499b8f78e7805 -
Trigger Event:
push
-
Statement type: