---
title: Branching
description: Three long-lived branches, forward-only merges, the drift guard that exists because of a real incident, and why CI's lint and test gates are muted.
---

Both repositories use the same model: **`dev` → `staging` → `main`**, always merged forward, never cherry-picked backward.

## Rules

- Work branches off `dev`. Pull requests target `dev`.
- Branch names are `tal-<id>-slug`, lowercase — this auto-links to Linear. The old `@feat/…` convention is retired.
- Commits are conventional: `feat:` / `fix:` / `chore:` / `docs:` / `test:`.
- Whoever lands a hotfix on `main` merges it forward to `staging` and `dev` the same day.

## The drift guard

`branch-drift.yml` runs on a weekday schedule and fails if `main` is ahead of `staging`, or if `staging` is ahead of `dev`.

:::danger[This guardrail is not optional]
An unmerged hotfix is exactly how the old `develop` branches got to **17 commits behind `main` with 0 of their own** in the backend, and 52 behind in the frontend, before both were deleted. The check exists to make that state loud instead of silent.
:::

:::warning[The drift check is not running yet]
Scheduled workflows fire only from a repository's **default branch**, which is `main`, and `branch-drift.yml` is not on `main` yet. As of 2026-09-09 nothing is checking for drift. Until `dev` is promoted all the way to `main`, forward merges are held together by discipline alone — the exact condition that produced the `develop` incident above.
:::

## CI

Both repos run `.github/workflows/ci.yml` on pull requests and on pushes to `dev`, `staging`, and `main`.

| Check | Blocking | Notes |
| --- | --- | --- |
| `npm run build` | **yes** | the only gate |
| lint | no | `continue-on-error` |
| tests (backend) | no | `continue-on-error` |

The report-only checks are not aspirational — they are suppressed because of a real backlog: roughly **99 pre-existing ESLint errors** in the backend and roughly **64** in the frontend, counted 2026-08-13, plus 6 auto-generated `should be defined` Nest CLI stub specs that fail on DI resolution (TAL-94).

Clear those backlogs, then drop `continue-on-error`. Turning the gates on first makes every PR red. TAL-94 covers the six stub specs; the two ESLint backlogs have no ticket and no owner.

:::warning[The backend's `npm run lint` rewrites your files]
The backend's script runs `eslint --fix`. CI calls `eslint` directly. Running the npm script locally to "check" lint will silently rewrite your working tree.
:::
