SKILL.md Spec
Every skill in this repo is described by a SKILL.md manifest, validated by the PR check. This page summarises what the validator enforces. The authoritative document lives at spec/SKILL_SPEC.md.
scripts/validate_skill.py against every changed skill. Run the same checks locally with uv run python scripts/validate_skill.py --all (or just one skill: uv run python scripts/validate_skill.py skills/<name>/SKILL.md).
Directory layout
A skill lives in its own directory under one of two roots. Both are validated against this spec; only skills/ is part of the public catalogue.
skills/<skill-name>/ # contributed skills (public catalogue)
.github/skills/<skill-name>/ # the repo's own meta-skills (internal)
├── SKILL.md # required: the manifest
├── scripts/ # optional, flat — one level deep
├── references/ # optional, flat
└── assets/ # optional, flat
1. Frontmatter (required)
The SKILL.md MUST begin with a YAML frontmatter block. Two fields are mandatory:
| Field | Type | Notes |
|---|---|---|
name | string | Unique, kebab-case identifier. Must match the skill's directory name. |
description | string | What the skill does and when it should trigger. This is the only metadata an agent sees when routing, so keep it specific. |
name rules
- Kebab-case (
^[a-z0-9]+(-[a-z0-9]+)*$): lowercase letters and digits in hyphen-separated groups, no leading, trailing, or consecutive hyphens. - At most 64 characters.
- Must exactly match the skill's directory name, so manifest, folder, and npm package all line up.
description rules
- At most 1024 characters.
- No angle brackets (
<or>): they get misread as markup where the manifest is injected. - Routing depends on it, so be specific about when the skill fires.
Allowed frontmatter keys
Only these keys are accepted (any other key is rejected by the validator): name, description, owner, tags, license, allowed-tools, compatibility, metadata. Of the optional ones, owner and tags are recommended.
version field. The merge-to-main state of the repo is the released state, and npx skills add reads the repo directly. Don't add a version field to SKILL.md or anywhere else in the skill.
Example
---
name: example-skill
description: One line on what this does and when it fires.
owner: your-team
tags: [example]
---
2. Body (required)
After the frontmatter, the body MUST contain — in this order — an H1 title followed by all seven ## sections. Each section must have at least one line of content.
# <Skill Name>
## Use When
- <specific situation>
## Don't Use When
- <adjacent case> → <other skill>
## Workflow
1. <step + tool>
2. ...
## Rules
- Always <X>
- Never <Y> (Why: <non-obvious reason>)
## Examples
- "<user phrasing>" → <action>
## Edge Cases
- If <lookup empty> → <fallback>
## References
See [references/REFERENCE.md](references/REFERENCE.md) for <heavy detail>
Keep it short: 500 lines max
A SKILL.md is loaded into the agent's context up front, so it must stay skimmable. The manifest is capped at 500 lines (the whole file, frontmatter included). When a skill needs deeper material — long procedures, lookup tables, worked examples, API detail — put it in a references/ file the agent pulls on demand, and link it from the ## References section.
Keep bundled resources flat: one level deep
Skills may ship scripts/, references/, and assets/ directories beside the SKILL.md. These must stay exactly one level deep: flat files only, no nested subdirectories. Use references/REFERENCE.md, not references/api/v1/REFERENCE.md. A flat layout keeps on-demand resources predictable for the agent to locate.
3. What the PR check enforces
For each changed skill, the validator confirms all of the following. If this list and the validator script (scripts/validate_skill.py) ever disagree, the script wins. If you see a failure on a PR, this list maps directly to the checks:
- The
SKILL.mdstarts with a closed YAML frontmatter block that parses as a mapping. nameanddescriptionare present and non-empty.nameis kebab-case, at most 64 characters, and matches the skill's directory name.descriptionis at most 1024 characters and contains no angle brackets.- No unexpected frontmatter keys are present (only
name,description,owner,tags,license,allowed-tools,compatibility,metadata). - The body has an H1 title.
- All seven required sections are present: Use When, Don't Use When, Workflow, Rules, Examples, Edge Cases, References.
- None of those required sections is empty.
- The whole
SKILL.mdis at most 500 lines. - Any bundled
scripts/,references/, orassets/directory is flat (no nested subdirectories). - No cross-skill duplicates. When more than one skill is being validated, the check refuses two skills sharing the same
name, the samedescription, or the same normalised body (the part after the H1 title and one-line summary). Catches copy-pasted manifests before they ship. Skip with--no-duplicateswhen you intentionally want to compare a single file.
Gotchas the validator can't always pinpoint
- Colons inside
description. YAML treatskey: valueas a mapping. If your description contains:(colon + space) — e.g."Use when: planning a deployment"— wrap the whole value in single quotes so PyYAML doesn't try to split it. Double quotes also work but then you have to escape any literal". - Section headings use a straight ASCII apostrophe. Write
## Don't Use Whenwith'(U+0027), not the curly'(U+2019) that some editors auto-insert. The validator normalises smart quotes to straight in the heading-match step, so the canonical form in your file is the ASCII one.
Run it locally
uv run python scripts/validate_skill.py skills/<your-skill>/SKILL.md
# or validate every skill
uv run python scripts/validate_skill.py --all
# or via the Makefile
make validate