Skip to main content

A Claude Code plugin's manifest is 12 lines

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 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.

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):

{
  "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):

{
  "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/:

.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 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.