plugin-conventions
ACTIVATE when creating a Claude Code plugin, writing plugin.json, marketplace.json, hooks.json, a hook script, or an evals/evals.json file. ACTIVATE for 'Claude plugin', 'plugin.json', 'marketplace.json', 'hooks.json', 'CLAUDE_PLUGIN_ROOT', 'permissionDecision', 'evals.json'. Covers: plugin directory structure, plugin.json/marketplace.json schemas, hooks.json format, the hook output contract (exit codes, which channel reaches Claude), CLAUDE_PLUGIN_ROOT portability, distribution best practices, the evals/evals.json format for a skill's routing and behaviour test cases, validation commands. DO NOT use for: SKILL.md writing conventions (see skill-authoring), agent .md format (see agent-authoring).
- License 许可
- MIT
- 最近更新
- 2026-10-13
- Stars
- ★ 9
- Forks
- 2
01 前言 Front Matter
- name
- plugin-conventions
- metadata
- { "version": "1.0" }
- description
- ACTIVATE when creating a Claude Code plugin, writing plugin.json, marketplace.json, hooks.json, a hook script, or an evals/evals.json file. ACTIVATE for 'Claude plugin', 'plugin.json', 'marketplace.json', 'hooks.json', 'CLAUDE_PLUGIN_ROOT', 'permissionDecision', 'evals.json'. Covers: plugin directory structure, plugin.json/marketplace.json schemas, hooks.json format, the hook output contract (exit codes, which channel reaches Claude), CLAUDE_PLUGIN_ROOT portability, distribution best practices, the evals/evals.json format for a skill's routing and behaviour test cases, validation commands. DO NOT use for: SKILL.md writing conventions (see skill-authoring), agent .md format (see agent-authoring).
02 资源文件 2
- 参考文档evals/evals.json1.4 KB
- 参考文档references/hook-output-contract.md4.5 KB
03 SKILL.md 正文
Claude Code Plugin & Marketplace Conventions
Best practices for creating, structuring, and distributing Claude Code plugins and marketplaces, based on official documentation and the anthropics/claude-plugins-official repository.
Plugin Directory Structure
plugin-name/
├── .claude-plugin/
│ └── plugin.json # Manifest (ONLY this file here)
├── commands/ # Slash commands (.md)
├── agents/ # Subagent definitions (.md)
├── skills/ # Skills (subdirectories with SKILL.md)
│ └── skill-name/
│ ├── SKILL.md
│ ├── scripts/
│ ├── references/
│ ├── examples/
│ └── evals/
│ └── evals.json # Routing + behaviour test cases (see below)
├── hooks/
│ ├── hooks.json # Hook configuration
│ └── scripts/ # Hook scripts
├── .mcp.json # MCP server definitions
├── .lsp.json # LSP configurations
├── settings.json # Default settings
├── scripts/ # Shared utilities
├── LICENSE
└── README.md
Critical rules:
.claude-plugin/contains ONLYplugin.json— never nest components inside it- All component directories at plugin root level
- Kebab-case for all directory and file names
- Only create directories for components actually used
plugin.json Schema
Minimal (auto-discovery handles the rest)
{
"name": "plugin-name"
}
Recommended
{
"name": "plugin-name",
"version": "1.0.0",
"description": "Brief plugin description",
"author": {
"name": "Author Name",
"email": "author@example.com",
"url": "https://github.com/author"
},
"repository": "https://github.com/author/plugin",
"license": "MIT",
"keywords": ["keyword1", "keyword2"]
}
Component Paths (optional, supplements auto-discovery)
{
"skills": "./skills/",
"agents": "./agents/agent-name.md",
"hooks": "./hooks/hooks.json",
"commands": "./commands/command-name.md",
"mcpServers": "./.mcp.json",
"lspServers": "./.lsp.json",
"outputStyles": "./styles/"
}
Path rules:
- All paths relative to plugin root
- Must start with
./ - Custom paths supplement default directories, never replace them
marketplace.json Schema
{
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
"name": "marketplace-name",
"description": "Brief marketplace description",
"owner": {
"name": "Owner Name",
"email": "owner@example.com"
},
"metadata": {
"version": "1.0.0",
"description": "Detailed marketplace description"
},
"plugins": [
{
"name": "plugin-name",
"source": "./plugins/plugin-name",
"description": "Plugin description",
"version": "1.0.0",
"author": { "name": "Author" },
"category": "development",
"license": "MIT",
"keywords": ["keyword"],
"tags": ["community-managed"]
}
]
}
Required fields
name— kebab-case, no spacesowner— object withname(required),email(optional)plugins— array of plugin entries, each withnameandsource
Reserved marketplace names
claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, life-sciences. Names impersonating official Anthropic marketplaces are blocked.
Plugin source types
| Type | Format |
|---|---|
| Local path | "./plugins/name" |
| GitHub | { "source": "github", "repo": "org/repo", "ref": "main", "sha": "abc" } |
| Git URL | { "source": "url", "url": "https://github.com/org/repo.git" } |
| Git subdir | { "source": "git-subdir", "url": "org/repo", "path": "plugins/name" } |
| npm | { "source": "npm", "package": "name", "version": "^1.0" } |
Plugin categories
development, productivity, security, testing, database, deployment, monitoring, design, learning
hooks.json Format
The official format uses nested matcher + hooks[] with type:
{
"description": "Human-readable description of hooks purpose",
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/validate.sh\"",
"timeout": 30
}
]
}
]
}
}
Hook types
| Type | Field | Description |
|---|---|---|
command |
command |
Shell command, receives JSON on stdin |
http |
url |
HTTP POST to endpoint |
prompt |
prompt |
Single-turn LLM evaluation |
agent |
prompt |
Agentic verifier with Read/Grep/Glob tools |
Hook events
Full reference: https://docs.claude.com/en/docs/claude-code/hooks
| Event | Matcher | Can block? |
|---|---|---|
SessionStart |
startup, resume, clear, compact, fork |
No |
SessionEnd |
clear, resume, logout, prompt_input_exit, other |
No |
UserPromptSubmit |
None | Yes (exit 2) |
PreToolUse |
Tool name | Yes (exit 2) |
PostToolUse |
Tool name | No |
Stop |
None | Yes (exit 2) |
SubagentStop |
Agent type | Yes (exit 2) |
Notification |
Notification type | No |
PreCompact |
manual, auto |
Yes (exit 2) |
An empty, * or absent matcher fires on every occurrence. A matcher written with letters, digits, _, - and spaces alone, its names separated by | or ,, compares exact names: Edit|Write fires on those two tools and never on NotebookEdit. Any other character makes it a JavaScript regular expression that may match anywhere in the value, so Edit.* also fires on NotebookEdit: anchor it as ^Edit$. A matcher on an event marked None is ignored silently.
Hook output contract
Output on the wrong channel fails silently: the action proceeds and the message reaches nobody. Five rules decide it:
- On most events, exit 2 is the only code that blocks by itself (
WorktreeCreateandWorktreeRemovefail on any non-zero exit). Without a schema-valid JSON object on stdout, any other non-zero exit is a non-blocking error and the action proceeds, soexit 1enforces nothing. Such an object is read on every exit code: at exit 1 a JSONdenystill decides, and no JSON overrides the block of exit 2. - At exit 0, stderr goes to the debug log only: neither Claude nor the user sees it.
- Plain stdout reaches Claude's context only on
SessionStart,UserPromptSubmit,UserPromptExpansionandPostModelSwitch; on other events, speak through exit 2 or JSON. - A
PreToolUsedecision ishookSpecificOutput.permissionDecision(allow,deny,askordefer) withpermissionDecisionReason. The top-leveldecision/reasonpair is deprecated for that event. - JSON stdout is exactly one object built with an encoder (
jq -n --arg …, Python'sjson.dumps): an unparsable one is dropped as a hook error, and a field at the wrong level is ignored.
The stdin fields per event, the effect of exit 2 per event and the JSON fields are tabled in references/hook-output-contract.md. How to test a hook (a sourced harness, payload files, one assertion per channel) lives in shell-test:shell-test-conventions.
Portability — ${CLAUDE_PLUGIN_ROOT}
Always use ${CLAUDE_PLUGIN_ROOT} for intra-plugin path references. Never hardcode paths.
In hooks.json: "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/tool.sh\"". Quote the whole path: a shell-form command runs through sh -c, and an unquoted root containing a space splits into several words, exits 127 and silently disables the hook. claude plugin validate only warns about an unquoted placeholder; --strict turns the warning into exit 1.
In MCP config: "args": ["${CLAUDE_PLUGIN_ROOT}/servers/server.js"]
In shell scripts: source "${CLAUDE_PLUGIN_ROOT}/lib/common.sh"
evals/evals.json Format
A skill's evals/evals.json pins the test cases that prove it triggers on the right prompts and
produces the right behaviour once loaded. This marketplace's convention (see
plugins/career/evals/evals.json for a worked example):
{
"$comment": "One object per case; `skills` names what should load, `expected_behavior` lists what a passing run must do.",
"routing": [
{
"query": "A prompt a real user would type",
"skills": ["plugin-name:skill-name"],
"expected_behavior": [
"loads plugin-name:skill-name, not a sibling skill",
"the specific guardrail that makes this skill different from its neighbours"
]
}
]
}
routingcases test discovery from the description alone: does the right skill load, and not a sibling that could plausibly match the same query?skillsis an array ofplugin:skill-namerefs, same shape as an agent'sskills:field.expected_behaviorlists assertions in plain language, not a machine-checked schema — a human or an agent replays the case and checks each line.- Keep at least one case per skill that has caused a real false-positive or false-negative activation; that is the evidence a routing rule was worth writing.
Distribution Best Practices
- Version bumps required — Claude Code caches by version; no bump = no update for users
- Version in one place — avoid setting version in both plugin.json and marketplace.json (plugin.json wins silently)
- Pin with
shafor reproducible builds in production marketplaces - Test locally with
claude --plugin-dir ./my-plugin - Reload without restart with
/reload-plugins - Debug loading with
claude --debugor/debug
Validation
# Validate marketplace (--strict turns every warning into exit 1: the CI form)
claude plugin validate --strict .
# Validate individual plugin
claude plugin validate --strict plugins/my-plugin
# Validate from within Claude Code
/plugin validate .
Naming Conventions
| Component | Convention | Example |
|---|---|---|
| Plugin | kebab-case | code-review-assistant |
| Skills | kebab-case directories | api-testing/ |
| Commands | kebab-case .md | run-tests.md |
| Agents | kebab-case .md | code-reviewer.md |
| Hooks | kebab-case with ext | validate-input.sh |
| Config | Standard names | hooks.json, .mcp.json |
Plugin skills are namespaced as /plugin-name:skill-name to prevent conflicts.
See also: skill-authoring for the SKILL.md format itself, agent-authoring for the agent .md
format referenced by agents/.
04 同仓库更多 Skills
vitest-test-conventions
ACTIVATE when writing Vitest tests, creating test files, using vi.fn()/vi.mock()/vi.spyOn(), or test factories. ACTIVATE for 'Vitest', 'it.each', 'NestJS testing module', 'supertest', 'vi.fn', 'vi.mock', 'spyOn'. Provides Vitest/NestJS-specific testing patterns; cross-language testing principles (DAMP, AAA, spy over mock, what NOT to test) live in craft:testing-principles. DO NOT use for: node:test files (a file importing node:test) or a project whose tests run under node --test (see node-test:node-test-conventions), TDD workflow/iterations (see vitest:vitest-tdd-workflow), PHP/PHPUnit tests (see phpunit:php-test-conventions).
vitest-tdd-workflow
ACTIVATE when building new features, endpoints, or user stories using TDD with Vitest in a TypeScript/NestJS context. ACTIVATE for '/feature-tdd-dev', 'TDD', 'red-green-refactor' in TS context. Provides Vitest/NestJS-specific TDD examples and commands; cross-language TDD workflow (cross-layer iterations, RED-GREEN-REFACTOR, working app, bug-fix-first-test, mocks-hiding-bugs) lives in craft:tdd-workflow-principles. DO NOT use for: node:test files (a file importing node:test) or a project whose tests run under node --test (see node-test:node-test-conventions), test writing conventions (see vitest:vitest-test-conventions), PHP TDD (see phpunit:php-tdd-workflow).
ts-refactoring
ACTIVATE when refactoring TypeScript code. ACTIVATE for 'refactor', 'extract', 'redesign', 'simplify', or 'clean up' in a TypeScript / Node / NestJS context. This skill provides TS-specific examples for the cross-language refactoring rules defined in craft:refactoring-principles. DO NOT use for: writing new features from scratch, general OOP patterns (see ts-oop).
ts-ports-adapters
ACTIVATE when a TypeScript codebase with no dependency-injection container has to declare a port and wire its adapter — a repository, a clock, an id generator, an event dispatcher the domain depends on. ACTIVATE for 'port', 'adapter', 'SPI', 'dependency inversion' or 'composition root' in a codebase that wires its dependencies by hand. Covers: the port as a type of functions in the domain layer, the adapter as an object literal annotated with it, the handler that takes its dependencies first and its data second, the composition root in the infrastructure layer, and the in-memory stub that substitutes for the adapter in a unit test. DO NOT use for: NestJS, or any container-based DI with decorators and tokens (see nest-ddd-conventions), aggregate operations and smart constructors (see ddd-ts-fp), the Result type itself (see ts-functional).
ts-oop
ACTIVATE when designing TypeScript classes, and the value objects or collections modelled as classes, or when the user asks about object design, encapsulation, Tell Don't Ask, or Symbol.iterator. Provides TS-specific examples for the cross-language OOP rules defined in craft:oop-principles, plus TS-specific patterns (Symbol.iterator, branded types for primitive identifiers). DO NOT use for: a value object or an aggregate modelled as an immutable record rather than a class (see ddd-ts-fp), refactoring methodology (see ts-refactoring).
ts-layer-boundaries
ACTIVATE when wiring a Worker entry point, a Router, a controller, or a domain event consumer in a TypeScript CQRS codebase, or when deciding where a port belongs. ACTIVATE for 'composition root', 'layer boundary', 'ESLint zones', 'domain must not import infrastructure'. Covers: the *Worker.ts composition root, the curried consumer, Router wiring of command endpoints, the five-gesture controller, the domain-to-infrastructure import boundary enforced by ESLint, infrastructure-only ports, in-memory doubles for every port, the read-can-reach-write asymmetry, and read endpoints going through a domain handler. DO NOT use for: the port's own shape and adapter binding (see ts-ports-adapters), the Result type (see ts-functional), domain event structure (see ts-ddd-events).