Skip to content

Instantly share code, notes, and snippets.

@jamesrochabrun
Created May 13, 2026 07:59
Show Gist options
  • Select an option

  • Save jamesrochabrun/6bd12ab35201db6d8c8aeccc926d78d4 to your computer and use it in GitHub Desktop.

Select an option

Save jamesrochabrun/6bd12ab35201db6d8c8aeccc926d78d4 to your computer and use it in GitHub Desktop.
Skills Reference Guide — discovery, authoring, invocation & quality signals for Claude Code skills
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Skills — Discovery, Authoring, Invocation & Best Practices</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,400&family=Instrument+Serif:ital@0;1&family=Geist:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0e0e10;
--bg2: #15151a;
--bg3: #1c1c23;
--bg4: #23232d;
--line: #2e2e3a;
--line2: #3a3a4a;
--text: #e8e8f0;
--text2: #9898b0;
--text3: #686880;
--accent: #7c6aff;
--accent2: #a594ff;
--accent-bg: rgba(124,106,255,0.08);
--green: #3fcf8e;
--green-bg: rgba(63,207,142,0.08);
--red: #f87171;
--red-bg: rgba(248,113,113,0.08);
--amber: #fbbf24;
--amber-bg: rgba(251,191,36,0.08);
--blue: #60a5fa;
--blue-bg: rgba(96,165,250,0.08);
--code-bg: #12121a;
--serif: 'Instrument Serif', Georgia, serif;
--sans: 'Geist', system-ui, sans-serif;
--mono: 'DM Mono', 'Fira Code', monospace;
--r: 8px;
--sidebar-w: 240px;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; font-size: 16px; }
body {
font-family: var(--sans);
background: var(--bg);
color: var(--text);
line-height: 1.7;
display: flex;
min-height: 100vh;
}
nav {
width: var(--sidebar-w);
position: fixed;
top: 0; left: 0; bottom: 0;
background: var(--bg2);
border-right: 1px solid var(--line);
overflow-y: auto;
padding: 2rem 0;
flex-shrink: 0;
z-index: 100;
}
.nav-logo {
padding: 0 1.5rem 1.5rem;
border-bottom: 1px solid var(--line);
margin-bottom: 1rem;
}
.nav-logo .logotype {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.12em;
color: var(--accent2);
text-transform: uppercase;
display: block;
margin-bottom: 4px;
}
.nav-logo h1 {
font-family: var(--serif);
font-size: 1.1rem;
font-weight: 400;
color: var(--text);
line-height: 1.3;
font-style: italic;
}
nav ul { list-style: none; }
nav .section-label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text2);
padding: 1rem 1.5rem 0.4rem;
font-weight: 500;
}
nav a {
display: block;
padding: 0.3rem 1.5rem;
font-size: 13px;
color: var(--text2);
text-decoration: none;
border-left: 2px solid transparent;
transition: all 0.15s;
}
nav a:hover, nav a.active {
color: var(--text);
border-left-color: var(--accent);
background: var(--accent-bg);
}
main {
margin-left: var(--sidebar-w);
max-width: 860px;
padding: 4rem 4rem 8rem;
flex: 1;
}
.hero {
border-bottom: 1px solid var(--line);
padding-bottom: 3rem;
margin-bottom: 3rem;
}
.hero .tag {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent2);
display: inline-block;
padding: 4px 10px;
background: var(--accent-bg);
border: 1px solid rgba(124,106,255,0.2);
border-radius: 4px;
margin-bottom: 1.5rem;
}
.hero h1 {
font-family: var(--serif);
font-size: 2.8rem;
font-weight: 400;
line-height: 1.15;
color: var(--text);
margin-bottom: 1rem;
}
.hero h1 em { color: var(--accent2); font-style: italic; }
.hero p {
color: var(--text2);
font-size: 1.05rem;
max-width: 580px;
}
section { margin-bottom: 4rem; }
h2 {
font-family: var(--serif);
font-size: 1.9rem;
font-weight: 400;
font-style: italic;
color: var(--text);
margin-bottom: 1.2rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--line);
}
h3 {
font-size: 0.85rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text2);
margin: 2rem 0 0.8rem;
}
p { color: var(--text2); margin-bottom: 1rem; font-size: 0.95rem; }
p strong { color: var(--text); font-weight: 500; }
pre {
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 1.25rem 1.5rem;
overflow-x: auto;
margin: 1.25rem 0;
position: relative;
}
pre code {
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.7;
color: #c0c0d8;
tab-size: 2;
}
code {
font-family: var(--mono);
font-size: 0.82em;
background: var(--bg3);
color: var(--accent2);
padding: 2px 6px;
border-radius: 4px;
border: 1px solid var(--line);
}
.pre-label {
position: absolute;
top: 10px; right: 14px;
font-family: var(--mono);
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text2);
opacity: 0.5;
}
.kw { color: #a594ff; }
.fn { color: #60a5fa; }
.str { color: #3fcf8e; }
.cm { color: #565680; font-style: italic; }
.hl { background: rgba(124,106,255,0.1); display: block; margin: 0 -1.5rem; padding: 0 1.5rem; }
.ok { color: var(--green); }
.no { color: var(--red); opacity: 0.7; }
.dim { color: var(--text2); }
.amber-t { color: var(--amber); }
.tree {
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 1.25rem 1.5rem;
font-family: var(--mono);
font-size: 12.5px;
line-height: 1.9;
color: #9898b0;
margin: 1.25rem 0;
overflow-x: auto;
}
.tree .dir { color: #a594ff; }
.tree .file { color: #e8e8f0; }
.tree .note { color: #4a4a65; font-style: italic; }
.tree .hot { color: var(--green); }
.tree .cold { color: #4a4a65; }
.callout {
border-radius: var(--r);
padding: 1rem 1.25rem;
margin: 1.25rem 0;
border-left: 3px solid;
font-size: 0.9rem;
}
.callout p { margin: 0; font-size: inherit; }
.callout strong { font-size: inherit; }
.callout.insight {
background: var(--accent-bg);
border-color: var(--accent);
}
.callout.insight strong { color: var(--accent2); }
.callout.insight p { color: #b8b0f0; }
.callout.warn {
background: var(--amber-bg);
border-color: var(--amber);
}
.callout.warn strong { color: var(--amber); }
.callout.warn p { color: #d4a840; }
.callout.tip {
background: var(--green-bg);
border-color: var(--green);
}
.callout.tip strong { color: var(--green); }
.callout.tip p { color: #60c490; }
.steps {
display: flex;
flex-direction: column;
gap: 0;
margin: 1.25rem 0;
position: relative;
}
.steps::before {
content: '';
position: absolute;
left: 19px;
top: 20px;
bottom: 20px;
width: 1px;
background: var(--line);
}
.step {
display: flex;
gap: 1rem;
align-items: flex-start;
padding: 0.5rem 0;
}
.step-num {
width: 38px;
height: 38px;
border-radius: 50%;
background: var(--bg3);
border: 1px solid var(--accent);
color: var(--accent2);
font-family: var(--mono);
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-weight: 500;
}
.step-body { padding-top: 6px; flex: 1; }
.step-body strong { color: var(--text); font-size: 0.9rem; }
.step-body p { font-size: 0.85rem; margin: 0.2rem 0 0; }
.scenarios {
border: 1px solid var(--line);
border-radius: var(--r);
overflow: hidden;
margin: 1.25rem 0;
}
.scenario-tabs {
display: flex;
background: var(--bg3);
border-bottom: 1px solid var(--line);
}
.scenario-tab {
padding: 0.6rem 1.2rem;
font-size: 12px;
font-family: var(--mono);
color: var(--text2);
cursor: pointer;
border: none;
background: none;
border-right: 1px solid var(--line);
transition: all 0.15s;
}
.scenario-tab:hover { background: var(--bg4); color: var(--text); }
.scenario-tab.active {
background: var(--bg);
color: var(--accent2);
border-bottom: 2px solid var(--accent);
margin-bottom: -1px;
}
.scenario-panel { display: none; padding: 1.25rem; }
.scenario-panel.active { display: block; }
.budget-bar-wrap {
margin: 1.25rem 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.budget-row {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
}
.budget-label {
font-family: var(--mono);
color: var(--text2);
width: 230px;
flex-shrink: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.budget-track {
flex: 1;
height: 8px;
background: var(--bg3);
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--line);
}
.budget-fill {
height: 100%;
border-radius: 4px;
transition: width 0.4s;
}
.budget-chars {
font-family: var(--mono);
color: var(--text2);
width: 90px;
text-align: right;
flex-shrink: 0;
}
.rules-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
margin: 1.25rem 0;
}
.rules-table th {
text-align: left;
padding: 0.6rem 1rem;
background: var(--bg3);
color: var(--text2);
font-weight: 500;
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
border-bottom: 1px solid var(--line);
}
.rules-table td {
padding: 0.6rem 1rem;
color: var(--text2);
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.rules-table td:first-child {
font-family: var(--mono);
color: var(--text);
font-size: 12px;
}
.rules-table tr:last-child td { border-bottom: none; }
.placement-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
margin: 1.25rem 0;
}
.placement-table th {
text-align: left;
padding: 0.6rem 1rem;
background: var(--bg3);
color: var(--text2);
font-weight: 500;
font-size: 11px;
letter-spacing: 0.06em;
text-transform: uppercase;
border-bottom: 1px solid var(--line);
}
.placement-table td {
padding: 0.65rem 1rem;
color: var(--text2);
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.placement-table td:first-child {
color: var(--text);
font-weight: 400;
font-size: 13.5px;
}
.placement-table td:last-child {
font-family: var(--mono);
font-size: 12px;
color: var(--green);
}
.placement-table tr:last-child td { border-bottom: none; }
.compare-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin: 1.25rem 0;
}
.compare-card {
background: var(--bg2);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 1.25rem;
}
.compare-card h4 {
font-family: var(--mono);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text2);
margin-bottom: 0.75rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--line);
}
.compare-row {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 0.4rem 0;
border-bottom: 1px solid var(--line);
font-size: 12px;
}
.compare-row:last-child { border-bottom: none; }
.compare-key {
color: var(--text2);
width: 110px;
flex-shrink: 0;
font-size: 11px;
}
.compare-val { color: var(--text); font-family: var(--mono); font-size: 11px; }
.compare-val.same { color: var(--text2); }
.compare-val.diff { color: var(--amber); }
.limits-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin: 1.25rem 0;
}
.limit-card {
background: var(--bg2);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 1rem;
}
.limit-val {
font-family: var(--mono);
font-size: 1.6rem;
font-weight: 300;
color: var(--accent2);
line-height: 1;
margin-bottom: 4px;
}
.limit-label {
font-size: 12px;
color: var(--text2);
}
.template-block {
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: var(--r);
overflow: hidden;
margin: 1.25rem 0;
}
.template-header {
background: var(--bg3);
padding: 0.6rem 1rem;
font-family: var(--mono);
font-size: 11px;
color: var(--text2);
letter-spacing: 0.08em;
text-transform: uppercase;
border-bottom: 1px solid var(--line);
display: flex;
justify-content: space-between;
}
.template-header .budget { color: var(--green); }
.layer-stack {
display: flex;
flex-direction: column;
gap: 8px;
margin: 1.25rem 0;
}
.layer {
border: 1px solid var(--line);
border-radius: var(--r);
padding: 0.9rem 1.1rem;
background: var(--bg2);
display: flex;
align-items: center;
gap: 12px;
}
.layer-num {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--accent-bg);
color: var(--accent2);
font-family: var(--mono);
font-size: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 1px solid var(--accent);
}
.layer-info { flex: 1; }
.layer-info strong { color: var(--text); font-size: 0.9rem; }
.layer-info p { font-size: 0.8rem; margin: 0.15rem 0 0; }
.layer-size {
font-family: var(--mono);
color: var(--green);
font-size: 11px;
text-align: right;
flex-shrink: 0;
}
footer {
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px solid var(--line);
font-size: 12px;
color: var(--text2);
}
.mono { font-family: var(--mono); }
.pill {
display: inline-flex;
align-items: center;
gap: 4px;
font-family: var(--mono);
font-size: 11px;
padding: 3px 8px;
border-radius: 4px;
}
.pill.green { background: var(--green-bg); color: var(--green); border: 1px solid rgba(63,207,142,0.2); }
.pill.red { background: var(--red-bg); color: var(--red); border: 1px solid rgba(248,113,113,0.2); }
.pill.blue { background: var(--blue-bg); color: var(--blue); border: 1px solid rgba(96,165,250,0.2); }
.pill.amber { background: var(--amber-bg); color: var(--amber); border: 1px solid rgba(251,191,36,0.2); }
@media (max-width: 860px) {
nav { transform: translateX(-100%); }
main { margin-left: 0; padding: 2rem 1.5rem; }
.compare-grid { grid-template-columns: 1fr; }
.limits-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<nav>
<div class="nav-logo">
<span class="logotype">Claude Code</span>
<h1>Skills<br>Reference Guide</h1>
</div>
<ul>
<li class="section-label">Foundations</li>
<li><a href="#what-is-skill">What is a Skill</a></li>
<li><a href="#anatomy">Anatomy of a Skill</a></li>
<li><a href="#discovery">Discovery Sources</a></li>
<li><a href="#lifecycle">Loading Lifecycle</a></li>
<li class="section-label">Architecture</li>
<li><a href="#disclosure">Progressive Disclosure</a></li>
<li><a href="#budget">Listing Budget</a></li>
<li><a href="#invocation">Invocation Modes</a></li>
<li><a href="#triggers">Trigger Types</a></li>
<li><a href="#tool">The Skill Tool</a></li>
<li class="section-label">Authoring</li>
<li><a href="#frontmatter">Frontmatter Schema</a></li>
<li><a href="#description">Writing Descriptions</a></li>
<li><a href="#resources">Bundled Resources</a></li>
<li><a href="#process">6-Step Creation</a></li>
<li><a href="#template">Template</a></li>
<li class="section-label">Quality</li>
<li><a href="#best-practices">Best Practices</a></li>
<li><a href="#evaluation">Evaluation</a></li>
<li><a href="#anti-patterns">Anti-Patterns</a></li>
<li><a href="#vs-claudemd">Skill vs CLAUDE.md</a></li>
<li class="section-label">Reference</li>
<li><a href="#permissions">Permissions Model</a></li>
<li><a href="#hot-reload">Hot Reload</a></li>
<li><a href="#constants">Hard Limits</a></li>
</ul>
</nav>
<main>
<div class="hero">
<span class="tag">Reference Guide</span>
<h1>How <em>Skills</em><br>Work in Claude Code</h1>
<p>Discovery, authoring, invocation, and quality — derived from observed runtime behavior and the authoritative <code>skill-creator</code> guide that ships with Claude Code.</p>
</div>
<!-- ── What is a Skill ── -->
<section id="what-is-skill">
<h2>What is a Skill?</h2>
<p>A <strong>Skill</strong> is a modular, self-contained package — an "onboarding guide" — that teaches Claude how to do a specialized task. It bundles procedural knowledge, references, and (optionally) executable scripts and assets.</p>
<p>From the <code>skill-creator</code> guide:</p>
<div class="callout insight">
<p><em>"Skills are modular, self-contained packages that extend Claude's capabilities by providing specialized knowledge, workflows, and tools. Think of them as <strong>onboarding guides</strong> for specific domains or tasks — they transform Claude from a general-purpose agent into a specialized agent equipped with procedural knowledge that no model can fully possess."</em></p>
</div>
<h3>What skills provide</h3>
<pre><code><span class="ok">1.</span> Specialized workflows <span class="cm">— multi-step procedures for specific domains</span>
<span class="ok">2.</span> Tool integrations <span class="cm">— how to work with file formats / APIs</span>
<span class="ok">3.</span> Domain expertise <span class="cm">— company-specific knowledge, schemas, business logic</span>
<span class="ok">4.</span> Bundled resources <span class="cm">— scripts, references, assets for repetitive tasks</span>
</code></pre>
<h3>Mental model</h3>
<p>A skill is the answer to: <em>"What would a senior engineer write in a runbook so a new teammate can do this task without asking?"</em> If your answer is <strong>"a one-line tip"</strong> → put it in CLAUDE.md. If it's <strong>"a procedure with reference docs and a script"</strong> → make it a skill.</p>
</section>
<!-- ── Anatomy ── -->
<section id="anatomy">
<h2>Anatomy of a Skill</h2>
<p>Every skill is a directory containing one required file (<code>SKILL.md</code>) and optional bundled resources. The directory name <em>is</em> the canonical skill identifier.</p>
<pre><code>skill-name/ <span class="cm">← directory name = skill identifier</span>
├── <span class="ok">SKILL.md</span> <span class="cm">← REQUIRED · YAML frontmatter + markdown body</span>
├── scripts/ <span class="cm">← OPTIONAL · executable code (.py / .sh / etc.)</span>
│ └── rotate_pdf.py <span class="cm"> deterministic, may run without loading into context</span>
├── references/ <span class="cm">← OPTIONAL · docs loaded on-demand by Claude</span>
│ ├── schema.md <span class="cm"> DB schemas, API specs, policies, detailed workflows</span>
│ └── api_docs.md
└── assets/ <span class="cm">← OPTIONAL · used IN OUTPUT (templates, images, fonts)</span>
└── template.html <span class="cm"> never loaded into Claude's context window</span>
</code></pre>
<div class="callout insight">
<p><strong>Three-folder convention.</strong> <code>scripts/</code> are run, <code>references/</code> are read, <code>assets/</code> are copied. Putting files in the wrong folder works but obscures intent — and Claude's behavior often follows the folder semantics.</p>
</div>
<h3>Reference example — <code>verify</code></h3>
<p>The bundled <code>verify</code> skill is a good minimal pattern: a short SKILL.md plus <code>examples/cli.md</code> and <code>examples/server.md</code> as reference documents.</p>
</section>
<!-- ── Discovery ── -->
<section id="discovery">
<h2>Where Skills Come From</h2>
<p>Skills are discovered from several sources at startup, then merged and deduplicated by name:</p>
<table class="rules-table">
<thead>
<tr><th>Source</th><th>Path</th><th>Notes</th></tr>
</thead>
<tbody>
<tr>
<td>Bundled</td>
<td>compiled into the CLI</td>
<td>Always available · e.g. <code>/loop</code>, <code>/verify</code>, <code>/claude-api</code>, <code>/batch</code></td>
</tr>
<tr>
<td>Plugin</td>
<td>installed plugin directories</td>
<td>Namespaced as <code>plugin:skill-name</code> in listings</td>
</tr>
<tr>
<td>User</td>
<td><code>~/.claude/skills/</code></td>
<td>Personal skills shared across all your projects</td>
</tr>
<tr>
<td>Project</td>
<td><code>{repo}/.claude/skills/</code></td>
<td>Walked up from cwd, like CLAUDE.md ancestry</td>
</tr>
<tr>
<td>Additional dirs</td>
<td><code>--add-dir</code> CLI flag</td>
<td>Explicit extra paths scanned for <code>.claude/skills/</code></td>
</tr>
<tr>
<td>MCP</td>
<td>dynamic via MCP servers</td>
<td>Namespaced as <code>server:skill-name</code> · only "skills" (not raw prompts) are surfaced</td>
</tr>
<tr>
<td>Policy</td>
<td><code>getManagedFilePath()/.claude/skills/</code></td>
<td>Org-managed · skipped if <code>CLAUDE_CODE_DISABLE_POLICY_SKILLS</code> set</td>
</tr>
</tbody>
</table>
<div class="callout tip">
<p><strong>Deduplication is name-based and respects realpath.</strong> If <code>~/.claude/skills/foo</code> is a symlink to a project skill, only one copy is loaded. Loading order determines the winner: bundled → plugin → user → project → MCP.</p>
</div>
</section>
<!-- ── Lifecycle ── -->
<section id="lifecycle">
<h2>The Loading Lifecycle</h2>
<p>Skill loading is intentionally lazy. Most of the work happens at startup, but full skill content is only ever loaded when invoked.</p>
<div class="steps">
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<strong>Startup — eager directory scan</strong>
<p><code>getSkillDirCommands(cwd)</code> walks all skill sources, parses each <code>SKILL.md</code> frontmatter, and builds a list of skill metadata. Memoized — runs once per process.</p>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<strong>Conditional skills are set aside</strong>
<p>Skills with a <code>paths:</code> frontmatter field are stored in a separate <code>conditionalSkills</code> map and only activated when matching files are touched.</p>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<strong>Listing injected as <code>&lt;system-reminder&gt;</code></strong>
<p>On turn 0 (and again when the set changes), <code>getSkillListingAttachments()</code> formats <code>name + description</code> for each skill into the system reminder Claude sees.</p>
</div>
</div>
<div class="step">
<div class="step-num">4</div>
<div class="step-body">
<strong>Per-turn dynamic discovery</strong>
<p>When a Read/Write/Edit touches a path, <code>discoverSkillDirsForPaths()</code> walks up from that file looking for new <code>.claude/skills/</code> dirs and <code>activateConditionalSkillsForPaths()</code> activates any glob-matching skills.</p>
</div>
</div>
<div class="step">
<div class="step-num">5</div>
<div class="step-body">
<strong>Invocation — full content loaded</strong>
<p>When Claude calls the <code>Skill</code> tool, <code>getPromptForCommand()</code> reads the full <code>SKILL.md</code> body, performs <code>${CLAUDE_SKILL_DIR}</code> substitution, and either expands inline or forks a subagent.</p>
</div>
</div>
<div class="step">
<div class="step-num">6</div>
<div class="step-body">
<strong>Hot-reload via chokidar</strong>
<p>A file watcher monitors all skill directories with a 300ms debounce. Edits clear the memoized cache and emit <code>skillsChanged</code> — no restart needed.</p>
</div>
</div>
</div>
</section>
<!-- ── Progressive disclosure ── -->
<section id="disclosure">
<h2>Progressive Disclosure</h2>
<p>The single most important architectural idea about skills is <strong>three-level progressive disclosure</strong>. Different parts of a skill load at different times, allowing the system to surface hundreds of skills without exhausting the context window.</p>
<div class="layer-stack">
<div class="layer">
<div class="layer-num">1</div>
<div class="layer-info">
<strong>Metadata — name + description</strong>
<p>Always in context. Listed in the <code>&lt;system-reminder&gt;</code> for every turn. This is what Claude uses to decide whether to invoke.</p>
</div>
<div class="layer-size">~100 words<br>each skill</div>
</div>
<div class="layer">
<div class="layer-num">2</div>
<div class="layer-info">
<strong>SKILL.md body</strong>
<p>Loaded only when the skill is invoked. Procedural instructions, references to bundled files, decision rules.</p>
</div>
<div class="layer-size">&lt; 5 000 words<br>recommended</div>
</div>
<div class="layer">
<div class="layer-num">3</div>
<div class="layer-info">
<strong>Bundled resources</strong>
<p>References loaded by Claude on-demand (Read/Grep). Scripts can be executed without ever entering the context window. Assets are copied into output.</p>
</div>
<div class="layer-size">unlimited*</div>
</div>
</div>
<div class="callout insight">
<p><strong>Design implication.</strong> Your <code>description</code> is the skill's "marketing copy" — it's the only part that's always in context. Your SKILL.md body is the runbook, loaded when used. Anything bigger goes in <code>references/</code> and gets pulled in only when needed.</p>
</div>
</section>
<!-- ── Budget ── -->
<section id="budget">
<h2>Listing Budget &amp; Truncation</h2>
<p>The skill listing in the system reminder has a hard budget:</p>
<div class="limits-grid">
<div class="limit-card">
<div class="limit-val">1%</div>
<div class="limit-label">of context window for the listing</div>
</div>
<div class="limit-card">
<div class="limit-val">8 000</div>
<div class="limit-label">default char budget (200k context)</div>
</div>
<div class="limit-card">
<div class="limit-val">250</div>
<div class="limit-label">max chars per description in listing</div>
</div>
<div class="limit-card">
<div class="limit-val">30</div>
<div class="limit-label">max entries when search is filtered</div>
</div>
</div>
<h3>Format injected into the conversation</h3>
<pre><code><span class="cm"># Wrapped as a system-reminder:</span>
The following skills are available for use with the Skill tool:
- <span class="ok">skill-name</span>: <span class="dim">first ~250 chars of description</span>
- <span class="ok">other-skill</span>: <span class="dim">…</span>
</code></pre>
<h3>Bundled skills are privileged</h3>
<p>When budget is tight, <strong>bundled skills always get their full description</strong>; user/plugin skills get truncated first. If you're authoring a third-party skill, every char in the description matters — front-load the trigger phrase.</p>
<div class="budget-bar-wrap">
<div class="budget-row">
<span class="budget-label">bundled / loop</span>
<div class="budget-track"><div class="budget-fill" style="width:35%;background:var(--green)"></div></div>
<span class="budget-chars ok">full</span>
</div>
<div class="budget-row">
<span class="budget-label">bundled / verify</span>
<div class="budget-track"><div class="budget-fill" style="width:30%;background:var(--green)"></div></div>
<span class="budget-chars ok">full</span>
</div>
<div class="budget-row">
<span class="budget-label">user / swiftui-pro</span>
<div class="budget-track"><div class="budget-fill" style="width:25%;background:var(--accent)"></div></div>
<span class="budget-chars" style="color:var(--accent2)">→ 250 ✂</span>
</div>
<div class="budget-row">
<span class="budget-label">plugin / kids-book-writer</span>
<div class="budget-track"><div class="budget-fill" style="width:25%;background:var(--amber)"></div></div>
<span class="budget-chars amber-t">→ 250 ✂</span>
</div>
<div class="budget-row">
<span class="budget-label">user / very-rare-skill</span>
<div class="budget-track"><div class="budget-fill" style="width:0%;background:var(--red)"></div></div>
<span class="budget-chars no">may drop</span>
</div>
</div>
</section>
<!-- ── Invocation ── -->
<section id="invocation">
<h2>Invocation Modes — Inline vs Fork</h2>
<p>Every skill runs in one of two modes, controlled by the <code>context:</code> frontmatter field. The choice fundamentally changes how the skill consumes tokens and shares state with the parent conversation.</p>
<div class="compare-grid">
<div class="compare-card">
<h4>context: inline (default)</h4>
<div class="compare-row">
<span class="compare-key">Where it runs</span>
<span class="compare-val same">in the parent conversation</span>
</div>
<div class="compare-row">
<span class="compare-key">Token budget</span>
<span class="compare-val same">shared with parent</span>
</div>
<div class="compare-row">
<span class="compare-key">Conversation history</span>
<span class="compare-val same">visible</span>
</div>
<div class="compare-row">
<span class="compare-key">Tool access</span>
<span class="compare-val same">parent's tools, optional permit-list</span>
</div>
<div class="compare-row">
<span class="compare-key">Best for</span>
<span class="compare-val same">quick procedures, helpers, slash commands</span>
</div>
<div class="compare-row">
<span class="compare-key">Cost model</span>
<span class="compare-val same">cheap, no extra round-trip</span>
</div>
</div>
<div class="compare-card">
<h4>context: fork</h4>
<div class="compare-row">
<span class="compare-key">Where it runs</span>
<span class="compare-val diff">isolated subagent</span>
</div>
<div class="compare-row">
<span class="compare-key">Token budget</span>
<span class="compare-val diff">separate budget</span>
</div>
<div class="compare-row">
<span class="compare-key">Conversation history</span>
<span class="compare-val diff">EMPTY — fresh start</span>
</div>
<div class="compare-row">
<span class="compare-key">Tool access</span>
<span class="compare-val diff">inherits parent's full toolset</span>
</div>
<div class="compare-row">
<span class="compare-key">Best for</span>
<span class="compare-val diff">heavy research, large outputs, isolation</span>
</div>
<div class="compare-row">
<span class="compare-key">Cost model</span>
<span class="compare-val diff">expensive, full new agent loop</span>
</div>
</div>
</div>
<div class="callout warn">
<p><strong>Forked skills lose all conversation context.</strong> They cannot ask "what were we talking about?" — your SKILL.md must be self-contained and your <code>args</code> must carry everything the subagent needs. Parent's tools come along for the ride; parent's chat history does not.</p>
</div>
</section>
<!-- ── Triggers ── -->
<section id="triggers">
<h2>How Skills Get Triggered</h2>
<p>There are three distinct ways a skill can be invoked — and each is configured by frontmatter fields on the skill itself.</p>
<div class="scenarios">
<div class="scenario-tabs">
<button class="scenario-tab active" onclick="showScenario('auto', this)">A · Description match</button>
<button class="scenario-tab" onclick="showScenario('user', this)">B · Slash command</button>
<button class="scenario-tab" onclick="showScenario('paths', this)">C · Path glob</button>
</div>
<div id="sp-auto" class="scenario-panel active">
<h3>Description match (auto-trigger)</h3>
<p>Claude reads each skill's <code>description</code> in the listing and decides — based on user intent — when to invoke. The Skill tool's prompt instructs Claude:</p>
<pre><code><span class="cm">When a skill matches the user's request, this is a</span>
<span class="amber-t">BLOCKING REQUIREMENT</span>: invoke the relevant Skill tool BEFORE
generating any other response about the task.
<span class="amber-t">NEVER</span> mention a skill without actually calling this tool.</code></pre>
<p>Your <code>description</code> needs explicit trigger language: starts with <em>"Use when…"</em>, lists keywords/file patterns/user phrases that should match.</p>
</div>
<div id="sp-user" class="scenario-panel">
<h3>Slash command — <code>user-invocable</code></h3>
<p>If <code>user-invocable: true</code>, the user can type <code>/skill-name args</code> directly. Default is <em>true</em> for files under <code>commands/</code> and <em>false</em> for files under <code>skills/</code>.</p>
<pre><code><span class="cm"># Force user-only invocation:</span>
<span class="kw">user-invocable</span>: <span class="str">true</span>
<span class="kw">disable-model-invocation</span>: <span class="str">true</span> <span class="cm"># model can't auto-call</span>
<span class="cm"># Result: only `/skill-name` works; Claude won't surface it.</span></code></pre>
</div>
<div id="sp-paths" class="scenario-panel">
<h3>Path glob — conditional skills</h3>
<p>Add a <code>paths:</code> field with gitignore-style globs. The skill stays dormant until Claude reads/edits a matching file, at which point it auto-activates and joins the listing on the next turn.</p>
<pre><code><span class="kw">paths</span>: <span class="str">"src/frontend/**/*.tsx, src/components/**/*.ts"</span>
<span class="cm"># Or as a YAML list:</span>
<span class="kw">paths</span>:
- <span class="str">"**/*.swift"</span>
- <span class="str">"**/Package.swift"</span></code></pre>
<p>Use this for stack-specific skills (<em>"only suggest SwiftUI patterns when editing Swift files"</em>) — keeps the global listing lean and on-topic.</p>
</div>
</div>
</section>
<!-- ── Skill tool ── -->
<section id="tool">
<h2>The <code>Skill</code> Tool</h2>
<p>The mechanism by which Claude actually invokes a skill. The tool's input schema:</p>
<pre><code><span class="kw">export const</span> inputSchema = lazySchema(() =>
z.object({
skill: z.string().describe(<span class="str">"The skill name. E.g., 'commit', 'review-pr', 'pdf'"</span>),
args: z.string().optional().describe(<span class="str">"Optional arguments for the skill"</span>),
})
)</code></pre>
<h3>System-prompt instructions to Claude</h3>
<pre><code><span class="cm"># What Claude is told about the Skill tool:</span>
When users reference a "slash command" or "/&lt;something&gt;",
they are referring to a skill. Use this tool to invoke it.
How to invoke:
- Set `skill` to the exact name of an available skill.
- For plugin-namespaced skills use `plugin:skill`.
- Set `args` to pass optional arguments.
Important:
- Available skills are listed in &lt;system-reminder&gt; messages
- When a skill matches, this is a <span class="amber-t">BLOCKING REQUIREMENT</span>
- <span class="amber-t">NEVER</span> mention a skill without calling this tool
- Do not invoke a skill that is already running
- Do not use this tool for built-in CLI commands (/help, /clear, …)</code></pre>
<h3>Two return shapes</h3>
<pre><code><span class="cm">// Inline result (skill content expands into the conversation)</span>
{ success: <span class="kw">true</span>, commandName, allowedTools?, model?, status: <span class="str">'inline'</span> }
<span class="cm">// Forked result (skill ran as subagent)</span>
{ success: <span class="kw">true</span>, commandName, status: <span class="str">'forked'</span>, agentId, result }</code></pre>
</section>
<!-- ── Frontmatter ── -->
<section id="frontmatter">
<h2>Frontmatter Schema</h2>
<p>SKILL.md begins with YAML frontmatter delimited by <code>---</code>. Only <code>description</code> is effectively required (it falls back to the first markdown line if missing). Everything else is optional but each field unlocks specific behavior.</p>
<table class="rules-table">
<thead>
<tr><th>Field</th><th>Type · default</th><th>Purpose</th></tr>
</thead>
<tbody>
<tr><td>name</td> <td>string · directory name</td> <td>Display override. Canonical id stays the directory name.</td></tr>
<tr><td>description</td><td>string · <em>required</em></td><td>The single most important field. Drives auto-trigger matching and appears in every system reminder.</td></tr>
<tr><td>when_to_use</td><td>string · undefined</td> <td>Long-form trigger guidance. Loaded with the skill body, not the listing.</td></tr>
<tr><td>allowed-tools</td><td>string / array</td> <td>Permission patterns: <code>Bash(git:*)</code>, <code>Read</code>. Restrictive permit-list for inline skills.</td></tr>
<tr><td>argument-hint</td><td>string</td> <td>Placeholder hint for the user (<code>&lt;target&gt; [--flag]</code>). UI-only.</td></tr>
<tr><td>arguments</td> <td>string / array</td> <td>Named args for <code>$arg_name</code> substitution in body.</td></tr>
<tr><td>user-invocable</td><td>bool · varies</td> <td><code>true</code> = user can type <code>/name</code>. Default depends on source dir.</td></tr>
<tr><td>disable-model-invocation</td><td>bool · false</td><td><code>true</code> = Claude can't auto-call; user-only.</td></tr>
<tr><td>model</td> <td>string · undefined</td> <td>Override model: <code>haiku</code> / <code>sonnet</code> / <code>opus</code> / <code>inherit</code>.</td></tr>
<tr><td>effort</td> <td>'low'..'max' / int</td> <td>Thinking budget for forked skills.</td></tr>
<tr><td>context</td> <td>'inline' / 'fork'</td> <td><code>inline</code> = expand in parent conv (default). <code>fork</code> = subagent.</td></tr>
<tr><td>agent</td> <td>string</td> <td>Agent type for <code>context: fork</code> (e.g. <code>general-purpose</code>).</td></tr>
<tr><td>paths</td> <td>string / array</td> <td>Gitignore-style globs. Skill auto-activates when matching files are touched.</td></tr>
<tr><td>shell</td> <td>'bash' / 'powershell'</td><td>Shell for <code>!`cmd`</code> blocks. Author-chosen, portable.</td></tr>
<tr><td>hooks</td> <td>object</td> <td>Pre/Post tool-use hooks, validated via Zod <code>HooksSchema()</code>.</td></tr>
<tr><td>version</td> <td>string</td> <td>Informational. Not currently validated.</td></tr>
<tr><td>license</td> <td>string</td> <td>Informational; e.g. <code>"Complete terms in LICENSE.txt"</code>.</td></tr>
</tbody>
</table>
<div class="callout warn">
<p><strong>Validation is forgiving.</strong> Bad <code>hooks</code>, invalid <code>effort</code>, unknown <code>shell</code> — all log a debug warning and the skill loads anyway with the offending field dropped. Don't rely on the loader to catch authoring mistakes; test by invoking.</p>
</div>
</section>
<!-- ── Description ── -->
<section id="description">
<h2>Writing the <code>description</code></h2>
<p>The description is the skill's only constant presence in Claude's context. Treat it like an API contract — a bad description is invisible to triggering, no matter how good the body is.</p>
<h3>Rules from the <code>skill-creator</code> guide</h3>
<div class="callout insight">
<p><strong>"The <code>name</code> and <code>description</code> in YAML frontmatter determine when Claude will use the skill. Be specific about what the skill does and when to use it. Use the third-person (e.g. 'This skill should be used when…' instead of 'Use this skill when…')."</strong></p>
</div>
<h3>Anatomy of an effective description</h3>
<pre><code><span class="cm"># Pattern: [WHAT it does] + [WHEN to use it] + [TRIGGER phrases]</span>
<span class="ok">✓ GOOD:</span>
<span class="str">"Create notarized macOS app releases with Sparkle auto-updates,
DMG installers, and GitHub releases. Use when releasing macOS apps,
creating DMG files, notarizing apps, or setting up Sparkle updates."</span>
<span class="cm">└─ what it does ──┘ └─ when ──┘ └── trigger phrases ──┘</span>
<span class="no">✗ BAD:</span>
<span class="str">"A helper for app releases."</span>
<span class="cm"> ↑ vague, no triggers, no scope</span>
<span class="no">✗ BAD:</span>
<span class="str">"Use this skill to release apps."</span>
<span class="cm"> ↑ second person · no specifics · no domain</span></code></pre>
<h3>Real examples from installed skills</h3>
<pre><code><span class="cm"># skill-creator</span>
<span class="kw">description</span>: <span class="str">"Guide for creating effective skills. This skill should be
used when users want to create a new skill (or update an existing skill)
that extends Claude's capabilities…"</span>
<span class="cm"># swiftui-animation</span>
<span class="kw">description</span>: <span class="str">"This skill provides comprehensive guidance for implementing
advanced SwiftUI animations, transitions, matched geometry effects, and
Metal shader integration. Use when building animations, view transitions,
hero animations, or GPU-accelerated effects in SwiftUI apps for iOS and
macOS."</span>
<span class="cm"># claude-api</span>
<span class="kw">description</span>: <span class="str">"… TRIGGER when: code imports `anthropic` / `@anthropic-ai/sdk`;
user asks for the Claude API … SKIP: file imports `openai`/other-provider
SDK …"</span> <span class="cm">← explicit positive AND negative triggers</span></code></pre>
<div class="callout tip">
<p><strong>The 250-char ceiling is real.</strong> Listings truncate user/plugin descriptions to 250 chars. Put the trigger language in the first sentence. If your description is longer, keep the body for <code>when_to_use</code> instead.</p>
</div>
</section>
<!-- ── Bundled resources ── -->
<section id="resources">
<h2>Bundled Resources</h2>
<p>From the <code>skill-creator</code> guide — three-folder convention with strict semantics:</p>
<h3>scripts/ — executable code</h3>
<pre><code><span class="ok">When to include:</span> Same code being rewritten repeatedly,
or deterministic reliability needed.
<span class="ok">Example:</span> scripts/rotate_pdf.py
<span class="ok">Benefits:</span> Token efficient, deterministic, can run
WITHOUT loading code into context.
<span class="ok">Note:</span> Claude may still need to Read for patches.</code></pre>
<h3>references/ — context-loaded docs</h3>
<pre><code><span class="ok">When to include:</span> Documentation Claude should reference
while working (schemas, API specs, policies).
<span class="ok">Examples:</span> references/schema.md, references/api_docs.md
<span class="ok">Benefits:</span> Keeps SKILL.md lean, loaded on-demand.
<span class="ok">Best practice:</span> For files &gt;10k words, include grep search
patterns in SKILL.md for navigation.</code></pre>
<h3>assets/ — output materials</h3>
<pre><code><span class="ok">When to include:</span> Files that end up IN the output —
templates, images, fonts, boilerplate.
<span class="ok">Examples:</span> assets/logo.png, assets/slides.pptx,
assets/frontend-template/
<span class="ok">Benefits:</span> Never loaded into context window;
copied/used directly in produced artifacts.</code></pre>
<h3>The <code>${CLAUDE_SKILL_DIR}</code> placeholder</h3>
<p>Inside SKILL.md, reference your own bundled files via <code>${CLAUDE_SKILL_DIR}</code>. The runtime substitutes the absolute skill directory path so shell commands and references resolve correctly:</p>
<pre><code>Run the rotation script:
<span class="str">!`python ${CLAUDE_SKILL_DIR}/scripts/rotate_pdf.py "$ARG"`</span>
Read the schema reference:
See <span class="str">${CLAUDE_SKILL_DIR}/references/schema.md</span> for table definitions.</code></pre>
<div class="callout warn">
<p><strong>Avoid duplication.</strong> Information should live in <em>either</em> SKILL.md or a references file — not both. Keep SKILL.md for procedural workflow; move detailed schemas, examples, and reference data to <code>references/</code>. Duplication rots fast.</p>
</div>
</section>
<!-- ── 6-step process ── -->
<section id="process">
<h2>The Skill Creation Process</h2>
<p>The <code>skill-creator</code> skill defines a 6-step process. Skip a step only when there's a clear reason it doesn't apply.</p>
<div class="steps">
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<strong>Understand with concrete examples</strong>
<p>Before writing any markdown, list 3–5 actual user requests the skill should handle. <em>"What would a user say to trigger this?"</em> If you can't answer, the skill isn't ready.</p>
</div>
</div>
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<strong>Plan reusable contents</strong>
<p>For each example, ask: what would I need to write or look up to do this from scratch? Repeated code → <code>scripts/</code>. Repeated lookup → <code>references/</code>. Repeated boilerplate → <code>assets/</code>.</p>
</div>
</div>
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<strong>Initialize via <code>init_skill.py</code></strong>
<p>Run <code>scripts/init_skill.py &lt;name&gt; --path &lt;dir&gt;</code> to scaffold SKILL.md with proper frontmatter and example resource folders. Don't hand-roll the structure.</p>
</div>
</div>
<div class="step">
<div class="step-num">4</div>
<div class="step-body">
<strong>Edit SKILL.md (imperative form)</strong>
<p>Write for another instance of Claude, not a human teammate. Use verb-first instructions: <em>"To accomplish X, do Y"</em> — not <em>"You should do X"</em>. Reference every bundled file you created.</p>
</div>
</div>
<div class="step">
<div class="step-num">5</div>
<div class="step-body">
<strong>Package via <code>package_skill.py</code></strong>
<p>Run <code>scripts/package_skill.py &lt;path&gt;</code>. Validates frontmatter, naming, description completeness, file organization. Produces a distributable <code>.zip</code>.</p>
</div>
</div>
<div class="step">
<div class="step-num">6</div>
<div class="step-body">
<strong>Iterate from real usage</strong>
<p>Use the skill on real tasks. Note where Claude struggles. Update SKILL.md or bundled resources. The first version is never the final version.</p>
</div>
</div>
</div>
</section>
<!-- ── Template ── -->
<section id="template">
<h2>Ideal SKILL.md Template</h2>
<p>An effective SKILL.md follows the inverted pyramid: trigger info first, procedure next, edge cases last. Aim for under ~200 lines in SKILL.md itself; push detail to <code>references/</code>.</p>
<div class="template-block">
<div class="template-header">
<span>SKILL.md</span>
<span class="budget">~150 lines · well within disclosure budget</span>
</div>
<pre style="border:none;border-radius:0;margin:0;padding:1.25rem 1.5rem;"><code><span class="cm">---</span>
<span class="kw">name</span>: <span class="str">my-skill</span>
<span class="kw">description</span>: <span class="str">"What it does. This skill should be used when X, Y, or Z.
TRIGGER when: explicit conditions. SKIP when: false-positive cases."</span>
<span class="kw">allowed-tools</span>: <span class="str">"Bash(git:*), Read, Write, Edit"</span>
<span class="kw">user-invocable</span>: <span class="str">true</span>
<span class="cm">---</span>
<span class="cm"># My Skill</span>
One paragraph: what this skill is for, in plain language.
<span class="cm">## When to Use</span>
- Trigger 1: explicit condition (e.g., "user asks to release the app")
- Trigger 2: file pattern observed
- Skip if: condition that means a different skill applies
<span class="cm">## Procedure</span>
To accomplish &lt;task&gt;:
1. First, run <span class="str">`!\`${CLAUDE_SKILL_DIR}/scripts/check.sh\`</span>` to verify state.
2. If &lt;condition&gt;, read <span class="str">`${CLAUDE_SKILL_DIR}/references/schema.md`</span>.
3. Apply the change using the template at <span class="str">`${CLAUDE_SKILL_DIR}/assets/template.html`</span>.
4. Verify by running &lt;test command&gt;.
<span class="cm">## Decision Rules</span>
- If X then Y
- If A and B then C, otherwise D
<span class="cm">## References</span>
- Detailed schemas: <span class="str">`references/schema.md`</span>
- Edge cases: <span class="str">`references/edge_cases.md`</span>
- API docs: <span class="str">`references/api.md`</span>
</code></pre>
</div>
</section>
<!-- ── Best practices ── -->
<section id="best-practices">
<h2>Best Practices</h2>
<h3>1 — Lead the description with a trigger</h3>
<p>The first sentence is what survives truncation. Put the strongest match signal first: <em>"This skill should be used when…"</em></p>
<h3>2 — Write in imperative form</h3>
<p>Skills are read by Claude, not humans. Use <strong>verb-first instructions</strong>: <em>"Run the script. Read the schema. Apply the template."</em> — not <em>"You should run the script."</em></p>
<h3>3 — Keep SKILL.md under 5 000 words</h3>
<p>The body is loaded on every invocation. If your skill has more material, split it: keep the procedure in SKILL.md and move details to <code>references/</code> with grep patterns for navigation.</p>
<h3>4 — Reference every bundled file</h3>
<p>If you have <code>scripts/foo.py</code> but SKILL.md never mentions it, Claude won't find it. Every script, every reference, every asset needs an explicit pointer in SKILL.md.</p>
<h3>5 — Use <code>${CLAUDE_SKILL_DIR}</code>, not relative paths</h3>
<p>Skills can be installed in many locations. Always anchor to <code>${CLAUDE_SKILL_DIR}</code> so the path resolves correctly regardless of cwd.</p>
<h3>6 — Choose the right invocation mode</h3>
<pre><code><span class="kw">context: inline</span> <span class="cm">→ default · fast · shares context</span>
<span class="cm"> use for: helpers, slash commands, additive procedures</span>
<span class="kw">context: fork</span> <span class="cm">→ subagent · isolated · separate token budget</span>
<span class="cm"> use for: heavy research, large outputs, parallelism</span></code></pre>
<h3>7 — Restrict <code>allowed-tools</code></h3>
<p>Declare only the tools the skill needs. Use patterns like <code>Bash(git:*)</code> to scope down — this both reduces permission prompts and signals intent to readers.</p>
<h3>8 — Make skills self-contained</h3>
<p>Forked skills have no conversation history. Inline skills <em>can</em> see context but shouldn't depend on it. Write each skill as if the invocation is the first thing the agent ever did.</p>
</section>
<!-- ── Evaluation ── -->
<section id="evaluation">
<h2>Evaluation &amp; Quality Signals</h2>
<div class="callout warn">
<p><strong>Claude Code does not ship a formal skill-evaluation framework.</strong> There is no eval CLI, no test harness, no scoring rubric in the runtime. The only authoritative guidance on improving a skill is <code>skill-creator</code>'s Step 6 — iterative dogfooding.</p>
</div>
<h3>What <code>skill-creator</code> says about iteration</h3>
<p>Quoted directly from the bundled <code>skill-creator</code> guide, Step 6:</p>
<div class="callout insight">
<p><em>"After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed."</em></p>
<p style="margin-top:0.5rem"><strong>Iteration workflow:</strong></p>
<p>1. Use the skill on real tasks<br>2. Notice struggles or inefficiencies<br>3. Identify how SKILL.md or bundled resources should be updated<br>4. Implement changes and test again</p>
</div>
<h3>Telemetry the runtime emits</h3>
<p>Two events are observable today and can serve as proxy signals:</p>
<table class="rules-table">
<thead><tr><th>Event</th><th>What it captures</th></tr></thead>
<tbody>
<tr><td>tengu_skill_tool_invocation</td><td>Each invocation logs <code>command_name</code>, <code>execution_context</code> (inline/fork), <code>invocation_trigger</code> (claude-proactive vs nested-skill), <code>query_depth</code>, and source.</td></tr>
<tr><td>tengu_skill_file_changed</td><td>Author edited the SKILL.md — emitted by the chokidar watcher.</td></tr>
</tbody>
</table>
<h3>Pre-flight checklist</h3>
<p>Items below are grounded in <code>skill-creator</code> guidance and observed runtime constraints. This is a sanity check — not a formal evaluation.</p>
<pre><code><span class="ok">[ ]</span> Description fits within the 250-char listing truncation
<span class="ok">[ ]</span> Frontmatter loads without warnings
<span class="ok">[ ]</span> Every bundled file is referenced from SKILL.md
<span class="ok">[ ]</span> Body is under 5 000 words; longer material lives in references/
<span class="ok">[ ]</span> Written in imperative voice (per skill-creator Step 4)
<span class="ok">[ ]</span> Description uses third-person form (per skill-creator)
<span class="ok">[ ]</span> Procedure is reproducible with no conversation context (fork-safe)
<span class="ok">[ ]</span> <span class="fn">scripts/package_skill.py</span> validates the skill (per skill-creator Step 5)</code></pre>
<div class="callout warn">
<p><strong>Anything beyond this section is a proposal.</strong> If you want a precision/recall trigger eval, a procedure-adherence rubric, or a shipping bar with numeric thresholds, those would be reasonable patterns to design — but no such methodology is defined by Claude Code or by <code>skill-creator</code>. Treat any quantitative skill-quality bar you encounter as the author's proposal, not an established standard.</p>
</div>
</section>
<!-- ── Anti-patterns ── -->
<section id="anti-patterns">
<h2>Anti-Patterns to Avoid</h2>
<table class="rules-table">
<thead><tr><th>Anti-pattern</th><th>Why it hurts</th></tr></thead>
<tbody>
<tr><td>Vague description</td><td>"A helper for X." has no trigger phrases — Claude can't decide when to invoke. The description IS the trigger.</td></tr>
<tr><td>Second-person voice</td><td>"You should run X" reads as advice; "Run X" reads as instruction. Skills are runbooks, not coaching.</td></tr>
<tr><td>Duplicating SKILL.md content in references/</td><td>The body of the skill says one thing; references/ says another. Diverges over time, confuses Claude.</td></tr>
<tr><td>Bundled scripts without doc</td><td>Files exist but SKILL.md doesn't reference them. Claude won't discover them.</td></tr>
<tr><td>Relative paths to bundled files</td><td>Breaks when skill is invoked from a different cwd. Always use <code>${CLAUDE_SKILL_DIR}</code>.</td></tr>
<tr><td>5000-word SKILL.md with no references/</td><td>Loaded on every invocation — wastes tokens. Push detail into <code>references/</code> and grep on demand.</td></tr>
<tr><td>Auto-trigger skill with sloppy description</td><td>Triggers on the wrong requests. Add explicit SKIP conditions for known false positives.</td></tr>
<tr><td>Mixing <code>context: inline</code> with heavy research</td><td>Bloats parent conversation. Use <code>context: fork</code> when output is large or doesn't need parent state.</td></tr>
<tr><td>Skipping <code>allowed-tools</code></td><td>Inline skills then run with parent's full toolset, surfacing more permission prompts than necessary.</td></tr>
<tr><td>Skill duplicates a CLAUDE.md rule</td><td>Memory and skills serve different purposes — one-line rules belong in CLAUDE.md, not their own skill.</td></tr>
</tbody>
</table>
</section>
<!-- ── vs CLAUDE.md ── -->
<section id="vs-claudemd">
<h2>Skill vs. CLAUDE.md</h2>
<p>Both are mechanisms for teaching Claude project-specific knowledge. They are not interchangeable.</p>
<div class="compare-grid">
<div class="compare-card">
<h4>CLAUDE.md</h4>
<div class="compare-row"><span class="compare-key">Loaded</span><span class="compare-val same">always (every turn)</span></div>
<div class="compare-row"><span class="compare-key">Budget</span><span class="compare-val same">12K chars total, 4K per file</span></div>
<div class="compare-row"><span class="compare-key">Best for</span><span class="compare-val same">style rules · constraints · architecture</span></div>
<div class="compare-row"><span class="compare-key">Granularity</span><span class="compare-val same">whole-project / directory</span></div>
<div class="compare-row"><span class="compare-key">Invocation</span><span class="compare-val same">passive — always present</span></div>
<div class="compare-row"><span class="compare-key">Bundled assets</span><span class="compare-val same">no</span></div>
</div>
<div class="compare-card">
<h4>Skill</h4>
<div class="compare-row"><span class="compare-key">Loaded</span><span class="compare-val diff">on demand (when triggered)</span></div>
<div class="compare-row"><span class="compare-key">Budget</span><span class="compare-val diff">listing 1% of context · body unlimited</span></div>
<div class="compare-row"><span class="compare-key">Best for</span><span class="compare-val diff">procedures · workflows · runbooks</span></div>
<div class="compare-row"><span class="compare-key">Granularity</span><span class="compare-val diff">single capability / task</span></div>
<div class="compare-row"><span class="compare-key">Invocation</span><span class="compare-val diff">active — Claude (or user) calls it</span></div>
<div class="compare-row"><span class="compare-key">Bundled assets</span><span class="compare-val diff">scripts · references · assets</span></div>
</div>
</div>
<div class="callout tip">
<p><strong>Decision rule.</strong> If the rule applies to <em>everything</em> Claude does in this project → CLAUDE.md. If the rule is the recipe for <em>one specific task</em> Claude sometimes does → Skill.</p>
</div>
</section>
<!-- ── Permissions ── -->
<section id="permissions">
<h2>Permissions Model</h2>
<p>Skill invocation is gated through the permission system. The order of checks:</p>
<pre><code><span class="ok">1.</span> Deny rules <span class="cm">→ matched? skip the skill, return error.</span>
<span class="ok">2.</span> Auto-allow remote <span class="cm">→ canonical remote skills auto-granted.</span>
<span class="ok">3.</span> Allow rules <span class="cm">→ whitelisted? execute.</span>
<span class="ok">4.</span> Safe properties <span class="cm">→ skill uses only safe metadata? auto-execute.</span>
<span class="ok">5.</span> Default <span class="cm">→ ask user for permission via UI prompt.</span></code></pre>
<h3>Tool access inside a skill</h3>
<table class="rules-table">
<thead><tr><th>Mode</th><th>Tool access</th></tr></thead>
<tbody>
<tr><td>inline</td><td>Parent's tools, with optional <code>allowed-tools</code> permit-list narrowing access.</td></tr>
<tr><td>fork</td><td>Inherits parent's <strong>full toolset unchanged</strong> — <code>allowed-tools</code> doesn't restrict subagent.</td></tr>
</tbody>
</table>
</section>
<!-- ── Hot reload ── -->
<section id="hot-reload">
<h2>Hot Reload</h2>
<p>Skills don't require a restart to test edits:</p>
<pre><code><span class="kw">const</span> watcher = <span class="fn">chokidar.watch</span>([
<span class="str">"~/.claude/skills"</span>,
<span class="str">"&lt;cwd&gt;/.claude/skills"</span>,
<span class="cm">// + any --add-dir paths</span>
])
<span class="cm">// On change:</span>
debounce(300ms) → <span class="fn">clearSkillCaches</span>()
→ <span class="fn">skillsChanged.emit</span>()
→ next listing rebuild picks up the edit</code></pre>
<p>Edit your SKILL.md, save, and the next turn sees the new version. No restart.</p>
</section>
<!-- ── Constants ── -->
<section id="constants">
<h2>Hard Limits &amp; Constants</h2>
<p>Observed limits and constants in the current Claude Code runtime:</p>
<table class="rules-table">
<thead><tr><th>Constant</th><th>Value</th></tr></thead>
<tbody>
<tr><td>SKILL_BUDGET_CONTEXT_PERCENT</td><td>1% of context window</td></tr>
<tr><td>DEFAULT_CHAR_BUDGET</td><td>8 000 chars (200k context)</td></tr>
<tr><td>MAX_LISTING_DESC_CHARS</td><td>250 chars per skill in listing</td></tr>
<tr><td>FILTERED_LISTING_MAX</td><td>30 entries when search is active</td></tr>
<tr><td>MAX_DIR_ENTRIES</td><td>1 000 (skill dir scan cap)</td></tr>
<tr><td>chokidar debounce</td><td>300ms</td></tr>
<tr><td>Bundled extract perms</td><td><code>0o700</code> dir / <code>0o600</code> file</td></tr>
<tr><td>Listing format</td><td><code>- name: description</code></td></tr>
<tr><td>System reminder header</td><td><em>"The following skills are available for use with the Skill tool:"</em></td></tr>
<tr><td>Path placeholder</td><td><code>${CLAUDE_SKILL_DIR}</code></td></tr>
<tr><td>Session placeholder</td><td><code>${CLAUDE_SESSION_ID}</code></td></tr>
<tr><td>Bundled-skill privilege</td><td>Never truncated in listing</td></tr>
<tr><td>Validation policy</td><td>Graceful degrade — log warning, load anyway</td></tr>
<tr><td>Dedup method</td><td>By <code>name</code> after realpath canonicalization</td></tr>
<tr><td>Recommended SKILL.md size</td><td>&lt; 5 000 words</td></tr>
<tr><td>Recommended description size</td><td>~100 words (always in context)</td></tr>
</tbody>
</table>
<div class="callout insight">
<p><strong>The system is built around progressive disclosure.</strong> Discovery is generous (many sources, hot-reload, dynamic activation), the listing is austere (1% of context, 250 chars per skill), and bodies are loaded only on invocation. Author skills with all three layers in mind.</p>
</div>
</section>
<footer>
<p>Compiled from observed Claude Code runtime behavior and the bundled <code>skill-creator</code> guide. Constants reflect the current build and may change between releases.</p>
</footer>
</main>
<script>
function showScenario(id, btn) {
document.querySelectorAll('.scenario-panel').forEach(p => p.classList.remove('active'));
document.querySelectorAll('.scenario-tab').forEach(t => t.classList.remove('active'));
document.getElementById('sp-' + id).classList.add('active');
btn.classList.add('active');
}
const sections = document.querySelectorAll('section[id], .hero');
const navLinks = document.querySelectorAll('nav a');
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
navLinks.forEach(a => {
a.classList.toggle('active', a.getAttribute('href') === '#' + entry.target.id);
});
}
});
}, { rootMargin: '-20% 0px -70% 0px' });
sections.forEach(s => observer.observe(s));
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment