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.2.tar.gz (40.4 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.2-py3-none-any.whl (22.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pr_bot-0.1.2.tar.gz
  • Upload date:
  • Size: 40.4 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.2.tar.gz
Algorithm Hash digest
SHA256 05aa3a3c8cfb00ff258abeb65de6fe6d16ca5b8334cd9df7df742e3816f91b87
MD5 3e999827468058568e450d531e74fd08
BLAKE2b-256 3259c3bf584c6e8cce03f0876f7ca5a93f716da8c5e3dd3631d5917c6274ea36

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pr_bot-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 22.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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 63c7225f9f0a02592f8a9862c2a284acd1be23bbb4d51c66df298daf9e389f14
MD5 88519724d5ebd856318bde01997f623d
BLAKE2b-256 610acd0ae5466286d1e2ca8adec75e905b5681b546cc4ae30c7a6f08a1396f83

See more details on using hashes here.

Provenance

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