Nice, this is the fun part: we’re going to codify your habits so future-you and future-team don’t have to re-remember all this from scratch.
I’ll give you:
docs/branching.md you can drop straight into your repocycle/... PRs going directly into maindocs/branching.md – Branch & Flow RulesCreate a file at: docs/branching.md with something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Branching & Flow Rules
This repository uses a three-stage branching model plus one-off TDD cycle branches.
The goals:
- Keep `main` stable and releasable.
- Use `dev` as the integration branch for active work.
- Use `stage` as the pre-release / staging branch.
- Use small, TDD-focused **cycle branches** for all actual work.
---
## Branch Roles
### `main` (stable / production)
- Always stable and deployable.
- Release tags are created from `main`.
- No direct development happens on `main`.
- Only receives changes via merges from `stage` (or explicit hotfixes, see below).
### `stage` (pre-release)
- Represents the current **release candidate**.
- Used for full regression/testing before promoting to `main`.
- Receives changes only from `dev`.
- What we’d deploy to a **staging environment** (when one exists).
### `dev` (integration)
- Base branch for all new work.
- All TDD cycle branches are created from `dev`.
- First integration point for features, fixes, and refactors.
- May be less stable than `stage` / `main`, but should at least build and pass basic tests.
---
## TDD Cycle Branches
All actual work is done in **cycle branches**, each representing a single TDD cycle.
### Naming convention
Each cycle branch must follow:
```text
cycle/p<phase>-c<cycle><agent>-<short-kebab-title>
Where:
p<phase> – phase number (e.g. p1, p2, …).c<cycle> – cycle number within that phase (e.g. c1, c2, …).<agent> – agent identifier; the length encodes wave order:
A, B)AA, BB)AAA, BBB)<short-kebab-title> – short description, all lowercase, - separated.Examples:
cycle/p1-c1A-happy-path-validationcycle/p1-c2B-invalid-input-handlingcycle/p1-c4AA-cross-field-validationcycle/p1-c5AAA-metrics-instrumentationA → AA → AAA.Longer-agent cycles must depend only on shorter-agent cycles:
Each cycle implements a complete TDD loop:
Each cycle branch should remain:
Small and reviewable:
MECE with other cycles (no overlapping scope).
The flow is one-directional:
1
cycle branches → dev → stage → main → tags
Update dev:
1
2
git checkout dev
git pull origin dev
Create a cycle branch from dev:
1
git checkout -b cycle/p<phase>-c<cycle><agent>-<short-kebab-title>
Do the TDD cycle work there (often via a worktree).
Push the cycle branch and open a PR into dev:
devcycle/pX-cY<agent>-...When the PR is approved and tests pass, merge into dev and delete the cycle branch.
Rule: Cycle branches must never be merged directly into
mainorstage.
dev → stage (staging / pre-release)Periodically (e.g. after a set of cycles are merged and tested on dev):
1
2
3
4
git checkout stage
git pull origin stage
git merge dev # or fast-forward if possible
git push origin stage
stage now represents the new release candidate.stage.stage → main (release)When staging passes and you’re ready to release:
1
2
3
4
git checkout main
git pull origin main
git merge stage
git push origin main
Create a release tag from main:
1
2
git tag vX.Y.Z
git push origin vX.Y.Z
Hotfixes should be rare, but when necessary:
Branch directly from main:
1
2
3
git checkout main
git pull origin main
git checkout -b hotfix/short-description
Implement fix, open PR into main, and merge.
Immediately propagate the fix forward:
1
2
3
4
5
6
7
8
9
git checkout dev
git pull origin dev
git merge main
git push origin dev
git checkout stage
git pull origin stage
git merge main
git push origin stage
This prevents
dev/stagefrom lagging behindmainon critical fixes.
Locally, multiple TDD cycles can run in parallel using git worktree.
Example for Phase 1:
1
2
3
4
5
6
# From the main repo root:
git fetch origin
git worktree add ../<repo>-p1-c1A origin/cycle/p1-c1A-happy-path-validation
git worktree add ../<repo>-p1-c2B origin/cycle/p1-c2B-invalid-input-handling
git worktree add ../<repo>-p1-c3C origin/cycle/p1-c3C-boundary-conditions
When a cycle is finished and merged into dev:
1
git worktree remove ../<repo>-p1-c1A
dev.dev integrates work; stage stabilizes it; main releases it.main or stage.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
You can tweak wording/project name, but that skeleton gives you a solid, team-ready spec.
---
## 2. GitHub Action: Block `cycle/...` → `main` PRs
Now let’s enforce part of this with automation: we’ll **fail any PR that tries to merge a `cycle/...` branch into `main` directly**.
Create a workflow at: **`.github/workflows/protect-main-from-cycle.yml`**
```yaml
name: Block cycle branches from targeting main
on:
pull_request:
types: [opened, edited, synchronize, reopened]
jobs:
prevent-cycle-to-main:
runs-on: ubuntu-latest
# Only run this job if the PR base is main AND the head branch starts with "cycle/"
if: github.base_ref == 'main' && startsWith(github.head_ref, 'cycle/')
steps:
- name: Fail PR targeting main from cycle branch
run: |
echo "❌ Cycle branches (head: '${GITHUB_HEAD_REF}') must target 'dev', not 'main'."
echo "Please change the base branch of this PR from 'main' to 'dev'."
exit 1
If:
base_ref (target) == main andhead_ref (source) starts with cycle/So if you (or future teammates) accidentally open a PR:
cycle/p1-c1A-... → main
→ the Action will block it and tell you to retarget to dev.PRs like:
cycle/... → dev ✅ (allowed)dev → stage ✅stage → main ✅will pass this particular check.
If you ever want to also block
cycle/...→stage, you can add another similar job that checks forgithub.base_ref == 'stage'.
This repository uses explicit branch prefixes to communicate intent, scope, and where changes are allowed to flow.
Choosing the correct branch type is important because automation and protection rules depend on it.
Absolutely — here’s a clean, paste-ready section you can drop directly into docs/branching.md. It aligns perfectly with everything you’ve already put in place and removes all ambiguity around cycle/, fix/, chore/, and hotfix/.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
## Branch Type Summary
This repository uses explicit branch prefixes to communicate **intent**, **scope**, and **where changes are allowed to flow**.
Choosing the correct branch type is important because automation and protection rules depend on it.
---
### Branch Types at a Glance
| Branch type | Base branch | Merge target | When to use |
|------------|------------|--------------|-------------|
| `cycle/...` | `dev` | `dev` | Planned TDD work (one complete red → green → refactor → regression cycle) |
| `fix/...` | `dev` | `dev` | Unplanned, non-emergency bug fixes discovered during development |
| `chore/...` | `dev` | `dev` | Maintenance tasks with no runtime behavior change (tooling, formatting, docs, CI) |
| `hotfix/...` | `main` | `main` | Emergency production fixes that must be released immediately |
---
### `cycle/...` — Planned TDD Cycles
Use `cycle/...` branches for **all planned development work**.
- Each branch represents **one TDD cycle**.
- Branch names encode:
- phase,
- cycle number,
- agent id (which implies wave order).
- All cycle branches:
- start from `dev`,
- merge into `dev`,
- flow through `stage` and then `main`.
Example:
```text
cycle/p1-c3AA-cross-field-validation
fix/... — Unplanned Development FixesUse fix/... branches when:
Rules:
devdevExample:
1
fix/null-pointer-in-validator
These fixes are later promoted naturally via:
1
dev → stage → main
chore/... — Maintenance & HousekeepingUse chore/... branches for changes that do not affect runtime behavior, such as:
Rules:
devdevExample:
1
chore/update-eslint-config
hotfix/... — Emergency Production FixesUse hotfix/... branches only for urgent issues already affecting production.
Rules (very important):
mainmainExample:
1
hotfix/fix-crash-on-startup
After merging a hotfix into main, the fix must be propagated forward to keep branches consistent:
1
2
main → dev
main → stage
This ensures development and staging do not drift from production.
cycle/..., fix/..., and chore/... branches never merge directly into stage or main.hotfix/... branches never merge into dev or stage.All non-hotfix work flows strictly:
1
cycle / fix / chore → dev → stage → main
All hotfix work flows:
1
hotfix → main → dev → stage
These rules are enforced by branch protection and GitHub Actions wherever possible.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
### Why this section matters
This table + explanation does three important things:
1. **Prevents accidental misuse**
You won’t second-guess where a branch should go during a stressful moment.
2. **Keeps automation predictable**
Your existing Actions (blocking merges, labeling PRs, wave checks) depend on branch prefixes meaning something.
3. **Scales cleanly to a team**
A new teammate can read this once and immediately understand:
- which branch to start from,
- where their PR should go,
- and why CI might block them otherwise.
If you want, next we can:
- Update the **dev-enforcement Action** to explicitly allow `fix/*` and `chore/*` (if you haven’t already), or
- Add **auto-labeling** for `fix` and `chore` PRs as well, so they’re just as visible in your dashboards as cycles.
Yes — you’ve got it. 👍 Let me restate it precisely and call out the couple of nuances so there’s zero ambiguity.
devThis is your base for all normal work.
1
2
git checkout dev
git pull origin dev
Create one branch per unit of work (cycle, fix, or chore):
1
2
3
4
5
git checkout -b cycle/p1-c1A-happy-path-validation
# or
git checkout -b fix/null-pointer-in-validator
# or
git checkout -b chore/update-eslint-config
All of these:
devdevOpen a PR:
devcycle/... (or fix/..., chore/...)When approved & green:
devAt this point:
dev contains the integrated workdev → merge into stageWhen you decide:
“This set of changes is ready for staging / release-candidate testing”
You open a promotion PR:
stagedevThis PR:
Runs:
devWhen it passes:
dev → stageNow:
stage = release candidatestage → main (release)When staging looks good:
Open another promotion PR:
mainstageAfter checks pass:
main1
2
git tag vX.Y.Z
git push origin vX.Y.Z
Now:
main reflects what’s released1
2
3
4
5
6
7
8
9
cycle / fix / chore
↓
dev
↓
stage
↓
main
↓
tag
stageOnly dev goes to stage.
Your fail-action enforces this.
mainOnly stage goes to main.
Branch protection + actions enforce this.
The only time this flow changes:
1
2
3
4
5
6
7
hotfix/... (from main)
↓
main
↓
dev
↓
stage
Hotfixes:
mainmainstart from dev, branch out for cycle, create pr (merge to dev), create pr for dev (merge to stage), then merge stage to main