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:

  1. A docs/branching.md you can drop straight into your repo
  2. A GitHub Action that blocks cycle/... PRs going directly into main

1. docs/branching.md – Branch & Flow Rules

Create 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:

Examples:

Agent / wave rules

TDD cycle content

Each cycle implements a complete TDD loop:

  1. RED – Write failing test(s).
  2. GREEN – Implement minimal code to pass tests.
  3. REFACTOR – Clean up design without changing behavior.
  4. REGRESSION – Run relevant regression suites.

Each cycle branch should remain:


Flow of Changes

The flow is one-directional:

1
cycle branches → dev → stage → main → tags

1. Starting work (cycle creation)

  1. Update dev:

    1
    2
    
    git checkout dev
    git pull origin dev
    
  2. Create a cycle branch from dev:

    1
    
    git checkout -b cycle/p<phase>-c<cycle><agent>-<short-kebab-title>
    
  3. Do the TDD cycle work there (often via a worktree).

2. Completing a cycle

  1. Push the cycle branch and open a PR into dev:

  2. 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 main or stage.

3. Promoting devstage (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

4. Promoting stagemain (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

Hotfixes should be rare, but when necessary:

  1. Branch directly from main:

    1
    2
    3
    
    git checkout main
    git pull origin main
    git checkout -b hotfix/short-description
    
  2. Implement fix, open PR into main, and merge.

  3. 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 / stage from lagging behind main on critical fixes.


Worktrees (local workflow)

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

Summary

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

What this does

So if you (or future teammates) accidentally open a PR:

PRs like:

will pass this particular check.

If you ever want to also block cycle/...stage, you can add another similar job that checks for github.base_ref == 'stage'.


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.


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 Fixes

Use fix/... branches when:

Rules:

Example:

1
fix/null-pointer-in-validator

These fixes are later promoted naturally via:

1
dev → stage → main

chore/... — Maintenance & Housekeeping

Use chore/... branches for changes that do not affect runtime behavior, such as:

Rules:

Example:

1
chore/update-eslint-config

hotfix/... — Emergency Production Fixes

Use hotfix/... branches only for urgent issues already affecting production.

Rules (very important):

Example:

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.


Important Invariants

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.


The canonical flow (your three-stage model)

1️⃣ Start from dev

This is your base for all normal work.

1
2
git checkout dev
git pull origin dev

2️⃣ Branch out for a cycle (or fix / chore)

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:


3️⃣ Create PR → merge into dev

Open a PR:

When approved & green:

At this point:


4️⃣ Create PR from dev → merge into stage

When you decide:

“This set of changes is ready for staging / release-candidate testing”

You open a promotion PR:

This PR:

When it passes:

Now:


5️⃣ Merge stagemain (release)

When staging looks good:

Open another promotion PR:

After checks pass:

1
2
git tag vX.Y.Z
git push origin vX.Y.Z

Now:


The flow in one line

1
2
3
4
5
6
7
8
9
cycle / fix / chore
        ↓
       dev
        ↓
      stage
        ↓
      main
        ↓
      tag

Two important clarifications (worth locking in)

🔹 You do not merge cycle branches directly to stage

Only dev goes to stage. Your fail-action enforces this.

🔹 You do not merge cycle branches directly to main

Only stage goes to main. Branch protection + actions enforce this.


Hotfix exception (for completeness)

The only time this flow changes:

1
2
3
4
5
6
7
hotfix/... (from main)
        ↓
       main
        ↓
       dev
        ↓
      stage

Hotfixes:


Sanity check (your understanding)

start from dev, branch out for cycle, create pr (merge to dev), create pr for dev (merge to stage), then merge stage to main