Created
May 13, 2026 07:59
-
-
Save jamesrochabrun/6bd12ab35201db6d8c8aeccc926d78d4 to your computer and use it in GitHub Desktop.
Skills Reference Guide — discovery, authoring, invocation & quality signals for Claude Code skills
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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><system-reminder></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><system-reminder></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">< 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 & 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 "/<something>", | |
| 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 <system-reminder> 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><target> [--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 >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 <name> --path <dir></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 <path></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 <task>: | |
| 1. First, run <span class="str">`!\`${CLAUDE_SKILL_DIR}/scripts/check.sh\`</span>` to verify state. | |
| 2. If <condition>, 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 <test command>. | |
| <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 & 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">"<cwd>/.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 & 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>< 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