Structured git workflow CLI: conventional commits, trunk-based branches, GitHub PR lifecycle
Project description
git-clerk
A structured git workflow CLI for conventional commits, trunk-based branching, and a clean GitHub PR lifecycle — all from the command line.
Philosophy
git-clerk is built on three practices that reinforce each other: trunk-based development, conventional commits, and squash-merge-only history. Short-lived branches stay close to main. Squash merges keep main's history linear and readable — one commit per feature. Conventional commit types make that history meaningful at a glance. git-clerk connects all three as a unit: you name your branch feat/user-auth once, and every commit message, PR title, and release tag follows from that single decision.
Opinions
git-clerk is intentionally opinionated. These constraints are not configurable:
- GitHub only — PR and release operations rely on
gh. GitLab and Bitbucket are not supported. - Squash merges —
shipalways squash-merges to keepmain's history linear. - Single trunk —
mainis the only integration branch.develop,release/*, and similar long-lived branches are out of scope. The trunk name is not configurable — repositories using a different default branch are not supported. - Conventional commits — branch names must follow
type/scopeusing one of the 11 standard types.
If your workflow diverges from any of these, git-clerk is not the right tool.
Prerequisites
Local
- Python 3.10+
- uv for installation
- GitHub CLI (
gh) 2.0 or later, authenticated to your GitHub account
Repository configuration
git-clerk assumes the repository is configured to match its workflow. Without this, the tool still works but its guarantees don't hold — anyone can bypass conventions by using git and gh directly.
Ask your infra or platform team to configure:
- Squash merges only — disable merge commits and rebase merges so
mainstays linear - Branch protection on
main— require pull requests before merging; disallow direct pushes - Required status checks — require CI to pass before a PR can be merged; this makes
git clerk ship's CI gate structural rather than advisory
Installation
uv tool install git-clerk
To use it as git clerk (recommended) rather than git-clerk, register a git alias:
git config --global alias.clerk '!git-clerk'
After this, git clerk prints help and git clerk commit --help (any subcommand) works as expected. Note that git clerk --help will not work — git intercepts --help before running the alias and tries to open a man page. Use git clerk or git-clerk --help for top-level help instead.
Workflow walkthrough
Here is a complete cycle from starting a feature to tagging a release.
1. Create a branch
git clerk branch feat/user-auth
Fetches the latest origin/main and creates feat/user-auth from it. The branch name is the only thing you decide upfront — type and scope flow into every subsequent command automatically.
2. Do your work, then commit
git clerk commit -A "add login form"
Stages all changes and commits with the message feat(user-auth): add login form. The type and scope come from the branch name — you only write the description.
For commits that need more context, pass the body as a second argument or open your editor with -e:
git clerk commit -A "add login form" "Supports email and SSO providers."
# → commits with inline body (useful in scripts and LLM workflows)
git clerk commit -A -e "add login form"
# → opens $EDITOR for the body, then commits
You can commit as many times as you want. Only the squash commit that lands on main is permanent.
3. Open a PR
git clerk pr "Add login form"
Pushes the branch with upstream tracking set, creates the PR against main, prints the URL, then watches CI checks until they complete. You can share the URL while CI is still running.
By default no body is added. To add one:
git clerk pr "Add login form" "Adds email/password and SSO login. Closes #42."
# inline body
git clerk pr -e "Add login form"
# opens $EDITOR for the body
4. Ship it
Once CI is green:
git clerk ship
Shows you the PR title and number, asks for confirmation, then: squash-merges into main, deletes the remote branch, switches to local main, pulls, and force-deletes the local branch. You end up on a clean, up-to-date main in one step.
5. Tag a release
git clerk release
Detects your versioning scheme from existing tags, computes the next version, shows you the tag, and asks for confirmation before pushing. On a fresh repo with no tags, it prompts you to choose CalVer or SemVer.
Board workflow
The board commands add a lightweight project layer on top of the core workflow using GitHub Milestones and Issues. They are optional, the branch/commit/pr/ship workflow works the same with or without them.
Create a milestone
git clerk milestone new "Auth System" --scope auth
# Milestone #1 created.
git clerk milestone new "Auth System" "Handles login, registration, and SSO." --scope auth
# → with inline description
git clerk milestone new "Auth System" --scope auth -e
# → opens $EDITOR for the description
The --scope is used to derive branch names for all issues in this milestone. Every issue started under the milestone will live on a type/scope branch.
Create issues
git clerk issue new "Add login form" --type feat --milestone 1
git clerk issue new "Fix token expiry" --type fix --milestone 1
git clerk issue new "Write auth docs" --type docs --milestone 1
A type is required. A milestone is optional at creation time — an issue without one sits in the backlog. Both are required before issue start can be used.
Start an issue
git clerk issue start 1
# On branch 'feat/auth', active issue is #1.
Creates the branch from the milestone's scope (feat/auth), switches to it, and records the active issue in local git config. From here, the standard commit/PR/ship workflow applies.
PR body gets Closes #N automatically
Because issue start recorded the active issue, git clerk pr appends Closes #1 to the PR body without any extra flags. The issue is closed on GitHub when the PR is squash-merged.
Ship closes the milestone when all issues are done
git clerk ship
Clears the active issue after merging. If the milestone has no remaining open issues, git-clerk closes it automatically and prints a confirmation.
Other board commands
git clerk milestone list # list open milestones with issue counts
git clerk milestone reopen 1 # reopen a closed milestone
git clerk issue list # list open issues (all milestones)
git clerk issue list --milestone 1 # filter by milestone
git clerk issue discard 3 # close an issue as not planned
Commands
branch TYPE/scope
Fetches the latest origin/main and creates a new branch from it.
git clerk branch feat/user-auth # → feat/user-auth
git clerk branch fix/payment-api # → fix/payment-api
git clerk branch chore/deps # → chore/deps
The branch name is the only decision you make upfront. Every subsequent commit and pr command reads the type and scope from it — you never repeat yourself.
The fetch happens before branch creation, so you always start from the latest main regardless of how long ago you last pulled. If the branch already exists locally, git will error — delete it first or choose a different name.
commit DESCRIPTION [BODY]
Creates a conventional commit by reading the type and scope from the current branch name. The commit message header is always type(scope): description — you only supply the description.
git clerk commit "add login form"
# → feat(user-auth): add login form
Body
By default there is no body — most commits don't need one. There are two ways to add one:
# Inline — pass the body as a second positional argument.
# Useful in scripts and LLM-driven workflows.
git clerk commit "add login form" "Supports email and SSO providers."
# Interactive — open $EDITOR. Save and exit to use the body; quit without
# saving to abort.
git clerk commit -e "add login form"
An empty string passed as BODY is treated the same as no body — only a non-empty string is included in the commit.
Options
| Flag | Description |
|---|---|
-A |
Stage all changes (git add -A) before committing |
-e |
Open $EDITOR to write the commit body interactively |
-t TYPE |
Override the type inferred from the branch name |
-s SCOPE |
Override the scope inferred from the branch name |
The -t and -s overrides are for cases where the commit type or scope differs from the branch — for example, bumping a lockfile on a feat branch:
git clerk commit -t chore "update lockfile" # chore(user-auth): update lockfile
git clerk commit -s auth-core "fix token TTL" # feat(auth-core): fix token TTL
pr TITLE [BODY]
Pushes the current branch to origin (with upstream tracking), creates a GitHub PR against main with a conventional title derived from the branch name, prints the PR URL, then watches CI checks until they complete.
The PR title is constructed the same way as a commit header: type(scope): title. You supply only the human-readable title.
Body
By default no body is added. There are two ways to add one:
# Inline — pass the body as a second positional argument.
# Useful in scripts and LLM-driven workflows.
git clerk pr "Add login form" "Adds email/password and SSO login. Closes #42."
# Interactive — open $EDITOR. Save and exit to use the body;
# quit without saving to abort.
git clerk pr -e "Add login form"
An empty string passed as BODY is treated the same as no body.
Options
| Flag | Description |
|---|---|
-e |
Open $EDITOR to write the PR body interactively |
-t TYPE |
Override the type in the PR title |
-s SCOPE |
Override the scope in the PR title |
The PR URL is printed to stdout as soon as the PR is created, before CI checks begin — you can share it while checks are still running. If checks fail, the run ends with a non-zero exit code.
If issue start was used to begin work on the current branch, pr automatically appends Closes #N to the PR body so the linked issue is closed when the PR merges.
ship
Squash-merges the current branch's PR and brings your local environment back to a clean state on main. Must be run from the feature branch, not from main.
git clerk ship
Displays the PR title and number, asks for confirmation, then executes in order:
- Squash-merges the PR into
main - Deletes the remote branch
- Switches to local
main - Pulls latest from
origin/main - Force-deletes the local branch
You end up on a clean, up-to-date main in one step.
If the branch was started with issue start, ship also clears the active issue from local git config. If the linked issue's milestone has no remaining open issues after the merge, the milestone is closed automatically.
Options
| Flag | Description |
|---|---|
-y / --yes |
Skip the confirmation prompt |
-u BRANCH |
After shipping, switch to BRANCH and merge origin/main into it |
-u is for cases where you paused work on one branch to ship a dependency first:
# Shipping fix/tech-debt while feat/user-auth is parked
git clerk ship -u feat/user-auth
# → ships fix/tech-debt, then switches to feat/user-auth and merges origin/main
-y is useful in automated contexts where you want to ship without interactive confirmation:
git clerk ship -y
watch
Re-attaches to CI checks for the current branch's PR. Useful when you want to check in on CI after navigating away from the terminal during a pr run.
git clerk watch
milestone new TITLE [DESCRIPTION]
Creates a GitHub Milestone. The --scope option is required and determines the branch name prefix used by all issues in this milestone.
git clerk milestone new "Auth System" --scope auth
git clerk milestone new "Auth System" "Handles login and SSO." --scope auth
git clerk milestone new "Auth System" --scope auth -e # opens $EDITOR
Options
| Flag | Description |
|---|---|
--scope SCOPE |
Branch scope for all issues in this milestone (required) |
-e |
Open $EDITOR for the description |
milestone list
Lists open milestones with their scope and open/closed issue counts.
git clerk milestone list
milestone reopen NUMBER
Reopens a closed milestone.
git clerk milestone reopen 1
issue new TITLE [BODY]
Creates a GitHub Issue. --type is required. --milestone is optional — an issue without one sits in the backlog. Both are required before issue start can be used. Type labels are created in the repository automatically on first use.
git clerk issue new "Add login form" --type feat
git clerk issue new "Add login form" --type feat --milestone 1
git clerk issue new "Add login form" --type feat --milestone 1 -e
Options
| Flag | Description |
|---|---|
--type TYPE |
Conventional commit type label (required) |
--milestone NUMBER |
Milestone number |
-e |
Open $EDITOR for the issue body |
issue list
Lists open issues. Filters to a specific milestone with --milestone.
git clerk issue list
git clerk issue list --milestone 1
issue start NUMBER
Starts work on an issue: creates the branch from the milestone's scope, switches to it, and records the active issue in local git config. Requires the issue to have a type label and be assigned to a milestone.
git clerk issue start 1
# On branch 'feat/auth', active issue is #1.
The recorded active issue is used by pr (to append Closes #N) and cleared by ship (after merging).
issue discard NUMBER
Closes an issue as "not planned".
git clerk issue discard 3
release
Tags the current tip of origin/main and pushes the tag. Supports CalVer and SemVer.
git-clerk fetches the latest tags, computes the next version, shows you what it's about to create, and asks for confirmation before pushing anything.
git clerk release # auto-detect scheme, prompt for bump if SemVer
git clerk release --semver --bump minor
git clerk release --semver --bump major
git clerk release --calver
Scheme detection
If the repository already has version tags, git-clerk detects the scheme automatically — --calver and --semver are not needed. If no tags exist yet, git-clerk prompts you to choose interactively. Pass --calver or --semver to skip the prompt.
If both CalVer and SemVer tags are found (e.g. after a scheme migration), git-clerk exits with an error. Pass --calver or --semver explicitly to proceed.
Options
| Flag | Description |
|---|---|
--calver |
Use calendar versioning |
--semver |
Use semantic versioning |
--bump patch|minor|major |
SemVer component to increment; prompted if not provided (ignored for CalVer) |
-y / --yes |
Skip the confirmation prompt |
Versioning schemes
CalVer — vYYYY.MM.N
Tags are tied to the calendar month, with a sequential counter that resets each month:
v2026.06.1
v2026.06.2
v2026.07.1 ← new month, counter resets
Good fit for projects that ship continuously and want version numbers that communicate when something was released.
SemVer — vMAJOR.MINOR.PATCH
Standard semantic versioning. The first tag on a repo with no prior tags starts at v0.1.0.
v0.1.0 → v0.1.1 (patch)
v0.1.1 → v0.2.0 (minor)
v0.2.0 → v1.0.0 (major)
The tag is always placed on origin/main, so run release after shipping all PRs for the version.
Branch naming
All commands that read from the branch name (commit, pr) expect the format type/scope:
feat/user-auth
fix/payment-timeout
chore/upgrade-deps
docs/api-reference
The type must be one of the standard conventional commit types: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test. The scope must start with a letter or digit and may contain letters, digits, hyphens, and underscores — spaces and special characters are not allowed. Both are validated by branch before the branch is created, and by commit and pr when reading the current branch name.
License
MIT
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 git_clerk-0.4.1.tar.gz.
File metadata
- Download URL: git_clerk-0.4.1.tar.gz
- Upload date:
- Size: 32.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
20095d572d97877ee4815fe86db00ce5e3cd13e0d9107628347c968f4c50e734
|
|
| MD5 |
33dc2e3239624f43550368916f6cab2f
|
|
| BLAKE2b-256 |
8fdc73ee9cc99e9fd6988547a2cdf31b5d5777e36436ee76fdc99bf51636e916
|
Provenance
The following attestation bundles were made for git_clerk-0.4.1.tar.gz:
Publisher:
publish.yml on nicobc/git-clerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_clerk-0.4.1.tar.gz -
Subject digest:
20095d572d97877ee4815fe86db00ce5e3cd13e0d9107628347c968f4c50e734 - Sigstore transparency entry: 1859756639
- Sigstore integration time:
-
Permalink:
nicobc/git-clerk@8759085561850ed97d401929219c74d8c6a5cdb9 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/nicobc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8759085561850ed97d401929219c74d8c6a5cdb9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file git_clerk-0.4.1-py3-none-any.whl.
File metadata
- Download URL: git_clerk-0.4.1-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
45a4e56a7778303e7b6a9d43dc00cc230293b6ea33713c7f6553dfe8d6c44ee6
|
|
| MD5 |
0291d590b572cb95bfa721fb70633352
|
|
| BLAKE2b-256 |
6ba9d4bf9621ac73405ae3333973a31b94def4e2f23b63d5955c9354b74e334d
|
Provenance
The following attestation bundles were made for git_clerk-0.4.1-py3-none-any.whl:
Publisher:
publish.yml on nicobc/git-clerk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_clerk-0.4.1-py3-none-any.whl -
Subject digest:
45a4e56a7778303e7b6a9d43dc00cc230293b6ea33713c7f6553dfe8d6c44ee6 - Sigstore transparency entry: 1859756651
- Sigstore integration time:
-
Permalink:
nicobc/git-clerk@8759085561850ed97d401929219c74d8c6a5cdb9 -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/nicobc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8759085561850ed97d401929219c74d8c6a5cdb9 -
Trigger Event:
push
-
Statement type: