Trunk-based

View as markdown

Trunk-based development keeps a single always-releasable trunk (main) and merges small, short-lived branches into it as fast as they go green. There's no long-lived integration branch and no drawn-out review column. Work goes from in progress to merged the moment checks pass.

In Shelbi this is the feature-branch model with the Review handoff collapsed: the merge action moves onto the InProgress → Done edge. Because Zen Mode's confidence bar is action-based (it gates any transition whose actions include merge, regardless of which statuses sit on either side), that collapsed edge trips exactly the same high-confidence probe the review-gated model does. Trunk-based is where Zen auto-merge earns its keep: the whole point is to land green work without a manual accept step.

The board

you@hub — api
Tasks · api 9 total Workflow: All ▾ Workspace: All ▾ BACKLOG (2) TO DO (2) IN PROGRESS (2) DONE (3) Split OTel spans by Add ratelimit to API Backfill order_state Retry webhook tenant index dead-letters ⎇ shelbi/backfill-ord… Wire webhook retries Sunset legacy /v1 Trim vendor bundle Redis cache for API size /profile ⎇ shelbi/trim-vendor-… Cache warm-up on cold start
h/l col j/k row open n new f filter r refresh

Four columns, no Review. A task branch is cut off main at In Progress and squash-merged straight back into main at Done, the moment checks pass and Zen clears the bar.

The workflow

Trunk-based reuses the shipped status catalog (workflows/statuses.yaml). It just leaves the review status out of the flow. Identity (name, category) stays in the catalog; the workflow references statuses by id and adds only owner and agent::

# workflows/trunk.yaml — reference-only (identity lives in statuses.yaml)
name: trunk
description: Trunk-based — short-lived branches, merge to main on green.
 
git:
  base_branch: main
  merge_strategy: squash               # one squashed commit per task on trunk
 
statuses:
  - { id: backlog,     owner: user,  agent: orchestrator }
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: done,        owner: user }
  - { id: canceled,    owner: user }
 
initial_status: backlog
 
transitions:
  - from: in-progress
    to: done
    actions: [push_branch, open_pr, merge, delete_branch]
 
  - from: in-progress
    to: canceled
    actions: [close_pr, delete_branch]
 
zen:
  checks:
    local:
      - cargo build --workspace
      - cargo test --workspace

The single in-progress → done transition does the whole cycle: push the short branch, open a PR into main, squash-merge it, and delete the branch. There is no handoff-category status because there's no separate review stage. The zen.checks that run before the merge bar are the gate.

Orchestrator adjustments

Trunk-based only pays off with auto-merge on, so the orchestrator prompt's Zen judgment does the accepting a human would otherwise do at a Review column. Two tweaks are worth encoding:

## Zen Mode — trunk-based
 
### Merge conditions
 
A task on the `InProgress → Done` edge may auto-merge when **all** hold:
 
1. The workspace has written its review marker (work is complete).
2. `shelbi zen probe` reports every local check green.
3. The diff touches no path in the danger set (migrations, auth, CI).
 
Otherwise, hold the card in `InProgress` and surface it: "`<task>` is
green but touches `<danger-path>` — merge to main?" Trunk stays
releasable because nothing lands without a green probe.

Keep branches short. If a card sits in InProgress long enough to drift from main, prefer restacking or re-cutting it over a big catch-up merge. That's the discipline trunk-based trades the review column for.

See also