Skip to main content

fs-plugin-anatomy

The structural contract of a founder-skills plugin: directory layout, plugin.json schema and version semantics (0.1.0 vs 0.2.0), marketplace.json dual registration (local ./plugins vs git-subdir cross-list), relative-path and <skill-dir> conventions, and per-plugin .gitignore hazards including the *.html swallow that shipped build-options broken. Use when a maintainer asks "what files does a plugin need", "what goes in plugin.json", "why is my asset missing after install", "how do I register a plugin in the marketplace", "what version should this plugin be", "is my .gitignore safe", or "why did claude plugin validate pass but the skill still broke".

インストールへ移動

ソース情報

リポジトリ
craigm26/founder-skills
ソースの最終更新活動
2026年8月18日 16:32
検出された SKILL.md の言語
英語
スター
0
フォーク
0

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
fs-plugin-anatomy
description
The structural contract of a founder-skills plugin: directory layout, plugin.json schema and version semantics (0.1.0 vs 0.2.0), marketplace.json dual registration (local ./plugins vs git-subdir cross-list), relative-path and <skill-dir> conventions, and per-plugin .gitignore hazards including the *.html swallow that shipped build-options broken. Use when a maintainer asks "what files does a plugin need", "what goes in plugin.json", "why is my asset missing after install", "how do I register a plugin in the marketplace", "what version should this plugin be", "is my .gitignore safe", or "why did claude plugin validate pass but the skill still broke".
# fs-plugin-anatomy — the structural contract of a plugin You are maintaining `founder-skills` (`/home/craigm26/projects/craigm26/founder-skills` on the origin machine; public GitHub `craigm26/founder-skills`, MIT). It is a Claude Code **marketplace** — 12 plugins committed at HEAD `2e4c9dd` plus 3 uncommitted `skill-*` plugins (2026-07-02), 15 on disk: a repo whose root `.claude-plugin/marketplace.json` lists installable plugins, each a self-contained directory under `plugins/`. This skill defines what a structurally correct plugin looks like, how the two manifest layers relate, and the one class of defect (gitignore swallow) that has already shipped a plugin broken once. All facts below verified against the repo at HEAD `2e4c9dd` on 2026-07-02 unless stamped otherwise. ## When NOT to use this skill | Your job | Load instead | |---|---| | Add a brand-new skill end to end (add-skill.sh workflow, stub → publish) | `fs-skill-authoring` | | Write or edit SKILL.md *content* (frontmatter voice, phases, tables, closers) | `fs-skill-style-guide` | | Ship a change to master / run the pre-push gate | `fs-release-and-publish` | | Get a spec/plan approved before touching anything | `fs-change-control` | | Run or extend the test suites, set up the venv | `fs-toolchain-and-tests` | | Read the full incident narratives (how the swallow happened and was rescued) | `fs-failure-archaeology` | | First orientation to the whole repo | `fs-orientation` | ## 1. The two manifest layers There are two different JSON manifests. Confusing them is the most common structural mistake. | Manifest | Location | Scope | Declares | |---|---|---|---| | `plugin.json` | `plugins/<name>/.claude-plugin/plugin.json` | one plugin | name, version, description, author, license | | `marketplace.json` | `.claude-plugin/marketplace.json` (repo root) | the whole marketplace | owner + the list of installable plugins and where their source lives | Both live inside a directory literally named `.claude-plugin/` — the per-plugin one inside the plugin dir, the marketplace one at repo root. A plugin is not installable until it appears in **both** places. ## 2. Directory layout contract Observed layout across all 15 plugins (verified 2026-07-02; the 3 uncommitted skill-* plugins ship only the three REQUIRED files below — no assets/references/tests): ``` plugins/<name>/ ├── SKILL.md # REQUIRED — at plugin ROOT, not in a skills/ subdir ├── README.md # REQUIRED here — marketing-facing walkthrough (all 15 have one) ├── .claude-plugin/ │ └── plugin.json # REQUIRED — the plugin manifest ├── references/ # optional — worked examples, method docs (5 plugins have it) ├── assets/ # optional — executable/template payloads (2: market-validation, build-options) ├── tests/ # optional — pytest + node:test suites (2: market-validation, build-options) ├── demos/ # tufte-viz only — shipped HTML demos └── .gitignore # only where the skill GENERATES output files (2: market-validation, build-options) ``` Rules of thumb: - **SKILL.md sits at the plugin root.** This repo does not use a `skills/` subdirectory convention. - **README.md is the marketing face**, SKILL.md is the runbook. README template (Before you install / What it will ask you / What it produces / Cost / 60-second first run / Built on) is `fs-skill-style-guide`'s territory. - **`assets/` = files the skill executes or fills at runtime** (workflow scripts, Python generators, HTML templates). **`references/` = files the skill reads for method/examples.** The distinction matters for `.gitignore` (see §6) and tests. - Runtime junk (`__pycache__/`, `.pytest_cache/`) appears on disk in the two tested plugins; it is gitignored, never committed. ## 3. plugin.json schema as used here Fields present in all 15 manifests (12 committed + 3 uncommitted skill-*; this is the house shape, not the full official schema): | Field | Value pattern | Notes | |---|---|---| | `name` | lowercase slug, matches directory name | `add-skill.sh` enforces `^[a-z0-9][a-z0-9-]*$` | | `version` | `0.1.0` or `0.2.0` | semantics in §4 — the installed-cache directory is literally named after this | | `description` | one line, job-first | mirrors (not necessarily verbatim-equals) the SKILL.md frontmatter description | | `author` | `{"name": "Craig Merry", "email": "craigm26@gmail.com"}` | full name here; the marketplace entry uses the handle instead | | `homepage` | `https://github.com/craigm26/founder-skills` | same for all | | `license` | `MIT` | same for all | | `keywords` | 4–5 slugs | present in all 15 manifests, but `add-skill.sh` **omits** it when generating — add manually after scaffolding | Validation: `claude plugin validate plugins/<name>` (and `--strict`). **Hard limitation, verified 2026-07-02: it validates the MANIFEST only** — its output says "Validating plugin manifest" and it passed build-options while that plugin's core runtime asset was missing from git. Passing validate proves the JSON is well-formed against the schema, nothing more. Never treat it as a ship gate; the real gate is `fs-release-and-publish`. ## 4. Version semantics: 0.1.0 vs 0.2.0 - **0.1.0** = as-published baseline. `add-skill.sh` stamps every new plugin `0.1.0`. - **0.2.0** = the Anthropic-primitives upgrade, commit `93d91a2` (2026-06-11): skills re-anchored on verified primitives (scheduling triggers, Outcome rubrics, `output_config.effort` disambiguation, the correctly-stated Fable 5 safety-boundary section). Verified version map (2026-07-02): | 0.2.0 (primitives-upgraded) | 0.1.0 (baseline) | |---|---| | effort, tasks, founder-loop-design, fable-orchestrated-feature-dev, fable-org-audit, fable-repo-audit | session-start, market-validation, build-options, prd, ecosystem-planning, tufte-viz + the 3 uncommitted skill-* plugins (2026-07-02) | Counting caveat: the commit title says "across five skills" but the diff bumps **six** plugin.json files (the message body groups fable-orchestrated-feature-dev + tasks as one upgrade). When citing, say "the 0.2.0 upgrade (six plugins bumped, commit `93d91a2`)" — count files, not the title. **Version bumps are load-bearing for installs**: the plugin cache stores each install under a directory named after `version` (§7). Shipping changed content without a bump means updated installs land in the *same* cache directory name; there is no signal to users that content changed. Bump on any behavior-relevant change. ## 5. marketplace.json and dual registration ### 5a. The local (canonical) marketplace Root `.claude-plugin/marketplace.json` — top level: `$schema` (`https://anthropic.com/claude-code/marketplace.schema.json`), `name` (`founder-skills`), `description`, `owner {name, email}`, `plugins[]`. Each plugin entry: ```json { "name": "build-options", "description": "one-liner (independently worded; NOT auto-synced with plugin.json)", "author": { "name": "craigm26" }, "category": "productivity", "source": "./plugins/build-options", "homepage": "https://github.com/craigm26/founder-skills" } ``` - `source` is a **relative path string** (`./plugins/<name>`) for the local marketplace. - `author.name` is the handle `craigm26` here vs `Craig Merry` in plugin.json — intentional, keep it. - `category` is `productivity` for all 15 (the `add-skill.sh` default). - **Three descriptions exist per plugin** (SKILL.md frontmatter, plugin.json, marketplace entry) and nothing keeps them in sync. When you change one, diff the other two. ### 5b. The cross-list (second registration) `scripts/add-skill.sh` also registers each new plugin in a **second** marketplace — the RobotRegistryFoundation repo at `$HOME/claude-code-plugins` (overridable via env var `RRF_MARKETPLACE`). There the entry's `source` is an **object**, not a path: ```json "source": { "source": "git-subdir", "url": "https://github.com/craigm26/founder-skills.git", "path": "plugins/<name>" } ``` Meaning: the cross-list marketplace does not vendor the plugin; installers pull the subdirectory straight from the founder-skills git repo. Consequences: - founder-skills master is the single source of truth for both marketplaces — cross-list entries go stale in *listing metadata* only (name/description), never in content. - The cross-list target is machine-local. **Verified 2026-07-02: `~/claude-code-plugins` does not exist on the origin Raspberry Pi host** — `add-skill.sh` detects this and skips with a warning (`--no-cross-list` skips explicitly). Do not treat cross-listing as guaranteed; it is best-effort. - Registration in both places is idempotent: the script checks `any(p["name"] == name)` before appending. ## 6. Path conventions inside a plugin A plugin must work from wherever it is installed (repo checkout, plugin cache, symlink). Three rules make that true, all observed in the golden plugins: 1. **SKILL.md prose uses relative paths** — `references/…`, `assets/…` — never absolute paths, never `/home/…`. 2. **Runnable commands in SKILL.md use the `<skill-dir>` placeholder** for the plugin's own root, e.g. (verbatim from market-validation SKILL.md): ``` python3 <skill-dir>/assets/market-map/emit_market_map.py deck-data.json <out-dir>/market-map node --test '<skill-dir>/tests/js/*.test.mjs' ``` `<skill-dir>` is a convention the executing model substitutes at runtime, not an env var. Output locations use `<out-dir>` the same way. 3. **Executable assets self-locate their siblings** via `Path(__file__).parent`, never cwd. Verified in both generators: `TEMPLATE = HERE / "matrix.template.html"` (build_matrix.py:16), `TEMPLATE = HERE / "deck.template.html"` (build_deck.py:15). This is why a missing template is a hard `FileNotFoundError` at runtime — and why the gitignore swallow (§7) broke build-options completely. ## 7. .gitignore hazards — the *.html swallow The one structural defect class that has already shipped broken (defect ledger #1; full incident narrative and rescue story live in `fs-failure-archaeology` — this section is the pattern, not the chronicle). Both chain plugins generate HTML output next to where they keep HTML **templates**. Their `.gitignore` files took opposite approaches (both verbatim, 2026-07-02): | `plugins/build-options/.gitignore` — the pattern that BROKE | `plugins/market-validation/.gitignore` — the pattern that was SAFE | |---|---| | `__pycache__/`<br>`*.pyc`<br>`*.pdf`<br>`*.pptx`<br>`*.html` ← swallow<br>`!assets/matrix.template.html` ← post-rescue negation<br>`.pytest_cache/` | `__pycache__/`<br>`*.pyc`<br>`*.pdf`<br>`*.pptx`<br>`deck.html` ← exact output filename only<br>`.pytest_cache/` | What happened: `*.html` matched the shipped runtime asset `assets/matrix.template.html`, so it was never committed. `claude plugin validate` passed anyway (manifest-only, §3). The plugin published broken; the sole surviving copy of the template lived in the mutable plugin cache (`~/.claude/plugins/cache/founder-skills/build-options/0.1.0/assets/`, regenerated at runtime 2026-06-24 — environment-specific path, origin machine). Rescued 2026-07-02: template copied back into the repo, `!assets/matrix.template.html` negation added, pytest back to 9/9. **As of 2026-07-02 the rescue is UNCOMMITTED** (`git status`: modified `.gitignore` + untracked `assets/matrix.template.html`) — operator commits per standing rule; re-check before relying on it being in history. Rules going forward: 1. **Prefer exact output filenames** (`deck.html`) over extension wildcards. The output filename is known — the skill's own generator names it. 2. **If you must wildcard an extension, immediately add a `!` negation for every shipped asset** of that extension — and remember negations must come after the wildcard line. 3. **Templates are inputs, not outputs.** Anything under `assets/` or `references/` or `demos/` that the skill reads must be tracked. (tufte-viz ships four HTML demos and — correctly — has no `.gitignore` at all; a repo-wide `*.html` rule would have nuked those too.) 4. **A plugin only needs a `.gitignore` if it generates files inside its own directory.** Thirteen of fifteen plugins have none. Verification commands (run from repo root; these caught / would have caught the swallow): ```bash # Is any shipped payload gitignored? Filter legit junk; ANY remaining line = defect. # (Plain check-ignore, not -v: -v also prints negation matches, which are NOT ignored.) git check-ignore plugins/*/assets/* plugins/*/references/* plugins/*/demos/* 2>/dev/null \ | grep -v -e __pycache__ -e '\.pyc$' # exit 1 + no output = clean (verified 2026-07-02) # Diff what git ships vs what's on disk, per plugin (LC_ALL=C: git sorts in C locale). # Lines prefixed '>' are on disk but NOT tracked — today this shows the uncommitted # matrix.template.html rescue; after it's committed, expect no output. diff <(git ls-files plugins/build-options/assets/ | LC_ALL=C sort) \ <(find plugins/build-options/assets -maxdepth 1 -type f ! -name '*.pyc' | LC_ALL=C sort) # Manifest well-formed (remember: manifest-ONLY) claude plugin validate plugins/<name> --strict && claude plugin validate . ``` ## 8. Install reality: the plugin cache Environment-specific (origin machine paths; the mechanism is Claude Code's, the paths are per-user): - Installed plugins live at `~/.claude/plugins/cache/founder-skills/<plugin>/<version>/` — the version directory name is literally `plugin.json`'s `version` field (verified: build-options → `0.1.0`, fable-org-audit/tasks/effort → `0.2.0`). A `.in_use/` subdir tracks live sessions. - The cache is a **copy**, frozen at install time (origin cache = HEAD `2e4c9dd`, installed 2026-06-24). Every subsequent push to master silently desyncs installed users until they run `/plugin marketplace update founder-skills`. - The cache is **mutable at runtime** — a session can write into it (that is how the swallowed template survived at all). Never treat cache contents as ground truth for what the repo ships; compare against `git ls-files`. - `add-skill.sh` separately symlinks `~/.claude/skills/<name>` → `plugins/<name>` on the authoring machine ("one copy" rule) — and line ~142 `rm -rf`s any existing non-symlink dir there first (hazard, ledger #7). Verified 2026-07-02: no founder-skills symlinks currently present in `~/.claude/skills/` on the origin host. ## 9. New-plugin structural checklist For the full authoring workflow use `fs-skill-authoring`; this is the anatomy-only gate. A plugin is structurally complete when:
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る