claude-skill-plugin-packaging — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited claude-skill-plugin-packaging (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
apple-dev-skills) into a new project..claude/skills/<lib>/... not found".Claude Code discovers plain project skills only at depth 1: .claude/skills/<skill>/SKILL.md. It does not recurse into subdirectories.
Consequences (each has burned someone):
.claude/skills/<lib>/ puts SKILL.md at.claude/skills/<lib>/skills/<skill>/SKILL.md (depth ≥ 2) → NOT discovered.
vestigial — the marketplace-installed copy is what loads, the submodule does nothing for discovery. (This is why a repo can carry .claude/skills/superpowers yet superpowers actually loads from the plugin cache.)
So: to share skills across repos, package them as a plugin and distribute via a marketplace. A bare submodule alone never works.
A repo becomes a Claude Code plugin with a manifest, and a marketplace (catalog) with a second manifest. One repo can be both (single-repo model):
your-skills-repo/
├── .claude-plugin/
│ ├── plugin.json # makes it a plugin; "name" becomes the namespace
│ └── marketplace.json # makes it a marketplace; lists plugins
└── skills/
└── <skill>/SKILL.md # one dir per skillplugin.json (the name is the plugin namespace prefix — skills surface as plugin-name:<skill>; this is independent of the marketplace name):
{ "name": "your-skills", "version": "0.1.0", "description": "…", "license": "MIT" }The marketplace name is the catalog identifier used in /plugin install plugin-name@marketplace-name. These are two distinct names that happen to be the same string in the single-repo model — that identity is a coincidence, not a requirement. A real-world example where they differ: "code-formatter@company-tools" (plugin name = code-formatter, marketplace name = company-tools).
marketplace.json — lists this plugin (and can list MANY plugins from other sources):
{
"name": "your-skills",
"owner": { "name": "you" },
"plugins": [
{ "name": "your-skills", "source": "./", "description": "…", "version": "0.1.0" }
]
}"source": "./" = the plugin is at the marketplace repo root.
/plugin marketplace add owner/your-skills-repo
/plugin install your-skills@your-skillsLoads globally (every project), namespaced. Not pinned per repo.
This is the recipe when a specific repo must depend on a pinned version, reproducibly, with no manual install for collaborators.
git submodule add https://github.com/owner/your-skills-repo.git .claude/skills/your-skills
cd .claude/skills/your-skills && git checkout v0.1.0 && cd -.claude/settings.json (the shared, committedfile — not .claude/settings.local.json, which is personal/gitignored):
{
"extraKnownMarketplaces": {
"your-skills": {
"source": { "source": "directory", "path": "./.claude/skills/your-skills" }
}
},
"enabledPlugins": { "<plugin-name>@<marketplace-name>": true }
}Replace <plugin-name> with the name from the plugin's plugin.json and <marketplace-name> with the name from the marketplace's marketplace.json. In the single-repo model these happen to be the same string (e.g. "your-skills@your-skills": true), but they are conceptually distinct — the plugin namespace and the catalog identifier.
source for a local dir is an object {"source":"directory","path":"./relative"} — a relative path (resolves against the repo's main checkout). A relative path only resolves because the submodule is itself a git repo.git clone --recurse-submodules + workspace-trust, Claude Codeauto-registers the marketplace and enables the plugin. Skills load as your-skills:<skill>. No `/plugin install` step.
Why both pieces: the submodule pins the exact version (a commit SHA); the committed settings.json is what actually makes Claude Code load it. Either alone is insufficient (submodule-only = not discovered; settings-only = nothing to point at).
npx …); out of scope here.A marketplace is a catalog of plugins from many sources — that is the native aggregation mechanism, no submodule required. Add more entries to plugins[], each with its own source:
"plugins": [
{ "name": "your-skills", "source": "./" },
{ "name": "someones-testing", "source": { "source": "github", "repo": "them/testing-skills" } },
{ "name": "vendored-thing", "source": { "source": "git-subdir", "url": "https://…", "path": "tools/plugin" } }
]Accepted plugin sources: relative "./path" (within the marketplace repo, must start with ./), github (repo,ref?,sha?), url (git URL), git-subdir (url,path), npm (package,version?). Use a submodule only when you need to vendor + pin another repo's content into yours.
.claude/skills/ loading without registration) is documented but not reliably in use — don't depend on it; register a marketplace.plugin.json/marketplace.json are new; -a will silently omit them. Use explicit git add and verify with git show --stat.~/.claude/plugins/known_marketplaces.json), but the committed project `.claude/settings.json` declaration is what makes it reproducible for everyone on trust./reload-plugins then check the skills list shows your-skills:<skill> entries.claude plugin details your-skills@your-skills lists the bundled skills + scope.git ls-files .claude/settings.json (it's committed) and the submodulegitlink point at the intended version.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.