Skip to content

Trunk-Based Development

Short-lived branches, continuous integration.

Trunk-based development is the corpus's default branching pattern. Branches live for hours, not weeks. Integration happens continuously into main. Long-lived feature branches are an anti-pattern that produces merge conflicts, hidden divergence, and integration drama at exactly the wrong time.

The pattern

  • One trunk: main. It is always shippable.
  • Branches are named: feat/grading-flow-hebrew-names-001. Slug includes the story or epic.
  • Branches live <2 days. Anything longer goes back behind a flag and merges to trunk in pieces.
  • Every commit on main runs through the pipeline (Part 4) and stays releasable.

Why short-lived

A branch that lives a week becomes a thing the team manages instead of a thing the team ships. Conflicts compound. Code reviews grow large. Integration testing happens against an old version of trunk. By the time the branch is merged, the work has to be re-validated against current trunk, which has moved on.

A branch that lives two hours is a small unit of meaning. The PR is small. The review is fast. The integration is immediate. Trunk has not moved meaningfully. The work flows.

How big features fit

The honest question: if branches are short-lived, how do we land big work?

Two answers.

  1. Slice the work behind a feature flag. New behavior is wrapped in if (flag('hebrew-names')). The flag starts off. Many small commits land on main, none of them visible to users. Eventually the work is complete; the flag is turned on. See Part 3 — Feature Flags.
  2. Land scaffolding first. Refactors, schema additions, and stub implementations land before the user-visible logic. By the time the visible work begins, the foundation is on trunk and tested.

The combination — flagged behavior + foundation-first commits — is what makes trunk-based development feasible for non-trivial features.

Conventional commits

Every commit on main follows the conventional commits pattern.

text
type(scope): description — STORY-ref

feat(grading): native Hebrew name support in submission view — GRD-142
fix(grading): handle NFD/NFC normalisation for student names — GRD-142
docs(adr): ADR-014 add unicode normalisation rationale — GRD-142
chore(deps): bump unicode-normalize to 1.4.2 — GRD-142
test(grading): cover RTL+LTR mixed names — GRD-142

The format is not aesthetic. It is functional.

  • Typefeat, fix, docs, chore, refactor, test, perf, build, ci, style. Determines whether the commit shows up in the changelog.
  • Scope — the area of the codebase. Helps reviewers and the changelog.
  • Description — what changed, in present tense. Imperative.
  • Story reference — the Volume III story slug or ID. Connects the code to the brief.

Changelogs (Part 8 — Changelog Generation) are auto-built from these. A commit that does not follow the pattern is a commit the chain cannot read.

Code review

Every PR reviewed by at least one other engineer. The review reads, in this order:

  1. The brief and story — what is this for?
  2. The diff — does it deliver what the story says, in domain language?
  3. The tests — are the Gherkin scenarios from amigos covered?
  4. The ADRs touched, if any — are constraints respected?
  5. The pipeline output — is the build green?

Approval is not I have read this and the code looks fine. Approval is this is the change the brief described, and it does what amigos predicted.

The reviewer is not the gatekeeper of code style. Style is the linter's job. The reviewer is the chain's last reader before the trunk receives the change.

The merge moment

Merge to main happens when:

  • Pipeline is green.
  • Reviewer has approved.
  • Story's Gherkin scenarios have been verified by QA on the branch (pre-merge QA — Part 5).
  • The flag is in place; the change is invisible to users.

After merge:

  • The pipeline runs against trunk.
  • The change is deployed to a staging environment within minutes.
  • The change is one switch away from production.

Branches that live too long — the recovery

Sometimes a branch grows. The team realises it is now four days old, three thousand lines, and merges are getting hard. The corpus pattern is to recover, not push through:

  1. Stop adding to the branch. No more features on this branch.
  2. Identify the foundation pieces. Schema additions, refactors, scaffolding. Cherry-pick or split into small PRs and land them.
  3. Wrap the rest behind the flag. Land the user-visible work in small PRs after the foundation.
  4. Postmortem the branch. What signal was missed two days ago that would have told the team to slice differently?

The corpus rule: a branch over five days old is a postmortem candidate, not a merge candidate.

Part 3 — Feature Flags →

200apps · How We Work · NWIRE