# A Claude Code plugin's manifest is 12 lines

> What ships in a Claude Code plugin: 12-line marketplace.json, 12-line plugin.json, no enumeration. Discovery is directory-based.

**Canonical URL**: https://agentcookbooks.com/blog/claude-code-plugin-anatomy/

**Published**: 2026-05-24

**Tags**: claude-code, skills, cookbook, codebase-analysis

---

I'd been treating Claude Code plugins as "skills with a marketplace listing" — a wrapper for distribution, basically a tarball that drops a `SKILL.md` somewhere `~/.claude/skills/` can find it. That model breaks the first time you read the actual manifest files. The [Understand-Anything](https://github.com/Lum1104/Understand-Anything) plugin ships eight slash-commands, nine subagents, two hooks, and a pile of bundled Node/Python scripts — and neither manifest file lists any of them. They're discovered by directory convention. The plugin format is a folder shape, not a manifest schema.

This is a tour of what `/plugin install understand-anything` actually contains, with both manifests verbatim and the producer/readers pattern that makes the "8 skills" number misleading. The wiki entry for the plugin itself lives at [understand-anything](/skills/understand-anything/).

## The two manifest files

Two JSON files anchor a plugin in a repo. Both are short.

`.claude-plugin/marketplace.json` (at the repo root, lists the plugin(s) shipped from this repo):

```json
{
  "name": "understand-anything",
  "metadata": {
    "description": "LLM-powered codebase analysis producing interactive knowledge graphs, guided tours, and deep-dive explanations"
  },
  "owner": {
    "name": "Lum1104"
  },
  "plugins": [
    {
      "name": "understand-anything",
      "source": "./understand-anything-plugin"
    }
  ]
}
```

`understand-anything-plugin/.claude-plugin/plugin.json` (at the plugin root, the actual manifest):

```json
{
  "name": "understand-anything",
  "description": "AI-powered codebase understanding — analyze, visualize, and explain any project",
  "version": "2.7.4",
  "author": {
    "name": "Lum1104"
  },
  "homepage": "https://github.com/Lum1104/Understand-Anything",
  "repository": "https://github.com/Lum1104/Understand-Anything",
  "license": "MIT",
  "keywords": [
    "codebase-analysis",
    "knowledge-graph",
    "architecture",
    "onboarding",
    "dashboard"
  ]
}
```

Twelve lines each. Neither enumerates the skills, the agents, or the hooks the plugin ships. There's no `skills: [...]`, no `agents: [...]`, no `hooks: [...]`, no entry-point declaration, no install script reference. A reader who only had these two files would know the plugin exists and what version it is, and that's it.

The structure is in the directory layout instead.

## Where the structure actually lives

Inside `understand-anything-plugin/`:

```text
.claude-plugin/
  plugin.json          # the manifest above
skills/
  understand/
    SKILL.md
    extract-structure.mjs      # tree-sitter structural pass
    build-fingerprints.mjs     # for incremental updates
    merge-batch-graphs.py      # dedup across concurrent batches
    merge-subdomain-graphs.py
    test_merge_batch_graphs.py
    frameworks/                # per-framework LLM context
    languages/                 # per-language LLM context
    locales/                   # translations
  understand-chat/SKILL.md
  understand-dashboard/SKILL.md
  understand-diff/SKILL.md
  understand-domain/SKILL.md
  understand-explain/SKILL.md
  understand-knowledge/SKILL.md
  understand-onboard/SKILL.md
agents/
  project-scanner.md
  file-analyzer.md
  assemble-reviewer.md
  architecture-analyzer.md
  tour-builder.md
  domain-analyzer.md
  article-analyzer.md
  graph-reviewer.md
  knowledge-graph-guide.md
hooks/
  hooks.json
  auto-update-prompt.md       # 15 KB hook payload
packages/                     # @understand-anything/core (pnpm workspace)
src/
package.json                  # the plugin is a pnpm workspace root
pnpm-workspace.yaml
tsconfig.json
```

The contract: `skills/<slug>/SKILL.md` becomes a `/<slug>` slash command. `agents/<slug>.md` becomes a subagent named `<slug>` that the plugin's own skills can dispatch. `hooks/hooks.json` registers PreToolUse / PostToolUse / Stop handlers. No declaration step — the directories are the schema.

That's the part that surprised me coming from the bare-skill model. With a single `SKILL.md` in `~/.claude/skills/foo/`, your skill is *just* that file. With a plugin, your skill can sit next to its own bundled subagents (which it can `Task`-dispatch by name without the user installing them separately) and its own bundled scripts (which it can shell out to with `$CLAUDE_PLUGIN_ROOT` resolved for it) and its own bundled hooks (which install atomically when the plugin installs). The plugin is the unit of distribution; everything inside it ships together.

## The "8 skills" number is misleading

You'd expect an 8-skill plugin to mean 8 independent capabilities. It's not. The `understand` skill is the producer, the other seven are readers.

`skills/understand/` contains the SKILL.md plus six scripts plus three bundled-context directories — it's the heavy pipeline that runs Tree-sitter structural extraction, dispatches `file-analyzer` in concurrent batches (≤5 in flight), merges via Python, then dispatches `assemble-reviewer`, `architecture-analyzer`, and `tour-builder`. The output is one file: `$PROJECT_ROOT/.understand-anything/knowledge-graph.json` conforming to a fixed schema of 13 node types and 26 edge types.

`skills/understand-chat/`, `skills/understand-dashboard/`, `skills/understand-diff/`, `skills/understand-domain/`, `skills/understand-explain/`, `skills/understand-knowledge/`, `skills/understand-onboard/` are each just `SKILL.md`. No bundled scripts. They read `knowledge-graph.json` and present it differently — chat, dashboard, diff, domain extract, file drill-down, knowledge query, onboarding tour. The artifact carries the work; the readers are display layers.

Five of the nine subagents (`project-scanner`, `file-analyzer`, `assemble-reviewer`, `architecture-analyzer`, `tour-builder`) exist solely to be dispatched by `/understand`. They're internal pipeline stages exposed as agents so the orchestration is legible — each agent's `description` field documents its phase, and `model: inherit` keeps it on whatever the user is running.

If you wanted to lift `/understand-chat` out as a standalone skill, you couldn't — it has no behavior without the producer's artifact. The plugin is the integration unit; the skills inside it are coupled through the file system.

## Why this matters for skill authors

Two practical reads:

**Ship as a plugin when your skills share an artifact, a subagent fleet, or a script bundle.** The `understand-anything` producer/readers pattern is the textbook case — one expensive analysis, many cheap views. Trying to distribute eight bare skills that all read `.understand-anything/knowledge-graph.json` would force every reader to document that dependency, and a user who installed only the chat skill would silently get "no graph found" errors with no install path that explains how to fix it. The plugin makes the bundle atomic.

**Ship as a bare skill when the skill is the whole capability.** A single `SKILL.md` with a tight prompt and no co-resident scripts has nothing to gain from the plugin wrapper. The marketplace.json/plugin.json overhead exists to coordinate the *bundle*, not to authenticate the skill.

The convention-over-configuration choice (no `skills: [...]` field in plugin.json) cuts both ways. It's clean to write — adding a ninth skill means dropping a folder into `skills/`, no manifest edit. It's also unverifiable from the manifest alone: a static check of `plugin.json` can't tell you whether the plugin's 8th sibling reader actually has a SKILL.md or if someone forgot to commit it. Discovery happens at install time, not at lint time.

## The footnote

Reading the manifests changed my mental model more than reading the SKILL.md files did. A plugin isn't a packaging format for skills — it's a packaging format for *cohabiting* skills, agents, hooks, and scripts that need to be installed together because they reference each other by path. The 12-line manifests are dumb on purpose; the smarts are in the layout, and the layout is the contract.

If you want to see the producer/readers pattern in practice without installing anything, the [`skills/understand/SKILL.md`](https://github.com/Lum1104/Understand-Anything/blob/main/understand-anything-plugin/skills/understand/SKILL.md) file is the load-bearing one. The seven reader SKILL.md files are 50–150 lines each and tell you a lot about which slices of `knowledge-graph.json` end up being worth displaying.