Problem
borromeanRings already follows trunk-based development in practice — short-lived feature branches → PR → gated merge (merge.sh --delete-branch) → delete — so main only ever receives PR merges (0 direct commits, verified via the GitHub activity log). But the policy is implicit, not enforced: nothing actually prevents a direct commit/push to main. Governed projects inherit the same gap, and at a glance they look "main-only" because feature branches are auto-deleted after merge.
Decision: standardize on trunk-based (chosen over GitFlow — for an always-green gated project a long-lived develop branch adds ceremony without payoff) and make the branching policy a declared, enforced borromeanRings capability.
Scope
- Declare protected branches in the spine (e.g.
[branching] protected = ["main"], default ["main"]).
- Preventive guard (PreToolUse): block
git commit when HEAD is on a protected branch, and block direct git push to a protected branch — with a clear message ("create a feature branch; land via PR + gate"). Pure logic unit-tested; fail-open on guard error (server-side protection is the backstop).
- Works for governed projects (reads policy from the project's
borromeanrings.toml, reference-mode safe).
- Document the model (trunk-based) and how it composes with the gated merge.
Out of scope / complementary
Acceptance criteria
Relates to: #60 (server-side branch protection), and the existing PreToolUse guard (.claude/hooks/pre_bash_guard.sh) + gated merge flow (merge.sh).
Problem
borromeanRings already follows trunk-based development in practice — short-lived feature branches → PR → gated merge (
merge.sh --delete-branch) → delete — somainonly ever receives PR merges (0 direct commits, verified via the GitHub activity log). But the policy is implicit, not enforced: nothing actually prevents a direct commit/push tomain. Governed projects inherit the same gap, and at a glance they look "main-only" because feature branches are auto-deleted after merge.Decision: standardize on trunk-based (chosen over GitFlow — for an always-green gated project a long-lived
developbranch adds ceremony without payoff) and make the branching policy a declared, enforced borromeanRings capability.Scope
[branching] protected = ["main"], default["main"]).git commitwhen HEAD is on a protected branch, and block directgit pushto a protected branch — with a clear message ("create a feature branch; land via PR + gate"). Pure logic unit-tested; fail-open on guard error (server-side protection is the backstop).borromeanrings.toml, reference-mode safe).Out of scope / complementary
main(require the gate check, block direct pushes) is tracked separately in Branch protection on main (require the gate) #60 — this issue is the local, harness-enforced, substrate-neutral layer; Branch protection on main (require the gate) #60 is the GitHub-side enforcement. Both together = defense in depth.Acceptance criteria
[branching].protecteddeclared in the spine (default["main"]); empty ⇒ enforcement off (opt-in, consistent with[git]/[layout]).git pushto a protected branch.Relates to: #60 (server-side branch protection), and the existing PreToolUse guard (
.claude/hooks/pre_bash_guard.sh) + gated merge flow (merge.sh).