Development record
abcd Is An Installable, Versioned Claude Code Plugin Whose Repo Is Its Own Marketplace And Whose Every Launch Bumps, Tags, And Publishes A New Version
Press Release
The abcd repo becomes a real Claude Code marketplace-and-plugin: it carries
.claude-plugin/marketplace.json(source./) and a semver-stampedplugin.json, so an end-user runs/plugin marketplace add <personal account>/abcdthen/plugin install abcd@abcd-marketplaceand gets the full/abcd:*command surface. Every/abcd:launch shipauto-selects a version bump (patch/minor/major per the brief's bump-tier rule), writes it intoplugin.jsonand the marketplace entry, and tags the repo — so end-users update with/plugin update abcdand always pull a coherent, versioned release. The development record under.abcd/**is excluded from the release artifact by packaging (adr-28) — one repo is both the marketplace and the workshop. Today abcd is developed but never CONSUMED as a plugin: there is no.claude-plugin/marketplace manifest,plugin.jsoncarries no version, and there is no install or update path. This intent makes abcd actually installable and keeps it current — the precondition for anyone (including its own maintainers) to run the/abcd:*surface.
"I've been building abcd but I can't actually install it — the repo has no plugin manifest and no version," said Kira, the maintainer. "Before we gate a launch payload, abcd has to BE an installable, updatable plugin. Adding the marketplace should be one command, updating should be one command, and every release should carry a real version number that falls out of the work we shipped."
Why This Matters
Every other launch concern is downstream of this one. The pre-flight gate suite ([[itd-65-launch-preflight-gate-suite]]) and the payload render/parity/smoke ([[itd-66-launch-payload-render-parity]]) both assume there is an installable, versioned plugin to gate and render — but there isn't: the repo has no .claude-plugin/, so it cannot be added as a marketplace or installed, and plugin.json has no version, so Claude Code has nothing to compare on update. The canonical launch brief (04-surfaces/04-launch.md §§ 2, 4) already specifies that the curated release is the versioned artefact — a single repo whose packaging excludes .abcd/** (adr-28) — and that launch ship bumps plugin.json, updates marketplace.json, and refreshes version references — but nothing builds it, so the version story is design-only. This intent makes the distribution real and self-updating: it is the difference between "abcd is a repo you read" and "abcd is a plugin you install and keep current." It also closes a self-consistency gap the maintainer just hit — being unable to invoke /abcd:intent because abcd isn't installed as a plugin in a working session.
What's In Scope
- The abcd repo carries
.claude-plugin/marketplace.json(marketplaceabcd-marketplace, one pluginabcd,source: "./") AND the plugin's.claude-plugin/plugin.jsonwith a real semverversion— the single repo is simultaneously the marketplace and the plugin, with.abcd/**excluded from the release artifact by packaging (adr-28). - A version field lands in
plugin.json— the single source of the installed version; git tags on the repo are the canonical release points (per project standards, the version lives in the manifest + tags, not scattered across files). /abcd:launch shipauto-selects the bump tier (patch/minor/major) via the brief §4 phase-completion detection, writes the new version intoplugin.json, updatesmarketplace.json(version + changelog entry), tags the repo, and records the tier + reason in the launch report.- A dedicated, auto-recorded changelog in the repo, generated from the canonical history (git tags + commit/spec history + launch report) — never hand-curated prose. This changelog is the single home for "what changed and why it changed" (per the
abcd-cli/CLAUDE.mddocs-describe-present rule), and is the reroute target for [[itd-65-launch-preflight-gate-suite]]'s doc-history gate: change-narration that gate strips from a doc body is appended here. - The install path is documented in the repo README (
/plugin marketplace add <personal account>/abcd→/plugin install abcd@abcd-marketplace) and the update path (/plugin update abcd). - A smoke check that the published manifest is installable:
marketplace.json+plugin.jsonparse,sourceresolves, and every declared command/skill/agent/hook path exists in the payload (shares the installed-surface assertion with [[itd-66-launch-payload-render-parity]]).
What's Out of Scope
- The pre-flight security/PII/marker gate suite ([[itd-65-launch-preflight-gate-suite]]) and the payload render/parity mechanics ([[itd-66-launch-payload-render-parity]]) — this intent is DISTRIBUTION + VERSIONING, and reuses their manifest/smoke assertions rather than reimplementing them.
- A separate dedicated marketplace repo — decision is the single abcd repo IS the marketplace (source
./, adr-28); multi-plugin marketplaces are a later concern. - Manual per-ship version entry as the primary path — auto bump-tier is the default (per brief §4);
--version <x.y.z>remains the override / major-bump escape hatch, not the norm. - Auto-publishing to any registry beyond the git repo + tag — distribution is git-native marketplace, not a package index.
- Touching the wrapped dependencies' own versions — this versions abcd's OWN release, never the tools it wraps (wrap-only rule; distinct from the
dep_watcherupstream-tracking machinery).
Scope Conditions
None stated.
Acceptance Criteria
Given-When-Then per the itd-1 discipline.
- Given the published abcd repo, when a user runs
/plugin marketplace add <personal account>/abcd, then the marketplace resolves and lists theabcdplugin frommarketplace.json, sourced from the latest release's pinned archive ({"source": "archive", "url": ".../releases/download/vX.Y.Z/abcd-plugin-vX.Y.Z.zip", "sha256": "<digest>"}). Amended 2026-09-23 (source./→ the pinned archive), on the product thinker's rulings E1 and E2 of that day: a relative-path source installs the unversioned working tree, so no install or update could receive a version-stamped release, which is what criterion 3 promises; the host harness reads a published release only through an archive source. The decision and the alternatives weighed are adr-2609231048308186, which amends adr-19 and adr-20. Until the first release past v0.9.0 is cut, the committed catalog still carries./(that ADR's bootstrap). - Given the added marketplace, when a user runs
/plugin install abcd@abcd-marketplace, then the plugin installs and the full/abcd:*command/skill/agent/hook surface registers in a session. - Given a shipped abcd with a recorded version, when a new
launch shippublishes, thenplugin.json.versionis bumped by the auto-selected tier (patch/minor/major per brief §4),marketplace.jsonis updated, the repo is tagged, and/plugin update abcdpulls the new version. - Given the bump-tier detection, when a phase completed since the last launch, then the bump is minor and the launch report names the completed phase; when none did, the bump is patch; a major bump occurs only via explicit
--version <x.0.0>. - Given the published manifest, when the installability smoke check runs, then
marketplace.json+plugin.jsonparse,sourceresolves, and every declared command/skill/agent/hook path exists — a missing path FAILS the check. - Given project standards forbidding git-inferable metadata in files, when the version is recorded, then it lives in
plugin.json+ git tags only, not duplicated across doc bodies.
Open Questions
- Does
plugin.json.versionseed atv0.1.0(pre-1.0 signalling in-development) or does the first public install implyv1.0.0? The bump-tier rule needs a defined starting point. - Where does the changelog live — a
CHANGELOG.mdin the repo, themarketplace.jsonentry, or git tag annotations — and is it generated from the launch report or hand-curated? - How does auto bump-tier detection read "phase completed since last launch" before the
phase:frontmatter anchor is active (brief §4 notes it falls back to editorial## Scopemembership until then)? - Should
launch shiprefuse to publish ifplugin.jsonversion would not change (nothing new since last tag), or always allow a forced patch re-snapshot? - Does the install-path documentation belong only in the repo README, or also mirrored in
docs/for the plugin's own help surface?
Audit Notes
Fidelity review — receipt rcp-7af1556ce4f7 (verifier intent-auditor claude-fable-5-1).
Provenance: intent-auditor@claude-fable-5-1 · rubric_hash sha256:effa65b3e9e88ff29433b443ec2be159522a8b0b71cf1434526514aa61edb13e · prompt_hash sha256:5c2255f4a1fe91bd174e3569f9aa39f48753cc8121b7cc595d5948c949815ac0 Input attestations: diff:tree at de3ba5fa (spc-11 delivered; main after PR #661)@sha256:4e7430d38ef6b7b6bc533fdf0b8b32a6e08a3e2449d0566027d43316036b8178;
Acceptance rollup: MET 1 · MET_WITH_CONCERNS 3 · NOT_MET 2 · INCONCLUSIVE 0
Per-criterion verdicts:
- ac-1 — MET_WITH_CONCERNS: marketplace.json names abcd-marketplace with one plugin abcd at source ./ and the light smoke resolves it over the committed payload; the criterion's <personal account>/abcd slug is stale (README documents [redacted-user]/abcd, marketplace owner url still points at the personal account) and the host-side add cannot be exercised in-tree evidence: .claude-plugin/marketplace.json:3 — ""name": "abcd-marketplace"" evidence: .claude-plugin/marketplace.json:11 — ""source": "./"" evidence: internal/core/launch/smoke_test.go:13 — "func TestSmokeLightPassesOnCommittedPayload" evidence: README.md:83 — "/plugin marketplace add [redacted-user]/abcd" evidence: .claude-plugin/marketplace.json:6 — ""url": "https://github.com/<personal account>""
- ac-2 — MET_WITH_CONCERNS: the light smoke asserts every declared command/agent/skill/hook path exists in the resolved bundle and passes over the committed payload; it asserts path presence only, not that the surface registers in a session (itd-66's deep tier is deferred by spc-11) evidence: internal/core/launch/smoke.go:110 — "for _, e := range surface.Entries {" evidence: internal/core/launch/smoke.go:118 — "declared %s %q is not in the payload" evidence: internal/core/launch/smoke_test.go:13 — "func TestSmokeLightPassesOnCommittedPayload" evidence: internal/core/launch/smoke.go:6 — "The light tier asserts the three things itd-67 names"
- ac-3 — NOT_MET: promised: a ship bumps plugin.json.version, updates marketplace.json, tags, and /plugin update pulls the new version; delivered: the version is stamped only into a payload staged outside the repo when --payload-dir is passed, the committed manifests carry no version at all, release.yml uploads binaries and checksums only, and the marketplace source ./ is the git tree — so /plugin update abcd never pulls a plugin.json.version; only the tag half arrives, via auto-release.yml evidence: internal/surface/cli/ship.go:224 — "if payloadDir != "" && ingested.Written {" evidence: internal/core/launch/render.go:365 — "func stampMarketplace(dest string, req PayloadRenderRequest) error {" evidence: .claude-plugin/plugin.json:3 — ""name": "abcd"," evidence: internal/core/launch/render_test.go:94 — "func TestRenderPayloadLeavesSourceTreeUnversioned" evidence: .github/workflows/release.yml:327 — "gh release create "${TAG}" bin/abcd-* bin/checksums.txt" evidence: .github/workflows/auto-release.yml:3 — "tag-and-release the NEWEST dated"
- ac-4 — NOT_MET: promised: phase completion drives a minor bump named in the report, patch otherwise, major only via explicit --version; delivered: the tier derives from record impact (pre-1.0 additive is a patch, breaking a minor, 1.0.0 never derivable), the report names the deciding impact and record and no phase, and the ship verb carries no --version flag at all — spc-11 records the supersession but the criterion was never amended evidence: internal/core/changelog/version.go:38 — "func DeriveNext(prev launch.Semver, bump Impact) (launch.Semver, bool) {" evidence: internal/core/changelog/version.go:29 — "NO input can derive 1.0.0 from a 0.x base" evidence: internal/surface/cli/ship.go:143 — "return string(cut.Impact) + ": " + strings.Join(cut.DecidedBy, ", ")" evidence: internal/surface/cli/ship.go:262 — "cmd.Flags().StringVar(&changelogJSON, "changelog-json"" evidence: .abcd/development/specs/closed/spc-11-installable-versioned-plugin.md:107 — "Bump-tier selection."
- ac-5 — MET: SmokeLight parses both manifests, resolves the marketplace source to a plugin manifest of the same name and asserts every declared path; a missing path is a finding that fails the smoke and blocks the ship, with one negative test case per failure kind evidence: internal/core/launch/smoke.go:56 — "func SmokeLight(tree PayloadTree) SmokeReport {" evidence: internal/core/launch/smoke.go:127 — "report.OK = len(report.Findings) == 0" evidence: internal/core/launch/smoke_test.go:26 — "func TestSmokeLightFailsAndNamesTheMissingPath" evidence: internal/core/launch/smoke_test.go:109 — "func TestRenderPayloadRefusesAnUninstallablePayload" evidence: internal/core/launch/ship.go:68 — "report.Smoke = SmokeLight(NewBundleTree(bundle))"
- ac-6 — MET_WITH_CONCERNS: no version is duplicated across doc bodies: the committed plugin.json carries none, the [redacted-user]-polarity lockstep test asserts the keys absent, and git tags are the release points; the concern is that the in-tree carrier is the CHANGELOG dated heading rather than plugin.json (adr-19/adr-37), so the version does not live in plugin.json + tags as the criterion says evidence: internal/core/launch/lockstep_repo_test.go:18 — "func TestCommittedTreeSatisfiesDevPolarity" evidence: internal/core/launch/lockstep.go:45 — "[redacted-user]: those keys must all be ABSENT" evidence: internal/surface/cli/ship.go:99 — "func publishedVersion(repoRoot string) string {" evidence: .github/workflows/auto-release.yml:81 — "tag="v$version""
Gap audit:
- honoured:
- the repo carries marketplace.json (abcd-marketplace, plugin abcd, source ./) and plugin.json evidence: .claude-plugin/marketplace.json:11 — ""source": "./"" evidence: .claude-plugin/plugin.json:3 — ""name": "abcd","
- a light installability smoke that fails on a missing declared path evidence: internal/core/launch/smoke.go:56 — "func SmokeLight" evidence: internal/core/launch/smoke_test.go:26 — "TestSmokeLightFailsAndNamesTheMissingPath"
- install and update path documented in the README evidence: README.md:89 — "/plugin install abcd@abcd-marketplace" evidence: README.md:98 — "/plugin update abcd"
- an auto-recorded changelog derived from records, never hand-curated evidence: internal/surface/cli/ship.go:176 — "derive the version and the record set from what shipped" evidence: internal/surface/cli/ship.go:284 — "Preview the next release cut"
- the version is not scattered across files evidence: internal/core/launch/lockstep_repo_test.go:18 — "TestCommittedTreeSatisfiesDevPolarity"
- diverged:
- bump tier auto-selected per brief phase completion — delivered from record impact, with a pre-1.0 row that maps additive to patch evidence: internal/core/changelog/version.go:21 — "prev is 0.x breaking -> minor++"
- launch ship tags the repo — delivered as auto-release.yml tagging the newest CHANGELOG heading on merge evidence: .github/workflows/auto-release.yml:3 — "tag-and-release the NEWEST dated"
- the version lives in plugin.json — delivered as the CHANGELOG dated heading in-tree and a stamped payload manifest out-of-tree evidence: internal/surface/cli/ship.go:99 — "func publishedVersion(repoRoot string) string {" evidence: internal/core/launch/render.go:365 — "func stampMarketplace"
- marketplace add <personal account>/abcd — the README documents [redacted-user]/abcd evidence: README.md:83 — "/plugin marketplace add [redacted-user]/abcd"
- missing:
- a bumped plugin.json.version and updated marketplace.json that /plugin update actually pulls — the stamped payload is staged only with --payload-dir and no workflow publishes it; the marketplace source is the unversioned git tree evidence: internal/surface/cli/ship.go:224 — "if payloadDir != "" && ingested.Written {" evidence: .github/workflows/release.yml:327 — "gh release create "${TAG}" bin/abcd-* bin/checksums.txt"
- an explicit --version < x.0.0> override on the ship verb evidence: internal/surface/cli/ship.go:262 — "cmd.Flags().StringVar(&changelogJSON, "changelog-json""
- a launch report naming the completed phase evidence: internal/surface/cli/ship.go:143 — "return string(cut.Impact) + ": " + strings.Join(cut.DecidedBy, ", ")"
Frontmatter
| id | itd-67 |
|---|---|
| type | intent |
| lifecycle | shipped |
| kind | standalone |
| severity | critical |
.abcd/development/intents/shipped/itd-67-installable-versioned-plugin.md
open on GitHub ↗ · commit history ↗
References2
Referenced by24
- 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-2609231048308186The catalog pins the latest release's plugin archiverelated
- iss-2610262032146377itd-67 picks the release bump through brief §4 phase-completion detection; the mental model computes the version from the impact of what shippedrelated
- iss-2610262032158578itd-67 generates the changelog from git tags, commit/spec history and the launch report; the scope page composes it from the records that reached a terminal folderrelated
- iss-2610262033173969itd-67 has launch ship write plugin.json and tag the repo; 04-launch says the verb neither commits, tags nor publishesrelated
- iss-2610262033179679itd-67 keeps a `--version <x.y.z>` override on ship; 04-launch says there is no version flag at allrelated
- iss-2610262033185478itd-67 has the catalog list the plugin with `source: "./"`; 04-launch has the cut replace the catalog's source with the pinned release archiverelated
- iss-2610262033189165itd-67 sends change-narration stripped from a doc into the changelog; 04-launch says rephrase the doc and never move the sentence into the changelogrelated
- iss-2610262035142666itd-67 picks the release bump tier from phase-completion detection; the intent chapter derives the version from each intent's impact judgementrelated
- iss-2610262035153070itd-67 keeps a hand-typed --version override for a release; the status-board chapter says the version is derived and never hand-authoredrelated
- iss-2610262035169561itd-67 generates the changelog from git tags, commit and spec history and the launch report; the intent chapter says the cut composes only from terminal record foldersrelated
- iss-2610262038066422itd-67 has `/abcd:launch ship` tag the repo; 37-reserve has the release workflow push the release tag after the mergerelated
- iss-2610262042162078itd-67 picks the bump tier from phase completion and writes the version into plugin.json; the version entry keeps the working tree unversioned and derives the version from impact.related
- iss-2610262042174713itd-67 builds the changelog from git tags, commit and spec history and the launch report, and lets a gate append to it; the release entry composes it from records that reached a terminal folder.related
- iss-2610262045198472itd-67 picks the release bump by phase-completion detection; itd-24 records that phases are retired and the release is the unitrelated
- iss-2610262045379282itd-67 picks the bump tier by phase-completion detection and keeps the version in plugin.json; itd-73 derives it from shipped intents' impact on an unversioned treerelated
- iss-2610262045380381itd-67 makes the changelog the reroute target for itd-65's doc-history gate; itd-65 supersedes the reroute wholerelated
- itd-65abcd Refuses To Publish A Payload Until The Full Ship-Time Pre-Flight Gate Suite Passes, Not Just The Secret Scanbuilt on by
- itd-69Plugin Metadata Stays Consistent Across Every Duplicated Surfacebuilt on by
- itd-72Publishing A Release Is One Atomic, Refusable `ship`blocks
- itd-105A fresh plugin install just works: the hook binary survives install and every updatebuilt on by
- itd-108The plugin installs from the curated release artifact, not the repo, and every cut release reaches users automaticallybuilt on by
- itd-2609231013154443Every release arrives with a press release composed from what shippedbuilt on by
- spc-11installable-versioned-pluginimplemented by
mentions15
- 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 it
- adr-34Intent lifecycle and phase scheduling are orthogonal axes
- adr-37Releases are changelog-driven — rolling `[Unreleased]` is the release decision, and automation tags exactly that commit
- dcn-26070800000000032026-07-08 — Consume-model interview: `spec_id` is SCALAR (never a list) —
- dcn-26072100000000012026-07-21 — itd-66 (`launch-payload-render-parity`) is DEFERRED to a follow-up
- dcn-26072100000000022026-07-21 — itd-67's "a phase completed since the last launch -> minor" bump
- 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-115There is no uniqueness lint for spec ids, so two concurrent branches always mint the same spc-N. abcd mints the next free id from the local tree, so any two branches cut from the same base collide silently: the itd-73/itd-67 programme and the itd-95/itd-96 programme both minted spc-10 and spc-11 on 2026-07-21. The collision is invisible on each branch and only appears on merge, where it breaks the bidirectional intent-spec link in BOTH directions at once — 'itd-95 names spc-10, but spc-10 claims itd-73' and 'itd-67 names spc-11, but spc-11 claims itd-96' — because whichever spec wins the store lookup leaves the other intent's link dangling. iss-74 added an issue-id uniqueness lint; specs need the same guard, and record-lint is the natural home so CI catches it on the PR rather than a human catching it at merge. Consider also whether minting should reserve ids in a way concurrent branches cannot duplicate.
- iss-2608270645473170The capture/scanner redaction over-masks the owner segment of a public 'owner/repo' string, replacing a legitimate public GitHub org/user name with [redacted-user] even when it is the very repository the record lives in. Observed 2026-08-27 when a captured issue body referencing this repo's own owner/repo slug had the owner masked. Not harmful (the slug still reads), but a false positive: the identity/path redaction treats an owner/repo owner as a sensitive identifier unconditionally. Consider exempting the repo's own owner (and public org names) from identity redaction, or narrowing the owner-segment match. Adjacent: iss-324 (scanner ispathsegmentbyte includes the slash in a third-party path).
- iss-2609230733497536itd-67 ac-3 promises that a ship bumps plugin.json.version, updates marketplace.json and that /plugin update abcd pulls the new version; delivered reality: launch ship stamps the version only into a payload staged outside the repository when --payload-dir is passed (internal/surface/cli/ship.go), release.yml uploads binaries and checksums.txt only, and the marketplace source ./ is the git tree whose committed manifests carry no version — so the version-stamped manifests are produced by nothing in the release path and consumed by nothing; the installed plugin's version arrives through the bootstrap binary download alone. Found by the itd-67 fidelity audit (rcp-7af1556ce4f7).
- iss-2609230733540527itd-67 ac-4 promises a phase-completion bump tier named in the launch report and a major bump only via an explicit --version <x.0.0>; delivered reality: the tier derives from record impact (internal/core/changelog/version.go), the report names the deciding impact and record and no phase, and the ship verb carries no --version flag at all — spc-11 records the supersession under Reconciliations but the intent's criterion was never amended, and the documented human override for the first 1.0.0 has no verb: the only path is a hand-written CHANGELOG heading. Found by the itd-67 fidelity audit (rcp-7af1556ce4f7).
- iss-2610021502084298No check meets the retired role word in an intent's press release at the record: the docs-lint rule roles/retired-role-word roots at the docs, the command pages and the rules, so a shipped or planned intent's press release carrying it is refused only when the site composer renders it as the landing page's hero quote, which is how itd-60's first MET audit broke the site-render gate (iss-2610021446271464). After the R5 reword of itd-60, thirteen shipped press releases still carry the word (itd-65, itd-66, itd-67, itd-69, itd-73, itd-74, itd-93, itd-102, itd-111, itd-114, itd-125, itd-2609111003026787, itd-2609212137129937), so the next MET audit ingested for any of them refuses the render again on a branch that only recorded an audit; the product thinker's ruling R5 (2026-10-02) accepted that a future slip breaks the build and did not ask for this check. Related to iss-2610020731591808, which names the brief, the principles and the personas registry as swept but unguarded.
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- spc-10derived-versioning
- spc-2610100613109045a-person-who-types-abcd-sees-only-the-commands-people-use