Skip to main content

Structured git workflow CLI: conventional commits, trunk-based branches, GitHub PR lifecycle

Project description

git-acta

PyPI version Python versions License: MIT CI

A structured git workflow CLI for conventional commits, trunk-based branching, and a clean GitHub PR lifecycle — all from the command line.

Philosophy

git-acta 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-acta 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-acta is intentionally opinionated. These constraints are not configurable:

  • GitHub only — PR and release operations rely on gh. GitLab and Bitbucket are not supported.
  • Squash mergesship always squash-merges to keep main's history linear.
  • Single trunkmain is 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/scope using one of the 11 standard types. An optional third segment (type/scope/topic) carries a human-readable topic; the commit type and scope are derived from the first two.

If your workflow diverges from any of these, git-acta 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-acta 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 main stays 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 acta ship's CI gate structural rather than advisory

Installation

uv tool install git-acta

This installs the acta command. All commands below are invoked as acta <subcommand>.

Workflow walkthrough

Here is a complete cycle from starting a feature to tagging a release.

1. Create a branch

acta 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

acta 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 with -b or open your editor with -e:

acta commit -A "add login form" -b "Supports email and SSO providers."
# → commits with inline body (useful in scripts and LLM workflows)

acta 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

acta 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:

acta pr "Add login form" -b "Adds email/password and SSO login.

## Changes
- Email/password with bcrypt hashing
- SSO via Google and GitHub

Closes #42."
# inline body

acta pr -e "Add login form"
# opens $EDITOR for the body

4. Ship it

Once CI is green:

acta 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

acta 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

acta milestone new "Auth System" --scope auth
# Milestone #1 created.

acta milestone new "Auth System" -d "Handles login, registration, and SSO." --scope auth
# → with inline description

acta 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/N-title-slug branch.

Create issues

acta issue new "Add login form" --type feat --milestone 1
acta issue new "Fix token expiry" --type fix --milestone 1
acta 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

acta issue start 1
# On branch 'feat/auth/1-add-login', active issue is #1.
#
# ## Description
# Login form with magic link.

Creates the branch from the issue's type and the milestone's scope, with a third segment from the issue number and title slug (feat/auth/1-add-login), switches to it, records the active issue in local git config, and prints the issue body so you have the description and acceptance criteria in front of you as you begin. From here, the standard commit/PR/ship workflow applies.

PR body gets Closes #N automatically

Because issue start recorded the active issue, acta 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

acta ship

Clears the active issue after merging. If the milestone has no remaining open issues, git-acta closes it automatically and prints a confirmation.

Other board commands

acta board                          # session snapshot: active work + current milestone
acta milestone list                 # list open milestones with issue counts
acta milestone reopen 1             # reopen a closed milestone
acta issue list                     # list open issues, grouped by milestone
acta issue list --milestone 1       # filter to one milestone (flat list)
acta 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.

acta branch feat/user-auth          # → feat/user-auth
acta branch fix/payment-api         # → fix/payment-api
acta branch chore/deps              # → chore/deps
acta branch feat/user-auth/sso      # → feat/user-auth/sso

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.

You may add an optional third segment for a human-readable topic — feat/user-auth/sso. The type and scope are read from the first two segments (feat, user-auth); the topic keeps the branch name distinct and descriptive, so two branches sharing a type and scope can coexist.

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

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.

acta 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 with -b. Useful in scripts and LLM-driven workflows.
acta commit "add login form" -b "Supports email and SSO providers."

# Interactive — open $EDITOR. Save and exit to use the body; quit without
# saving to abort.
acta commit -e "add login form"

-b and -e are mutually exclusive. An empty string passed to -b is treated the same as no body — only a non-empty string is included in the commit.

Options

Flag Description
-A / --stage-all Stage all changes (git add -A) before committing
-P / --push Push to origin after committing (combine as -AP)
-b BODY / --body Commit body (mutually exclusive with -e)
-e / --edit Open $EDITOR to write the commit body interactively
-t TYPE / --type Override the type inferred from the branch name
-s SCOPE / --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:

acta commit -t chore "update lockfile"    # chore(user-auth): update lockfile
acta commit -s auth-core "fix token TTL"  # feat(auth-core): fix token TTL

-P pushes after committing, handy for follow-up commits once a PR is already open. It prints a reminder to refresh the PR description, since new commits may change the PR's scope.

pr TITLE

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. Pass --breaking to mark a breaking change — it appends ! to give type(scope)!: title, the conventional-commits marker. Because ship squash-merges the PR title onto main, this is where the breaking marker is recorded in history.

Body

By default no body is added. There are two ways to add one:

# Inline — pass the body with -b. Useful in scripts and LLM-driven workflows.
acta pr "Add login form" -b "Adds email/password and SSO login. Closes #42."

# Interactive — open $EDITOR. Save and exit to use the body;
# quit without saving to abort.
acta pr -e "Add login form"

-b and -e are mutually exclusive. An empty string passed to -b is treated the same as no body.

Options

Flag Description
-b BODY / --body PR body (mutually exclusive with -e)
-e / --edit Open $EDITOR to write the PR body interactively
-t TYPE / --type Override the type in the PR title
-s SCOPE / --scope Override the scope in the PR title
--breaking Append ! to type(scope) to mark a breaking change

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.

acta ship

Displays the PR title and number, asks for confirmation, then executes in order:

  1. Squash-merges the PR into main
  2. Deletes the remote branch
  3. Switches to local main
  4. Pulls latest from origin/main
  5. 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
acta 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:

acta 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.

acta watch

board

Prints a session snapshot: the current branch and active issue, the milestone in focus with its open issues expanded, and the remaining open milestones as one-line counts. The focused milestone is the active issue's milestone, or the first open milestone when no issue is active.

acta board
# Current branch: feat/foundation
# Active issue: #4 Auth
#
# #1  Foundation — 3 issues open, 2 closed
#   #3  chore  Full data model — schema and migrations
#   #4  feat   Auth — magic link login, session, protected routes
#
# #2  Portfolio — 2 issues open

milestone new TITLE

Creates a GitHub Milestone. The --scope option is required and determines the branch name prefix used by all issues in this milestone.

acta milestone new "Auth System" --scope auth
acta milestone new "Auth System" -d "Handles login and SSO." --scope auth
acta milestone new "Auth System" -e --scope auth    # opens $EDITOR

Options

Flag Description
--scope SCOPE Branch scope for all issues in this milestone (required)
-d DESC / --description Milestone description (mutually exclusive with -e)
-e / --edit Open $EDITOR for the description

milestone list

Lists open milestones with their open/closed issue counts, under a repo header.

acta milestone list
# renov milestones:
# #1  Foundation — 3 issues open, 2 closed
# #2  Portfolio — 2 issues open

The closed count is omitted when nothing is closed.

milestone reopen NUMBER

Reopens a closed milestone.

acta milestone reopen 1

issue new TITLE

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.

acta issue new "Add login form" --type feat
acta issue new "Add login form" --type feat --milestone 1
acta issue new "Add login form" --type feat --milestone 1 -b "Magic-link login."
acta issue new "Add login form" --type feat --milestone 1 -e

Options

Flag Description
--type TYPE Conventional commit type label (required)
--milestone NUMBER Milestone number
-b BODY / --body Issue body (mutually exclusive with -e)
-e / --edit Open $EDITOR for the issue body

issue list

Lists open issues grouped by milestone. With --milestone, lists just that milestone's issues as a flat list.

acta issue list
# #1 Foundation
#   #3  chore  Full data model — schema and migrations
#   #4  feat   Auth — magic link login, session, protected routes
#
# #2 Portfolio
#   #6  feat   Portfolio

acta issue list --milestone 1
# #3  chore  Full data model — schema and migrations
# #4  feat   Auth — magic link login, session, protected routes

Issues with no milestone are grouped last under No milestone.

issue start NUMBER

Starts work on an issue: creates the branch from the milestone's scope, switches to it, records the active issue in local git config, and prints the issue body. Requires the issue to have a type label and be assigned to a milestone.

The branch is type/scope/N-title-slug — the issue's type and the milestone's scope, plus a third segment built from the issue number and a slug of its title. This keeps each issue on a distinct, self-describing branch even when several issues in a milestone share the same type and scope.

acta issue start 1
# On branch 'feat/auth/1-add-login', active issue is #1.
#
# ## Description
# Login form with magic link.

The issue body is printed so the description and acceptance criteria are in front of you as you start. 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".

acta issue discard 3

release

Tags the current tip of origin/main and pushes the tag. It fetches the latest tags, computes the next version, shows it, and asks for confirmation before pushing. Supports CalVer and SemVer.

For SemVer the bump is derived from the conventional-commit subjects since the last tag: a feat bumps minor, anything else bumps patch, and once the project is stable (1.0+) a ! breaking change bumps major. While still 0.x a breaking change is capped at minor, since a pre-1.0 breaking change must not force v1.0.0. There is no --bump: the commits decide.

acta release                  # derive and tag the next version (auto-detects scheme)
acta release --scheme semver  # force SemVer (only needed for the very first tag)
acta release --scheme calver  # use calendar versioning instead
acta release --stable         # one-time promotion of a 0.x project to v1.0.0

Going stable. v1.0.0 is the one version the commits cannot derive: the 0.x cap stops at minor, because declaring stability is a deliberate decision. Pass --stable once to make that jump. After 1.0, breaking changes drive majors automatically, so there is no further override.

Scheme detection. If the repository already has version tags, git-acta detects the scheme automatically, so --scheme is not needed. If no tags exist yet, it prompts you to choose. If both CalVer and SemVer tags are found, it exits with an error; pass --scheme explicitly to proceed.

Options

Flag Description
--scheme SCHEME Versioning scheme: calver or semver
--stable Promote a 0.x project to v1.0.0 (SemVer only, one-time)
-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, with an optional third topic segment:

feat/user-auth
fix/payment-timeout
chore/upgrade-deps
docs/api-reference
feat/user-auth/sso

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. An optional third segment may follow the scope (type/scope/topic) to give the branch a distinct, human-readable name; the type and scope are read from the first two segments. Type and scope 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

git_acta-1.0.0.tar.gz (42.4 kB view details)

Uploaded Source

Built Distribution

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

git_acta-1.0.0-py3-none-any.whl (27.2 kB view details)

Uploaded Python 3

File details

Details for the file git_acta-1.0.0.tar.gz.

File metadata

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

File hashes

Hashes for git_acta-1.0.0.tar.gz
Algorithm Hash digest
SHA256 6eb60c2cf87f14f8d8856035d08881cda21329848490da43fe5fd33566aeb75a
MD5 70303289a2286f2a2d83b52f5ed79f39
BLAKE2b-256 5ca34c3f0ad094d5d402b2893d11c0024dfa919b336da3299a8f46245d7fc610

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_acta-1.0.0.tar.gz:

Publisher: publish.yml on nicobc/git-acta

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

File details

Details for the file git_acta-1.0.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for git_acta-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 52269439f84be034120b303a90ca7c442595ca1094fb7c54c9675b329961fc65
MD5 b84e124545d6df6a1b8e765bff25439e
BLAKE2b-256 7a66a6938985587d462a1a1545ac32fd60dea3d80796be8503ac34b613269067

See more details on using hashes here.

Provenance

The following attestation bundles were made for git_acta-1.0.0-py3-none-any.whl:

Publisher: publish.yml on nicobc/git-acta

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