Installs into .claude/skills of the current project.
Are you the author of Write Skill?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/ivy-write-skill)
---
name: write-skill
description: Use when the user wants to create a new Claude Code skill. Guides skill creation with playbook patterns.
argument-hint: "[global|local] [skill-name] [purpose...]"
disable-model-invocation: true
allowed-tools:
- Read
- Glob
- Grep
- Write
- Bash(chezmoi apply:*)
- Bash(chezmoi diff:*)
- Bash(chezmoi status:*)
- Bash(chezmoi source-path:*)
- Bash(ls:*)
---
# Skill Creation Playbook
**Autonomy:** human-only · acts autonomously — writes the skill and README files, spawns the single reviewer pass, and deploys with `chezmoi apply` without further confirmation
**CRITICAL: Before deploying, spawn exactly one reviewer agent with REVIEW.md to audit `allowed-tools` — overly permissive access (e.g. `Bash(git:*)`) can cause data loss or leak secrets. The reviewer advises on **facts** and has **no standing on intent**: never apply its opinion about how much autonomy a skill should have. One agent, one pass — see step 5.**
**Note on substitutions (workaround):** This playbook is itself loaded as a skill, so Claude Code would substitute platform variables — ARGUMENTS, CLAUDE_SKILL_DIR, CLAUDE_SESSION_ID, etc. — *in this file* at load time if they appeared in their literal `$`-prefixed form. To teach the syntax without triggering premature substitution, the playbook below names variables **without** the leading `$` and describes the syntax in prose. In the new skill file you write, **always prefix with `$`** (or use the braced form for clarity). The body-structure example below uses `` \!\`echo` `` to inject one literal placeholder for copy/paste — dynamic-context output is not re-scanned for substitutions. The supporting files (LIFECYCLE.md, REVIEW.md, SHIM-PATTERN.md) are not loaded as skill bodies, so they show literal forms directly.
Create skills as flexible playbooks, not rigid scripts.
## Arguments
```
$ARGUMENTS
```
## Pre-computed Context
The following paths and listings were computed before this skill was invoked:
**Chezmoi source path:** !`chezmoi source-path 2>/dev/null || echo "(chezmoi not available)"`
**Global skills source:** !`chezmoi source-path 2>/dev/null`/dot_claude/skills
**Global skills deployed:** ~/.claude/skills
## Process
### 0. Determine Scope
Check if arguments specify `global` or `local`:
- **`global`** or **`in ~/.claude/skills/`** → Skill goes in dotfiles via chezmoi, available everywhere
- **`local`** or **`in .claude/skills/`** → Skill goes in current project's `.claude/skills/`, scoped to this repo
If scope is unclear, ask:
> "Should this skill be **global** (available in all projects via dotfiles) or **local** (scoped to this project only)?"
### 1. No Arguments? Extract from Conversation
If arguments are empty, the user wants to codify the task just performed:
1. Analyze the conversation for the repeatable pattern
2. Identify: trigger conditions, tools used, decision points, inputs/outputs
3. Propose a skill name and description based on what was done
4. Ask about scope (global vs local)
5. Draft the skill capturing the workflow as a playbook
6. Ask user to confirm or refine before writing
### 2. Gather Context (when args provided)
If unclear, ask:
- Trigger? (user/auto/always)
- Tools needed?
- Fork context? (noisy output)
- Side effects? (commits/deploys/APIs)
### 3. Draft Skill
Every skill is **two files**: the `SKILL.md` the agent runs and a `README.md` a human reads. Write both — a skill without a README is not finished. Shape and section-by-section guidance: README-PATTERN.md.
**For global skills:** Create `$(chezmoi source-path)/dot_claude/skills/<name>/SKILL.md` and `README.md` alongside it. Use a `.tmpl` suffix on `SKILL.md` only when the body needs a chezmoi directive (`.chezmoi.os`, `lookPath`, …); the README is never templated.
**For local skills:** Create `.claude/skills/<name>/SKILL.md` and `README.md` in the current working directory (plain `.md`, no template).
**Frontmatter template** (both scopes):
```yaml
---
name: <kebab-case>
description: <When to use + what it does>
argument-hint: <flexible, use brackets>
disable-model-invocation: <true if user-only>
context: <fork if output not needed>
allowed-tools: <minimal safe subset>
---
```
Leave `model:` and `effort:` unset. A skill runs in the main conversation, and a value that differs from the session's forces a full prompt-cache miss into and out of that turn (ADR-009, `docs/adrs/` in the dotfiles repo). Pin a model on a subagent instead — it has its own context.
Both follow the same body structure (the line inside the Arguments block uses `` \!\`echo` `` to print a literal ARGUMENTS placeholder, `$`-prefixed — copy that placeholder as-is into your skill):
```markdown
# <Title>
**Autonomy:** <trigger> · <commit posture>
## Arguments
\`\`\`
\!\`echo '$ARGUMENTS'`
\`\`\`
## Instructions
<Decision-tree playbook>
## Examples
<Varied inputs and outcomes>
```
**For global skills:** After writing, run:
- `chezmoi diff` to preview
- `chezmoi apply ~/.claude/skills/<name>` to deploy
- `chezmoi status` to verify
**For local skills:** No deployment needed - the skill is immediately available in the project.
### 4. Principles
**Frontmatter:**
- `description`: "Use when..." for auto-invoke
- `when_to_use`: trigger phrases — appended to `description`; combined cap is 1,536 chars, key use case first
- `paths`: glob like `src/**/*.ts` to scope auto-activation to matching files
- `context: fork`: noisy output that won't inform follow-up
- `disable-model-invocation: true`: side-effect skills (user-only trigger)
- `user-invocable: false`: hide from `/` menu — background knowledge Claude loads silently
- `model` / `effort`: leave unset (see Draft Skill above)
- `hooks`: lifecycle hooks scoped to this skill
- `allowed-tools`: tools that run without approval. Omitted tools still work; whether they *prompt* is **mode-dependent** — under `defaultMode: auto` they may run silently, so omission is never a gate
- `Skill(<child>)` in `allowed-tools`: declares the delegation graph — the parent may invoke exactly these children, and its authorization flows down to them. It never relaxes a child's evidence bar
**Arguments** *(prefix each name with `$` in your skill — see note at top):*
- Free-form input pattern: `[package | url...]` not `<package>`
- Positional substitutions: `ARGUMENTS` (full string), `ARGUMENTS[N]` or shorthand `N` (Nth positional, 0-indexed)
- Named: declare `arguments: [issue, branch]` in frontmatter → reference each by its name (e.g. the placeholder for `issue`)
- Show varied inputs in examples; parse flexibly, not strictly
**Content:**
- Decision trees, not linear scripts
- <60 lines; externalize reference material (skill body persists across turns — see LIFECYCLE.md)
- Refer to "arguments" after the Arguments section
- Dynamic values via braced `${...}` substitutions: `CLAUDE_SESSION_ID`, `CLAUDE_EFFORT`, `CLAUDE_SKILL_DIR` (full list in LIFECYCLE.md)
- Multi-line shell injection: open a fenced code block with three backticks + `!` instead of the inline single-backtick `!` form
- Include the keyword `ultrathink` in the body to request deep reasoning at runtime
**Autonomy** *(three independent axes — conflating them is the most common bug):*
- **A. Trigger** — who invokes. `disable-model-invocation: true` for human-only. Use it when *unrequested* invocation is the risk; never to make a skill timid.
- **B. Judgment** — **never gated.** A skill always reaches a verdict, gives the one-line reason, and says what would change its mind. "The user should decide" is not a posture: abstention deadlocks any orchestrator that composes the skill.
- **C. Commit** — who performs the irreversible act. Prefer *withholding the capability* over prompting for it. A gate that must hold in every mode is an explicit in-chat confirmation in the body — never omission from `allowed-tools`.
- Declare A and C in the body's `**Autonomy:**` line; the reviewer audits `allowed-tools` against it. Depth: AUTONOMY.md.
- A skill named with a verb **acts**. If the request already carries the decision, act on it.
### 5. Review (REQUIRED — one agent, one pass)
Spawn **one** reviewer agent, **once**. Give it `REVIEW.md`, the drafted files (`SKILL.md` **and** `README.md`), **the user's request verbatim**, and the `**Autonomy:**` line — a reviewer that can't see the intent will invent one.
Dispose of findings by the reviewer's **standing**, not by how urgent they sound:
| Finding | Standing | Disposition |
|---|---|---|
| **Fact** — checkable against source, docs, or observed behavior | Full; it read the code, you didn't | Verify, then apply. Mechanical and small. |
| **Intent** — what the skill *should* do, who decides, how much autonomy | **None**; it never saw the request | Surface verbatim, **unapplied**. The user decides. |
Never spawn a second reviewer, re-review after fixing, or run reviewers in parallel. And never apply an intent finding on the reviewer's authority — it is an amnesiac with no memory of why this skill exists, and deferring to it overwrites the user's stated intent with a stranger's risk appetite. A reviewer that recommends a skill withhold a judgment is wrong by construction (axis B).
## Quick Reference
| Goal | Frontmatter |
|------|-------------|
| User-only trigger | `disable-model-invocation: true` |
| Auto-invoke | `description` with "Use when..." |
| Extra trigger phrases | `when_to_use: ...` (counts toward 1,536-char cap) |
| Scope by file path | `paths: "src/**/*.ts"` |
| Hidden from `/` menu | `user-invocable: false` |
| Isolate context | `context: fork` |
| Specific agent | `context: fork` + `agent: Explore` |
| Named arguments | `arguments: [foo, bar]` → reference each by its `$`-prefixed name |
| Lifecycle hooks | `hooks: ...` |
| Declare autonomy | `**Autonomy:**` line in the body (axes A + C) |
| Delegate to sub-skills | `Skill(<child>)` in `allowed-tools` |
## Supplementary Docs
- **REVIEW.md** - **REQUIRED** checklist for the single reviewer pass; audits `allowed-tools` before deployment
- **README-PATTERN.md** - **REQUIRED** shape for the `README.md` every skill ships; what belongs there vs in `SKILL.md`
- **AUTONOMY.md** - The three axes, declaration grammar, delegation graphs, and writing skills that compose into unattended loops
- **LIFECYCLE.md** - Skill content lifecycle, compaction budget, description cap, discovery rules
- **SHIM-PATTERN.md** - Wrapper scripts for enforcing constraints (advanced)
| Safe | Unsafe |
|------|--------|
| `Bash(git status:*)` | `Bash(git:*)` |
| `Bash(npm test:*)` | `Bash(npm:*)` |
| `Read, Grep, Glob` | `Bash(rm:*)` |
## Examples
```
/write-skill → extract skill from current conversation (will ask about scope)
/write-skill global deploy → global skill for deployments (via chezmoi)
/write-skill local lint-fix → project-local skill for this repo only
/write-skill in ~/.claude/skills/ search → global skill (explicit path)
/write-skill in .claude/skills/ format → local skill (explicit path)
```
## Scope Decision Guide
| Choose **global** when... | Choose **local** when... |
|---------------------------|--------------------------|
| Skill is useful across many projects | Skill is specific to this codebase |
| General-purpose workflow (git, testing) | Project-specific conventions |
| You want it in your dotfiles | Collaborators should have it too |
| Personal preference/style | Team or repo-specific process |
## Anti-patterns
- Shipping a skill without a `README.md`, or templating one as `.md.tmpl`
- A README that restates `SKILL.md` instead of arguing why the skill exists
- Strict positional arguments
- Broad tool access
- Verbose prose over terse bullets
- Missing `context: fork` for noisy ops
- Hardcoded paths vs arguments
- Global skill for project-specific logic
- Local skill for personal workflows you'd want everywhere
- More than one reviewer agent, or a second review pass (see step 5)
- Applying a reviewer's *intent* findings without the user — the crux failure
- Gating judgment: a skill that reports findings without reaching a verdict
- A confirmation prompt where withholding the capability would do
- Treating omission from `allowed-tools` as a gate