Skip to main content

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 into main; branch is deleted on merge.
  • Release branch: release/X.Y is cut from a commit on main. Long-lived. Receives only hotfixes.
  • Tags: X.Y.Z (annotated) placed on a commit on the matching release/X.Y branch.
  • Hotfixes: hotfix/<slug> off release/X.Y, PR'd back to release/X.Y, then forward-ported to main as a fix/<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.Z tags 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.md for 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

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

pr_bot-0.3.0.tar.gz (93.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pr_bot-0.3.0-py3-none-any.whl (51.0 kB view details)

Uploaded Python 3

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

Hashes for pr_bot-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5268a0616397805028f5017221c409072f168e467719f12d038882b1911dd48e
MD5 8ba1e79674605fbeab4dac501376f7c9
BLAKE2b-256 ab37c73d50c8fbc63a556fe4a2da23f6374ff88e441063ee42b875ddd72ab755

See more details on using hashes here.

Provenance

The following attestation bundles were made for pr_bot-0.3.0.tar.gz:

Publisher: publish.yml on andrewrothstein/pr-bot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pr_bot-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6d940610f0c9b5ff39647f2a3e871b55ef085c3d19fc39dbdbad0506c5d04a16
MD5 d6c8caefa14178f0b6e82f97063d0307
BLAKE2b-256 cc29cb8e0c2e3e9e1d4719916d1b234efaf0e11aa826b5f53e25185108603890

See more details on using hashes here.

Provenance

The following attestation bundles were made for pr_bot-0.3.0-py3-none-any.whl:

Publisher: publish.yml on andrewrothstein/pr-bot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page