devkit-skill-authoring
by @chempa
Use when writing, validating, publishing, or versioning a skill for a devkit registry - the SKILL.md format, the frontmatter rules, and the init/validate/publish loop.
devkit-skill-authoring
How to write a skill that a devkit registry will accept and that Claude will actually load. Everything here is the behaviour of @devkit/core, which is the one validator the CLI, the server, CI, and the in-browser editor all share - so what passes locally passes on publish, for the same reasons.
When to use this
- Writing a new skill, or fixing one the registry rejected
- Deciding what belongs in
SKILL.mdversus areferences/file - Publishing, versioning, yanking, or forking
- Editing a skill that is already published, and needing the version bump right
- Working against more than one registry and needing the right one
The whole loop
devkit init my-skill # scaffold a folder that already validates
$EDITOR my-skill/SKILL.md
devkit validate my-skill # the registry's own checks, locally
devkit publish my-skill # upload it
If you do not have the CLI yet, it comes from the registry itself and needs no token - it is a client and holds no skills:
curl -sSL https://your-registry.example.com/install/cli | sh
devkit login https://your-registry.example.com # prompts for a token
Mint the token at /manage/settings. Publishing needs the publish scope.
How we publish here
The CLI is the only way a skill reaches the registry. Never commit a skill folder to the devkit-site repository and never push it to GitLab. That repo is the site and the validator; it is not the publishing path, and its older "add a folder, open an MR" instructions in CONTRIBUTING.md describe a flow we no longer use. A skill that lands by git is a skill the registry does not have, with no version, no digest, and no way for anyone to install it.
The house setup:
| Thing | Where |
|---|---|
| Registry | https://devkit.chempa.dev, account @chempa |
| Skill sources | ~/skills/<slug>/, one folder per skill |
| Installed copies | ~/.claude/skills/<slug>/, written by devkit install |
cd ~/skills
devkit init my-skill
$EDITOR my-skill/SKILL.md
devkit validate my-skill
devkit publish my-skill # this is the push
devkit install my-skill # put it on this machine
Run devkit whoami before publishing anything you would not want in the wrong place. It prints the active registry, the token, and why that one was chosen.
Changing a skill that is already published
Versions are immutable, so an edit is always a new version. The sequence:
cd ~/skills/my-skill # or clone it back, below
$EDITOR SKILL.md # edit, then bump version: and updated:
devkit validate .
devkit publish .
devkit update my-skill # refresh this machine's copy
Bumping is a judgement about the reader, not about the diff size:
| Change | Bump |
|---|---|
| Typos, a clearer sentence, a fixed link | patch |
New guidance, a new references/ file, a widened description | minor |
| Guidance that reverses what the previous version told people to do | major |
No local source folder? Every published file is in the install, so the installed copy is the source:
cp -r ~/.claude/skills/my-skill ~/skills/my-skill
rm -f ~/skills/my-skill/.devkit.json # the install marker, never published
Forget the bump and publish returns a 409 for the version that already exists. That is the check working: fix the number and publish again.
A skill is one folder
my-skill/
SKILL.md ← required, at the root
references/ ← optional, ships with the skill
checklist.md
There is no manifest. The YAML frontmatter at the top of SKILL.md is the metadata, and the body below it is what Claude loads.
The folder name is the slug, and it must equal the frontmatter name. Slugs are lowercase, hyphen-separated, 3-64 characters, starting and ending alphanumeric. all and cli are refused - the registry already serves those install URLs.
Frontmatter
---
name: code-reviewer
description: Use when reviewing a diff for correctness, dead code, and missing tests.
version: 1.0.0
author: your-handle
license: MIT
tags: [ai, claude, code-review]
status: beta
created: 2026-08-02
updated: 2026-08-02
---
| Key | Required | Rule |
|---|---|---|
name | yes | Must equal the folder name |
description | yes | 10-200 characters, one line |
version | yes | SemVer; must not already exist in the registry |
author | yes | Letters, digits, _ . -; max 40 |
license | yes | An SPDX identifier (MIT, Apache-2.0, …) |
tags | yes | Inline list, 1-8 entries |
status | yes | stable, beta, or deprecated |
created / updated | no | YYYY-MM-DD |
featured | no | Set by the registry, not by you |
The frontmatter parser is deliberately not YAML. It handles key: scalar, inline lists ([a, b, c]), quoted scalars, and # comments - and it throws on indentation, nested maps, block sequences (- item), and multi-line scalars (|, >) rather than mis-parsing them quietly. Keep every value on one line.
For the exact rules, every error message, and what each one means, read references/frontmatter.md.
The description is the field that matters
It is the only part of the skill Claude sees before deciding whether to load it. It has to carry the trigger conditions by itself. Write when to use this, not what this is:
# good - names the situations that should match
description: Use when reviewing a diff, preparing a PR, or asked to check code for bugs.
# bad - accurate, and gives nothing to match against
description: A code review helper.
A skill with a vague description is not a broken skill; it is a skill that never gets loaded, which is worse, because nothing reports it.
The body
Everything below the closing --- is the skill. It is what Claude loads and what the catalogue renders, so it has two readers and must work for both. It must be non-empty.
Keep SKILL.md focused. Anything long - checklists, tables, worked examples - goes in a sibling file that the body points at on demand:
For the full colour formula, read `references/palette.md`.
Every file in the folder is published and lands in the installer's ~/.claude/skills/<slug>/, so those siblings are on their disk afterwards.
For how to write a body Claude actually follows, read references/writing-well.md.
What gets published
Everything in the folder, except housekeeping: .DS_Store, Thumbs.db, .git, .gitignore, .gitkeep, .keep, node_modules, .devkit.json, and editor leftovers matching ~, .#, or .tmp.. Those are your repository's business and nobody downstream's.
There are no secrets in a skill folder. Everything in it is published, and on a public registry, world-readable.
Limits, all enforced at publish:
| Limit | Value | Why |
|---|---|---|
| Files per skill | 64 | A skill is documentation, not a package |
| One file | 512 KB | Same |
| Whole skill | 2 MB | Bounds the upload and the unpacked size |
| Path length | 100 bytes incl. <slug>/ | ustar's name field |
Paths may not be absolute, contain .. or . segments, use backslashes, or contain NUL. The install writes straight into ~/.claude/skills/<slug>/, so this is a security boundary rather than tidiness.
Versions are immutable
Publishing a version that already exists is a 409. To change a published skill, bump version: and publish again.
An install pins a content digest, verified on download, so "the skill I installed" and "the skill in the registry" can be compared - and a bad version is rolled back by installing the previous one rather than by hoping somebody reconstructs it.
latest is the highest release. A pre-release (2.0.0-rc.1) is installable by exact version but never becomes the default.
Publishing, registries, tokens, yanking, forking, and visibility are covered in references/publishing.md.
Before you publish
- Folder name and frontmatter
nameare identical -
descriptionnames trigger conditions, 10-200 chars, one line - Every frontmatter value is on a single line
-
versionis SemVer and has not been published before - The body is non-empty and reads on its own
- No secrets anywhere in the folder
-
updated:is today, if this is a new version of an existing skill - You are publishing with the CLI, not committing the folder anywhere
devkit validate my-skill
Then try the real install, somewhere disposable:
export CLAUDE_HOME=$(mktemp -d)
devkit install my-skill
find "$CLAUDE_HOME"
CLAUDE_HOME redirects the install target, so you can inspect exactly what an installer gets without touching your real ~/.claude.
When something is rejected
references/troubleshooting.md maps each error the validator, the CLI, and the server produce to its cause and its fix.