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
)