Version the Gem Interface, Not the Generated Content
June 5, 2026 · View on GitHub
Context and Problem Statement
Features that were previously extracted into separate plugin gems are being bundled back into this gem as opt-in built-in features (Changelog, Code of Conduct, Decision Records, Inclusive Language, Pull Request Template). Each feature embeds an external standard at a particular version — for example the Code of Conduct feature ships Contributor Covenant v2.1, and Decision Records ships MADR v3.
This project follows Semantic Versioning. If every breaking change to a bundled standard (e.g. upgrading Contributor Covenant 2.1 → 3, which is effectively a rewrite) were treated as a breaking change to the gem, the major version would inflate rapidly towards numbers like v14. How should we version the gem as the standards its features embed evolve?
Decision Drivers
- Rapid major-version inflation forces downstream consumers who pin
~> N.0to hand-edit their constraints for changes that do not actually break the gem's interface. - Existing users should not have their generated artefacts change underneath them without opting in.
- The gem recommends Semantic Versioning to its own users (via the
versioning-semverfeature), so it should model correct SemVer rather than abandon it. - Each feature's history should remain possible to trace even though all features share one version.
Considered Options
- Scope SemVer to the gem's interface — the version reflects the CLI, require paths and plugin API; the content a generator emits is not part of the versioned contract.
- Calendar Versioning (CalVer) — date-based versions (e.g.
2026.06.0); drop breaking-change signalling entirely. - Per-feature version numbers — give each feature its own independently incremented version.
Decision Outcome
Chosen option: "Scope SemVer to the gem's interface", combined with additive variants for drastic standard upgrades, because it keeps the major version meaningful and slow-moving while still giving users a clear, opt-in path through breaking standard changes.
The versioned public contract is:
- the CLI surface — commands and their required options
(e.g.
way_of_working init code_of_conduct --contact-method), - the require paths of built-in features
(e.g.
require 'way_of_working/code_of_conduct/contributor_covenant'), - the plugin registration API (
SubCommands::*.register, and theway_of_working-*autoload inlib/way_of_working.rb).
It follows that:
- Refreshing the standard a feature embeds is new behaviour → MINOR.
- A bug fix in generation is PATCH.
- MAJOR is reserved for breaking the contract above — renaming or removing a command or option, moving a require path, or changing the plugin API.
For a standard update drastic enough to surprise existing users, prefer adding a new variant
rather than mutating the existing one. Features are already namespaced as category/variant
(code_of_conduct/contributor_covenant, decision_record/madr), so a v3 of Contributor Covenant
can ship as a new variant alongside the existing one; both coexist and consumers opt in by require
path. This keeps the change additive (MINOR) and leaves existing users untouched.
Consequences
- Good, because the major version stays meaningful and rare, so
~> N.0pins keep working across routine standard refreshes. - Good, because breaking standard upgrades become opt-in rather than imposed on re-running a generator.
- Good, because it requires no architectural change — the variant namespace already exists.
- Neutral, because all features still share a single version number; per-feature history is carried by a changelog convention (see Validation) rather than separate version streams.
- Bad, because contributors must consciously classify each change against the interface contract rather than reflexively bumping major for anything that "looks breaking".
Validation
- The version-bump checklist in
CONTRIBUTING.mdencodes this policy and is applied at release time. - Feature-affecting changelog entries are scoped with a bold feature prefix
(e.g.
- **Code of Conduct:** …) so each feature's history is greppable within the singleCHANGELOG.md.
More Information
- Rejected CalVer because it discards the breaking-change signal that
~>pinning relies on and would undercut the SemVer the gem advocates to its users. - Rejected per-feature version numbers because re-decoupling versions runs counter to the consolidation of features back into the single gem.
- The concrete Contributor Covenant 2.1 → 3 upgrade is tracked separately; this decision only establishes how it should be versioned when it lands.