Skip to main content

generation-standards

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.

Aller à l'installation

Informations de source

Dépôt
Aznatkoiny/claude-dev-toolkit
Dernière activité de la source
2 août 2026 à 20:26
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
10 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
generation-standards
description
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.
user-invocable
false
# 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): ```yaml 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 | Situation | Frontmatter | Effect | |---|---|---| | Side-effectful, user-gated workflow (deploy, publish, ship, commit) | `disable-model-invocation: true` | Only 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: false` | Hidden from the `/` menu; the model loads it on demand. Does NOT block Skill-tool access. | | Conventions relevant only to certain files | `paths:` globs | Auto-activation limited to matching files; cuts false triggers and listing cost. | | Skill that must be BOTH user-callable and chainable by a conductor | neither 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): | Freedom | Use for | Generate | Enforce with | Grade with | |---|---|---|---|---| | **Low** | Fragile/regulated operations: lab protocols, finance postings, destructive migrations, data-integrity rules | Locked, deterministic bundled scripts (stdlib-only), invoked via paired-path allowed-tools | PreToolUse/PostToolUse hooks (mechanical denial, not instructions) | Deterministic graders: workspace/file state, exit codes, `state_check`; numeric checks REQUIRE a `tolerance` | | **Medium** | Repeatable procedures with judgment at the edges | Stepwise checklists / pseudocode in skill bodies | Prose + spot-check hooks | Transcript graders (tool_called, budgets) + judges for the edges | | **High** | Judgment work: analysis, synthesis, review, writing | Prose skills with principles and worked examples | Prose (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. ```bash 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. | Template | Emits | Load when | |---|---|---| | [templates/skill.tmpl.md](templates/skill.tmpl.md) | `skills/<name>/SKILL.md` | Writing any generated skill | | [templates/agent.tmpl.md](templates/agent.tmpl.md) | `agents/<name>.md` | Writing any generated subagent | | [templates/hooks.tmpl.json](templates/hooks.tmpl.json) | `hooks/hooks.json` | Wiring generated hook events | | [templates/hook-script.tmpl.sh](templates/hook-script.tmpl.sh) | `hooks/scripts/<name>.sh` | Writing any hook handler | | [templates/mcp.tmpl.json](templates/mcp.tmpl.json) | `.mcp.json` | Bundling an MCP server | | [templates/plugin-manifest.tmpl.json](templates/plugin-manifest.tmpl.json) | `.claude-plugin/plugin.json` | Creating the manifest | | [templates/marketplace-entry.tmpl.json](templates/marketplace-entry.tmpl.json) | entry in `marketplace.json` `plugins[]` | Registering in a marketplace | | [templates/readme.tmpl.md](templates/readme.tmpl.md) | plugin `README.md` | Writing the plugin's README (ship phase) | | [templates/settings-snippet.tmpl.json](templates/settings-snippet.tmpl.json) | consumer `.claude/settings.json` snippet | Emitting the team-distribution / permission story |
Voir sur GitHub