Development record
The plugin installs from the curated release artifact, not the repo, and every cut release reaches users automatically
Press Release
Installing the abcd plugin now delivers the plugin, not the source tree that builds it. Today the plugin cache holds a second full clone of the repo — 11 MB of
internal/,cmd/,evals/,go.sumand the.abcd/design record — so that the harness can read 296 KB of commands, agents and hooks. With this intent the marketplace's PLUGIN source points at a curated zip published with each release: about 340 KB, no source, no git history, and no Go toolchain or git needed on the machine. Because the catalog names the latest release rather than a pinned one, cutting a release is the whole publication step — the next update a user takes brings the new surface, and the bootstrap that already tracksreleases/latestbrings the matching binary into the same fresh plugin root. Surface and binary arrive from one release, together, with no manual step between the tag and the user.
What this deliberately does NOT do, stated here because the obvious reading is wrong: the marketplace SOURCE stays
<personal account>/abcd-cli, so adding the marketplace still clones the repo (~16 MB including git history and.abcd/). The design record therefore keeps reaching users, and this intent does not stop it. That is an accepted cost, taken deliberately in the grill below: the alternative front door is a 99-character catalog URL typed into the harness as a slash-command argument, against 38 characters for the repo form, and the repo is public so the residue is weight rather than exposure.
"I install a plugin, I expect a plugin," said Alice, who added abcd on a laptop with no Go toolchain. "I got a source tree, somebody's architecture decision records, and a 54-second wait the first time I ran a command." Bob, who maintains abcd, had the mirror-image complaint: "Cutting a release published binaries, and then the plugin surface just… didn't move. I'd tag, and nothing reached anyone until I remembered the second step." Carol, who reviews what their team installs, put it plainest: "I could not tell you which of those 11 megabytes in the plugin cache the harness actually executes."
Why This Matters
[[adr-28-single-repo-curated-release]] already decided this. It says .abcd/**
"stays in-tree" but is "excluded from the release artifact by packaging,"
and that "the repo is the marketplace." The packaging half exists and works:
.abcd/config/launch-payload.json names the payload (.claude-plugin,
commands, agents, hooks, scripts, docs, README.md, LICENSE), and
abcd launch --dry-run computes it today — 54 files, 0 scan hardfails.
What is missing is the last hop. .github/workflows/release.yml publishes the
four binaries and checksums.txt and nothing else, so the curated bundle is
computed, gated, and then discarded. Meanwhile
.claude-plugin/marketplace.json carries "source": "./" — a relative-path
source, meaning "the marketplace repo is the plugin." The curated artifact and
the install path have never been connected, which is what AGENTS.md concedes
when it says the launch bundler "denies the namespace structurally, though that
filter has yet to run on a cut release."
Measured on a first manual install (2026-08-10):
| what a user gets | size | why |
|---|---|---|
marketplaces/abcd-marketplace |
16 MB | whole repo incl. 4.5 MB .git, to read one catalog file |
plugins/cache/.../<sha> |
22 MB | whole repo again (11 MB) + the bootstrapped binary (11 MB) |
| what the harness reads | 296 KB | commands/ + agents/ + hooks/ + .claude-plugin/ |
The second half — automatically — is the part with no existing answer at all.
The binary already has this property: hooks/bootstrap.sh fetches
releases/latest, so a new cut reaches users with no edit anywhere. The plugin
surface has the opposite property: it tracks the repository tip through a
relative-path source, which is why [[itd-105]] had to invent a version-skew
notice to report surface-ahead-of-binary drift in the first place. If the
catalog names the latest release instead, both halves resolve to the same cut by
construction — a plugin update creates a fresh plugin root, and a fresh plugin
root is exactly what makes the bootstrap fetch the matching binary. The skew
this project currently reports becomes a skew it cannot have.
Decisions (grilled 2026-08-11)
The maintainer resolved the shape questions in a grill session; these are commitments, not options.
- The marketplace source stays
<personal account>/abcd-cli; only the PLUGIN source becomes an archive. The install line is typed into the harness as a slash-command argument, where a catalog URL runs to 99 characters against 38 for the repo form. Users are the audience and contributors are incidental, so the short front door wins. Accepted cost, stated rather than discovered later: the marketplace clone remains (~16 MB including git history), so the plugin cache falls from ~22 MB to the payload plus the binary while the catalog clone does not move. The "abcd stops shipping its design record to strangers" half of the press release therefore does NOT hold under this shape — and since the repo is public, that residue is weight, not exposure. - No second repository. A public-facing
abcdbesideabcd-cliwas considered and rejected: it is the dev→public mirror [[adr-28-single-repo-curated-release]] retired, and it hides nothing, becauseabcd-cliis already public. - [[adr-28-single-repo-curated-release]] needs no amendment. An earlier reading of this intent assumed the catalog would move off the repo, which would have falsified "the repo is the marketplace". With the catalog still committed, that clause stays literally true.
- The archive is unpinned, and the attestation extends to cover it. A
committed catalog cannot carry a per-release
sha256without a per-release commit, and a bot commit to the default branch during a release run tripsrelease.yml's no-branch-commit tripwire. So the catalog names a stablereleases/latest/download/URL and pins nothing. The integrity story is the signingrelease.ymlALREADY performs —actions/attest-build-provenanceover the binaries andchecksums.txt— extended to the plugin zip, so both halves of the distribution carry the same guarantee instead of one having none. Overtaken 2026-09-23: the product thinker's ruling E1 chose the pinned archive (address andsha256, rewritten by every ship), recorded in adr-2609231048308186. - An offline signing key is recorded as a future extension, not adopted now. The attestation proves "built by this workflow in this repo", which defends a stolen upload token and not a full repository compromise, because the identity root is still the forge. A key held offline (minisign) closes that and costs a key to keep. It becomes relevant, and is deliberately deferred.
- One bundle, less
docs/assets/**. The plugin zip is the release payloadlaunch-payload.jsonalready declares, rather than a second manifest to keep in step. But two PNGs —intro.pngat 1.07 MB andlogo.pngat 182 KB — are 1.25 MB of a 1.6 MB payload, against 42 KB for all eight markdown files, and nothing the harness loads reads the shippeddocs/at all (thedocs/paths incommands/andagents/address a lifeboat's internal record or a target repo's pages, never this one's). Excluding the assets takes the payload to ~340 KB and keeps the prose. Two mechanics the implementer needs and the review surfaced:launchPayloadConfig(internal/core/launch/includes.go) carries anincludeslist and no exclusion syntax, so this is expressed as NARROWER INCLUDES, never as an exclude; andREADME.md— itself in the payload — embeds twodocs/assets/img/*.pngtags, so trimming the assets leaves a payload README with broken images unless those tags are pointed at absolute URLs in the same change. - No migration is owed, because there are no users yet. The archive source requires harness v2.1.224 or later; below it the install fails, and older hosts still fail to load the marketplace. With no installed base the question is moot and is not designed for. The version floor is still stated in the README, and the multi-harness install story is captured separately as a getting-started concern rather than carried here.
Superseded direction (pre-grill)
Kept only to show what the grill moved. Every bullet below is now governed by the Decisions above where the two disagree.
- Mechanism: an
archiveplugin source. It is the only source type that needs neither git nor npm on the user's machine, and the harness looks for.claude-plugin/at the archive root or one folder down, which the curated bundle already satisfies. - Automation: a stable, unpinned
releases/latest/download/URL, so the catalog is written once and never rewritten per cut. This is what makes "cut a release" the entire publication step. - Catalog delivery: by direct URL to
marketplace.json, so adding the marketplace fetches one file rather than cloning 16 MB. This keeps ADR-28's "the repo is the marketplace" literally true — the catalog stays a file in this repo; only the fetch method changes. - The binary keeps its own path. The zip carries the surface; the bootstrap keeps fetching the platform binary from the same latest release. Four platform binaries in one archive would quadruple every download to save a step that already works.
What's In Scope
- Publishing the curated bundle as a zip release asset from the existing release workflow, and extending the build-provenance attestation to cover it.
- Repointing
.claude-plugin/marketplace.json's PLUGIN source at that asset. The marketplace source stays<personal account>/abcd-cliand the catalog stays committed, so the README'saddline does not change. - Amending
.abcd/config/launch-payload.jsonso the declared payload is what a plugin needs — see the Decisions ondocs/assets. One manifest, not two. - The record consequence, narrowly:
AGENTS.mdstates that.abcd/**is "present in every repository checkout — marketplace installs and release source archives included." Under the decided shape that stays TRUE of a marketplace install, whose clone remains, and becomes false only of the plugin payload. The sentence is re-scoped, not deleted. Neither [[adr-28-single-repo-curated-release]] nor [[adr-19-plugin-json-version-carve-out]] needs amending, and the change should say so explicitly so a later reader does not re-open them. - A stated minimum host version in the README, since
archivesources require harness v2.1.224 or later and fail loudly below it.
What's Out of Scope
- The release/versioning pipeline itself — how versions are derived, bumped, and tagged stays with [[itd-73-derived-versioning]] and [[adr-31-derived-versioning-from-intents]]. This intent consumes a cut, it does not change how one is decided.
- The binary's own distribution and trust bar — [[itd-105]] settled that (same-origin checksums, build-from-source as the full-trust escape hatch) and this intent does not reopen it.
- Windows support, which remains its own future intent.
- The binary's user-invoked update verb — [[itd-130]] (builds on this intent
and [[itd-105]]). Its commitment that matters HERE:
abcd updatenever replaces a plugin-root binary — in that context it refuses and names the host's plugin-update path — so the one-cut surface/binary coherence this intent establishes stays structural rather than depending on updater discipline. - Restructuring
docs/itself, or where its images live in the repository. The manifest is amended to stop shippingdocs/assets/**in the payload; how the repository organises those assets is untouched.
Acceptance Criteria
Given-When-Then per the itd-1 discipline.
- Given a machine with no Go toolchain and no git, when a user adds the
abcd marketplace and installs the plugin, then the install succeeds and
the PLUGIN ROOT contains the curated payload only — no
internal/, nocmd/, no.abcd/, nogo.mod, and no git history. (The marketplace clone is out of scope by decision and is not what this criterion measures.) - Given a completed install, when the user runs any
/abcd:*command, then it executes against the plugin-root binary without falling back to a source build — because no source is present to build from. - Given an installed user at release N, when release N+1 is cut and published and the user takes a plugin update, then the new surface and the N+1 binary are both present, and no maintainer edited the catalog, the manifest, or any workflow between the tag and that update.
- Given a release cut, when the release workflow completes, then the curated zip is attached to the release with its digest recorded next to the binaries', and the workflow's no-branch-commit tripwire still passes — the release job pushed nothing to any branch.
- Given the published zip, when its contents are compared against
.abcd/config/launch-payload.json, then they match the bundleabcd launch --dry-runreports, so the preview and the published artifact cannot disagree. - Given a host older than the minimum version an
archivesource requires, when a user attempts the install, then the failure names the required host version rather than presenting as a broken plugin.
SOTA
Surveyed on 2026-10-23; sources, evidence tiers and the local fit risks are in the install-update-release SOTA note.
- The update rule is documented now. The harness's plugin-loading
reference states that a plugin's version is the manifest
version, else the entry's, else for an archive "thesha256pin in the marketplace entry, or the digest of the downloaded file when there is no pin", and that a cached copy is replaced only when that computed version changes. The 2026-08-20 experiment's "observed, not contractual" caveat is retired; its corollary stands (a re-published zip under an unchanged version never reaches installed users). - The archive source is as assumed.
url(HTTPS only) andsha256("refuses a download that doesn't match"), harness v2.1.224 or later, root at the top of the zip or one level down, 256 MiB and five redirects at most (marketplace reference). The vendor's own hosting guide says "Pin each archive withsha256", which is the E1 ruling. - Automatic reach is opt-in. Background auto-update is off by default for
a third-party marketplace; a user enables it per marketplace or an admin
sets
autoUpdatein managed settings; the pass runs up to ten minutes after the first message and the running session keeps its loaded version (hosting guide, loading reference). "Every cut reaches users" holds for opted-in users. releases/latestis picked by commit date: "the most recent non-prerelease, non-draft release, sorted by thecreated_atattribute", which "is the date of the commit used for the release" (releases REST reference). A hotfix cut from an older commit would not become latest; the bootstrap's cold start still follows that URL.- Attestations are SLSA Build L2 on their own, L3 only through a reusable
workflow;
gh attestation verifytreats only the certificate and the timestamps as tamper-proof and the predicate as "often user-controlled" (artifact attestations concept,gh attestation verifymanual). - Immutable releases are generally available (2025-10-28): published
assets "can't be added, modified, or deleted", the tag is protected, and a
release attestation is minted; the draft-then-publish flow they require is
the one
release.ymlalready runs. Enabling the setting makes the pin's target unchangeable by anyone with push access. - The offline key stays deferred: minisign's trusted comment covers the downgrade case, and nothing found forces the key now.
Path 1 — adopt the forge's features as they are: the documented version rule closes open question 1, the pin closes the install-time verification gap, and enabling immutable releases is the one addition worth a record. The independent fit-challenge runs at plan time.
Open Questions
- (SOTA 2026-10-23) Immutable releases still allow a superseded Release and its tag to be deleted, but the tag name can never be reused and a published release's assets cannot be changed or removed; does the record keep itd-70's prune under that constraint, or retire it (itd-70)?
- (SOTA 2026-10-23) Does the bootstrap's cold start resolve the tag from the
pinned catalog or the release mark rather than from
releases/latest, so a hotfix cut from an older commit is not skipped? - Does an unpinned archive at a stable URL actually re-check for a new
release? The remaining empirical unknown, and the only thing between this
shape and settled. The catalog never changes and the URL never changes, so the
harness must fetch or head the zip to notice a new cut. The published version
ladder says an archive source resolves its version from the
sha256pin, or from the digest of the downloaded file when no pin is set — which implies a fetch — but "implies" is not "observed". Settle it against a real pre-release cut before Cut B is called done, not by reading the documentation twice. It must be a FULL release: GitHub'sreleases/latestexcludes prereleases, so a prerelease leavesreleases/latest/download/pointing at the previous cut and the experiment reports "no update" whatever the harness does — a false negative that would wrongly void the unpinned decision. The no-users-yet decision makes a throwaway full release free. Narrowed by local experiments, 2026-08-20 (agent-run, recorded here for the Cut B decision): against harness v2.1.237, an archive-source plugin at a stable URL with nosha256pin was installed from a local rig, the served bytes were swapped in place, andplugin updatewas run — in BOTH version shapes, since the shipped artifact declares a version (adr-19) while a bare zip does not. Observed, no-version shape: the harness re-downloaded on every update invocation (server access log), installed new bytes into a fresh cache dir named by the digest's first 12 hex chars, and determined "already at the latest version" on a no-change run by re-downloading too. Observed, declared-version shape (the one abcd ships): the re-download still happens on every update — there is no pre-download version skip — but the INSTALL decision is version-keyed: same declared version with new bytes is fetched, compared, and DISCARDED ("already at the latest version", old content retained); a version bump installs. Corroborating basis for the mechanism claim: the update-engine code embedded in the harness binary was read directly, and its archive branch fetches before any version comparison — implementation-observed on one harness version, not a documented contract. Three consequences for this intent: (1) the unpinned decision holds — cutting a release propagates, because every cut bumps the declared version; (2) an operational invariant, recorded here so it is never discovered in production: a re-published plugin asset under an unchanged version never reaches already-installed users — fixing a botched zip requires a version bump, i.e. a new cut; (3) a baremarketplace updaterefreshes the CATALOG only and never touches the archive — the re-fetch is a plugin-update act (explicit, or the session auto-update pass). Accepted risk, stated rather than implied: the whole publication story rests on harness behaviour that is observed but not contractual, and adr-38 forbids any ambient canary that would detect a future harness regression — the only detector is a user runningversion --check. Residue still owed at Cut B: the GitHubreleases/latest/download/redirect chain and the background auto-update pass were not in the local loop, so the real-cut check below stands, now as confirmation rather than as the open question. The next sentence's cache-forever branch is therefore believed dead; it remains recorded because the real-cut check is what retires it. If it turns out the harness caches by URL and never re-fetches, the unpinned decision above is void — and note the fallback is foreclosed TWICE, not once: a per-release version write into the committed catalog tripsrelease.yml's no-branch-commit tripwire AND violates [[adr-20-manifest-version-lockstep]]'s source polarity, which requires/plugins/0/versionABSENT in the working tree. That branch has no in-record recovery and would need adr-19/adr-20 amended, so it is a genuine dead end rather than a cheaper alternative. - Does extending the attestation to a new asset cost anything? The decision to
cover the plugin zip assumes adding it to the attest step's
subject-pathis sufficient. The signing path itself is NOT unproven — v0.4.2 carries a verifiable build-provenance attestation for its binaries, so the machinery runs on a real cut. Only the extension is unexercised. - Nothing verifies the zip at install time, and that gap is accepted rather than
closed.
bootstrap.shchecksums the binary and refuses on mismatch, per [[itd-105]]'s bar; the harness would verify an archive against asha256pin, which the Decisions decline. So the two halves carry the same attestation but not the same operative check — the binary is machine-verified on every install and the zip is verified by nobody. Recorded so the asymmetry is a known position, not an oversight, and so the future offline-key work has a stated target.
Prerequisites
- iss-205 is resolved; the sequencing constraint it imposed is met. Every
file in
commands/resolves the binary from${CLAUDE_PLUGIN_ROOT}first,PATHsecond, withgo run ./cmd/abcdonly in a source checkout, and a detector test underinternal/core/launchfails anycommands/file that regresses the ladder. A curated payload withoutcmd/or source therefore no longer kills the command surface; what this intent still owes is verifying that ladder on a real archive install before the payload slims. - iss-206 gains a second cause. An archive install's cache directory is not
named for a commit, so
plugin_shastaysunknownfor a reason unrelated to the 40-hex gate. Its fix should stop assuming a commit sha rather than widen the gate. Note also that if this intent lands as designed, the skew notice's reason for existing largely evaporates — surface and binary come from one release — which is worth weighing before investing in repairing it.
Audit Notes
Empty. Populated by intent-fidelity-reviewer when intent moves to shipped/.
Frontmatter
| id | itd-108 |
|---|---|
| type | intent |
| lifecycle | drafts |
| severity | major |
.abcd/development/intents/drafts/itd-108-the-plugin-installs-from-the-curated.md
open on GitHub ↗ · commit history ↗
References2
Referenced by6
- adr-38Implicit checks are disk-only — the network answers only an explicit askrelated
- adr-2609231048308186The catalog pins the latest release's plugin archiverelated
- iss-205Every one of the 17 files under commands/ resolves the binary as bare 'abcd' on PATH, with a documented fallback to 'go run ./cmd/abcd'. None of them mentions ${CLAUDE_PLUGIN_ROOT} — grep confirms zero occurrences across commands/. But itd-105 decided 'PATH: deferred to ahoy', so a by-the-book marketplace install provisions the binary ONLY into the plugin root and leaves nothing on PATH. The two halves therefore still do not meet: bootstrap installs a checksum-verified binary that no command can see. Observed on the first manual install (2026-08-10): 'which abcd' reports not found, and bare /abcd fell through to the source-build path — 54s, requiring a Go toolchain, and succeeding only because the marketplace clone happened to be on disk. On a clean machine without Go the entire plugin command surface is non-functional despite a healthy binary sitting in the plugin root. This directly contradicts itd-105's own premise: 'Nobody clones the repo to use abcd: the binary is all a user needs.' The fix is supported and cheap — the plugins reference confirms ${CLAUDE_PLUGIN_ROOT} substitutes in 'Skill and agent content: anywhere the placeholder appears'. Put "${CLAUDE_PLUGIN_ROOT}/abcd" first in the resolution order in all 17 command files, PATH second, 'go run' last.related
- iss-206The version-skew notice promised by itd-105 ('Given an installed surface newer than the newest published binary, when a session starts, then the skew is surfaced') can never fire, and fails silently. itd-105's warrant says the harness names each plugin cache directory for the commit it was cloned from; that holds only as a 12-character SHORT sha. Observed .binary-meta from the first manual install (2026-08-10): plugin_root_basename=872f0a1e6aab, which is exactly 'git rev-parse --short=12 HEAD', and plugin_sha=unknown. hooks/bootstrap.sh gates plugin_sha on exactly 40 hex chars, and internal/surface/cli/skew.go resolvedSHA() requires 40 again, so plugin_sha is permanently 'unknown' and binarySkewNotice() returns "" forever on every machine. This is precisely the failure the comment at hooks/bootstrap.sh anticipated and armed a diagnostic for — recording the raw ungated plugin_root_basename beside the gated field so that 'why has the skew notice never fired' has an answer in the file rather than only in a comment. The diagnostic worked exactly as designed; the gate is what is wrong. Fix direction: accept 7-40 hex in both places and compare on the shorter value's prefix length, since one side is a short sha and the other a full one.related
- itd-111A Stale abcd Never Answers Silentlybuilt on by
- itd-130`abcd update` completes a chosen update in one verbbuilt on by
mentions15
- 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-coded
- 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-28One repository, a curated release artifact — no dev→public mirror
- adr-31The release version is derived from the intents in it, never authored
- iss-216Install instructions need a getting-started page once more than one harness is supported, and the version floor needs a home in it. Raised at the itd-108 grill (2026-08-11) while ruling that no migration is owed to existing users, because there are none yet.
- iss-321persona pronoun discipline itd-79 is violated by gendered pronouns in seven intent records including a shipped intent
- iss-338Seven registry personas are given he/she/his/her pronouns across intents/drafts (itd-92,97,98,108,126,128) and intents/shipped (itd-100), violating the they/them persona discipline (itd-79); review-enforced convention with no mechanical gate, concentrated in one 2026-08 authoring batch — a cosmetics-batch cleanup
- iss-379In-process build-provenance (SLSA attestation) verification in abcd update. Trigger: the trust gap the README concedes is worth closing (e.g. after the public flip). itd-130 ships at the same-origin checksums bar (itd-105); the release workflow already attests binaries and checksums.txt, so abcd update could verify the attestation before the swap for a stronger root of trust than same-origin checksums — at the cost of a heavy dependency (sigstore-go). Twin of itd-108's deferred offline signing key (minisign): both close the 'the forge is the identity root' gap that an attestation alone does not.
- iss-2609231050273096abcd update verifies a downloaded binary only against the checksums.txt published in the same GitHub release (internal/core/update/update.go), so it catches corruption in transit but not a release page whose binary and checksums.txt were both replaced; the binary needs an independent check next cycle, such as a digest committed in the repository's history the way the plugin catalog now pins the plugin archive (adr-2609231048308186), or verification of the release's signed build-provenance attestation (a new verification dependency needs sign-off first). Ruled by the product thinker on 2026-09-23 (E5, 10:27Z): harden next cycle; this release keeps the same-page check.
- itd-1Every Intent Has Acceptance Criteria; Every Audit Verifies Them
- itd-70Drawn-out intent
- itd-73abcd Cuts the Version — You Never Type One
- itd-2609292106557115A fresh machine installs abcd through one small starter that hands over to a trusted abcd binary
- itd-2610161617096084After a plugin update, abcd says what changed for this repository
- spc-32`abcd update` — fetch, verify, swap, in one verb