[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DVZFC0EE44A9AMFTZ94CDR":3,"skill-same:01M6DVZFC0EE44A9AMFTZ94CDR":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":51,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":57,"ownerDescription":6,"updatedAt":58,"install":59,"resources":72},"01M6DVZFC0EE44A9AMFTZ94CDR","plugin-conventions",null,"ACTIVATE when creating a Claude Code plugin, writing plugin.json, marketplace.json, hooks.json, a hook script, or an evals\u002Fevals.json file. ACTIVATE for 'Claude plugin', 'plugin.json', 'marketplace.json', 'hooks.json', 'CLAUDE_PLUGIN_ROOT', 'permissionDecision', 'evals.json'. Covers: plugin directory structure, plugin.json\u002Fmarketplace.json schemas, hooks.json format, the hook output contract (exit codes, which channel reaches Claude), CLAUDE_PLUGIN_ROOT portability, distribution best practices, the evals\u002Fevals.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).","FabienSalles\u002Fclaude-marketplace","FabienSalles","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F937901?v=4","01M6DVJBD31CJKRB382FQFZ5FH","Fabien Salles","01M6DVJBQNY8Q18KJD1ESAD6TX",9,"MIT","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fskills\u002Fskills\u002Fplugin-conventions\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000037","律师与法务","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"plugin conventions","plugin","conventions","hooks","distribution","contract","structure","do","activate","exit","evals","writing","use","marketplace","file","best practices","code","routing","test","claude code plugin","2026-11-10 10:12:30",false,"---\nname: plugin-conventions\ndescription: \"ACTIVATE when creating a Claude Code plugin, writing plugin.json, marketplace.json, hooks.json, a hook script, or an evals\u002Fevals.json file. ACTIVATE for 'Claude plugin', 'plugin.json', 'marketplace.json', 'hooks.json', 'CLAUDE_PLUGIN_ROOT', 'permissionDecision', 'evals.json'. Covers: plugin directory structure, plugin.json\u002Fmarketplace.json schemas, hooks.json format, the hook output contract (exit codes, which channel reaches Claude), CLAUDE_PLUGIN_ROOT portability, distribution best practices, the evals\u002Fevals.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).\"\nmetadata:\n  version: \"1.0\"\n---\n\n# Claude Code Plugin & Marketplace Conventions\n\nBest practices for creating, structuring, and distributing Claude Code plugins and marketplaces, based on official documentation and the `anthropics\u002Fclaude-plugins-official` repository.\n\n## Plugin Directory Structure\n\n```\nplugin-name\u002F\n├── .claude-plugin\u002F\n│   └── plugin.json          # Manifest (ONLY this file here)\n├── commands\u002F                 # Slash commands (.md)\n├── agents\u002F                   # Subagent definitions (.md)\n├── skills\u002F                   # Skills (subdirectories with SKILL.md)\n│   └── skill-name\u002F\n│       ├── SKILL.md\n│       ├── scripts\u002F\n│       ├── references\u002F\n│       ├── examples\u002F\n│       └── evals\u002F\n│           └── evals.json    # Routing + behaviour test cases (see below)\n├── hooks\u002F\n│   ├── hooks.json            # Hook configuration\n│   └── scripts\u002F              # Hook scripts\n├── .mcp.json                 # MCP server definitions\n├── .lsp.json                 # LSP configurations\n├── settings.json             # Default settings\n├── scripts\u002F                  # Shared utilities\n├── LICENSE\n└── README.md\n```\n\n**Critical rules:**\n- `.claude-plugin\u002F` contains ONLY `plugin.json` — never nest components inside it\n- All component directories at plugin root level\n- Kebab-case for all directory and file names\n- Only create directories for components actually used\n\n## plugin.json Schema\n\n### Minimal (auto-discovery handles the rest)\n\n```json\n{\n  \"name\": \"plugin-name\"\n}\n```\n\n### Recommended\n\n```json\n{\n  \"name\": \"plugin-name\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Brief plugin description\",\n  \"author\": {\n    \"name\": \"Author Name\",\n    \"email\": \"author@example.com\",\n    \"url\": \"https:\u002F\u002Fgithub.com\u002Fauthor\"\n  },\n  \"repository\": \"https:\u002F\u002Fgithub.com\u002Fauthor\u002Fplugin\",\n  \"license\": \"MIT\",\n  \"keywords\": [\"keyword1\", \"keyword2\"]\n}\n```\n\n### Component Paths (optional, supplements auto-discovery)\n\n```json\n{\n  \"skills\": \".\u002Fskills\u002F\",\n  \"agents\": \".\u002Fagents\u002Fagent-name.md\",\n  \"hooks\": \".\u002Fhooks\u002Fhooks.json\",\n  \"commands\": \".\u002Fcommands\u002Fcommand-name.md\",\n  \"mcpServers\": \".\u002F.mcp.json\",\n  \"lspServers\": \".\u002F.lsp.json\",\n  \"outputStyles\": \".\u002Fstyles\u002F\"\n}\n```\n\n**Path rules:**\n- All paths relative to plugin root\n- Must start with `.\u002F`\n- Custom paths supplement default directories, never replace them\n\n## marketplace.json Schema\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fanthropic.com\u002Fclaude-code\u002Fmarketplace.schema.json\",\n  \"name\": \"marketplace-name\",\n  \"description\": \"Brief marketplace description\",\n  \"owner\": {\n    \"name\": \"Owner Name\",\n    \"email\": \"owner@example.com\"\n  },\n  \"metadata\": {\n    \"version\": \"1.0.0\",\n    \"description\": \"Detailed marketplace description\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"plugin-name\",\n      \"source\": \".\u002Fplugins\u002Fplugin-name\",\n      \"description\": \"Plugin description\",\n      \"version\": \"1.0.0\",\n      \"author\": { \"name\": \"Author\" },\n      \"category\": \"development\",\n      \"license\": \"MIT\",\n      \"keywords\": [\"keyword\"],\n      \"tags\": [\"community-managed\"]\n    }\n  ]\n}\n```\n\n### Required fields\n- `name` — kebab-case, no spaces\n- `owner` — object with `name` (required), `email` (optional)\n- `plugins` — array of plugin entries, each with `name` and `source`\n\n### Reserved marketplace names\n`claude-code-marketplace`, `claude-code-plugins`, `claude-plugins-official`, `anthropic-marketplace`, `anthropic-plugins`, `agent-skills`, `life-sciences`. Names impersonating official Anthropic marketplaces are blocked.\n\n### Plugin source types\n\n| Type | Format |\n|------|--------|\n| Local path | `\".\u002Fplugins\u002Fname\"` |\n| GitHub | `{ \"source\": \"github\", \"repo\": \"org\u002Frepo\", \"ref\": \"main\", \"sha\": \"abc\" }` |\n| Git URL | `{ \"source\": \"url\", \"url\": \"https:\u002F\u002Fgithub.com\u002Forg\u002Frepo.git\" }` |\n| Git subdir | `{ \"source\": \"git-subdir\", \"url\": \"org\u002Frepo\", \"path\": \"plugins\u002Fname\" }` |\n| npm | `{ \"source\": \"npm\", \"package\": \"name\", \"version\": \"^1.0\" }` |\n\n### Plugin categories\n`development`, `productivity`, `security`, `testing`, `database`, `deployment`, `monitoring`, `design`, `learning`\n\n## hooks.json Format\n\nThe official format uses nested `matcher` + `hooks[]` with `type`:\n\n```json\n{\n  \"description\": \"Human-readable description of hooks purpose\",\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Write|Edit\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"\\\"${CLAUDE_PLUGIN_ROOT}\u002Fhooks\u002Fscripts\u002Fvalidate.sh\\\"\",\n            \"timeout\": 30\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n### Hook types\n\n| Type | Field | Description |\n|------|-------|--------------|\n| `command` | `command` | Shell command, receives JSON on stdin |\n| `http` | `url` | HTTP POST to endpoint |\n| `prompt` | `prompt` | Single-turn LLM evaluation |\n| `agent` | `prompt` | Agentic verifier with Read\u002FGrep\u002FGlob tools |\n\n### Hook events\n\nFull reference: https:\u002F\u002Fdocs.claude.com\u002Fen\u002Fdocs\u002Fclaude-code\u002Fhooks\n\n| Event | Matcher | Can block? |\n|-------|---------|------------|\n| `SessionStart` | `startup`, `resume`, `clear`, `compact`, `fork` | No |\n| `SessionEnd` | `clear`, `resume`, `logout`, `prompt_input_exit`, `other` | No |\n| `UserPromptSubmit` | None | Yes (exit 2) |\n| `PreToolUse` | Tool name | Yes (exit 2) |\n| `PostToolUse` | Tool name | No |\n| `Stop` | None | Yes (exit 2) |\n| `SubagentStop` | Agent type | Yes (exit 2) |\n| `Notification` | Notification type | No |\n| `PreCompact` | `manual`, `auto` | Yes (exit 2) |\n\nAn 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.\n\n### Hook output contract\n\nOutput on the wrong channel fails silently: the action proceeds and the message reaches nobody. Five rules decide it:\n\n- On most events, exit 2 is the only code that blocks by itself (`WorktreeCreate` and `WorktreeRemove` fail 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, so `exit 1` enforces nothing. Such an object is read on every exit code: at exit 1 a JSON `deny` still decides, and no JSON overrides the block of exit 2.\n- At exit 0, stderr goes to the debug log only: neither Claude nor the user sees it.\n- Plain stdout reaches Claude's context only on `SessionStart`, `UserPromptSubmit`, `UserPromptExpansion` and `PostModelSwitch`; on other events, speak through exit 2 or JSON.\n- A `PreToolUse` decision is `hookSpecificOutput.permissionDecision` (`allow`, `deny`, `ask` or `defer`) with `permissionDecisionReason`. The top-level `decision`\u002F`reason` pair is deprecated for that event.\n- JSON stdout is exactly one object built with an encoder (`jq -n --arg …`, Python's `json.dumps`): an unparsable one is dropped as a hook error, and a field at the wrong level is ignored.\n\nThe stdin fields per event, the effect of exit 2 per event and the JSON fields are tabled in `references\u002Fhook-output-contract.md`. How to test a hook (a sourced harness, payload files, one assertion per channel) lives in `shell-test:shell-test-conventions`.\n\n## Portability — ${CLAUDE_PLUGIN_ROOT}\n\nAlways use `${CLAUDE_PLUGIN_ROOT}` for intra-plugin path references. Never hardcode paths.\n\n**In hooks.json:** `\"command\": \"\\\"${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Ftool.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.\n**In MCP config:** `\"args\": [\"${CLAUDE_PLUGIN_ROOT}\u002Fservers\u002Fserver.js\"]`\n**In shell scripts:** `source \"${CLAUDE_PLUGIN_ROOT}\u002Flib\u002Fcommon.sh\"`\n\n## evals\u002Fevals.json Format\n\nA skill's `evals\u002Fevals.json` pins the test cases that prove it triggers on the right prompts and\nproduces the right behaviour once loaded. This marketplace's convention (see\n`plugins\u002Fcareer\u002Fevals\u002Fevals.json` for a worked example):\n\n```json\n{\n  \"$comment\": \"One object per case; `skills` names what should load, `expected_behavior` lists what a passing run must do.\",\n  \"routing\": [\n    {\n      \"query\": \"A prompt a real user would type\",\n      \"skills\": [\"plugin-name:skill-name\"],\n      \"expected_behavior\": [\n        \"loads plugin-name:skill-name, not a sibling skill\",\n        \"the specific guardrail that makes this skill different from its neighbours\"\n      ]\n    }\n  ]\n}\n```\n\n- **`routing` cases** test discovery from the description alone: does the right skill load, and\n  not a sibling that could plausibly match the same query?\n- **`skills`** is an array of `plugin:skill-name` refs, same shape as an agent's `skills:` field.\n- **`expected_behavior`** lists assertions in plain language, not a machine-checked schema — a\n  human or an agent replays the case and checks each line.\n- Keep at least one case per skill that has caused a real false-positive or false-negative\n  activation; that is the evidence a routing rule was worth writing.\n\n## Distribution Best Practices\n\n1. **Version bumps required** — Claude Code caches by version; no bump = no update for users\n2. **Version in one place** — avoid setting version in both plugin.json and marketplace.json (plugin.json wins silently)\n3. **Pin with `sha`** for reproducible builds in production marketplaces\n4. **Test locally** with `claude --plugin-dir .\u002Fmy-plugin`\n5. **Reload without restart** with `\u002Freload-plugins`\n6. **Debug loading** with `claude --debug` or `\u002Fdebug`\n\n## Validation\n\n```bash\n# Validate marketplace (--strict turns every warning into exit 1: the CI form)\nclaude plugin validate --strict .\n\n# Validate individual plugin\nclaude plugin validate --strict plugins\u002Fmy-plugin\n\n# Validate from within Claude Code\n\u002Fplugin validate .\n```\n\n## Naming Conventions\n\n| Component | Convention | Example |\n|-----------|-----------|---------|\n| Plugin | kebab-case | `code-review-assistant` |\n| Skills | kebab-case directories | `api-testing\u002F` |\n| Commands | kebab-case .md | `run-tests.md` |\n| Agents | kebab-case .md | `code-reviewer.md` |\n| Hooks | kebab-case with ext | `validate-input.sh` |\n| Config | Standard names | `hooks.json`, `.mcp.json` |\n\nPlugin skills are namespaced as `\u002Fplugin-name:skill-name` to prevent conflicts.\n\nSee also: `skill-authoring` for the SKILL.md format itself, `agent-authoring` for the agent .md\nformat referenced by `agents\u002F`.\n","# Claude Code Plugin & Marketplace Conventions\n\nBest practices for creating, structuring, and distributing Claude Code plugins and marketplaces, based on official documentation and the `anthropics\u002Fclaude-plugins-official` repository.\n\n## Plugin Directory Structure\n\n```\nplugin-name\u002F\n├── .claude-plugin\u002F\n│   └── plugin.json          # Manifest (ONLY this file here)\n├── commands\u002F                 # Slash commands (.md)\n├── agents\u002F                   # Subagent definitions (.md)\n├── skills\u002F                   # Skills (subdirectories with SKILL.md)\n│   └── skill-name\u002F\n│       ├── SKILL.md\n│       ├── scripts\u002F\n│       ├── references\u002F\n│       ├── examples\u002F\n│       └── evals\u002F\n│           └── evals.json    # Routing + behaviour test cases (see below)\n├── hooks\u002F\n│   ├── hooks.json            # Hook configuration\n│   └── scripts\u002F              # Hook scripts\n├── .mcp.json                 # MCP server definitions\n├── .lsp.json                 # LSP configurations\n├── settings.json             # Default settings\n├── scripts\u002F                  # Shared utilities\n├── LICENSE\n└── README.md\n```\n\n**Critical rules:**\n- `.claude-plugin\u002F` contains ONLY `plugin.json` — never nest components inside it\n- All component directories at plugin root level\n- Kebab-case for all directory and file names\n- Only create directories for components actually used\n\n## plugin.json Schema\n\n### Minimal (auto-discovery handles the rest)\n\n```json\n{\n  \"name\": \"plugin-name\"\n}\n```\n\n### Recommended\n\n```json\n{\n  \"name\": \"plugin-name\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Brief plugin description\",\n  \"author\": {\n    \"name\": \"Author Name\",\n    \"email\": \"author@example.com\",\n    \"url\": \"https:\u002F\u002Fgithub.com\u002Fauthor\"\n  },\n  \"repository\": \"https:\u002F\u002Fgithub.com\u002Fauthor\u002Fplugin\",\n  \"license\": \"MIT\",\n  \"keywords\": [\"keyword1\", \"keyword2\"]\n}\n```\n\n### Component Paths (optional, supplements auto-discovery)\n\n```json\n{\n  \"skills\": \".\u002Fskills\u002F\",\n  \"agents\": \".\u002Fagents\u002Fagent-name.md\",\n  \"hooks\": \".\u002Fhooks\u002Fhooks.json\",\n  \"commands\": \".\u002Fcommands\u002Fcommand-name.md\",\n  \"mcpServers\": \".\u002F.mcp.json\",\n  \"lspServers\": \".\u002F.lsp.json\",\n  \"outputStyles\": \".\u002Fstyles\u002F\"\n}\n```\n\n**Path rules:**\n- All paths relative to plugin root\n- Must start with `.\u002F`\n- Custom paths supplement default directories, never replace them\n\n## marketplace.json Schema\n\n```json\n{\n  \"$schema\": \"https:\u002F\u002Fanthropic.com\u002Fclaude-code\u002Fmarketplace.schema.json\",\n  \"name\": \"marketplace-name\",\n  \"description\": \"Brief marketplace description\",\n  \"owner\": {\n    \"name\": \"Owner Name\",\n    \"email\": \"owner@example.com\"\n  },\n  \"metadata\": {\n    \"version\": \"1.0.0\",\n    \"description\": \"Detailed marketplace description\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"plugin-name\",\n      \"source\": \".\u002Fplugins\u002Fplugin-name\",\n      \"description\": \"Plugin description\",\n      \"version\": \"1.0.0\",\n      \"author\": { \"name\": \"Author\" },\n      \"category\": \"development\",\n      \"license\": \"MIT\",\n      \"keywords\": [\"keyword\"],\n      \"tags\": [\"community-managed\"]\n    }\n  ]\n}\n```\n\n### Required fields\n- `name` — kebab-case, no spaces\n- `owner` — object with `name` (required), `email` (optional)\n- `plugins` — array of plugin entries, each with `name` and `source`\n\n### Reserved marketplace names\n`claude-code-marketplace`, `claude-code-plugins`, `claude-plugins-official`, `anthropic-marketplace`, `anthropic-plugins`, `agent-skills`, `life-sciences`. Names impersonating official Anthropic marketplaces are blocked.\n\n### Plugin source types\n\n| Type | Format |\n|------|--------|\n| Local path | `\".\u002Fplugins\u002Fname\"` |\n| GitHub | `{ \"source\": \"github\", \"repo\": \"org\u002Frepo\", \"ref\": \"main\", \"sha\": \"abc\" }` |\n| Git URL | `{ \"source\": \"url\", \"url\": \"https:\u002F\u002Fgithub.com\u002Forg\u002Frepo.git\" }` |\n| Git subdir | `{ \"source\": \"git-subdir\", \"url\": \"org\u002Frepo\", \"path\": \"plugins\u002Fname\" }` |\n| npm | `{ \"source\": \"npm\", \"package\": \"name\", \"version\": \"^1.0\" }` |\n\n### Plugin categories\n`development`, `productivity`, `security`, `testing`, `database`, `deployment`, `monitoring`, `design`, `learning`\n\n## hooks.json Format\n\nThe official format uses nested `matcher` + `hooks[]` with `type`:\n\n```json\n{\n  \"description\": \"Human-readable description of hooks purpose\",\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \"Write|Edit\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"\\\"${CLAUDE_PLUGIN_ROOT}\u002Fhooks\u002Fscripts\u002Fvalidate.sh\\\"\",\n            \"timeout\": 30\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n### Hook types\n\n| Type | Field | Description |\n|------|-------|--------------|\n| `command` | `command` | Shell command, receives JSON on stdin |\n| `http` | `url` | HTTP POST to endpoint |\n| `prompt` | `prompt` | Single-turn LLM evaluation |\n| `agent` | `prompt` | Agentic verifier with Read\u002FGrep\u002FGlob tools |\n\n### Hook events\n\nFull reference: https:\u002F\u002Fdocs.claude.com\u002Fen\u002Fdocs\u002Fclaude-code\u002Fhooks\n\n| Event | Matcher | Can block? |\n|-------|---------|------------|\n| `SessionStart` | `startup`, `resume`, `clear`, `compact`, `fork` | No |\n| `SessionEnd` | `clear`, `resume`, `logout`, `prompt_input_exit`, `other` | No |\n| `UserPromptSubmit` | None | Yes (exit 2) |\n| `PreToolUse` | Tool name | Yes (exit 2) |\n| `PostToolUse` | Tool name | No |\n| `Stop` | None | Yes (exit 2) |\n| `SubagentStop` | Agent type | Yes (exit 2) |\n| `Notification` | Notification type | No |\n| `PreCompact` | `manual`, `auto` | Yes (exit 2) |\n\nAn 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.\n\n### Hook output contract\n\nOutput on the wrong channel fails silently: the action proceeds and the message reaches nobody. Five rules decide it:\n\n- On most events, exit 2 is the only code that blocks by itself (`WorktreeCreate` and `WorktreeRemove` fail 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, so `exit 1` enforces nothing. Such an object is read on every exit code: at exit 1 a JSON `deny` still decides, and no JSON overrides the block of exit 2.\n- At exit 0, stderr goes to the debug log only: neither Claude nor the user sees it.\n- Plain stdout reaches Claude's context only on `SessionStart`, `UserPromptSubmit`, `UserPromptExpansion` and `PostModelSwitch`; on other events, speak through exit 2 or JSON.\n- A `PreToolUse` decision is `hookSpecificOutput.permissionDecision` (`allow`, `deny`, `ask` or `defer`) with `permissionDecisionReason`. The top-level `decision`\u002F`reason` pair is deprecated for that event.\n- JSON stdout is exactly one object built with an encoder (`jq -n --arg …`, Python's `json.dumps`): an unparsable one is dropped as a hook error, and a field at the wrong level is ignored.\n\nThe stdin fields per event, the effect of exit 2 per event and the JSON fields are tabled in `references\u002Fhook-output-contract.md`. How to test a hook (a sourced harness, payload files, one assertion per channel) lives in `shell-test:shell-test-conventions`.\n\n## Portability — ${CLAUDE_PLUGIN_ROOT}\n\nAlways use `${CLAUDE_PLUGIN_ROOT}` for intra-plugin path references. Never hardcode paths.\n\n**In hooks.json:** `\"command\": \"\\\"${CLAUDE_PLUGIN_ROOT}\u002Fscripts\u002Ftool.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.\n**In MCP config:** `\"args\": [\"${CLAUDE_PLUGIN_ROOT}\u002Fservers\u002Fserver.js\"]`\n**In shell scripts:** `source \"${CLAUDE_PLUGIN_ROOT}\u002Flib\u002Fcommon.sh\"`\n\n## evals\u002Fevals.json Format\n\nA skill's `evals\u002Fevals.json` pins the test cases that prove it triggers on the right prompts and\nproduces the right behaviour once loaded. This marketplace's convention (see\n`plugins\u002Fcareer\u002Fevals\u002Fevals.json` for a worked example):\n\n```json\n{\n  \"$comment\": \"One object per case; `skills` names what should load, `expected_behavior` lists what a passing run must do.\",\n  \"routing\": [\n    {\n      \"query\": \"A prompt a real user would type\",\n      \"skills\": [\"plugin-name:skill-name\"],\n      \"expected_behavior\": [\n        \"loads plugin-name:skill-name, not a sibling skill\",\n        \"the specific guardrail that makes this skill different from its neighbours\"\n      ]\n    }\n  ]\n}\n```\n\n- **`routing` cases** test discovery from the description alone: does the right skill load, and\n  not a sibling that could plausibly match the same query?\n- **`skills`** is an array of `plugin:skill-name` refs, same shape as an agent's `skills:` field.\n- **`expected_behavior`** lists assertions in plain language, not a machine-checked schema — a\n  human or an agent replays the case and checks each line.\n- Keep at least one case per skill that has caused a real false-positive or false-negative\n  activation; that is the evidence a routing rule was worth writing.\n\n## Distribution Best Practices\n\n1. **Version bumps required** — Claude Code caches by version; no bump = no update for users\n2. **Version in one place** — avoid setting version in both plugin.json and marketplace.json (plugin.json wins silently)\n3. **Pin with `sha`** for reproducible builds in production marketplaces\n4. **Test locally** with `claude --plugin-dir .\u002Fmy-plugin`\n5. **Reload without restart** with `\u002Freload-plugins`\n6. **Debug loading** with `claude --debug` or `\u002Fdebug`\n\n## Validation\n\n```bash\n# Validate marketplace (--strict turns every warning into exit 1: the CI form)\nclaude plugin validate --strict .\n\n# Validate individual plugin\nclaude plugin validate --strict plugins\u002Fmy-plugin\n\n# Validate from within Claude Code\n\u002Fplugin validate .\n```\n\n## Naming Conventions\n\n| Component | Convention | Example |\n|-----------|-----------|---------|\n| Plugin | kebab-case | `code-review-assistant` |\n| Skills | kebab-case directories | `api-testing\u002F` |\n| Commands | kebab-case .md | `run-tests.md` |\n| Agents | kebab-case .md | `code-reviewer.md` |\n| Hooks | kebab-case with ext | `validate-input.sh` |\n| Config | Standard names | `hooks.json`, `.mcp.json` |\n\nPlugin skills are namespaced as `\u002Fplugin-name:skill-name` to prevent conflicts.\n\nSee also: `skill-authoring` for the SKILL.md format itself, `agent-authoring` for the agent .md\nformat referenced by `agents\u002F`.\n",{"name":5,"metadata":49,"description":7},{"version":50},"1.0","main","479b264ec17cc5fafbf36287a1087472d3a386d3","plugins\u002Fskills\u002Fskills\u002Fplugin-conventions",2,"https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace","https:\u002F\u002Fgithub.com\u002FFabienSalles","https:\u002F\u002Ffabiensalles.com","2026-10-13 04:33:22",{"source":8,"skillName":5,"commands":60,"prompt":70,"zipAvailable":71},[61,64,67],{"tool":62,"command":63},"npx","npx skills add FabienSalles\u002Fclaude-marketplace --skill plugin-conventions",{"tool":65,"command":66},"bunx","bunx skills add FabienSalles\u002Fclaude-marketplace --skill plugin-conventions",{"tool":68,"command":69},"pnpm","pnpm dlx skills add FabienSalles\u002Fclaude-marketplace --skill plugin-conventions","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fskills\u002Fskills\u002Fplugin-conventions\u002FSKILL.md",true,[73,79],{"path":74,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":77,"sourceHtmlUrl":78},"evals\u002Fevals.json","REFERENCE","参考文档",1423,"https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fskills\u002Fskills\u002Fplugin-conventions\u002Fevals\u002Fevals.json",{"path":80,"resourceType":75,"resourceTypeDesc":76,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Fhook-output-contract.md",4615,"https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fskills\u002Fskills\u002Fplugin-conventions\u002Freferences\u002Fhook-output-contract.md",[84,109,128,148,171,188],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":95,"publishedAt":44},"01M6DW2YS1MGF65JNS1QQ15QHS","vitest-test-conventions","ACTIVATE when writing Vitest tests, creating test files, using vi.fn()\u002Fvi.mock()\u002Fvi.spyOn(), or test factories. ACTIVATE for 'Vitest', 'it.each', 'NestJS testing module', 'supertest', 'vi.fn', 'vi.mock', 'spyOn'. Provides Vitest\u002FNestJS-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\u002Fiterations (see vitest:vitest-tdd-workflow), PHP\u002FPHPUnit tests (see phpunit:php-test-conventions).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fvitest\u002Fskills\u002Fvitest-test-conventions\u002FSKILL.md",[90],{"categoryNo":91,"categoryName":92,"taxonomyType":93,"taxonomyTypeDesc":94},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[42,96,26,97,98,35,99,100,38,101,36,102,103,32,31,104,105,106,107,108],"vitest","vitest test conventions","testing","tdd","workflow","testing patterns","files","nestjs","project","tests","module","fn","testing principles",{"skillNo":110,"name":111,"nameZh":6,"description":112,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":118,"publishedAt":44},"01M6DW2VFE6NPFCGQKQ68P1MVZ","vitest-tdd-workflow","ACTIVATE when building new features, endpoints, or user stories using TDD with Vitest in a TypeScript\u002FNestJS context. ACTIVATE for '\u002Ffeature-tdd-dev', 'TDD', 'red-green-refactor' in TS context. Provides Vitest\u002FNestJS-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).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Fvitest\u002Fskills\u002Fvitest-tdd-workflow\u002FSKILL.md",[115],{"categoryNo":116,"categoryName":117,"taxonomyType":93,"taxonomyTypeDesc":94},"01JBSEEDCAT000000000000023","Skill与智能体开发",[99,100,119,96,120,35,121,38,122,123,36,124,102,32,31,125,104,105,126,127],"tdd workflow","vitest tdd workflow","dev","examples","fix","red-green-refactor","green","feature","app",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":134,"publishedAt":44},"01M6DW2RGMQE10D8C5Z5MA2RMY","ts-refactoring","ACTIVATE when refactoring TypeScript code. ACTIVATE for 'refactor', 'extract', 'redesign', 'simplify', or 'clean up' in a TypeScript \u002F Node \u002F 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).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Ftypescript\u002Fskills\u002Fts-refactoring\u002FSKILL.md",[],[135,136,35,137,40,122,138,139,140,141,142,36,143,144,145,103,146,32,31,147],"refactoring","ts refactoring","typescript","refactor","simplify","Rules","node","redesign","up","general","clean","craft","patterns",{"skillNo":149,"name":150,"nameZh":6,"description":151,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":152,"categories":153,"keywords":155,"publishedAt":44},"01M6DW2MHYXNBBVYEGB058Y48J","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).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Ftypescript\u002Fskills\u002Fts-ports-adapters\u002FSKILL.md",[154],{"categoryNo":91,"categoryName":92,"taxonomyType":93,"taxonomyTypeDesc":94},[156,157,158,42,159,160,36,161,162,103,163,32,31,164,165,166,167,168,169,170],"ports","ts ports adapters","memory","ddd","Composition","container","functions","Literal","dependency-injection","domain","tokens","as","type","id","adapter",{"skillNo":172,"name":173,"nameZh":6,"description":174,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":175,"categories":176,"keywords":177,"publishedAt":44},"01M6DW2GRXZFYT05HEDB26C9B8","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).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Ftypescript\u002Fskills\u002Fts-oop\u002FSKILL.md",[],[178,179,180,137,159,122,181,182,135,140,183,36,184,146,32,31,185,186,187,167],"oop","ts oop","design","methodology","immutable","ask","refactoring methodology","objects","record","collections",{"skillNo":189,"name":190,"nameZh":6,"description":191,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":192,"categories":193,"keywords":194,"publishedAt":44},"01M6DW2D26XG5MJMTP9MSE43S2","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).","https:\u002F\u002Fgithub.com\u002FFabienSalles\u002Fclaude-marketplace\u002Fblob\u002Fmain\u002Fplugins\u002Ftypescript\u002Fskills\u002Fts-layer-boundaries\u002FSKILL.md",[],[195,158,159,196,197,160,156,36,198,199,200,32,31,30,165,201,202,168,170,203],"ts layer boundaries","controller","boundary","router","only","endpoints","cqrs","worker","import"]