Initialize a new plugin with make create-plugin NAME=my-plugin.
This command generates the standard directory structure
and configuration files. Before writing code,
review the patterns in plugins/abstract to understand how existing plugins
handle state and tool calls. Once you have a basic implementation,
run make validate to check the structure,
followed by make lint and make test to verify code quality.
Plugins use a standard directory structure so Claude Code can reliably discover capabilities without manual configuration.
plugins/my-plugin/
├── skills/ # YAML/Markdown skill definitions
├── commands/ # Slash command definitions
├── agents/ # Specialized sub-agent configurations
├── hooks/ # Event-driven automation scripts
├── scripts/ # Python utility logic
└── plugin.json # Metadata and entry points
Public APIs should be strict to prevent hidden state
and support interoperability. We use ruff and mypy to catch errors early.
When handling user input,
fail with specific error messages rather than guessing intent.
This reduces debugging time and prevents unexpected behavior.
We enforce strict quality gates to catch bugs before they reach production.
Plugins must achieve 80% code coverage via pytest-cov,
and Python code must pass ruff linting
and mypy type checking without overrides.
Security scans via bandit must report zero high-severity issues.
To maintain responsiveness, we limit token usage to a 15K budget per operation. Error messages must be specific to help users resolve issues quickly. Versioning follows the 1.1.0 scheme to maintain compatibility across the ecosystem.
Start by installing dependencies with uv and setting up pre-commit hooks.
The make create-plugin command generates the necessary scaffold.
We recommend examining plugins/abstract to understand core patterns for state
management and tool calls before starting your implementation.
Add logic to skills/ and commands to commands/.
Tests in tests/ should cover both success paths and edge cases.
If your plugin requires automation,
implement hooks to intercept tool usage or lifecycle events.
If performance lags, profile token usage with python -m cProfile.
Documentation in README.md should include copy-pasteable examples to help
users get started quickly. Once released,
monitor logs for runtime errors and update dependencies using uv.
We use Python 3.9+ for plugin packages, managed by uv.
pytest handles testing, while ruff manages linting and formatting.
mypy enforces type checking, and bandit performs security analysis.
pre-commit and GitHub Actions handle automation tasks.
The ecosystem has a two-tier Python requirement:
| Code Type | Minimum Python | Reason |
|---|---|---|
| Hooks | 3.9+ | Execute under macOS system Python (3.9.6), outside virtual environments |
| Plugin packages & scripts | 3.10+ | Run inside uv-managed virtual environments |
| Root project & CI tooling | 3.12+ | Development-only, not user-facing |
Hook compatibility is critical. If a hook imports from a src/ package,
the entire transitive import chain must be 3.9-compatible.
Avoid these patterns in hook code:
| Avoid | Python Version | Use Instead |
|---|---|---|
X | Y union types |
3.10+ | from __future__ import annotations |
@dataclass(slots=True) |
3.10+ | @dataclass (omit slots) |
match/case statements |
3.10+ | if/elif chains |
datetime.UTC |
3.11+ | datetime.timezone.utc |
import tomllib |
3.11+ | import tomli with fallback |
type X = ... aliases |
3.12+ | TypeAlias from typing |
import yaml (pyyaml) |
not stdlib | try/except ImportError with yaml = None fallback |
Before release, verify that tests pass with over 80% coverage and that ruff,
mypy, and bandit checks are clean.
The README must include clear usage examples.
Verify that token usage remains within the 15K limit and that version tags
and the changelog are updated.
---
name: skill-name
description: Clear description with "Use when..." clause
category: workflow|analysis|generation|utility
context: fork # Run in isolated sub-agent context
agent: agent-name # Specify agent type for execution
user-invocable: false # Hide from slash command menu (default: true)
model: sonnet # Model override
allowed-tools:
- Read
- Grep
- Glob
- Bash(npm *) # Wildcard patterns supported
hooks:
PreToolUse:
- matcher: "Bash"
command: "echo 'Pre-validation' >&2"
once: true # Run once per session
PostToolUse:
- matcher: "Write|Edit"
command: "./scripts/format-on-save.sh"
Stop:
- command: "echo 'Skill completed' >> ~/.claude/skill-log.txt"
---
# Skill Title
## Overview
## How to Use
## ExamplesSet context: fork to run skills in an isolated sub-agent context.
This prevents output from polluting the main conversation thread.
Use the agent field to route skills to specific sub-agents,
such as python-tester. Tool permissions use YAML lists for allowed-tools,
supporting wildcards like Bash(npm *) or Bash(* install).
Lifecycle hooks (PreToolUse, PostToolUse,
Stop) defined in the frontmatter automate validations or cleanup.
---
name: command-name
description: Action-oriented description
context: fork # Optional: Run in forked sub-agent context
agent: agent-name # Optional: Specify agent type
hooks:
PreToolUse:
- matcher: "Edit"
command: "./validate.sh"
Stop:
- command: "echo 'Command completed'"
parameters:
- name: required-param
type: string
required: true
examples:
- "command-name --value example"
------
name: agent-name
description: |
Agent purpose and specialization.
Triggers: keyword1, keyword2, keyword3
Use when: specific use cases for this agent
DO NOT use when: when other tools are better suited
tools: [Read, Write, Bash, Glob, Grep]
model: haiku # Model for this agent
permissionMode: acceptEdits # Permission handling
skills: [skill-name-1, skill-name-2] # Skills to auto-load
hooks:
PreToolUse:
- matcher: "Bash"
command: "./validate-command.sh"
once: true # Run once per session
PostToolUse:
- matcher: "Write|Edit"
command: "./post-edit-hook.sh"
Stop:
- command: "./teardown.sh"
escalation:
to: sonnet # Escalate to stronger model
hints:
- ambiguous_input
---
# Agent Name
Agent body content...Control the model by setting model to haiku, sonnet, or opus.
Define how the agent handles tool approvals via permissionMode (e.g.,
acceptEdits, dontAsk). List skills to auto-load in the skills field.
Note that agents do not inherit skills from their parents.
Configure escalation to move tasks to a more capable model when specific
hints (like ambiguous_input) are detected.
Catch specific exceptions to provide actionable feedback.
Log expected errors as warnings
and use PluginError for failures that require user intervention.
try:
result = risky_operation()
except SpecificError as e:
logger.warning(f"Operation skipped: {e}")
result = default_value
except Exception as e:
logger.error(f"Critical failure: {e}")
raise PluginError("Failed to complete operation. Check network or permissions.") from e- Plugin not loading: Verify syntax
and paths in
.claude-plugin/plugin.jsonand entry points inmarketplace.json. - Tests failing: Run
make installto check dependencies, thenpytest tests/test_specific.py -vto isolate. - Performance: Profile with
python -m cProfile. Consider caching or lazy loading for heavy modules.
make validate # Check plugin structure
uv pip list # Verify installed dependencies
uv run python scripts/complexity_calculator.pySkills located in ~/.claude/skills
or .claude/skills reload immediately upon saving.
This removes the need to restart the session to test changes.
SessionStart hooks receive an agent_type field in their input.
You can use this to skip heavy context loading for lightweight agents.
For example, skipping context for a quick-query agent can save between 200
and 800 tokens per session.
Specific environment variables control behavior:
CLAUDE_CODE_HIDE_ACCOUNT_INFO- Sanitize recordings by hiding account infoCLAUDE_CODE_DISABLE_BACKGROUND_TASKS- Force synchronous execution in CI environmentsCLAUDE_CODE_TMPDIR- Override temp directory for restricted environments (e.g., Termux)
Temp Directory Best Practice: When writing to temp files in hooks,
use ${CLAUDE_CODE_TMPDIR:-/tmp} to respect user configuration:
echo "log entry" >> ${CLAUDE_CODE_TMPDIR:-/tmp}/my-audit.log