Branch naming and protection policy checker for teams. Scans your remotes, reports violations, exits non-zero for CI.
Zero runtime dependencies · Works offline (with --local) · Open-core (MIT)
Every team agrees on a branch convention in the retro. Two sprints later the
remote has wip, TEMP-fix, john-backup-final2, and nobody remembers which
protection rules got dropped when the repo was migrated.
branchpol makes your branch policy executable:
- Naming policy — required patterns (
feat/*,fix/*,release/*), forbidden names (master,wip, anything matching deny-lists), lowercase enforcement, max length. - Protection policy — required branches must exist on the remote, banned branches must not, and the remote's default branch must match your policy.
- Staleness sweep — flag remote-tracking branches with no commits for N days.
- CI-native — human output locally, JSON for tooling, GitHub Actions annotations in CI, and a non-zero exit code when violations are found.
Branch sprawl is a governance problem that shows up as real risk: stale
release/ branches nobody patched, unprotected default branches, personal
branches holding unreviewed work. Code review catches code problems; nothing
catches branch problems — until now.
npm install --global branchpolRequires Node 18+ and git on your PATH. Works on macOS, Linux and Windows.
cd my-project
branchpol init # create branchpol.config.json with sensible defaults
branchpol check # scan origin's branches against policy
echo $? # 0 = clean, 1 = violations, 2 = errorExample output:
branchpol — origin · 14 branches scanned
wip
✗ forbidden pattern: matches forbidden pattern ^wip$
! naming: does not match any required pattern (^(feat|fix|...)/.+)
TEMP-auth-retry
! case: should be lowercase (a-z, 0-9, . _ / -)
1 error(s), 2 warning(s)
In GitHub Actions, use --format github (or just run it in CI — it's detected
automatically) and violations appear inline on the PR:
- run: npx branchpol check --format githubBy default, branchpol talks to the remote directly with read-only git plumbing:
git ls-remote --heads <remote>— every branch, no fetch, no checkout, no working-tree changes. Safe to run anywhere.git ls-remote --symref <remote> HEAD— the true remote default branch.
Prefer fully offline? Use local remote-tracking refs instead:
git fetch --prune && branchpol check --local # or let branchpol fetch:
branchpol check --fetch # also enables the staleness sweepbranchpol never mutates your repository.
branchpol.config.json (created by branchpol init):
| Rule | Severity | Meaning |
|---|---|---|
require |
error | Branch matches no required naming pattern |
forbid |
error | Branch matches a forbidden pattern |
case |
warning | Branch name has characters outside a-z0-9._/- |
maxLength |
warning | Branch name longer than naming.maxLength |
defaultBranch |
error | Remote's HEAD doesn't match defaultBranch |
required |
error | A protected branch is missing from the remote |
forbiddenBranch |
error | A banned branch exists on the remote |
stale |
warning | Tracking branch idle beyond stale.days |
branchpol <command> [options]
Commands
init Create branchpol.config.json
check Scan remote branches against policy (default)
protect Print suggested GitHub branch-protection payloads
Options
--remote <name> Remote to scan (default: origin)
--format <f> text | json | github
--local Scan local remote-tracking refs (no network)
--fetch Run `git fetch --prune` first; enables staleness
--out <file> Write report to file
--strict Treat warnings as errors
--cwd <dir> Repository directory
Exit codes: 0 clean · 1 violations (or warnings with --strict) · 2 error.
GitHub Actions:
name: branch-policy
on: [push, schedule]
jobs:
policy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npx branchpol check --format githubAny other CI: parse --format json's .summary.errors, or trust the exit code.
Need branchpol across an organization rather than a repo? Branchpol Pro ($9/month) adds org-wide scheduled scans with drift alerts, a policy dashboard per repository, Slack/email notifications when protection rules change or get deleted, and ready-to-apply GitHub ruleset templates generated from your config. License via Gumroad — link placeholder.
The CLI itself is free and MIT-licensed forever.
npm test
node bin/branchpol.js helpMIT — see LICENSE.
Part of the stealth-alpha toolkit — eight zero-dependency CLIs for release automation, agent security, and repo hygiene.
{ "project": "my-project", "remote": "origin", "defaultBranch": "main", "naming": { // Branch must match at least ONE require pattern (default branch exempt). "require": ["^(feat|fix|chore|docs|refactor|perf|test|build|ci|release|hotfix)/.+"], // Branch must match NONE of these. "(?i)" prefix = case-insensitive. "forbid": ["^master$", "^wip$", "^tmp$", "^temp$", "(?i)^dont.?touch"], "case": "lower", // or "any" "maxLength": 60 }, "protection": { "required": ["main"], // must exist on the remote "forbidden": [] // must NOT exist on the remote }, "stale": { "enabled": false, // requires --local/--fetch tracking refs "days": 90 } }