Claude Code ships with a powerful extensibility layer that most developers haven't touched yet: skills (custom slash commands you define) and hooks (shell commands that run automatically around Claude's responses). Together they let you turn Claude Code into a fully customised engineering assistant tuned to your specific stack and workflow.
What Are Skills?
A skill is just a Markdown file that lives in ~/.claude/skills/ (user-level) or .claude/skills/ (project-level). When you type /skill-name in Claude Code, Claude reads that file and follows its instructions.
That's it. No plugins to install, no build step, no API registration.
~/.claude/skills/
review.md → /review
deploy.md → /deploy
tdd.md → /tdd
changelog.md → /changelogWriting Your First Skill
Let's build a /review skill that performs a consistent code review:
# /review
Perform a comprehensive code review of the changes in the current branch.
## What to check
1. **Correctness** — Does the logic do what it claims? Check edge cases.
2. **Security** — Look for injection risks, exposed secrets, insecure defaults.
3. **Performance** — Identify N+1 queries, missing indexes, unnecessary allocations.
4. **Test coverage** — Are new code paths covered by tests?
5. **API contracts** — Are public interfaces backward-compatible?
## Output format
Return your review as:
- A one-paragraph overall summary (green/yellow/red status)
- A bulleted list of specific issues, each with: severity (🔴 critical / 🟡 warning / 🟢 suggestion), file:line reference, and a concrete fix
Focus on what matters. Skip style comments — the linter handles those.Save this as ~/.claude/skills/review.md, then type /review in Claude Code. Claude will run git diff or check staged changes automatically and apply the framework from your skill file.
A TDD Skill
Here's a more opinionated skill that enforces test-driven development:
# /tdd
Implement the following feature using strict test-driven development.
## Process
1. Write a failing test first — do not write implementation code yet
2. Show me the test and ask for confirmation before continuing
3. Write the minimum implementation to make it pass
4. Refactor if needed (no new functionality)
5. Repeat for the next case
## Rules
- One failing test at a time
- Tests go in the appropriate __tests__/ directory, mirroring src/ structure
- Use the existing test framework (detect from package.json)
- Name tests as: `describe('FeatureName', () => { it('should ...', () => {}) })`
- Do NOT add tests retroactively — always test-first
When I give you a requirement, start with step 1 immediately.Now /tdd add user authentication with JWT will walk through the whole TDD cycle with you, one red-green-refactor loop at a time.
What Are Hooks?
Hooks are shell commands configured in .claude/settings.json that Claude Code runs automatically at specific points in its lifecycle. They let you enforce rules without remembering to ask Claude about them.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo 'Running: $CLAUDE_TOOL_INPUT'"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bun run typecheck 2>&1 | head -20"
}
]
}
]
}
}Hook timing options
| Hook | When it fires |
|---|---|
PreToolUse | Before Claude calls any tool |
PostToolUse | After a tool completes |
Stop | When Claude finishes responding |
Notification | On permission prompts |
Practical Hook: Auto-lint on File Edit
Run ESLint automatically every time Claude edits a file:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "eslint --fix \"$CLAUDE_TOOL_INPUT_FILE_PATH\" 2>&1 | tail -5"
}
]
}
]
}
}If lint fails, Claude Code sees the output and automatically fixes the issues before moving on. You never have to say "also run the linter" — it just happens.
Practical Hook: Test Runner After Changes
Run relevant tests every time a source file changes:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bun test --testPathPattern=$(basename $CLAUDE_TOOL_INPUT_FILE_PATH .ts) 2>&1 | tail -20"
}
]
}
]
}
}Claude won't declare a task done if tests are failing — the hook output feeds back into its context.
Combining Skills + Hooks
The real power is combining both. Example: a /ship skill that uses hooks for safety:
~/.claude/skills/ship.md:
# /ship
Prepare this feature for merge.
Steps:
1. Run the full test suite and fix any failures
2. Run the linter and fix issues
3. Check for console.logs or debug code and remove them
4. Update the CHANGELOG.md with the feature description
5. Create a conventional commit: feat(scope): description
6. Push the branch and open a draft PR
Do not proceed to the next step until the current one passes cleanly..claude/settings.json:
{
"hooks": {
"Stop": [
{
"type": "command",
"command": "bun run typecheck && echo '✅ Types OK' || echo '❌ Type errors found'"
}
]
}
}Now /ship triggers a full pre-flight check, and the Stop hook ensures types pass before Claude hands control back to you.
Where Settings Live
- User-level (
~/.claude/settings.json) — applies to every project - Project-level (
.claude/settings.json) — applies to this repo only, committed to git - Local override (
.claude/settings.local.json) — gitignored personal overrides
Skills follow the same pattern: ~/.claude/skills/ for global, .claude/skills/ for project-specific.
Getting Started
- Create
~/.claude/skills/if it doesn't exist - Write your first skill file (start with
/review— it's immediately useful) - Add a
PostToolUsehook that runs your type checker - Iterate — skills are just markdown, change them as your workflow evolves
The best skills are ones that encode the things you always have to remind Claude about. Write them down once, and you'll never have to repeat yourself again.
