Development record
The release version is derived from the intents in it, never authored
Context
abcd's working tree is unversioned by decision (ADR-19): the plugin version lives only in the released artefact, and the two release manifests are held in lockstep (ADR-20). Those decisions fix where the version lives, not how the number is chosen — and today that choice is implicitly manual, someone picking the next vX.Y.Z.
abcd's product model makes the intent the unit of the why, and the launch flow already cuts a curated release from a v* tag (ADR-28). A manually-chosen version is release-thinking imposed on a tool whose whole grain is product-thinking in intents. It is also unsafe: a hand-picked SemVer can silently disagree with what actually changed — a "minor" that removed a command breaks the consumers who trusted the contract.
Decision
The release version is derived, not authored. Two signals feed it, and the working tree never carries a version (extending ADR-19):
- Intent-declared impact (primary). Every intent carries
impact: additive | breaking | fix, set once when the intent is shaped. At release,/abcd:launchgathers the intents shipped since the previous release and takes the highest-severity impact: anybreaking→ major, else anyadditive→ minor, else patch.internal/core/lintenforces that every intent carries a validimpact.
- Surface diff (guardrail).
launchsnapshots the/abcd:*command, flag, and manifest surface and compares it to the previous release. A removed or altered surface with nobreakingintent in the release fails the launch — a mislabel cannot ship a compatibility lie.
The computed number is written only to the release artefact — the tag, the GitHub Release, and the marketplace manifest (ADR-20 lockstep) — never to the working tree.
Alternatives Considered
- Conventional-commit derivation (semantic-release style). Compute the bump from
feat:/fix:/feat!:commit prefixes since the last tag. Rejected as the primary signal because the commit is the wrong unit for abcd — the intent is where product judgement lives — but retained as a fallback for changes not tied to an intent. - Manual SemVer. The status quo. Rejected: it is release-thinking, and it lets the number disagree with reality.
- Pure surface-diff, mechanical only. Derive the whole version from the surface snapshot with no human input. Rejected: it cannot see behavioural breaks behind an unchanged surface, so the human
breakingjudgement is still required; surface-diff is the guardrail, not the sole source.
Consequences
- Contributors think in intents and mark each
additive,breaking, orfix; nobody chooses a version number. - The derived SemVer keeps its promise to consumers, because the guardrail blocks a minor or patch that structurally broke the surface.
- The intent schema gains an
impactfield,internal/core/lintgains an impact-presence check, andlaunchgains version-derivation and surface-diff stages. This implementation is tracked by itd-73. - Pre-release channels, deprecation windows, and changelog prose are out of scope here and left to later decisions.
Frontmatter
| id | adr-31 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-07-07 |
.abcd/development/decisions/adrs/0031-derived-versioning-from-intents.md
open on GitHub ↗ · commit history ↗
References4
- adr-19The plugin version lives ONLY in the released artifact; the working tree stays unversioned, and the version location is chosen by a schema-validated decision, not hard-codedrelated
- adr-20The two release manifests stay version-consistent by an anti-drift checker over a pinned path list; the source view stays unversioned; `--allow-dirty` must never bypass itrelated
- adr-28One repository, a curated release artifact — no dev→public mirrorrelated
- itd-73abcd Cuts the Version — You Never Type Onerelated
Referenced by3
- adr-33Launch phase ownership is tiered; the phase index is the sole ownership sourcerelated
- adr-37Releases are changelog-driven — rolling `[Unreleased]` is the release decision, and automation tags exactly that commitrelated
- adr-2609151513118583An intent owns one or more specs, and it ships when its last spec closesrelated
mentions10
- dcn-26071700000000052026-07-17 — Release burst (maintainer-directed): adr-37 adopts
- iss-2release-version source of truth conflicts: roadmap README tracks releases by plugin.json; ADR-31 derives versions from shipped intents; 04-launch.md still describes --version and phase-completion tiering. ADR-bound.
- iss-2610150749052356The generated ADR and drafts indexes dropped hand-written relationship notes the old rows carried ('refines adr-50' on adr-55, 'amends adr-19' and 'extends adr-19, adr-20' on adr-31, adr-39/adr-23 links on itd-22, a reframe note on itd-16), because a row is derived from the record's H1 and status only.
- iss-2610160608404245The brief at .abcd/development/brief/04-surfaces/04-launch.md:498 says every intent carries an impact, set when the intent is shaped and enforced by the record lint (adr-31). At 0de27c795, 91 intents in drafts/ and 4 in planned/ carry no impact key; the record lint's intent_impact_valid checks only the value set, and impact is required only at the close that moves an intent to shipped/, as 05-intent.md states.
- itd-72Publishing A Release Is One Atomic, Refusable `ship`
- itd-93abcd Scaffolds a Release Gate That Works on the First Try
- itd-108The plugin installs from the curated release artifact, not the repo, and every cut release reaches users automatically
- itd-2609150819432059A managed repository that is not a plugin gets the same release gate
- spc-14abcd-scaffolds-a-hardened-changelog-driven-release-gate-into
- spc-2609201955277614launch-payload-render-parity