feat: add the webgrip bundle plugin so the estate can be namespaced #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/bundle-plugin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PR
What / why
The catalog offers per-skill plugins only, so every skill loads flat. That makes this estate un-installable next to another estate shipping the same names — and one exists: Code 14's
ai-skillscollides on eight of our ten (adr-writer,domain-language,guard-secrets,harvest-knowledge,protocol-fit-research,skill-usage,skillsmith,worktree-herd-dev). Installed flat side by side, whichever lands last silently wins, and the agent has no way to address the other. Same-named skills across estates encode different org policy, so that's a real loss, not a cosmetic one.This makes the repo root a plugin.
/plugin install webgrip@ai-skillsloads all ten skills prefixedwebgrip:<skill>, so the two estates coexist and stay individually addressable. Per-skill plugins are untouched and still work for anyone wanting a minimal surface.What's in it
.claude-plugin/plugin.json— the bundle manifest, namewebgrip(deliberately not askills/dir name, or the catalog would carry two entries with that name).hooks/hooks.json— re-declares theguard-secretsPreToolUse andskill-usagePostToolUse hooks withskills/<name>/prefixes. This one is load-bearing: a skill's ownhooks.jsonresolves${CLAUDE_PLUGIN_ROOT}to that skill's directory, which under the bundle is the repo root, so${CLAUDE_PLUGIN_ROOT}/scripts/guard_secrets.pypoints at a path that doesn't exist. Claude Code loads that without complaint — the bundle would ship both hooks silently dead.check_manifests.py— validates the bundle manifest, rejects a bundle name colliding with askills/dir, and fails if a skill grows a hook the bundle doesn't mirror. That last check is what stops the trap above from reappearing; verified against a negative case (removed the PreToolUse entry → fails, naming the exact command it wants).sync_marketplace.py— emits the bundle entry (source: "./") ahead of the per-skill entries. Catalog stays generated, never hand-edited.release_skills.py— bumps the bundle version whenever any skill releases, at the strongest level in the run. Consumers only pull an update when the catalog version moves, so a release leaving it untouched would never reach anyone installed on the bundle. Versioned, deliberately never tagged —<skill>-v<X.Y.Z>stays the only release tag and unit of pinning.Verified in a live session:
claude plugin details webgripreports Skills (10), Hooks (2) PreToolUse + PostToolUse.Notes for review
${CLAUDE_PLUGIN_ROOT}differs between the two install shapes. The new check is what keeps the pair honest instead of a comment asking people to remember.1.0.0.check_plugin_commits.pyonly inspects four-partskills|plugins/<name>/.claude-plugin/plugin.jsonpaths, so a root manifest isn't covered by the no-hand-bumps guard. Worth deciding whether that guard should grow to cover the root manifest now that one exists.Checklist (author)
npm run check && npm testpass locallyfeat)marketplace.jsonregenerated viasync_marketplace.py; no skillversiontouchedevals/, or bundled skill script changed; this is packaging onlyChecklist (reviewer)
skills/guard-secrets/scripts/guard_secrets.py,skills/skill-usage/scripts/log_usage.py)hook_commands()/ mirror check incheck_manifests.pyas application code${CLAUDE_PLUGIN_ROOT}/. A future hook without that prefix would pass trivially — acceptable, or should it fail loudly?vikunja-product-owner's.mcp.jsonalso needs bundle-level re-declaration, or whether MCP config resolves differently from hooks under a bundle installThe catalog offered per-skill plugins only, so every skill loaded flat. That makes the estate un-installable next to another one shipping the same names (adr-writer, domain-language, guard-secrets, harvest-knowledge, protocol-fit-research, skill-usage, skillsmith, worktree-herd-dev all collide with Code 14's estate) — whichever installs last wins, silently. Make the repo root a plugin, so `/plugin install webgrip@ai-skills` loads all ten skills prefixed webgrip:<skill> and the two estates can coexist. - .claude-plugin/plugin.json: the bundle manifest, name `webgrip` - hooks/hooks.json: re-declares the guard-secrets and skill-usage hooks with skills/<name>/ prefixes. A skill's own hooks.json resolves ${CLAUDE_PLUGIN_ROOT} to the skill dir, which is the repo root under the bundle — without this the bundle ships both hooks silently dead - check_manifests.py: validates the bundle manifest, rejects a bundle name that collides with a skills/ dir, and fails if a skill grows a hook the bundle doesn't mirror - sync_marketplace.py: emits the bundle entry (source "./") ahead of the per-skill entries; the catalog stays generated, never hand-edited - release_skills.py: bumps the bundle version whenever any skill releases, at the strongest level in the run. Consumers only pull an update when the catalog version moves, so a release that left it untouched would never reach anyone on the bundle. Versioned, never tagged