- 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---
8.3 KiB
<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-architecturephase/02-opencode-integrationphase/03-agent-implementations
Lifecycle:
- Created at phase start by
GitBranch.createPhaseBranch() - All task commits for the phase land on this branch
- Merged to their milestone branch (or main if no milestone branch) on phase completion
- 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-nativemilestone/v1.0-mvp
Lifecycle:
- Created at first phase of milestone by
GitBranch.createMilestoneBranch() - Spans multiple phases within the same milestone
- All phase branches merge into this branch on completion
- Merged to main on milestone completion
- 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.1–v0.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:
- Tag must be strictly greater than all existing tags on the same major.minor line
- Milestone completion tag must be next minor (feature) or next major (schema-breaking)
- 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
- Always create phase branches from the current milestone branch (or main if no milestone branch exists)
- Never create a branch for a completed phase — it should already be merged
- Milestone branches span phases — don't create one per phase
- Use
GitBranch.createPhaseBranch()to ensure consistent naming - 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>