Add a plugin to the catalog
A plugin joins this marketplace by being cataloged: referenced by a
SHA-pinned git-subdir source that CI re-verifies fail-closed before the
entry can merge. This holds whether the plugin lives in this same repo (like
github-sdlc-planning and github-pull-requests, which point the
git-subdir source back at this repo — see
catalog-pinning.md) or in a genuinely external one. A
plugin SHA that does not verify does not enter the catalog.
The flow:
author plugin -> its repo attests its tarball (provenance + SBOM + gate verdicts) -> add a git-subdir + sha entry to marketplace.json -> catalog-admission re-verifies the attestations fail-closed -> mergeBefore you start
Section titled “Before you start”- The plugin must follow the canonical layout: a
.claude-plugin/plugin.jsonwith requiredname, plus any ofskills/ agents/ hooks/ .mcp.json. - An external plugin’s source repo must produce an attested tarball — SLSA build provenance, a CycloneDX SBOM, and the seam-signed gate verdicts — at a specific commit. Catalog admission verifies those attestations; it does not re-scan the plugin from scratch.
- A vendored (in-repo) plugin goes through this repo’s own
quality-gates.ymlandrelease.ymldirectly, which produces the same attested tarball this catalog requires of any external plugin. Itsmarketplace.jsonentry starts as a local relative-path source (below). Promoting it to a self-referentialgit-subdirpin is a one-time manual step done after its first tagged release —pin-catalogonly re-pins an existinggit-subdirentry, it does not create one (see catalog-pinning.md). Every release after that first promotion re-pins it automatically.
1. For an external plugin: resolve the source commit SHA
Section titled “1. For an external plugin: resolve the source commit SHA”Pin to an immutable 40-char commit SHA, never a tag or branch. Resolve it at use time:
gh api repos/<owner>/<plugin-repo>/git/ref/tags/<tag> \ --jq '.object.sha'2. Add an entry to marketplace.json
Section titled “2. Add an entry to marketplace.json”Vendored (in-repo) — starts as a local relative-path source; promoting it
to a self-referential git-subdir entry is a one-time manual step done after
its first tagged release (see
catalog-pinning.md) —
every release after that re-pins it automatically, no manual edit needed:
{ "name": "<plugin-name>", "source": "./plugins/<plugin-name>", "description": "<one-line summary>", "version": "0.1.0", "author": { "name": "modeled-information-format" }, "category": "planning", "license": "MIT", "keywords": ["<...>"]}External — a git-subdir + sha entry (the sha is the effective pin;
ref is a human-readable label only):
{ "name": "<plugin-name>", "description": "<one-line summary>", "author": { "name": "<author>" }, "source": { "source": "git-subdir", "url": "https://github.com/<owner>/<repo>.git", "path": "plugins/<plugin-name>", "ref": "v1.2.3", "sha": "<40-char-commit-sha>" }, "license": "<SPDX-id>", "keywords": ["<...>"]}3. Declare cross-plugin dependencies, if any
Section titled “3. Declare cross-plugin dependencies, if any”- Same-marketplace dependency (e.g.
github-pull-requestsongithub-sdlc-planning): a bare name or{"name": "...", "version": "..."}entry in the dependent plugin’s ownplugin.jsondependenciesarray. No catalog-level change needed — resolution happens by name within this marketplace. - Cross-marketplace dependency (e.g.
github-sdlc-planningonmif-docs@modeled-information-format): the target marketplace must be listed in this catalog’sallowCrossMarketplaceDependenciesOn, and the dependent plugin’splugin.jsonnames the dependency explicitly with amarketplacefield. Without the allowlist entry, install fails with across-marketplaceerror.
4. Open a PR — catalog admission runs fail-closed
Section titled “4. Open a PR — catalog admission runs fail-closed”The catalog-admission gate runs on every pull request (so it can be a hard required status check) and fails closed unless all of these hold:
- every external plugin source — including a vendored plugin’s
self-referential entry, once it has one — is pinned to a full 40-char
sha; arefwithout ashais mutable and rejected; - the pinned
shaactually resolves to a plugin: admission fetches the.claude-plugin/plugin.jsonat that commit and rejects the entry if it is not there; - the marketplace
nameis not an Anthropic-reserved name; claude plugin validatepasses (canonical manifest check);- each external entry’s pinned release attestations verify fail-closed (SLSA provenance).
The soft-fail manifest-review (manifest/v1) gate reports the same
SHA-pin findings to the Security tab. catalog-admission is set as a
required check in branch protection so the pin requirement is enforced at
merge, not by convention.
5. Verify, then merge
Section titled “5. Verify, then merge”In-pipeline green is not the acceptance test. Re-verify the pinned plugin’s attestations independently from a clean workstation before approving — the exact commands are in SECURITY.md and ../security/verify.md.
Once admission passes and the attestations re-verify, merge. The merged
marketplace.json is re-signed (cosign keyless) as part of the release so
consumers can prove they fetched the catalog this repo published.
Updating a cataloged plugin
Section titled “Updating a cataloged plugin”For a vendored plugin, bump its version in plugin.json and tag a
release; release.yml’s pin-catalog job re-pins its marketplace.json
entry’s ref/sha/version to match automatically (see
catalog-pinning.md) — no manual marketplace.json edit.
For an external plugin, re-pin its sha to the new commit and let catalog
admission re-verify the new digest’s attestations. Never edit a plugin’s
content in place behind an unchanged SHA — a different content hash is a
different artifact, and the old attestations do not describe it.