Phased Versioning koseka.net

Phased Versioning

Phased Versioning is a versioning system — a standalone specification, the way Semantic Versioning has its own official document. It defines how software is versioned, how versions map onto Git, and what that implies for commits and changelogs.

Projects that follow it state: "This project uses Phased Versioning."

Summary

A full version has the form:

vN-phase.i

where N is the generation number, starting at 1 (vN alone is the base version), phase is the phase name, and i is the revision number. Given a full version: increment the generation when you make breaking changes, open a new phase when backward-compatible functionality significant enough to deserve recognition has accumulated, and increment the revision for all other backward-compatible changes — fixes, improvements, and small features included.

Example timeline: v1-alameda.0 → v1-bravo.29 → v1-cyrus.5 → v2-atlas.0 → …

Motivation

Phased Versioning is best understood as Semantic Versioning with the MINOR replaced by a named phase, and a Git-native distribution model. A versioning system must speak to machines and humans, and humans are not good with numbers: subjectively, the jump from 7 to 8 feels bigger than the jump from 29 to 30, even though programmatically each step is one feature release. Named phases fix this — going from alameda to bravo communicates a real, memorable step in a way that going from 13 to 14 never does — while machines still get a strictly ordered sequence.

Deliberate differences from Semantic Versioning:

Specification

  1. Software using Phased Versioning is distributed as a Git repository, by branch — the branch is the version selector, not a package registry entry.
  2. Each generation vN is a version branch. Full versions are tags on that branch, with at most one release tag per commit.
  3. Breaking changes are any modification to a software component — whether syntactic, semantic, or environmental — that requires the consumers of that component (be they other programs, developers, or automated scripts) to fundamentally alter their usage, code, or environment to restore previous functionality. Fixing bugs, adding features, and refactoring can all potentially introduce breaks — or none at all; a change is judged by its effect on consumers, not by its intent.
  4. A version branch must never receive breaking changes. Importing any vN branch therefore guarantees stability of the contract: fixes and features may arrive, breaks never do. Breaking changes live only on the dev branch.
  5. A new phase (vN-phase.0) is opened when the developer judges that backward-compatible functionality significant enough to deserve recognition has accumulated. Phases are a communication device — they tell the audience "here's something worth looking at."
  6. A revision bump (vN-phase.i+1) is made for backward-compatible changes that do not warrant a phase: fixes, improvements, and small features. Whether a feature warrants a phase is the developer's judgment, not a mechanical rule.
  7. A new generation (branch vN+1) is cut when breaking changes accumulated on dev are ready to ship.

Distribution by Git branch (rule 1) is the convention, not a prerequisite. Phased Versioning can version anything — a library, an application, an AI model — and a project may distribute however it likes (installers, package registries, GitHub releases, downloadable weights), as long as the published versions follow the format and the stability rules. When distribution happens through releases rather than branch imports, each release simply carries a full version as its name; the branch and tag rules still govern the repository itself.

Phase names

Phase names are arbitrary lowercase words ([a-z] only) that follow looped alphabetic progression: the first phase of a generation starts with a, each subsequent phase's first letter is the next letter of the alphabet, and after z the loop wraps back to a. So alameda → bravo → cyrus is a valid progression, and — perhaps surprisingly — the classic alpha → beta → gamma is not (b → g is an illegal jump). Phase names carry no fixed semantics: they are ordered, human-memorable checkpoints, and what actually changed is told by the commits and the changelog.

Writing versions

A version is always written with a lowercase v — in code, prose, headings, and even at the start of a sentence. There are exactly three valid ways to write a version, differing only in how much of the full form is kept:

  1. Full versionvN-phase.i (e.g. v1-cyrus.5): generation, phase name, and revision.
  2. Phase versionvN-phase (e.g. v1-cyrus): the revision is dropped; refers to a phase as a whole.
  3. Base versionvN (e.g. v1): only the generation; refers to a generation as a whole.

The two short forms are legal abbreviations of a project's actual version for when the finer detail is irrelevant. Anything else is invalid — a capital V, a SemVer-style v1.0, a phase name without its generation, or a bare revision.

Branches

The flow: all work lands on dev. As long as dev has accumulated no breaking changes, it is regularly merged into the most recent version branch, where phases and revisions are tagged. Once a breaking change is committed on dev, the current generation is closed to new features, and the next merge from dev cuts a new version branch instead.

Maintain at most two branches: dev and the most recent version branch. Older generations may occasionally receive a patch, but effort concentrates on the newest generation.

Every repository's README documents its branches in a table (branch name + description), so stability is readable at a glance — and enforced by which branch you import.

Commits

Phased Versioning does not define its own commit format: projects use Conventional Commits (type(scope): description, with ! or a BREAKING CHANGE: footer marking breaking changes). AI models and tooling know it natively, and it carries exactly the impact signal the versioning rules need.

Every commit carries a typed prefix — merges included, for consistency: merging the development branch into a version branch is written as merge: `dev` -> `v1` (and merge!: would mark a breaking merge, which rule 1 below forbids on version branches anyway).

Two hard rules connect commits to the versioning system, and both are mechanically verifiable by tools and AI models:

  1. Breaking commits (! or BREAKING CHANGE:) may only exist on dev (they are breaking; version branches never break).
  2. Full-version tags live on version branches only; dev is never tagged.

Everything else is judgment, not mechanics: feat commits may ship in either bump type. Small features can land in revision bumps; a new phase is opened when the accumulated features are significant enough to deserve recognition (see rule 5 of the Specification). Phased Versioning is deliberately not a one-to-one projection of Semantic Versioning here — the phase is a communication device for humans, not an automatic consequence of a feat in the history.

Changelog

Every project keeps a CHANGELOG.md: an adherence/license preamble, then entries newest first, one per full version, with the version and release date as the heading and a short bulleted list of user-facing changes.

# Changelog

This project is licensed under the [Apache License (Version 2.0)](LICENSE). The format of this file
(and the project as a whole) follows [Phased Versioning](https://phased-versioning.koseka.net).

## v1-alpha.1 (2026-02-26)

- Refined the progress indicators.
- Added a `-v/--version` flag to the CLI.

## v1-alpha.0 (2026-02-21)

First stable version.

A project with no releases yet states so in place of entries: "This project has no official stable releases yet."

Tooling

The oyo CLI automates Phased Versioning in Git repositories with three commands: oyo gen creates the next version branch, oyo phase <name> opens a new phase (enforcing looped alphabetic progression and resetting the revision to 0), and oyo rev bumps the revision — each phase/revision producing a full-version tag on the latest commit.

Coexisting with SemVer

For ecosystems that force Semantic Versioning, the mapping is direct: MAJOR = generation number, MINOR = the phase's ordinal within its generation (first phase = 0), PATCH = revision. For example, v1-cyrus.5 (cyrus being the third phase of v1) maps to 1.2.5. The ordinal is a cumulative counter, not the alphabet index of the phase name's first letter: it increments by one for every phase opened and keeps counting through the z→a wrap of the looped alphabetic progression, so the phase after a 26th phase (MINOR 25) starting with z gets MINOR 26, not 0.

A repository that only has a dev branch — no version branch cut yet — projects to 0.0.0, and stays at 0.0.0 until the first version branch (v1) exists; from then on, the projection of the latest full version applies. Since generations start at 1, every released version projects to a MAJOR of at least 1: under this mapping, a SemVer major of 0 always means "not a release" — the entire 0.x namespace belongs to the conceptual zeroth generation, dev.

Note one deliberate divergence from strict SemVer semantics: since small features may ship in revision bumps, a projected PATCH can occasionally contain minor features. Prefer publishing on platforms that do not enforce SemVer (like GitHub, via branches and tags) whenever possible.