Skip to content

Add plugin manifests, validation workflows, and release assertions - #141

Open
jposluns wants to merge 2 commits into
mainfrom
plugin-manifests
Open

Add plugin manifests, validation workflows, and release assertions#141
jposluns wants to merge 2 commits into
mainfrom
plugin-manifests

Conversation

@jposluns

@jposluns jposluns commented Sep 5, 2026

Copy link
Copy Markdown
Owner

Closes TODO 27.3 to 27.7. Makes Clean Language installable as a Claude plugin without changing how it works as a skill.

The zip and the plugin are independent channels over one source tree. The zip is built from cleanlanguage/ and flattened so SKILL.md sits at the archive root, which is what makes one file work for Claude, ChatGPT, and Copilot Studio. The plugin is the repository itself, with skills pointing at ./cleanlanguage in source. Nothing here touches cleanlanguage/, the published archive, the checksum, or the site.

Manifests. plugin.json carries a $schema that resolves; marketplace.json carries none, because the marketplace schema URL returns 404 and a URL that does not resolve is worse than an absent optional field. The marketplace entry sets no version: Claude Code always takes plugin.json's value, so a version here could be masked by a stale manifest.

Validation. plugin-validate.yml gates pull requests on a pinned CLI. Two checks, because neither covers the other: validating the root reads marketplace.json and recurses into plugin.json through the source: "./" entry, while the skill's own frontmatter is read only when the holding directory is named skills, so the skill is staged into one. --strict is load-bearing there, since a missing frontmatter block is only a warning. No paths filter, so the check stays promotable to required without hanging forever-pending on unrelated pull requests.

Drift. plugin-drift.yml runs the same checks weekly against the current release, catching the false green a stale pin produces while readers install with current versions. It gates nothing. Its header records what it does not prove, and that GitHub disables scheduled workflows after 60 days of repository inactivity.

Release assertions. release-package.sh gains two, reading the git tree at HEAD like the rest of that script: plugin.json's version must equal the Version: line in SKILL.md, and marketplace.json's plugins[0].name must equal plugin.json's name. Both are proved to fail on a deliberate mismatch. release-dry-run.sh now bumps the plugin version alongside the skill version, because a real release bumps both together and the dry run would otherwise fail on a mismatch it created itself.

CLI version 2.1.261 was verified against all four positive and negative cases before being chosen as the pin.

All ten local gates pass, each run to a real exit code.

Not in this PR: 27.8, making these checks required on main. Until that lands they are advisory, since only Cloudflare Pages is required today.

Makes Clean Language installable as a Claude plugin without changing how it
works as a skill. The zip and the plugin are independent channels over one
source tree: the zip is built from cleanlanguage/ and flattened so SKILL.md sits
at the archive root, while the plugin is the repository itself, with skills
pointing at ./cleanlanguage in source. Nothing here touches cleanlanguage/, the
published archive, or the site.

.claude-plugin/plugin.json and .claude-plugin/marketplace.json at the root.
plugin.json carries a $schema that resolves; marketplace.json carries none,
because https://json.schemastore.org/claude-code-plugin-marketplace.json returns
404 and a URL that does not resolve is worse than an absent optional field. The
marketplace entry sets no version: Claude Code always takes plugin.json's value,
so a version here could be masked by a stale manifest.

plugin-validate.yml gates pull requests on a pinned CLI. Two checks, because
neither covers the other: validating the root reads marketplace.json and
recurses into plugin.json through the source: './' entry, while the skill's own
frontmatter is read only when the holding directory is named skills, so the
skill is staged into one. --strict is load-bearing there, since a missing
frontmatter block is a warning. No paths filter, so the check stays promotable
to required without hanging forever-pending on unrelated pull requests.

plugin-drift.yml runs the same checks weekly against the current release, to
catch the false green a stale pin produces while readers install with current
versions. It gates nothing. Its header records what it does not prove and that
GitHub disables scheduled workflows after 60 days of repository inactivity.

release-package.sh gains two assertions reading the git tree at HEAD like the
rest of that script: plugin.json's version must equal the Version: line in
SKILL.md, and marketplace.json's plugins[0].name must equal plugin.json's name.
Version 2.1.261 was verified against all four positive and negative cases before
being chosen as the pin.
My own assertion caught this before it could ship: the dry run simulates a
synthetic next release by bumping the Version: line in SKILL.md but left
plugin.json alone, so release-package.sh failed on a mismatch the dry run had
created itself. That would have red-CI'd the release gate on every run.

A real release bumps both together, which is what the assertion enforces, so
the dry run now does the same and is a faithful simulation rather than a broken
one.

Both assertions are proved to fail on a deliberate mismatch: a wrong
plugin.json version and a wrong marketplace plugins[0].name each fail with the
expected message, and the tree was restored and re-verified green afterwards.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant