Git flow
Trunk-based workflow — branches, PRs, squash merge, drift, cleanup.
Model
main is the only long-lived branch. Every bet is a short branch → PR → Squash and merge → auto-delete.
main ──●──●──●──●──►
feat/foo ──●──● → PR → squash → delete
No sprint/*, worktree-*, or long-lived side branches.
Branch names
Match Conventional Commits types:
| Prefix | Use |
|---|---|
feat/ | new behavior |
fix/ | bugfix |
docs/ | specs, playbook, build queue |
chore/ | tooling, hooks, deps |
refactor/ | no user-visible change |
Example: feat/insert-library-qa, fix/suggested-strip-spinner
Session loop
Start
git checkout main && git pull
npm run git:drift
Continue (prior close exists): paste ## nertia session handoff block — or architect boots docs/superpowers/handoffs/LATEST.md. See Playbook.
Work
git checkout -b feat/short-name
# … commits (Conventional Commits — see caveman-commit skill) …
git push -u origin feat/short-name
Or one command after commits:
npm run ship # push → open PR → babysit Vercel/CI → squash merge
Auto ship (hooks)
| Hook | Behavior |
|---|---|
pre-commit on main | auto-creates cursor/* branch so commits never land on main |
post-push on feature branch | background babysit: open PR if missing, sync main, wait for Vercel green, squash merge |
Cursor postToolUse after git push | agent gets babysit context if checks fail |
Bypass babysit: SKIP_SHIP_BABYSIT=1 git push. Watch only (no auto-merge): SHIP_NO_AUTO_MERGE=1 git push. Babysit log: .git/ship-babysit.log.
Merge
- PR not behind
main(ship script updates branch automatically) - Vercel check green (ship script waits, then squash merges)
- Squash and merge only
Close (I'll be back)
git checkout main && git pull
npm run git:cleanup-branches
Handoff — npm run session:handoff → docs/superpowers/handoffs/LATEST.md + paste block in chat. See Playbook.
GitHub settings (ps2pdx/nertia)
Configured on the repo:
| Setting | Value |
|---|---|
| Default branch | main |
| Merge commits | off |
| Squash merging | on (default message: PR title + description) |
| Rebase merging | off |
| Suggest updating PR branches | on |
| Auto-delete head branches | on |
Ruleset main | Active — PR required, squash only, Vercel required, up-to-date required, block force push, restrict deletions |
Private repo note: GitHub rulesets on this private repo require Team/Pro (or public repo) before server-side enforcement. Until then, local hooks + habits enforce trunk flow.
Drift
| Drift | Signal | Fix |
|---|---|---|
Branch behind main | PR banner “N commits behind” | Update branch on PR |
Local main stale | git:drift shows behind origin | git pull |
| Branch pile | many locals / remotes | npm run git:cleanup-branches |
| Squash-merged branch still exists | git branch --merged misses it | GitHub auto-delete (squash ≠ merged to git) |
npm run git:drift # snapshot
npm run git:cleanup-branches # prune merged locals + worktrees
npm run git:cleanup-branches -- --dry-run
npm run git:cleanup-branches -- --remote # merged remotes (slow if many)
Auto cleanup also runs on Cursor sessionEnd and after merges on main (post-merge hook).
Local hooks
| Hook | Behavior |
|---|---|
pre-commit on main | auto-creates cursor/* feature branch |
pre-push | blocks direct push to main; skips verify on branch-delete pushes |
pre-push | runs npm run verify on feature-branch pushes |
post-push on feature branch | npm run ship:babysit -- --detach (PR + Vercel + squash merge) |
post-merge on main | runs git:cleanup-branches |
Emergency bypass: git push --no-verify (use rarely).
Merge methods (why squash only)
| Method | Lands on main |
|---|---|
| Squash | one commit per PR ✓ |
| Rebase | every branch commit, replayed |
| Merge commit | branch fork preserved in history |
Squash + auto-delete keeps history linear and kills branch pile-up. Squash merges do not make the old branch tip an ancestor of main — that is why auto-delete matters.