Contributing
How to add or change a skill, a pack, a loop or a subagent, what an RFC is needed for, and the checks a pull request must pass.
skilldrop has one primitive: a skill — a plain folder at packs/<pack>/skills/<name>/ that any agentic IDE can install by copying it. Two supporting surfaces exist to make skills findable and installable: the packs/ folders (role bundles, each with a pack.json) and the skilldrop-cli npm package (bin/skilldrop.js). Contributions come in three shapes, one per lane below.
Before you start
Two reads, in order:
- AGENTS.md — the source of truth. Golden rules, verified commands, file placement, skill anatomy, the pre-commit checklist, and the voice rules. This file routes you to it; it does not replace it.
- README.md — what each skill does and how it installs into each IDE.
No setup step beyond one line: git config core.hooksPath .githooks, which stops a commit that would publish your own name or email (SECURITY.md). The checks are stdlib Python and Node — python3 and node are the only prerequisites, and only if you want to run the linters locally (CI runs them either way).
Write an RFC first if you are adding a new skill, a new top-level file or directory, changing the skill anatomy or manifest schema, or introducing a repo-wide convention. Copy docs/rfcs/0000-template.md to docs/rfcs/NNNN-<kebab-slug>.md with the next sequential number. Keep it under a page — the RFC records the decision, the PR does the work. No RFC needed for fixes and improvements to an existing skill, doc corrections, or eval additions.
The invariant this repo protects
A skill is one self-contained folder. It has one home pack. The name matches in three places.
skilldrop's entire premise is copy-install portability: cp -R packs/<pack>/skills/<name> ~/.claude/skills/ has to work, in every IDE, forever. Three rules fall out of that, and they are the ones a well-meaning PR most often breaks:
- A skill lives at
packs/<pack>/skills/<kebab-name>/, in exactly one pack. No per-IDE projection directories, no build step that generates a skill from an upstream. What is in the tree is what gets copied. Skills every role needs go incore(RFC-0033, RFC-0034). - Folder name =
SKILL.mdname=manifest.jsonname. Kebab-case, use-case-first, no version suffix. Change one without the others and slash-command invocation breaks.validate.pyfails the build on this. packs/,README.md, andLICENSEdo not move, and a skill does not change packs without an RFC. Every install instruction and GitHub link is a hard-coded path.
The three lanes
1. Adding a new skill
- RFC first —
docs/rfcs/NNNN-<slug>.md, including the four-part fit check (concrete artifact / portable / opinionated / category). A skill that is a generic chat helper with no artifact, a thin wrapper around one CLI command, or a duplicate of an existing skill does not belong here — improve the existing one instead. - Create the folder —
packs/<pack>/skills/<your-skill>/SKILL.md+manifest.json, per the frontmatter and manifest shapes in AGENTS.md. KeepSKILL.mdunder ~500 lines; spill intoreference.md,templates/,examples/,lenses/, orrubrics/. - Ship
Quality barandAnti-patterns to avoidsections. A skill without them is a description, not a generator. - Add
evals/—evals.json(≥1 realistic prompt + assertions) andeval_queries.json(phrases that should and should not trigger the skill). The no-trigger list is what keeps thedescriptionhonest. - Set the model tier in both places — the
modelblock inmanifest.jsonand the skill's entry inmodel-routing.json. Tiers are abstract (light/standard/heavy); a vendor model name in a skill is always wrong. See MODEL-ROUTING.md. - Declare an audience — the pack folder you put it in is its audience, and it also goes in at least one
outcomesentry incatalogue.json. A skill with no audience should not have passed the RFC. - Update the skill catalogue — a row in
guides/reference/skill-catalogue.mdunder an existing category, and a row in Installing dependencies (guides/reference/install-targets.md) if it has runtime deps. - Match the voice. Opinionated, not hedged; decisions, not options; ≤2 clarifying questions;
✅/❌are semantic, never decorative. The full rules are in AGENTS.md — re-read them before drafting.
2. Changing an existing skill
No RFC. The bar is that the skill stays internally consistent, because validate.py checks four kinds of drift:
- Description sync —
SKILL.mdfrontmatter andmanifest.jsonmust carry the same string. relatedsync, both directions — every backticked sibling skill mentioned inSKILL.mdmust appear in the manifest'srelatedlist, and everyrelatedentry must be a real skill folder thatSKILL.mdactually references.- Tier sync —
manifest.jsonmodel.tiermust equal the skill's tier inmodel-routing.json. - One home — the skill's folder exists in exactly one pack.
If the change alters what the skill produces, redo the manual test pass (lane 3's gates, below) and update evals/ to match. If it changes the trigger surface, update eval_queries.json.
3. Changing repo machinery
The CLI (bin/skilldrop.js), the installers (pack.py), routing (route.py, model-routing.json), hooks (RFC-0006), or validate.py itself. RFC required for anything that changes a convention or adds a top-level file; not for a bug fix.
Two things to keep true:
- The CLI copies skills verbatim and never transforms them. The moment it rewrites a skill on the way in, a skill installed by the CLI stops being identical to one installed by
cp -R, and portability is gone. package.jsonfilesmust keepbin/,packs/,catalogue.json, andmodel-routing.json. Dropping one ships a CLI that cannot find its catalog.
If you add a new invariant, teach validate.py to enforce it. A rule that only lives in prose is a rule that drifts.
Before you open the PR
python3 validate.py # repo consistency lint (stdlib only)
node bin/skilldrop.js validate # catalog structural check
node cli_smoke.js # the CLI, run as a user runs it
python3 -m unittest discover -s tests -t . # the skill scripts' unit tests
All four run in CI on every push and PR. The last two run on Linux, macOS and Windows (.github/workflows/cli.yml). There is no make target.
A skill with a script needs its tests. They go in tests/test_<skill>.py (hyphens become underscores), and must name every script in the skill's scripts/ folder; validate.py fails otherwise. Use only the standard library and the helpers in tests/support.py, and keep them offline. If a test finds a bug you aren't fixing in the same change, mark it @unittest.expectedFailure with a # BUG: line; don't write the test to expect the wrong behaviour.
A script that parses a file a user hands it (an export, a report, a config) should also get a fuzz target in tests/fuzz/targets.py: the command line, the exit codes that mean "handled", and one or two small valid seeds. python3 tests/fuzz/fuzz.py --target <name> --iterations 2000 runs it anywhere; CI fuzzes it with Atheris (RFC-0062).
Then:
- Manual test pass. Install the skill into a clean Claude Code session and run it end-to-end on a realistic input. The output has to clear the skill's own quality bar. This is the check the linters cannot do, and it is not optional for a new or substantially changed skill.
- Work the full checklist in AGENTS.md → Before you commit. It is the authoritative list; the lanes above are a summary of it.
- Branch, don't push to
main:
git checkout -b feat/<short-kebab-name> # or fix/… docs/… chore/…
git push origin feat/<short-kebab-name>
gh pr create --base main --head <handle>:feat/<short-kebab-name>
- Commit messages are lowercase imperative, one line, naming the RFC when one applies — e.g.
add third-party catalogs to skilldrop-cli (RFC-0003). Not Conventional Commits. - Flip the RFC status to
implementedin the same PR that ships it, and fill in its Decision section. - Every PR requires review from the repo owner (
.github/CODEOWNERS).
Cutting a release
The npm package skilldrop-cli publishes itself. Bump version in package.json and merge to main — CI publishes via npm OIDC trusted publishing with provenance, then pushes a v<version> tag. The job is version-gated: a push without a bump publishes nothing and fails nothing. Rationale in RFC-0004.
Bump whenever a skill change is worth shipping — users' skilldrop outdated only lights up on a release, so unreleased skill improvements are invisible to everyone who installed via the CLI. The manual fallback (npm publish from the repo root, with the 2FA browser step) still works if CI is down.
Increment the patch digit, one step at a time: 0.4.0 → 0.4.1 → 0.4.2. Never skip a number, and don't jump to a minor because the release feels significant.
| Bump | When |
|---|---|
Patch (0.4.1) — the default | New skills, skill edits, pack changes, tier changes, docs, CLI bug fixes. Almost everything. |
Minor (0.5.0) | The CLI's public surface changed: a new command, a new flag, or a changed default. |
Major (1.0.0) | A deliberate stability commitment. Not yet. |
A docs-only change that ships nothing in package.json files (bin/, packs/, catalogue.json, model-routing.json) needs no bump at all — there is nothing new for the registry to carry.
Where to find authoritative information
| Question | File |
|---|---|
| Rules, file placement, checklist, voice | AGENTS.md |
Claude-Code-specific ergonomics (dogfooding, ${CLAUDE_SKILL_DIR}) | CLAUDE.md |
| What each skill does, per-IDE install steps | README.md |
| Why a structural decision was made | docs/rfcs/ |
| Long-form design (CLI surface, telemetry spec) | docs/designs/ |
| Which skills a role gets | packs/ — each pack's folder and pack.json |
| What model tier a skill runs at, and why | model-routing.json + MODEL-ROUTING.md |
| What CI actually runs | .github/workflows/release.yml |
When this file is wrong
Flag the drift in your PR instead of working around it. AGENTS.md wins any disagreement — this file is a router over it, and a router that points somewhere stale is a bug. Correcting it is a docs fix and needs no RFC; changing the process it describes needs one.
License
MIT or Apache-2.0, at the user's option (LICENSE). Opening a PR means you agree your contribution ships under both.