Personal Claude Code skills repo. Source of truth for the npx skills package install on every machine I use.
Get the skills onto a machine.
npx skills add https://github.com/leoluyi/skills -g -a claude-code -y
npx skills update --all
npx skills needs network access, and Claude Code's skill loader has a discovery bug when ~/.claude/skills/ itself is a symlink (it walks the link strangely and fails to enumerate children). The fix is per-skill symlinks instead of a directory-level one.
tools/sync-skills
That script symlinks each skills/<name>/ into both ~/.claude/skills/<name>/ (Claude Code, Cursor) and ~/.agents/skills/<name>/ (Codex, OpenHands), refuses to overwrite real directories, and prunes dangling links left behind by archives.
Clone the repo and wire it into Claude Code for local authoring.
git clone git@github.com:leoluyi/skills.git ~/.skills
cd ~/.skills
tools/sync-skills.
├── README.md
├── backlog.md # ideas not yet drafted (signal: friction hit 2+ times)
├── .gitignore
├── skills/ # active skills — each is a SKILL.md folder
├── _archive/ # retired skills (kept for reference + usage-report scope)
├── evals/ # mirrors skills/ — one prompts.json per skill
└── tools/ # repo scripts — see Tools below
| Script | Purpose |
|---|---|
tools/new-skill <name> |
Scaffold a new skill (SKILL.md + eval stub + next-step hints). |
tools/sync-skills |
Per-skill symlinks into ~/.claude/skills/ (Claude Code, Cursor) and ~/.agents/skills/ (Codex, OpenHands). Offline / airgapped fallback. |
tools/archive-skill <name> |
git mv skill (and its evals) to _archive/, commit archive: <name>. |
tools/usage-report [days] |
Count skill triggers in ~/.claude/projects/ JSONL transcripts. Default 90 days. |
skills/<name>/
├── SKILL.md # frontmatter + body — the index, not the manual
├── references/ # optional — large docs the body points at on demand
└── scripts/ # optional — deterministic logic, no LLM needed
evals/<name>/
└── prompts.json # with-vs-without baselines (mirrors skills/<name>/)
The SKILL.md frontmatter is two fields. The description is the trigger gate — the only thing Claude sees when deciding whether to load the skill. Name what should fire it and what should not:
---
name: rfp-writing
description: Write or review technical RFP documents from the issuer's perspective…
Trigger only when the user explicitly asks for an RFP; do not invoke for migration
plans, runbooks, or design docs — those conventions conflict with RFP rules.
---The description is an unquoted YAML plain scalar, and YAML does not decode \u escapes in plain scalars — the skill loader hands Claude the raw \u-escaped codepoints, not the decoded 台灣. A zh skill whose description is \u-escaped therefore has dead Chinese triggers: Claude sees escape gibberish, not 「去除 AI 味」, and never fires on Chinese prompts. The body is unaffected (it isn't parsed as YAML), which is why the bug hides — the skill reads fine, it just won't trigger. This silently disabled every Chinese trigger in avoid-ai-writing-zh until it was caught.
- Detect:
rg -l '\\u[0-9A-F]{4}' skills/*/SKILL.md— any hit on adescription:line is the bug. An escaped emoji on a double-quoted line (emoji: "✍️") is fine; double-quoted scalars do decode. - Fix: write the description in real Traditional Chinese, same as the body.
tools/new-skillis the suspected source of escaping — check new skills before shipping. - Related: a
:(colon-space) inside an unquoted description breaks strict YAML parsers. The fix is a>-folded block scalar (seeplain-speak) — inside a block scalar:and"are literal, so a description can hold them freely. Scaffold new descriptions as>-.
A skill goes through these seven stages. Most die at stage 3.
- Capture — friction hit twice → add a one-liner to
backlog.md. Don't draft yet. - Draft —
tools/new-skill <kebab-name>scaffoldsskills/<name>/SKILL.mdplusevals/<name>/prompts.json. - Test — write 3+ realistic prompts in
prompts.json, run each with the skill and without. See Test discipline. - Iterate — adjust the body until with-skill beats vanilla on every prompt. If you can't get there, cut it.
- Optimize description — rewrite the trigger gate until Claude invokes it on the right prompts and not on lookalike-but-wrong ones.
- Deploy — commit and push. Consumer machines pull via
npx skills update --allortools/sync-skills. - Maintain / retire —
tools/usage-reportquarterly; archive dormant skills viatools/archive-skill <name>. See Maintenance.
- kebab-case, lowercase
- action-flavored — verbs over nouns when possible
- specific over generic —
oracle-exadata-cutovernotmigration,rfp-writingnotdocuments
- Write the description pushy. It's the trigger gate (see Anatomy). Name the explicit phrases that should fire it and what should NOT. Vague descriptions either misfire on unrelated prompts or silently never load.
- Body under 500 lines. Lean on progressive disclosure — the SKILL.md is the index, push detail into
references/and logic intoscripts/. - Explain why, not just what. Bullet lists describe a procedure; the why lets a reader extrapolate to edge cases the bullets miss. If the skill says "do X", say what X is preventing.
- Every skill must be portable. It runs unchanged on Claude Code and Codex — not Claude Code only. See Portability. Tool-specific power (hooks,
context: fork,model) may be present but never load-bearing.
This repo is the single source of truth, symlinked into each agent's skills tree. So every skill must run unchanged on Claude Code and Codex (Cursor and OpenHands then come nearly free). Portability is a shipping requirement, not a nice-to-have — a skill that only works in Claude Code is half-built.
What actually breaks portability, and the rule for each:
name+descriptionare the only universally-required frontmatter. That's the agentskills.io 1.0 core, and exactly what Codex requires. Claude-Code extras (hooks,context: fork,model,effort,agent,argument-hint,disable-model-invocation) are no-ops in Codex — keep them where they earn it, but never let a skill's core behavior depend on one. Custom data goes in the spec'smetadata:map, not new top-level keys — Codex tolerates unknown keys, but the spec only guarantees a home undermetadata.- The body stays invocation-agnostic. Write "when the user asks to X", never "when the user runs
/x" (Claude) or "$x" (Codex) — invocation syntax differs per tool. Letdescriptioncarry the trigger; keep the body about what, not how it's typed. - No CWD assumptions. Different tools launch from different directories. Reference bundled files by relative path from
SKILL.md(both honor that); resolve machine paths through an env var with a default (learn'sLEARN_VAULT), never a hardcoded absolute. Don't put${CLAUDE_SKILL_DIR}in text Codex must execute — it won't expand there. - Tool-specific runtime features live outside the skill, registered separately. Claude Code hooks are the worked example:
learn's vault guard isskills/learn/hooks/guard-vault-path.shplus a~/.claude/settings.jsonregistration — not a line inSKILL.md. A machine without Claude Code just loses the guard; the skill still runs. Same discipline for MCP-only tools — name plainBash/Read/Grepboth agents have, not a specific MCP tool, unless both are guaranteed to have it. - Sync must bridge every tool's tree — or "portable" is only a claim.
~/.claude/skills/is read by Claude Code and Cursor. Codex and OpenHands do not read it — they read~/.agents/skills/, the emerging cross-tool standard dir.tools/sync-skillslinks eachskills/<name>/into both trees, so this is enforced, not just documented.
Who reads what:
| Tool | reads ~/.claude/skills? |
native skills dir |
|---|---|---|
| Claude Code | yes (home) | ~/.claude/skills/ |
| Cursor | yes (compat) | ~/.cursor/skills/, ~/.agents/skills/ |
| Codex | no | ~/.agents/skills/ |
| OpenHands | no | ~/.agents/skills/ |
Sources: code.claude.com/docs/en/skills · learn.chatgpt.com/docs/build-skills · agentskills.io/specification · cursor.com/docs/context/skills.
Expands on SDLC stages 3–4. Every skill ships with a evals/<name>/prompts.json. Every change to a skill is validated against those prompts with-vs-without baselines. The skill ships only if it's materially better than vanilla Claude on every prompt. No bar-clearing, no skill.
This is the single most important rule in this repo. A skill that exists but doesn't help is worse than no skill — it eats context, it pollutes the trigger surface for other skills, and it makes the portfolio look healthier than it is.
Expands on SDLC stage 7.
tools/usage-reportquarterly. Skills with zero hits in 90 days → archive candidate.- A skill firing too often on wrong prompts → tighten the description.
- A skill firing too rarely on right prompts → loosen / make the description more pushy, or add example trigger phrases.
- Archive aggressively. The repo is supposed to feel small.