Skip to content

0020. Semantic versions, one version source, a changelog and tagged releases

  • Status: accepted
  • Date: 2026-10-08

Applications, plugins and teaching material build on shuffle_engine, and an AI coding agent writes code against whatever the documentation says today. Both need to know which version they have, what a new version breaks and how to move. Until now the version appeared in three places (CMake, CITATION.cff, nowhere in the code), there was no changelog and no release procedure. The framework is young: most modules will still change shape before their API settles, and a promise that cannot be kept is worse than none.

  • Semantic Versioning 2.0.0: MAJOR.MINOR.PATCH, with a pre-release label for candidates (1.0.0-rc.1) and for the work between releases (0.3.0-dev, never released).
  • Before 1.0.0 (0.y.z): a minor version (0.y) may break the public API; every break is listed in CHANGELOG.md under Changed or Removed with what to do instead. A patch version (0.y.z) never breaks it: fixes, documentation, new tests, nothing removed or renamed.
  • From 1.0.0: a break needs a new major version; a minor version adds; a patch version fixes.
  • No binary compatibility is promised. The libraries are static and built from source with the application; rebuild everything on every update.
  • The version is declared once, in the top-level CMakeLists.txt: project (ShuffleEngine VERSION x.y.z), SHUFFLE_ENGINE_VERSION_PRERELEASE ("" on a release, "dev" between releases, "rc.N" for a candidate) and SHUFFLE_ENGINE_API_LEVEL.
  • CMake generates shuffle/base/Version.h (kVersionMajor, kVersionMinor, kVersionPatch, kVersionPrerelease, kVersionString, kApiLevel, and SHUFFLE_ENGINE_* macros for the preprocessor) and the package version file find_package (ShuffleEngine 0.2) checks.
  • The API level is an integer that grows by one with every minor or major version and never otherwise, so code can select an API with #if SHUFFLE_ENGINE_API_LEVEL >= 3 without comparing version triples.
  • tools/bump_version.py changes the version, CITATION.cff and the changelog heading in one step; tools/check_repo.py (rule 7) fails when they disagree; the release workflow refuses a tag that is not the declared version.
  • find_package compatibility: SameMinorVersion before 1.0, then SameMajorVersion.

The version number speaks for these, and only these:

Surface Includes
C++ headers everything under core/<module>/include/shuffle/<module>/ and the include directories of adapters/; the fakes and contract suites under testing/ and contracts/ (an application’s tests use them)
Ports and their contracts the interfaces and their documented rules: threads, SHUFFLE_RT marks, ordering, ownership, error values. A stricter rule for implementations (a new contract test an adapter may fail) is a break for adapter authors
Commands dotted names, parameters (names, types, ranges, units), risk levels, results and snapshot schemas (specs/commands.md)
Protocol surfaces the generated OSCQuery, OpenAPI, Companion, MCP and GBNF outputs, the ui_bridge protocol and its TypeScript client, the open tempo-sync protocol. Wire protocols also carry their own version field and negotiate it; their changes are still listed in the changelog
CMake the package ShuffleEngine and its components, the imported targets shuffle::<module>, shuffle_engine_add_app, shuffle_engine_use, the project generator’s variables, the SHUFFLE_ENGINE_WITH_* options
Persisted data show documents and settings keys the framework writes: a version reads what earlier versions of the same major wrote

Not public: src/ directories, anything in a detail namespace, internal CMake (cmake/ShuffleEngine.cmake), presets, tests, tools, benchmark figures, log and error message wording (error codes are public).

Each module is stable or experimental, derived from its spec:

  • stable: the spec’s status is implemented (every requirement done). Before 1.0 a stable module breaks only in a minor version, and keeps a forwarding alias for one minor version wherever one is cheap (a type alias, a forwarding header, an inline wrapper).
  • experimental: the spec is draft, reviewed or implemented in part, or the module is an adapter whose backend contract is still settling. It may change in any minor version, with a changelog entry and no deprecation period. After 1.0 it stays outside the SemVer promise until promoted.
  • base and resample have no spec of their own; their contracts are those of the specs that use them. base is stable; resample follows the modules that use it.
  • A module is promoted by the change that completes its spec; the module README’s summary block and docs/roadmap.md say which level it is at.

At 0.1.0 (amended 2026-10-09 by the maintainer: a smaller stable set for the first tag, widened as modules are used in practice): stable base, time, timecode, commands, midi, dsp; experimental every other module (osc, resample, loudness, routing, setlist, cues, showcontrol, netaudio, redundancy, synth, formats, engine, ui_model, ui_theme, ui_bridge) and every adapter. A spec implemented in full makes a module eligible for stable; the promotion is a changelog line. The level is recorded in each module README’s summary block (Stability) and in modules.md.

  • From 1.0.0: a public name is deprecated in a minor version, at least one minor version before the major version that removes it. It carries [[deprecated ("use <replacement>")]] naming the replacement (a CMake function prints a DEPRECATION message the same way), and the changelog lists it under Deprecated.
  • Before 1.0.0: the same marks where they are cheap; otherwise the Changed or Removed entry is the deprecation, with the migration in one line.
  • A deprecated name keeps working, with the same behaviour, until it is removed.

Platforms, compilers and pinned backends per release

Section titled “Platforms, compilers and pinned backends per release”
  • A release supports the five platforms of decision 0006 and WebAssembly, built by the CI of the tagged commit: Windows (MSVC, Visual Studio 2026), macOS and iOS (Apple Clang of the Xcode on the CI image, macOS 11 and later), Linux (GCC of the CI image; Clang 20 for the real-time and sanitizer jobs), Android (NDK, API 26, arm64), Emscripten. CMake 3.22 or later (presets need 3.25).
  • The release notes record the compilers the release workflow actually used and the pinned third-party versions from NOTICES.md (JUCE, the Tracktion commit, Catch2, CPM, the codec libraries, miniaudio and the other fetched libraries; their hashes are in cmake/ShuffleEngineDeps.cmake, the JUCE tag and Tracktion commit also in .github/workflows/ci.yml).
  • Changing a pin is a changelog entry under Changed. A patch release changes a pin only for a security or crash fix in that dependency.
  • CHANGELOG.md follows Keep a Changelog 1.1.0. A pull request that changes a public surface adds its line under Unreleased; tools/check_repo.py warns in CI when public headers, specs or the application CMake functions change without one (a warning, so small fixes are not blocked).
  • A release is an annotated tag vX.Y.Z on a commit where CI is green; .github/workflows/release.yml verifies it, builds and tests the cores on three operating systems, and publishes the source archives, the SPDX bill of materials, the documentation site and their SHA-256 sums. The procedure is docs/releasing.md.
  • Patch releases come from a release/X.Y branch cut from the tag, with fixes that landed on main first.
  • An application can state find_package (ShuffleEngine 0.2 CONFIG REQUIRED) and test kApiLevel; an agent reads CHANGELOG.md to learn what moved.
  • Stable and experimental are visible per module, so early adopters know which parts may still change shape.
  • Every public change costs one changelog line in the same pull request.
  • No prebuilt binaries yet: releases are source archives. Prebuilt core libraries per platform and vcpkg / Conan ports are future work and will follow the same version.