SemVer Derivation

How SemVer versions are derived by the automatic versioning engine.

Tag Recognition

Tags are recognised if they are annotated and parse as valid SemVer (optional v/V prefix). Lightweight tags are ignored.

Pre-release classifiers are recognised case-insensitively:

Classifier Aliases Example Tag
Development dev v1.0.0-dev.1
Milestone milestone, m v1.0.0-m.1
Alpha alpha, a v1.0.0-alpha.1
Beta beta, b v1.0.0-beta.1
Release Candidate rc, cr v1.0.0-rc.1
Snapshot SNAPSHOT v1.0.0-SNAPSHOT

When multiple tags exist on one commit, a final release outranks a pre-release of the same core. Otherwise, the highest version wins.

Keyword Mapping

Keyword Component Effect
major, breaking Major Increment major, reset minor and patch
minor, feat, feature Minor Increment minor, reset patch
patch, fix Patch No effect - patch is the default advancement

Each keyword works in all three directive forms - version: major, breaking: <text>, [breaking] - and as an absolute set, version: major: 3. target: 2.0.0 sets the version explicitly, subject to validation.

Initial Development (Major Version 0)

While the major version is 0 the public API is not yet stable, so a major/breaking bump advances the minor component instead of the major - a breaking change during initial development never forces a premature 1.0.0. Reaching 1.0.0 is deliberate: an explicit target: 1.0.0, or an absolute set such as version: major: 1.

Base Directive Result core
0.93.9 version: major 0.94.0
0.93.9 version: minor 0.94.0
0.93.9 version: major: 1 1.0.0
0.93.9 target: 1.0.0 1.0.0
1.4.5 version: major 2.0.0

Default Behaviour

When no directives apply:

Base Version Result
Final release (e.g. 1.4.5) Patch + 1 (1.4.6)
Pre-release (e.g. 3.0.0-rc.3) Core unchanged (3.0.0)
No reachable tags, repo has tags Highest tag + a breaking bump (4.3.0 -> 5.0.0; pre-1.0 0.5.0 -> 0.6.0)
No tags anywhere 0.1.0

Default Development Rendering

The SemVer scheme's developmentVersion writes the resolution metadata into the + build-metadata section in this fixed order:

<core>-SNAPSHOT+<yyyymmddhhmm>.<branch>.<sha>[.pr<N>][.dirty]

The 12-character UTC committer timestamp leads so that raw string comparison of two snapshots of the same base sorts them in commit-time order. The branch slot carries the active branch, or, on PR builds, the target branch where the merge will land. Branch names are sanitised for the SemVer build-metadata grammar at render time; the raw label remains available via DevelopmentMetadata.branch. The SHA is the basis commit's full hash; SemVer.Formatter.Full.withShaLength(n) truncates it for display.

This is the default rendering. Whether build metadata appears in the final version string is controlled by the Formatter; a custom Formatter can render the metadata identifiers in any other shape needed.

Examples (rendered with SemVer.Formatter.Full.withShaLength(12) to keep the SHA readable):

Scenario Output
Clean tag v2.3.1 2.3.1
After 1.4.5, no directives 1.4.6-SNAPSHOT+202605170145.main.1234567890ab
After 1.0.0, breaking: API change 2.0.0-SNAPSHOT+202605170145.main.1234567890ab
Dirty worktree at v1.0.0 1.0.1-SNAPSHOT+202605170145.main.1234567890ab.dirty
No tags, fresh repo 0.1.0-SNAPSHOT+202605170145.main.1234567890ab
PR build (PR 42 targeting main) 1.2.4-SNAPSHOT+202605170145.main.1234567890ab.pr42
PR build on release/v2.0.x branch 1.2.4-SNAPSHOT+202605170145.release-v2-0-x.1234567890ab.pr42

Custom Tag Parsing

By default, tags are parsed by stripping a v/V prefix and calling SemVer.parse. Override tagParser in ResolutionConfig for non-standard tag formats:

val config = ResolutionConfig.default[SemVer]("/path/to/repo").copy(
  tagParser = name =>
    val stripped = name.stripPrefix("release-")
    val raw = if stripped.startsWith("v") then stripped.drop(1) else stripped
    SemVer.parse(raw).toOption
)