Files
ci/opencode/ci/references/branch-strategy.md
T
Jon Chery e31afe3b59 docs(rebrand): rename & rebrand CI → CIAgent across all documentation, templates, and scripts
- README.md: title, project name, CLI commands, .ci/ → .ciagent/, ci-files → ciagent-files, CI Modification → CIAgent Modification
- AGENTS.md: title, project name, architecture tree, agent count (18→19), test count (25→31 suites, 218→370 tests), version (0.4.0→0.6.0), ci-files → ciagent-files, CIConfig → CIAgentConfig, CiMetadata → CIAgentMetadata, .ci/ → .ciagent/
- templates/DECISIONS.md: .ci/audit/ → .ciagent/audit/, ci audit → ciagent audit
- scripts/postinstall.js: CI postinstall → CIAgent postinstall
- scripts/install.sh: CI → CIAgent, ci-init → ciagent-init, INSTALL COMPLETE banner
- opencode/ci/workflows/*.md (11 files): .ci/ → .ciagent/, CI → CIAgent project name, ci-command → ciagent-command usage lines
- opencode/ci/references/*.md (5 files): .ci/ → .ciagent/, CI → CIAgent project name, ci-files → ciagent-files references
- opencode/ci/contexts/*.md (3 files): .ci/ → .ciagent/, CI → CIAgent project name
- opencode/agents/ci-*.md (18 files): .ci/ → .ciagent/, CI → CIAgent project name
- opencode/command/ci-*.md (11 files): CI → CIAgent project name

Preserved: ---ci---/---/ci--- markers, opencode/ci/ dir paths, ci-*.md filenames, ci listProjects()/ci setActiveProject() API names, repo URLs

---ci---
phase: 1
milestone: v0.6
plan: 01-01
task: 01-01-01
status: execute
---/ci---
2026-05-29 17:58:48 +00:00

8.3 KiB
Raw Blame History

<branch_strategy>

Canonical branch naming and lifecycle conventions for CIAgent. Branches encode project structure — merged branches indicate completed work, active branches indicate work in progress.


Branch Types

Phase Branches

Format: phase/NN-slug

Part Convention
NN Zero-padded phase number (01, 02, ..., 12)
slug Lowercase, hyphenated phase name

Examples:

  • phase/01-git-native-architecture
  • phase/02-opencode-integration
  • phase/03-agent-implementations

Lifecycle:

  1. Created at phase start by GitBranch.createPhaseBranch()
  2. All task commits for the phase land on this branch
  3. Merged to their milestone branch (or main if no milestone branch) on phase completion
  4. Merged = phase complete, active = phase in progress, absent = not started

Milestone Branches

Format: milestone/vX.X-slug

Part Convention
vX.X Semver milestone version
slug Lowercase, hyphenated milestone name

Examples:

  • milestone/v0.2-git-native
  • milestone/v1.0-mvp

Lifecycle:

  1. Created at first phase of milestone by GitBranch.createMilestoneBranch()
  2. Spans multiple phases within the same milestone
  3. All phase branches merge into this branch on completion
  4. Merged to main on milestone completion
  5. Merged = milestone complete, active = milestone in progress

Hotfix Branches

Format: hotfix/description

Created for urgent fixes outside the normal phase flow. Merged directly to main (exception to hierarchy).

Branch Hierarchy (Enforced)

main ─── milestone/vX.X-slug ─── phase/NN-slug

Rules:
- Phase branches MUST merge into their milestone branch first
- Milestone branches merge into main only after all phase branches are merged
- If no milestone branch exists, phases may merge directly to main
- Hotfix branches merge directly to main (exception)

Validation is enforced in GitBranch.mergePhaseBranch() and createShipCommand():

  • Phase → main: rejected if milestone branch exists for this milestone
  • Phase → milestone: allowed
  • Milestone → main: allowed only after all phase branches are merged
  • Hotfix → main: allowed

Branch Status Inference

The GitBranch class and GitContext class determine status from the branch list:

const branches = gitContext.getBranches();
// Phase branches: type = "phase", phaseNumber, merged boolean
// Milestone branches: type = "milestone", milestone string, merged boolean
Branch State Meaning
Branch exists, not merged Phase/milestone is active (in progress)
Branch exists, merged Phase/milestone is complete
Branch does not exist Phase/milestone has not started

Merge Strategy

Default: squash merge.

Phase branches squash-merge into their milestone branch. Milestone branches squash-merge into main. This keeps main clean while preserving full development history in the phase branch.

// Phase → milestone (enforced when milestone branch exists)
gitBranch.mergePhaseBranch("phase/01-git-native-architecture", "milestone/v0.5-honest-baseline", true);

// Milestone → main (after all phases merged)
gitBranch.mergeMilestoneBranch("milestone/v0.5-honest-baseline", "main", true);

Phase branches can be deleted after merge if desired.

Versioning and Releases

Every merge to main creates a release. No exceptions. Versioning follows a 3-tier model based on milestone type:

3-Tier Versioning Model

Milestone Type Condition Phase release Milestone release
NFR All phases: fix/chore/docs/perf/refactor/test Patch (vX.Y.Z) None
Feature Any phase is feat, no schema break Patch (vX.Y.Z) Minor — vX.(Y+1).0
Schema-breaking Refactor/schema break/new direction Minor — vX.(Y+N).0 per phase Major — v(X+1).0.0

IMPORTANT: Milestone tags are always the NEXT version, never the base:

  • Feature: patches v0.5.1v0.5.5 → milestone tag is v0.6.0 (NOT v0.5.0)
  • Schema-breaking: minors v0.3.0, v0.4.0, v0.5.0 → milestone tag is v1.0.0
  • NFR: no milestone tag — the milestone is implicit from the patch sequence

Determine milestone type via getMilestoneType() which returns "nfr" | "feature" | "schema-breaking".

Phase completion

NFR/Feature (patch release):

git checkout milestone/v0.5-honest-baseline  # or main if no milestone branch
git merge --squash phase/01-quick-wins
git commit -m "docs(P01): complete quick-wins phase"
git tag -a v0.5.1 -m "v0.5.1: quick-wins"
git push origin main --tags
# Create Gitea release for v0.5.1

Phase number within the milestone determines the patch version (1st phase = .1, 2nd phase = .2, etc.)

Schema-breaking (minor release per phase):

git checkout milestone/v0.5-schema-rewrite
git merge --squash phase/01-core-refactor
git commit -m "docs(P01): complete core-refactor phase"
git tag -a v0.5.0 -m "v0.5.0: core-refactor"
git push origin main --tags
# Create Gitea release for v0.5.0

Each schema-breaking phase bumps the minor. 1st phase = next available minor, 2nd = minor+1, etc.

Milestone completion

Feature (minor release):

# All phases already merged into milestone branch
git checkout main
git merge --squash milestone/v0.5-honest-baseline
git commit -m "docs(milestone): complete honest-baseline"
git tag -a v0.6.0 -m "v0.6.0: honest-baseline"  # NEXT minor, NOT v0.5.0
git push origin main --tags
# Create Gitea release for v0.6.0 with full milestone summary

Schema-breaking (major release):

# All phases already merged into milestone branch
git checkout main
git merge --squash milestone/v0.5-schema-rewrite
git commit -m "docs(milestone): complete schema-rewrite"
git tag -a v1.0.0 -m "v1.0.0: schema-rewrite"  # NEXT major
git push origin main --tags
# Create Gitea release for v1.0.0 with full milestone summary

NFR milestones produce no milestone tag. The last phase's patch version is the final release.

Version Validation

Before creating any tag:

  1. Tag must be strictly greater than all existing tags on the same major.minor line
  2. Milestone completion tag must be next minor (feature) or next major (schema-breaking)
  3. NEVER create a tag that is semantically below existing phase tags

Multi-Project Branch Naming

When operating in multi-project mode (.ciagent/config.json has projects[] with length > 0):

Branch Type Format Example
Phase <slug>/phase/NN-slug auth/phase/01-jwt-setup
Milestone <slug>/milestone/vX.X-slug auth/milestone/v0.2-login

Single-project mode keeps the existing phase/NN-slug and milestone/vX.X-slug conventions (no slug prefix).

Phase Discovery

const gitBranch = new GitBranch(projectPath);
const phases = gitBranch.listPhases();
// Returns: PhaseBranchInfo[] with phaseNumber, slug, branchName, status

const milestones = gitBranch.listMilestones();
// Returns: MilestoneBranchInfo[] with version, slug, branchName, status

Branch Creation Rules

  1. Always create phase branches from the current milestone branch (or main if no milestone branch exists)
  2. Never create a branch for a completed phase — it should already be merged
  3. Milestone branches span phases — don't create one per phase
  4. Use GitBranch.createPhaseBranch() to ensure consistent naming
  5. Use GitBranch.createMilestoneBranch() to ensure consistent naming

Working with Phase Branches

# Create a milestone branch first
git checkout main
git checkout -b milestone/v0.5-honest-baseline

# Create a phase branch from the milestone
git checkout -b phase/01-quick-wins

# Commit work with ---ci--- blocks
git commit -m "feat(P01-01-01): implement commit parser

---ci---
phase: 1
milestone: v0.5
plan: 01-01
task: 01-01-01
status: execute
---/ci---"

# Merge phase into milestone on completion
git checkout milestone/v0.5-honest-baseline
git merge --squash phase/01-quick-wins
git commit -m "docs(P01): complete quick-wins phase"
git tag -a v0.5.1 -m "v0.5.1: quick-wins"

# After all phases, merge milestone into main
git checkout main
git merge --squash milestone/v0.5-honest-baseline
git commit -m "docs(milestone): complete honest-baseline"
git tag -a v0.6.0 -m "v0.6.0: honest-baseline"
git push origin main --tags

</branch_strategy>