Adding a skill touches four places — the canonical skills/<name>/SKILL.md, its llmwiki/skills/<loop>/<name>.md mirror, the sync-skills.py LOOP_MAP, the marketplace.json publish list, and a row in the AGENT.md/CLAUDE.md ## Skills table. Miss one and the trees drift (that is how marketplace.json fell ~30 skills behind). This skill drives fdk/tools/new-skill.py, which writes the two mechanical files identically and prints the exact lines for the three curated places — it never edits them itself.
WHAT
Purpose and context
- Purpose: create a new skill in both publish trees at once (canonical + byte-identical mirror) following the framework
solid-what-how/1, then provide the exact lines to paste to register it on the curated surfaces. - Trigger (when to use):
- The user asks to create / add / scaffold a new skill, or invokes
/new-skill. - You are about to hand-create a
skills/<name>/SKILL.mdand would otherwise forget the mirror or the registry surfaces.
Write for the craft properly — read
[[skill-craft]]first. The vocabulary so a skill is predictable: two kinds of cost (context load ↔ cognitive load — a manual-only skill declaresdisable-model-invocation: true, no context spent), a completion criterion for each step, a leading word instead of three sentences, and five failure modes to avoid (duplication/sediment/sprawl/no-op/negation)./lintstep 8b measures these things by machine.
- Non-goals: does not itself edit LOOP_MAP / marketplace.json / AGENT.md / CLAUDE.md; does not migrate existing skills (edit in place); does not create skills pulled from upstream (that goes into
skills/external/).
Mental model
tên + loop + description (trigger) → new-skill.py → skills/<tên>/SKILL.md (khung WHAT/HOW) + llmwiki/skills/<loop>/<tên>.md → điền TODO → swh-lint → sync-skills → đăng ký 3 bề mặt curated → sync-skills --check + skill-registry --check.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | <name> |
yes | kebab-case, does not yet exist in skills/ |
| In | --loop |
yes | dev-loop · orchestrate · wiki-loop · utils |
| In | --desc |
yes | a line rich in trigger phrases (the router matches on it) |
| Out | skills/<name>/SKILL.md + mirror |
yes | two identical files, the SWH skeleton with TODO filled in |
| Out | 3 curated surfaces registered | yes | both final check commands are green |
Rules and capabilities
- RULE-01 (MUST): The tool writes ONLY the two mechanical files (canonical + mirror). It never edits LOOP_MAP, marketplace.json, AGENT.md, or CLAUDE.md — those are user-curated; you add the printed lines by hand.
- RULE-02 (MUST): After editing the generated skeleton, ALWAYS re-run
sync-skills.py— parity is enforced, and your edits will have made the mirror stale. - RULE-03 (MUST): A vague
--descis a skill that never fires. Put concrete trigger phrases and/-commands in it. - RULE-04 (MUST): Never overwrite an existing skill — if the name is taken, edit
skills/<name>/SKILL.mdin place or pick another name. - Capabilities: there is a file skill; read the existing skill index (BM25) to warn about duplicates; curated surfaces are hand-pasted by people.
Failure boundaries
- Name already exists → blocked (tool refuses), edit in place or rename.
- Capability overlap above BM25 threshold → warning; with
--strictthen blocked. swh-lintor the last two check commands are red → not done, fix and rerun.
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | judgment | user request | settle <name>, --loop, --desc rich with triggers |
3 inputs | ambiguous → ask |
| W02 | effect | 3 inputs | new-skill.py --dry-run (print matching catalog template) then run for real — if a matching template exists, --from <asset_id> --params p.json |
2 files + recipe reuse_decision |
name duplicate → blocked · wrong parameter → INVALID_PARAMETER |
| W03 | judgment | skeleton | fill every TODO of WHAT/HOW, run swh-lint |
skill passes structure | red → fix, repeat W03 |
| W04 | effect | skill | sync-skills.py so the mirror matches |
mirror identical | — |
| W05 | effect | tool-printed line | register LOOP_MAP, marketplace, AGENT/CLAUDE table | 3 surfaces | — |
| W06 | deterministic | repo | sync-skills.py --check + skill-registry.py --check |
both green | red → fix the mismatched surface |
Details per step (source of truth for W01–W06):
- Settle three inputs: a kebab-case
<name>, a--loop(dev-loop|orchestrate|wiki-loop|utils), and a--descthat is rich with trigger phrases (the description is what the skill router matches on). Manual-invoke-only skill (only the user types it, no other skill calls it) → adddisable-model-invocation: true,--desccondensed into a human-readable line (see[[skill-craft]]). - Preview first, then create:
It refuses ifpython3 fdk/tools/new-skill.py <name> --loop <loop> --desc "<description>" --dry-run # preview body + next steps python3 fdk/tools/new-skill.py <name> --loop <loop> --desc "<description>" # write both filesskills/<name>/already exists. - Fill in the generated
skills/<name>/SKILL.md— the skeleton is the compactsolid-what-how/1template (## WHATpurpose/contract/rules/failure +## HOWmain workflow table/branches/examples, see[[solid-what-how]]). Replace every TODO with real, verifiable content, then check the shape:python3 fdk/tools/swh-lint.py --skills <name> --ci - Re-mirror after editing so the llmwiki copy stays byte-identical:
python3 harness/scripts/sync-skills.py - Register on the curated surfaces by pasting the exact lines the tool printed: add the LOOP_MAP entry in
harness/scripts/sync-skills.py, (optionally) the./skills/<name>line in.claude-plugin/marketplace.json, and one row in BOTHllmwiki/AGENT.mdandllmwiki/CLAUDE.md## Skillstables. - Verify every surface agrees — both must pass:
python3 harness/scripts/sync-skills.py --check # mirror parity python3 harness/scripts/skill-registry.py --check # cross-surface drift
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional_required | manual-invoke skill only | add disable-model-invocation: true, remove --desc to a single line reader |
model-invoked skill → skip | W02 |
| B03 | user_optional | catalog has a template matching the contract (filter effect first, rank after) | --from render typed template + pin sha256 into fdk/skill-catalog/recipes/<name>.recipe.json; if not, scratch with --reason |
catalog unreadable → decision catalog_unavailable, still generate a compact skeleton |
W03 |
| B02 | recovery | BM25 reports duplicate capability | rename as a variant + make the description different, or modify an existing skill | --strict → stop |
W01 |
Validation and stopping
Every run writes reuse_decision (reuse · scratch · catalog_unavailable) — Reuse Layer SWH v1.1, catalog fdk/skill-catalog/, tool fdk/tools/skill-reuse.py. Reuse does not exempt any gate: skills generated from templates must still pass swh-lint.
Done when swh-lint --skills <name> --ci, sync-skills.py --check and skill-registry.py --check all have rc 0. Real behavior (whether the skill is invoked correctly) is tested with 1–2 sample sentences; lint cannot prove it.
Examples
- Positive:
new-skill.py csv-preview --loop utils --desc "Xem trước CSV… Trigger: 'preview csv'"→ two skeleton files, fill in WHAT/HOW, three checks green. - Boundary/failure:
new-skill.py ship …→ tool refuses becauseskills/ship/already exists; fixskills/ship/SKILL.mdin place.