agentleFS
virajpvirajp/claude-plugins/.claude/skills/plugin-authoring/SKILL.md

plugin-authoring

Skill1 starsChanged 8 days agoMITView on GitHub

This repo's plugin doctrine — how a plugin is structured,

6 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Never edit either by hand.

    line 40
  2. Distinctive:

    No gate lints or formats the shell a pack ships.

    line 52
  3. Very specific:

    A dependency stays inside this marketplace: Add the name to dependencies in plugin.json with "marketplace": "virajp-plugins".

    line 76
  4. Very specific:

    prose: no citation a pack or /vwf:init lands may be plugin-relative, because the file is copied into a repo where no plugin is installed …

    line 119

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Plugin Authoring — this repo

Plugin creation is this repo's alone and is deliberately not distributed. There is no shipped plugin-authoring plugin: the one that existed was dissolved on 2026-08-29 because the toolkit has no business teaching plugin creation to anyone else.

The doctrine split in two on the way out, and the halves have different homes:

  • How a plugin is structured, packaged and registered — the manifest, directory-convention discovery, versions, the marketplace and its two traps — is here, in structure.md.
  • What makes a skill, agent or hook valid — the invocation states and their silent failure, strict-YAML frontmatter, hook verdict shapes, MCP and LSP wiring — is distributed via stackgen, at plugins/stackgen/assets/artifact-doctrine.md, because stackgen generates those artifacts and its reviewer gates them against it. Read it when writing a skill or a hook anywhere, including here: the rules are the host's and apply to a plugin's artifacts exactly as they apply to a generated one.

plugins/<name>/ is the only authored tree — what you edit is exactly what a user installs. There is no template layer, no render step, and no per-target variant.

Two files are generated: .claude-plugin/marketplace.json at the repo root, a projection of the 2 per-plugin manifests, and plugins/stackgen/stacks/inventory.md, a projection of the stacks tree. Never edit either by hand.

The one rule

A change under plugins/ is not done until the gates pass:

mise run p:plugins:check       # validates the authored tree
mise run p:plugins:marketplace # regenerate, if you touched a manifest
mise run p:plugins:inventory   # regenerate, if you touched stackgen's stacks/ or kinds.md

No gate lints or formats the shell a pack ships.

--check on the two generators is what CI and pre-commit run. It exists because each output is generated and committed, so a source edited without a regenerate is invisible to every other check — this is the one piece of staleness the retired plugins:render-clean was really guarding, narrowed to the two files that still have the problem.

Three traps that are ours, not Claude's

  1. dprint deliberately excludes plugins/**/*.md. Do not reach for the formatter — match the surrounding fold width by hand. (The exclusion outlived its original reason, which was Eta reflowing folded scalars. It stays because formatting ~2000 authored prose files is a decision, not a side effect.) CLAUDE.md, readme.md and docs/ are formatted.
  2. plugins/*/stacks/*/*/config/ is excluded too, and for a different reason. That tier is payload — copied byte-for-byte into a target repo, where the dprint config bootstrap syncs there formats it. A payload file formatted here under a different config comes out different from what that config produces, and a freshly shaped repo fails its own first --all-files hook run on a file nobody touched. It has happened. The exclusion is on the directory, so a new payload file type cannot silently re-acquire the defect; when a payload file genuinely needs formatting, run bootstrap's config over it, never this repo's overrides.
  3. A dependency stays inside this marketplace. Add the name to dependencies in plugin.json with "marketplace": "virajp-plugins"; the marketplace entry is generated from it, and p:plugins:check asserts it resolves. Nothing here is url-sourced and nothing should be — the two outside dependencies vwf once had are vendored skills now, with provenance under plugins/vwf/vendor/.

Hooks

Hooks are authored directly as a plugin's hooks/hooks.json, in Claude's own format, with the scripts beside it. Three rules that bite every command hook, whichever plugin it belongs to:

  1. Scripts must be portable to macOS BSD sed — no \s, no \b.
  2. Plugin hooks are never written to settings.json. They are auto-discovered from hooks/hooks.json, so verify them with /hooks.
  3. A script's verdict shape is decided by its event, not by convention. hookSpecificOutput.permissionDecision is PreToolUse-only; Stop and PreCompact deny with the top-level decision/reason, and Claude rejects the whole verdict if a hookSpecificOutput arrives without a matching hookEventName — which reads exactly like a hook that decided to stay quiet.

A hooks module is the other kind: a Claude Code mod in TypeScript, listed under modules in the same hooks/hooks.json, beside the hooks block. vwf ships one, hooks/monitor/register.tsx, the /vwf:execute progress pane. Its $.state keys sit under the plugin's own name, because a ref's plugin must be the plugin that loads the module, and they are declared in a types contract (types/index.d.ts) the manifest's types key names. Its local gates are claude plugin test <plugin>, which runs only the *.test.ts(x) inside the plugin folder, and claude plugin validate --strict <plugin>, which p:plugins:check also runs when claude is on PATH. CI has no claude, so neither runs there. The three rules above are for command hooks; a module has no script, verdict or sed.

p:plugins:check's hook rule reads only the command hooks of a plugin's own hooks/hooks.json, but rule 11 covers a stackgen pack's payload scripts from the other end — exec bit and shebang, bash or sh only; nothing lints their bodies. The shebang set is narrower than a task's on purpose: a hook is wired into settings.json as a bare path, so only a shell the host can find on PATH will do. What the stackgen-plugin skill still owns is what those scripts are for. The host rules in full are stackgen's assets/artifact-doctrine.md §4.

The payload's other two halves have rules of their own. Rule 13 covers its prose: no citation a pack or /vwf:init lands may be plugin-relative, because the file is copied into a repo where no plugin is installed and the path resolves to nothing without a word. Rule 4 covers its frontmatter, parsing every pack skill and pack agent under stacks/*/*/, on the same strict terms as a plugin's own.

Skill scripts

A skill may ship a node script for the mechanical half of its work — vwf:plan-management's scripts/plan.mjs plus scripts/lib/, vwf:execute's scripts/log.mjs — and the judgement stays with the session. Bash or node only, never python. Rule 16 holds every skills/*/scripts/**/*.mjs: an entry directly under scripts/ starts #!/usr/bin/env node and is executable, and no file calls require( or imports anything but a node: built-in or a relative module, since the installed plugin ships no node_modules. No formatter and no shell gate read .mjs; its gate is a vitest suite under scripts/src/ that spawns the script in temp repos, as execute-log.test.ts does. Only the owning plugin may cite the script's path — rule 6 refuses ${CLAUDE_PLUGIN_ROOT} across plugins.

Rule 17 fails any mise use a plugin ships, dot directories included, unless the line forbids it (never or bare right before it, is never or — never right after). A tool is entered into the mise.toml of the conf.d/ folder that owns it first — zz-repo/ for a repo's own — then installed with mise install.

Comments in shipped config and task files

Anything under a pack's config/ or hooks/ lands in a repo as its own file, so its comments are read by every person who opens it. Keep only the comments something reads:

  • #MISE and #USAGE lines, and shebangs
  • # shellcheck directives
  • MARKED POSITION lines, plus whatever a filler needs beside one to find its value
  • grype ignore-reason comments, one unit with their entry
  • commented-out templates a skill fills in and uncomments
  • any comment the owning reference names as load-bearing — grep that reference before removing a comment

Beyond those, a comment is at most one line, and only as a warning where a reader would otherwise break something non-obvious. Every longer explanation goes: dropped when the owning reference already says it, moved into it when it does not — the pack's conventions.md for a payload. Boilerplate repeated across files goes; a directive it sat above stays. A comment-only payload change still bumps its pack. stackgen's assets/pack-format.md states the same rule for pack authors.

References

ReferenceCovers
structure.mdthe authored tree, plugin.json, versions, the marketplace and its traps
checks.mdwhat p:plugins:check asserts, rule by rule, and the technology-free guard

Artifact validity — frontmatter, invocation, hooks — is not in this table. It is stackgen's assets/artifact-doctrine.md, and it applies here too. Nor is the language-plugin contract that used to sit beside these: it is retired with the curated language plugins, and a language is a stackgen language-bundle kind now, per plugins/stackgen/assets/kinds.md.

Documentation

Any change to plugin behaviour must reconcile readme.md, CLAUDE.md and site/src/content/docs/plugins/<plugin>.md in the same commit — the repo's hard rule. Delegate the sweep to /vwf:docs-sync (its surveyor agent reads the docs) rather than reading those files inline; CLAUDE.md and the vwf manual are large enough that loading them costs the rest of the session.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.