Development record
The 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-coded
Context
abcd ships as an installable, versioned Claude Code plugin through a curated release artifact cut from the single repo (adr-28). That raises two coupled questions the framework has to settle before any manifest is written:
- Where does the version live? Claude Code discovers a plugin by directory
convention and reads its manifest metadata. A published plugin needs a version
so
/plugin update abcdcan compare installed-vs-available. But adr-5 establishes that the working record carries no version label — versions are an output of the release cut, not a sequencing input in the working tree. So the working tree's committed.claude-plugin/plugin.jsonstays unversioned while the release artifact carries the version.
- Is
versioneven a legal field? Whether the Claude Code plugin-manifest schema accepts aversionproperty is an external unknown until it is validated. Hard-codingplugin.json.versionas the answer would be a guess. The version-location decision resolves it against the pinned schemastore fixtures and records the outcome in a machine-readable decision artifact (.abcd/config/version-location.json), with three fully-specified terminal outcomes: ACCEPT (versionis an explicit schema property → writeplugin.json.version), FALLBACK (a different schema-valid explicit-property location), or BLOCKED (no schema-valid explicit version location → escalate).
The recorded outcome is ACCEPT: version is an explicit property of the
pinned plugin-manifest schema, so the version location is
.claude-plugin/plugin.json at pointer /version, seed 0.1.0. The marketplace
source resolves from the repo root (marketplace_source_resolution_base: repo_root, marketplace_source_to_root: ./), unblocked.
Decision
We will carry the plugin version only in the curated release artifact, at the
location the version-location decision artifact selects — today
plugin.json.version. The working tree's committed manifests stay
unversioned. The version is written at release-cut time (/abcd:launch ship)
by the manifest render step, which reads version-location.json and writes the
version at the recorded manifest_path + json_pointer — never by parsing a
location string, and never from a hard-coded field name. When the decision records
blocked: true, the render refuses: there is no schema-valid location to write, so
a versioned release manifest cannot be produced and the escalation stands.
This is compatible with the Claude Code plugin schema: spc-77.1 validated version
as an explicit property of the pinned manifest schema (not merely permitted by
additionalProperties), so a version-carrying public manifest is schema-valid. The
decision artifact records the exact accepting schema clause in its
version_property_clause field —
claude-code-plugin-manifest.schema.json#/properties/version — so this
compatibility claim traces to the pinned schema pointer, not to prose.
Alternatives Considered
- Version the working-tree
plugin.jsontoo, and let the release cut copy it. Rejected: it contradicts adr-5 (the working record carries no version) and would make the committed state drift with every routine cut. The version is an output of the release cut; it belongs to the artifact, not the source. - Hard-code
plugin.json.versionas the version location. Rejected: whether the schema acceptsversionwas an external unknown, and a hard-code would have silently shipped a schema-invalid manifest in the REJECT world. The decision-artifact indirection lets every downstream consumer (render, public writes, smoke) read a validated location without re-deriving it, and keeps the framework honest if a future schema revision moves or removes the field. - Store the version in
marketplace.jsononly. This is the FALLBACK branch, kept live in the decision schema but not selected —plugin.json.versionis the more conventional, install-visible location and it validated as an explicit property, so ACCEPT wins.
Consequences
- The version is single-sourced in the release artifact; the working tree never carries a version, so there is nothing to keep in sync on the source side (honours adr-5).
- Every version-writing capability (the manifest render step, the
launch shipwriter, the release smoke) is version-neutral: it readsversion-location.jsonrather than assuming a field name, and refuses whenblocked: true. A future schema change that relocates the version is absorbed by re-running the version-location decision, not by editing call sites. - The release artifact is installable+versioned;
/plugin update abcdgains a version to compare against. The lifecycle is explicit: the working tree = unversioned source of truth; the release artifact = versioned published output. - A new obligation: the no-half-state lint asserts the terminology and docs
describe the selected location (or the escalation text under BLOCKED), never a
stale hard-coded
plugin.json.versionclaim. - adr-2609231048308186 amends this decision: the catalog sources the plugin from the
latest release's pinned archive instead of the tree, so the version reaches an
install through that published archive. The working tree still carries no
version key, and the
./source this ADR's Context records holds only until the first pinned ship. The contract carries nomarketplace_source_to_rootkey: nothing read it, and the pin would have made it false.
Frontmatter
| id | adr-19 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-07-01 |
.abcd/development/decisions/adrs/0019-plugin-json-version-carve-out.md
open on GitHub ↗ · commit history ↗
References4
- adr-5Brief is the current state; no version label, no archive directoryrelated
- adr-28One repository, a curated release artifact — no dev→public mirrorrelated
- adr-2609231048308186The catalog pins the latest release's plugin archiverelated
- itd-67abcd Is An Installable, Versioned Claude Code Plugin Whose Repo Is Its Own Marketplace And Whose Every Launch Bumps, Tags, And Publishes A New Versionrelated
Referenced by4
- 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-31The release version is derived from the intents in it, never authoredrelated
- adr-37Releases are changelog-driven — rolling `[Unreleased]` is the release decision, and automation tags exactly that commitrelated
- itd-2611011151475128Every documentation surface is rendered from its one homerelated
mentions12
- dcn-26092300000000152026-09-23 — How a release reaches `/plugin update` (product thinker, rulings E1 to E3 of the run A interview, 09:50Z to 10:13Z). This supersedes the interview entry's "no criterion amended" for the version-stamped plugin payloads. E1, the form, 09:50Z: a pinned archive. release.yml renders the plugin payload reproducibly as `abcd-plugin-vX.zip`, the ship pull request commits the marketplace entry with that release's URL and sha256, and release verify refuses a digest mismatch. The product thinker's stated want, in their words: "the user should get the latest cut release from github; verified as such and fingerprinted through a hash". The window between the ship merge and the asset upload, when the listing may name a zip not yet published, is to be confirmed and then closed or documented. E2, the amendments, 09:55Z: itd-67's first acceptance criterion is amended from source `./` to a pinned archive source, and an ADR minted with `abcd decide` amends adr-19 and adr-20 so that the default branch's marketplace.json carries the latest release's URL and sha256 while the plugin.json version stays out of the working tree; both land with the build, each saying why, with no review before the build. E3, the harness floor, 10:13Z: Claude Code v2.1.224, the minimum the archive source needs, is accepted and stated in the install instructions and the release notes; no install-time detection is asked for. E4 owed: whether contributors keep a source-`./` development entry once installs stop following the default branch, and under what name.
- iss-2609100506255436Sharpens iss-2608270559313719, which reports that abcd's release flow cannot serve a managed repo that is not a plugin. That record lists the symptoms; this one names the single line that causes most of them, and shows that abcd already solved the neighbouring problem, so the fix has a precedent inside the same file.
- 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.
- itd-69Plugin Metadata Stays Consistent Across Every Duplicated Surface
- itd-73abcd Cuts the Version — You Never Type One
- 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
- itd-2610031348087517Setup asks whether abcd keeps parts of the README current, and which
- itd-2611011239589823A project's how-to guides and learning material stay current with the product
- spc-10derived-versioning
- spc-11installable-versioned-plugin
- spc-2609211957080006plugin-metadata-lockstep-update