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) — 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 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: 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 tag X.Y.Z /release-tag Tag X.Y.Z on release/X.Y, push tag, create GitHub release.
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.Z tags 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 drive pr-bot in 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

pr_bot-0.1.0.tar.gz (38.8 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.1.0-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file pr_bot-0.1.0.tar.gz.

File metadata

  • Download URL: pr_bot-0.1.0.tar.gz
  • Upload date:
  • Size: 38.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pr_bot-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f5a2eb26785df8822188ce1977cdaedd78b5b7995428697566d540a7794bda95
MD5 e123353c44cfef9c95c42a61fb4487b3
BLAKE2b-256 19657a4341e87af87eb4447538e024ddbee6c110038497e8e928f55e44c00277

See more details on using hashes here.

Provenance

The following attestation bundles were made for pr_bot-0.1.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.1.0-py3-none-any.whl.

File metadata

  • Download URL: pr_bot-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pr_bot-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8483ce9167b584d9f25c03466f5ef3da298277f295a806a09196aa95195df944
MD5 56b971590ad34acdb5f7cf1791fdb196
BLAKE2b-256 58009050b8abf262cf340d0c42fb8a78661220f44d68d0993ff884c458681ffb

See more details on using hashes here.

Provenance

The following attestation bundles were made for pr_bot-0.1.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