agentleFS
AznatkoinyAznatkoiny/plugin-forge/skills/generation-standards/SKILL.md

generation-standards

Skill1 starsChanged 3 months agoMITView on GitHub

House style for every file plugin-forge emits into a generated plugin. Load before writing or reviewing any generated SKILL.md, agent, hooks.json, hook script, .mcp.json, plugin manifest, marketplace entry, README, or settings snippet — and before ship runs the freshness-guard sweep. Covers description discipline, invocation-control decisions, paired-path allowed-tools, persistent-state placement, hook output discipline, degrees-of-freedom mapping, and the deprecated-shape greps.

The file

Show its rules and text

We split this skill into the rules it gives an agent

14 rules. About 0 are common, 5 are distinctive, and 9 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈5

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 9

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Write description as trigger conditions only — when to invoke, never a summary of the workflow.

    line 18
  2. Distinctive:

    Put the key use case in the first 200 characters.

    line 21
  3. Distinctive:

    Write standing instructions (rules that hold every turn), not narration or one-time setup steps.

    line 38
  4. Very specific:

    Never use @-links to files (they force-load content into context).

    line 46
  5. Very specific:

    Every bundled script a generated skill runs gets an allowed-tools rule whose string is IDENTICAL to the invocation string in the body.

    line 55
  6. Distinctive:

    Write one line of rationale per skill into the PDR slot ledger.

    line 105
  7. Very specific:

    Every hook script: chmod +x, set -euo pipefail, read stdin exactly once with INPUT="$(cat)", parse JSON with python3 -c (stdlib), and take …

    line 115
  8. Very specific:

    Never the deprecated {"decision":"approve"} shape.

    line 125
  9. Very specific:

    Every generated hook ships a fixture test: echo '<recorded event JSON>' | script.sh with asserted exit code and output channel.

    line 137
  10. Distinctive:

    If a capability's enforcement column says "prose" but its failure cost is high, the composition is wrong: push it down a row (script + hook …

    line 151
  11. Very specific:

    Never scaffold a commands/ directory: it is the legacy form.

    line 167
  12. Very specific:

    Always set frontmatter name on plugin skills (it becomes the command's last segment.

    line 169
  13. Distinctive:

    Before packaging, run this exact block against the generated plugin directory.

    line 180
  14. Very specific:

    All templates carry {{PLACEHOLDER}} markers and a header note naming the filler (the build loop).

    line 228

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “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

Generation standards — plugin-forge house style

These rules govern every file the build loop writes into a target plugin and every file plugin-forge itself ships. Apply them at generation time: plugin skills cannot be fixed post-install via skillOverrides, and a stale shape emitted today is a silently dead component on the user's machine tomorrow. Fill from the templates in templates/ (index at the end of this file); the FRESHNESS GUARD greps below are the exact block ship runs before packaging.

1. Description discipline

  • Write description as trigger conditions only — when to invoke, never a summary of the workflow. A description that summarizes the steps makes Claude follow the description and skip the body (empirically observed by the superpowers CSO testing).
  • Put the key use case in the first 200 characters. The listing may be cut early under budget pressure; the front of the string does all the routing work.
  • description + when_to_use combined must be ≤ 1,536 characters — the listing truncates there. Include the natural keywords users actually type (error messages, symptoms, task nouns), not internal jargon.
  • The skill listing budget is ~1% of the context window shared across all installed skills. Every generated description competes with every other plugin on the user's machine: shorter is safer; only disable-model-invocation: true skills cost nothing in the listing (user-invocable: false skills still carry their full description — the flag only hides them from the / menu).
  • Third-person imperative everywhere: "Use when...", "Covers...", never "I will..." or "This skill helps you...".

2. SKILL.md size and structure

  • < 500 lines. The body is a recurring token cost; it persists for the whole session once invoked.
  • Write standing instructions (rules that hold every turn), not narration or one-time setup steps.
  • Critical guidance goes in the first 5,000 tokens: auto-compaction re-attaches only the first 5,000 tokens of each invoked skill, inside a 25,000-token combined budget, most-recently-invoked first. Tails of long skills vanish.
  • Bulk material goes to references/ (one level deep). SKILL.md must name each supporting file with what it contains and when to load it, e.g. - For the full grader taxonomy, see references/<topic>.md — load when choosing graders.
  • Never use @-links to files (they force-load content into context). Never put README/CHANGELOG inside a skill directory (plugin-level README is separate and required).
  • Scripts live in scripts/ under the skill and are executed, not loaded.

3. Paths and portability

Paired-path allowed-tools (the zero-prompt bundled-script pattern)

Every bundled script a generated skill runs gets an allowed-tools rule whose string is IDENTICAL to the invocation string in the body. Make the script executable with a shebang and invoke it by path (no python3 / bash prefix — a prefix breaks the rule match):

allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.py *)

and in the body:

${CLAUDE_SKILL_DIR}/scripts/render.py --input data.csv
  • Skill-local assets → ${CLAUDE_SKILL_DIR}/…. Plugin-level assets (hooks/, bin/, shared scripts/) → ${CLAUDE_PLUGIN_ROOT}/…. Never relative paths, never ../ escapes (the marketplace cache copy breaks them; symlinks outside the marketplace are skipped).
  • ${CLAUDE_SKILL_DIR} inside allowed-tools requires Claude Code ≥ 2.1.129 (older versions treat it as a literal string that never matches, so every run prompts). State the floor in the generated README.
  • allowed-tools grants last one turn — they clear on the user's next message even though skill content persists. Multi-turn workflows need a permissions.allow rule in the consumer settings snippet instead (see templates/settings-snippet.tmpl.json).

Persistent state → ${CLAUDE_PLUGIN_DATA}, never ROOT

  • ${CLAUDE_PLUGIN_ROOT} is the install directory: it changes on every update and old copies are garbage-collected (~14 days). Anything written there is lost.
  • Durable data — traces, run artifacts, caches, learned state — goes to ${CLAUDE_PLUGIN_DATA} (survives updates, created on first reference, removed on last-scope uninstall unless --keep-data).
  • Both are substituted in skill/agent content, hook and monitor commands, and MCP/LSP config fields, and exported as env vars to hook/MCP/LSP subprocesses.

4. Invocation control — decide per skill, record the rationale

SituationFrontmatterEffect
Side-effectful, user-gated workflow (deploy, publish, ship, commit)disable-model-invocation: trueOnly the user invokes. Description never enters context (costs nothing). ALSO blocks Skill-tool programmatic invocation, subagent preloading, and scheduled-task invocation.
Pure background knowledge (schemas, conventions, house style)user-invocable: falseHidden from the / menu; the model loads it on demand. Does NOT block Skill-tool access.
Conventions relevant only to certain filespaths: globsAuto-activation limited to matching files; cuts false triggers and listing cost.
Skill that must be BOTH user-callable and chainable by a conductorneither flag (default)Both can invoke. Guard misuse inside the body (state checks), not with invocation flags.
  • The D2 worked example (record it in every PDR): a conductor cannot Skill-tool invoke a skill marked disable-model-invocation: true. Pipelines that chain phase skills must leave those skills on default invocation and self-guard by reading their state file, reserving disable-model-invocation: true for the entry and exit points only. plugin-forge itself does exactly this: only forge and ship carry the flag.
  • Write one line of rationale per skill into the PDR slot ledger. "Default because nobody decided" is a finding, not a rationale.
  • Booleans are true/false only — yes/on/1 need ≥ 2.1.218 and silently degrade on older versions.

5. Hook output discipline

  • Exec-form commands only: "command": "${CLAUDE_PLUGIN_ROOT}/hooks/scripts/x.sh" (plus optional "args": []). ${user_config.*} is rejected in shell-form fields — pass it via exec-form args or read CLAUDE_PLUGIN_OPTION_<KEY> from the env.
  • Every hook script: chmod +x, set -euo pipefail, read stdin exactly once with INPUT="$(cat)", parse JSON with python3 -c (stdlib), and take a fast no-op path before reading stdin when the guard cannot apply (< 10ms exit 0).
  • Exit-code contract: exit 0 = no objection (it does NOT force-approve PreToolUse; normal permission flow continues). Exit 2 = block, with the reason on stderr (fed back to Claude). Any other exit code = action proceeds with a logged error.
  • Never mix exit 2 with JSON output. Stdout JSON is IGNORED when the script exits 2. Choose per hook: stderr-then-exit-2, OR exit 0 + structured JSON. Not both.
  • Per-event output schemas differ — emit the right one:
    • PreToolUse: {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow|deny|ask","permissionDecisionReason":"…"}}. Never the deprecated {"decision":"approve"} shape.
    • PostToolUse, Stop: top-level {"decision":"block","reason":"…"} (blocking only; exit 0 silent otherwise). Stop scripts must honor stop_hook_active to avoid loops.
    • UserPromptSubmit / SessionStart: stdout enters context; structured additions go in hookSpecificOutput.additionalContext (a top-level additionalContext key is silently ignored).
  • Matchers: event names are case-sensitive; tool matchers are regex (Edit|Write). Matchers and permission rules that reference a bundled MCP server's tools must use the plugin-scoped form mcp__plugin_<plugin>_<server>__<tool> — a bare mcp__<server>__<tool> never fires for bundled servers.
  • A malformed hooks/hooks.json prevents the ENTIRE plugin from loading. Validate the JSON before shipping.
  • Every generated hook ships a fixture test: echo '<recorded event JSON>' | script.sh with asserted exit code and output channel. Hooks are code; untested hooks are prose.

6. Degrees-of-freedom mapping

Match the freedom level of each capability to its fragility, and let it drive the whole composition (this is the C3/C5 backbone of the PDR):

FreedomUse forGenerateEnforce withGrade with
LowFragile/regulated operations: lab protocols, finance postings, destructive migrations, data-integrity rulesLocked, deterministic bundled scripts (stdlib-only), invoked via paired-path allowed-toolsPreToolUse/PostToolUse hooks (mechanical denial, not instructions)Deterministic graders: workspace/file state, exit codes, state_check; numeric checks REQUIRE a tolerance
MediumRepeatable procedures with judgment at the edgesStepwise checklists / pseudocode in skill bodiesProse + spot-check hooksTranscript graders (tool_called, budgets) + judges for the edges
HighJudgment work: analysis, synthesis, review, writingProse skills with principles and worked examplesProse (justify "prose" in the PDR C3 column)Calibrated LLM judges — one rubric dimension per judge call, Unknown verdict allowed

If a capability's enforcement column says "prose" but its failure cost is high, the composition is wrong: push it down a row (script + hook + deterministic grader).

7. Scripts, manifests, and layout

  • Python: stdlib only, #!/usr/bin/env python3, no third-party deps, no install step. Bash: #!/usr/bin/env bash, set -euo pipefail, shellcheck-clean.
  • Manifest (.claude-plugin/plugin.json): include "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", kebab-case name, full metadata. No version field during development — omitted means the git SHA is the version and every commit reaches users; a pinned semver that never gets bumped strands users on a stale cache. Pin semver at release only (ship's job).
  • Component directories (skills/, agents/, hooks/, .mcp.json, bin/, …) live at the plugin root, never inside .claude-plugin/ (only plugin.json goes there — the #1 documented mistake).
  • Never scaffold a commands/ directory: it is the legacy form. Every entry point is skills/<name>/SKILL.md.
  • Always set frontmatter name on plugin skills (it becomes the command's last segment; without it, marketplace installs can surface a version-string name).
  • Live-state skills (dynamic !-backtick injection headers) need a documented fallback for environments with disableSkillShellExecution — the injected line degrades to a policy notice, so the body must say what to do when the state read is absent.

8. FRESHNESS GUARD — the greps ship runs

Deprecated shapes rot silently: the host toolkit's own hook-builder emitted the old {"decision":"approve"} JSON and skill-forge referenced dead /mnt/skills paths. Before packaging, run this exact block against the generated plugin directory. Any FG hit is a blocking finding — fix the emitter, not just the output.

PLUGIN_DIR="${1:?usage: freshness-guard <generated-plugin-dir>}"
FAIL=0

# FG-1: deprecated hook decision JSON — modern PreToolUse uses
# hookSpecificOutput.permissionDecision, never {"decision":"approve"}.
grep -rnE '"decision"[[:space:]]*:[[:space:]]*"approve"' "$PLUGIN_DIR" && FAIL=1

# FG-2: stale sandbox doc paths — /mnt/skills does not exist in Claude Code.
grep -rn '/mnt/skills' "$PLUGIN_DIR" && FAIL=1

# FG-3: bare MCP tool names — bundled-server tools are
# mcp__plugin_<plugin>_<server>__<tool>; bare mcp__<server>__<tool> never fires.
grep -rnE 'mcp__[A-Za-z0-9_-]+__[A-Za-z0-9_-]+' "$PLUGIN_DIR" | grep -v 'mcp__plugin_' && FAIL=1

# FG-4: legacy commands/ scaffolding — entry points are skills/<name>/SKILL.md.
[ -d "$PLUGIN_DIR/commands" ] && { echo "$PLUGIN_DIR/commands: legacy commands/ directory"; FAIL=1; }
grep -rn '"commands"' "$PLUGIN_DIR/.claude-plugin/plugin.json" 2>/dev/null && FAIL=1

exit "$FAIL"

Reading the results:

  • FG-1 has no legitimate hits. {"decision":"block"} remains valid for PostToolUse/Stop and is deliberately NOT flagged.
  • FG-3 allows one reviewed exception: references to servers the plugin does not bundle (user-level or external MCP servers) legitimately use unscoped names. Confirm each hit is external before waiving it; note the pipeline can miss a bare name that shares a line with a scoped one, so scan multi-reference lines manually.
  • FG-4: "commands" in the manifest is only valid when deliberately remapping the legacy dir — for generated plugins, treat any hit as a failure.
  • Self-scans: this file necessarily contains the patterns it hunts. When running the guard over plugin-forge itself (dogfood lint), exclude skills/generation-standards/SKILL.md; generated plugins never contain it, so ship runs the block verbatim.

Advisory greps (warn, do not fail): grep -rn 'streamable-http' "$PLUGIN_DIR" ("streamable-http" is an accepted alias for "http"; prefer the canonical "type": "http" in generated .mcp.json for consistency), and grep -rn 'dangerously-skip-permissions' "$PLUGIN_DIR" (generated harnesses use a sandbox settings profile instead — see templates/settings-snippet.tmpl.json).

9. Templates index

All templates carry {{PLACEHOLDER}} markers and a header note naming the filler (the build loop). JSON templates hold their notes in "//" keys — delete every "//" key and template comment after filling. Load a template only when emitting that file type.

TemplateEmitsLoad when
templates/skill.tmpl.mdskills/<name>/SKILL.mdWriting any generated skill
templates/agent.tmpl.mdagents/<name>.mdWriting any generated subagent
templates/hooks.tmpl.jsonhooks/hooks.jsonWiring generated hook events
templates/hook-script.tmpl.shhooks/scripts/<name>.shWriting any hook handler
templates/mcp.tmpl.json.mcp.jsonBundling an MCP server
templates/plugin-manifest.tmpl.json.claude-plugin/plugin.jsonCreating the manifest
templates/marketplace-entry.tmpl.jsonentry in marketplace.json plugins[]Registering in a marketplace
templates/readme.tmpl.mdplugin README.mdWriting the plugin's README (ship phase)
templates/settings-snippet.tmpl.jsonconsumer .claude/settings.json snippetEmitting the team-distribution / permission story

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.