Setup
Install paths, supported manifest layouts, and the choice between versioning approaches. Command semantics live in Commands; hook and Action snippets in Git hook and GitHub Action.
Install
| Surface | Install |
|---|---|
| CLI | uv tool install agent-marketplace-versioner==<version> (PyPI) → agent-marketplace-versioner on PATH; uvx agent-marketplace-versioner@<version> runs it without installing |
| pre-commit hook | Repo stanza in the consumer's .pre-commit-config.yaml, then prek install — see Git hook |
| GitHub Action | uses: Jamie-BitFlight/agent-marketplace-versioner@<rev> — see GitHub Action |
No per-consumer adapter or versioner-specific configuration file is required.
The CLI needs git on PATH.
Pin one release everywhere
Pin the hook rev, the Action ref, and the CLI version to the same release.
List the release tags:
git ls-remote --tags https://github.com/Jamie-BitFlight/agent-marketplace-versioner
An annotated tag prints two lines; the refs/tags/vX.Y.Z^{} line holds the
commit SHA. Use that SHA for rev: and uses:, with the tag as a comment:
# .pre-commit-config.yaml
- repo: https://github.com/Jamie-BitFlight/agent-marketplace-versioner
rev: 562755fd8f14ee76280130eb6db4612af72fdf45 # v0.1.2
hooks:
- id: agent-marketplace-versioner
# .github/workflows/*.yml
- uses: Jamie-BitFlight/agent-marketplace-versioner@562755fd8f14ee76280130eb6db4612af72fdf45 # v0.1.2
To upgrade, repeat the lookup and replace every pin in one commit. The moving
v1 tag advances with each v1.x.y release, and exists only once a v1.x.y
release does.
Manifest layouts
The versioner manages Git-visible manifests at these paths and ignores every other path, including gitignored files:
- Plugin manifests —
.<host>-plugin/plugin.json(source root: the directory holding.<host>-plugin/), and<name>.plugin.jsonor<name>-plugin.json(source root: its own directory). -
Marketplace catalogs —
.<host>-plugin/marketplace.jsonand.agents/plugins/marketplace.json. -
Pi packages —
package.jsonin a plugin manifest's source root, when it declares apikey or thepi-packagekeyword. It shares that root's version but never enters a catalog. Any otherpackage.json, and a Pi package with no plugin manifest in its directory, is ignored.
All plugin manifests under one source root share one version. A repository root
holding .claude-plugin/plugin.json, .codex-plugin/plugin.json,
kimi.plugin.json, and a Pi package.json bumps all four together.
A manifest inside another plugin's source root, such as an eval fixture's
plugin.json or marketplace.json, is content of the enclosing plugin, not a
manifest. A nested plugin stays a plugin when a catalog outside every plugin
root lists it as a local source. A plugin at the repository root encloses
nothing, so manifests below it, including its own fixtures, still count.
A catalog is versioned only when it already carries version or
metadata.version. A versionless catalog still has its entries reconciled and
never gains a version field.
Exclude manifests from formatters and linters
The versioner rewrites plugin.json, *.plugin.json, *-plugin.json, and
marketplace.json with a canonical style (2-space indent, trailing newline).
If a commit-time formatter (biome, prettier, oxfmt/oxlint, ...) also touches
them, each pass reformats against the other and hooks cycle until one side
stops; CI format checks flag the same churn. Exclude these files everywhere
they are formatted or linted — the formatter's own config so CI inherits it:
# prettier: .prettierignore
**/plugin.json
**/*.plugin.json
**/*-plugin.json
**/marketplace.json
// biome.json — formatter and linter; "**" must lead for negations to apply
{
"files": {
"includes": [
"**",
"!**/plugin.json",
"!**/*.plugin.json",
"!**/*-plugin.json",
"!**/marketplace.json"
]
}
}
For oxlint/oxfmt, list the same four globs in its ignore/exclude option.
For pre-commit, also set the formatter hook's exclude so it never receives the
manifests, keeping each versioner-managed file owned by exactly one tool. When
the formatter runs only through pre-commit, this exclude is the whole
exclusion:
- id: oxfmt
exclude: '(^|/)([^/]+[.-])?plugin\.json$|(^|/)marketplace\.json$'
Choosing a versioning approach
Hook + CI — plugin authors commit locally and want immediate cache-busting:
- The pre-commit hook runs
syncon every commit: plugin manifest versions bump, catalog entries reconcile, and both stage locally. - A post-merge workflow on the default branch runs the Action with
command: sync,marketplace: true: catalog entries reconcile and existing marketplace versions bump. The workflow owns commit and push of the result. When branch protection blocks the push, the workflow opens a PR holding every changed manifest;GITHUB_TOKENPRs trigger no workflows, so use a token whose PRs do. Skip this step when every catalog is versionless. - Optional belt-and-braces: a PR workflow runs the default
command: checkgate.
CI-only — commits arrive from agents or bots, or hook maintenance is unwanted; enforcement lives entirely at merge:
- A PR workflow runs the Action default
command: check(checkout withfetch-depth: 0). It exits 1 when changed plugin content lacks a version bump. - The PR author — human or agent — clears the gate by running
agent-marketplace-versioner repairlocally and committing the bumps. - The same post-merge workflow as Hook + CI step 2 reconciles catalog entries and bumps catalog versions. With no hook, it runs even when every catalog is versionless.
Merge-time — the hook never bumps; the post-merge workflow assigns patch bumps. Many open PRs touch the same plugins, and per-commit bumps on each branch conflict on every rebase:
- The pre-commit hook uses the check-only id
agent-marketplace-versioner-check(reconcile --dry-run --staged). It reports component and catalog drift in staged content and edits no version. To clear the drift, runreconcile; it minor-bumps each plugin whose component arrays change. - A branch may still set a deliberate minor or major version in its manifests. That version can still conflict with post-merge patch bumps on the default branch.
- A post-merge workflow on the default branch checks out with
fetch-depth: 0, runsrepair, thensync --marketplace --base-ref <before> --head-ref <sha>, and owns commit and push.repairexits 1 in a shallow checkout. It patch-bumps each manifest whose source root changed after the last version-changing commit. A branch version merged by squash, or by a merge commit whose branch bumped last, gets no extra bump. When the default branch also changed that plugin after the branch point,repairadds a patch on top of the branch version, for example1.1.0becomes1.1.1.
Choose Hook + CI when local plugin evaluation matters during development; choose CI-only when one enforcement point at merge beats per-machine setup; choose Merge-time when concurrent PRs on the same plugins conflict on versions.
Troubleshooting
| Symptom | Fix |
|---|---|
check exits 1 in CI |
Changed plugin content is missing a version bump; run repair, commit the result |
check cannot find a revision |
Shallow checkout; use fetch-depth: 0 so both refs exist locally |
| A manifest never bumps | Its path matches no manifest layout, or Git ignores it |
Hook runs an older release than its rev tag now names |
Runners cache the revision first resolved for a moving tag; refresh with prek clean && prek install --install-hooks — see Git hook |
| Action ran but nothing was committed | By design; the Action never commits or pushes — the calling workflow owns publication |
git executable not found in PATH |
Install git; manifest discovery shells out to it |