newskill — add a skill without drift
/newskill scaffold <name> "<description>" <category> — create a new skill, registered
/newskill fix [<name>] — complete a skill that exists in some surfaces but not others
A skillhub skill lives in four surfaces, and it is only really "added" when all four agree:
- Folder —
.claude/skills/<name>/SKILL.mdwith valid frontmatter. - README table row — a row in the right category table (a prose mention does not count; the validator checks for a table row specifically).
- Marketplace —
<name>listed in one plugin'sskills[]in.claude-plugin/marketplace.json. - Trigger evals — cases in
evals/triggers.json(model-invoked skills only; user-invoked skills are exempt).
Drift is these four disagreeing — the default failure of hand-adding a
skill. The oracle for drift is python3 scripts/validate-skills.py: it already
reports which surfaces are missing. This skill's whole job is to make the four
agree and leave the validator green.
The loop (both modes end here)
Run python3 scripts/validate-skills.py → read each FAIL/WARN → fix that exact
surface → re-run. Repeat until no FAIL. Never assert done from memory; the
validator is cheap and authoritative.
Mode: scaffold
Adding a brand-new skill from a name + description + category.
- Guard. If
.claude/skills/<name>/already exists, stop — this is afix, not a scaffold; never overwrite an existingSKILL.md. Also reject a<name>that collides with a harness built-in (the validator'sBUILTINSset) or an existing skill. - Folder. Write
.claude/skills/<name>/SKILL.mdwith frontmatter (name= folder name) and adescription. Ifout/tune/principles.mdexists, read it before writing the body — each line is an authoring rule distilled from accepted past fixes. Keep the description ≤ 950 chars (soft limit; hard wall is 1024) — front-load the leading word, one trigger per branch. For a user-invoked tool adddisable-model-invocation: trueand write a human-facing one-line description (no trigger list). - Category → plugin + table.
<category>is one ofcoding-loop/research/daily. Add./.claude/skills/<name>to that plugin'sskills[]inmarketplace.json, and add one row to the matching README category table. - Trigger evals (model-invoked only). Add ≥2 cases to
evals/triggers.json, including a routing pair against the nearest neighbour skill. Skip for user-invoked skills. - Pre-flight the body's snippets. Every snippet the new
SKILL.mdembeds is tried once against real data before the skill ships, output quoted in-session: execute each bash line (placeholders filled with real values), fetch each API endpoint, read each referenced path. A snippet that fails as written is a bug in the skill, not a formatting nit. Two honest outs, both flagged unverified and never skipped silently: one that can't run here (external account, missing binary, a file that exists only mid-run), and one that would mutate real state — never run something destructive just to prove it. This is tune's prove-the-rules-bite extended from config to authoring. (Fix mode is exempt — it never writes a body.) - Run the loop above until green.
Completion criterion: validator prints no FAIL; the new <name> appears in
all four surfaces it's required in; every embedded snippet has run clean or is
flagged unverified.
Mode: fix
Completing a skill that already drifted (e.g. a folder with no registration).
- Diagnose. Run the validator; it lists exactly which surfaces
<name>(or every skill, if<name>omitted) is missing from. - Register only — never rewrite the body. Fill each missing surface:
marketplace entry, README table row, trigger cases. Touch
SKILL.mdonly if the validator flags the frontmatter itself (e.g. description over the 1024-char limit) — and then edit the frontmatter alone, leaving the body unchanged. - Run the loop until green.
Completion criterion: validator prints no FAIL; the previously-missing
surfaces now list <name>, and its SKILL.md body is unchanged.
After a description change — run the eval
Adding or rewording a model-invoked description changes the skill's only
trigger surface, so a green validator is necessary but not sufficient. Re-run
the trigger eval per evals/README.md (a fresh judge sub-agent over
evals/triggers.json using validate-skills.py --descriptions): confirm the
skill wins its own prompts and doesn't steal a neighbour's. User-invoked skills
carry no description reach, so this step doesn't apply to them.