Skip to main content

ship

Package and release a verified forge-built plugin: claude plugin validate --strict, frontmatter/scoped-name/freshness lints, README with the permission story, versioning decision (SHA vs pinned semver), distribution menu (skills-dir, private marketplace, community), and installing the trace-capture flywheel. Use when the user says ship, release, publish, or distribute the plugin.

설치로 이동

소스 정보

저장소
Aznatkoiny/claude-dev-toolkit
최근 소스 활동
2026년 8월 2일 20:26
감지된 SKILL.md 언어
영어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
ship
description
Package and release a verified forge-built plugin: claude plugin validate --strict, frontmatter/scoped-name/freshness lints, README with the permission story, versioning decision (SHA vs pinned semver), distribution menu (skills-dir, private marketplace, community), and installing the trace-capture flywheel. Use when the user says ship, release, publish, or distribute the plugin.
when_to_use
Only when .forge/state.json phase is ship (verification green). User-gated release: the user invokes /plugin-forge:ship; the model never auto-invokes it.
argument-hint
[skills-dir|marketplace|community]
disable-model-invocation
true
allowed-tools
Bash(claude plugin *), Bash(grep *), Bash(python3 *), Bash(mkdir *), Bash(cp *), Bash(chmod +x *)
# Ship — validate, lint, document, version, distribute Current forge state: !`cat .forge/state.json 2>/dev/null || echo NO_STATE` If the line above shows a policy notice instead of JSON (org setting `disableSkillShellExecution`), read `.forge/state.json` with the Read tool before continuing. ## Phase gate (hard) Proceed only when `phase` is `ship` — verification must already be green. For any other state, run NOTHING and reply with exactly the remediation below, then stop: | Injected state | Reply and stop | |---|---| | NO_STATE | "No forge run in this project. Run `/plugin-forge:forge` to start one." | | `interview` | "Interview incomplete. Run `/plugin-forge:forge`." | | `contract` | "No approved contract yet. Run `/plugin-forge:write-contract`." | | `evals` | "Suite not armed. Run `/plugin-forge:build-evals`, then `/plugin-forge:arm-evals`." | | `armed` | "Nothing built yet. Run `/plugin-forge:build-loop`." | | `building` | "Build loop has not gone green. Run `/plugin-forge:build-loop`." | | `smoke` or `verify` | "Verification is not complete. Run `/plugin-forge:verify`." | Inputs to read before Step 1: `plugin_name` and `plugin_dir` from the injected state; `design/CONTRACT.md` (C6 distribution decisions, runtime answers — cloud/routine or local, version floors, permission story, cost sheet); the latest `runs/<ts>/verify-report.md` (baseline numbers for the README); and `.forge/freeze.json` (suite version). ## Step 1 — Static gates (all four pass before anything is written) **1a. Strict validation** — the same check the community pipeline runs: ``` claude plugin validate --strict <plugin_dir> ``` **1b. Frontmatter YAML lint** on every generated SKILL.md. Malformed YAML fails silently: the body loads with no metadata, so the skill never auto-triggers. Run: ``` python3 - <plugin_dir> <<'EOF' import sys, re, pathlib root, bad = pathlib.Path(sys.argv[1]), [] files = sorted(root.glob('skills/**/SKILL.md')) + sorted(root.glob('SKILL.md')) for f in files: t = f.read_text(encoding='utf-8') m = re.match(r'\A---\r?\n(.*?)\r?\n---\r?\n', t, re.S) if not m: bad.append(f'{f}: frontmatter fence missing or unclosed'); continue fm = m.group(1) tops = [(k.group(1), k.start()) for k in re.finditer(r'^([A-Za-z][\w-]*):', fm, re.M)] names = [k for k, _ in tops] for req in ('name', 'description'): if req not in names: bad.append(f'{f}: missing required key {req}:') if '\t' in fm: bad.append(f'{f}: literal tab in frontmatter (YAML hazard)') for b in re.finditer(r':\s*(yes|no|on|off)\s*$', fm, re.M | re.I): bad.append(f'{f}: boolean "{b.group(1)}" needs >=2.1.218 - use true/false') def block(key): d = dict(tops) if key not in d: return '' starts = sorted(s for _, s in tops); i = starts.index(d[key]) return fm[d[key]:starts[i+1]] if i + 1 < len(starts) else fm[d[key]:] if len(block('description')) + len(block('when_to_use')) > 1536: bad.append(f'{f}: description+when_to_use exceed the 1536-char listing cut') for b in bad: print('FAIL', b) print('frontmatter lint:', 'FAIL' if bad else 'PASS') sys.exit(1 if bad else 0) EOF ``` **1c. Scoped-name lint** — bundled-MCP references using a bare server name never fire; they must be `mcp__plugin_<plugin>_<server>__<tool>` (and `plugin:<plugin>:<server>` in `mcp_tool` hook `server` fields): ``` grep -rnE 'mcp__' <plugin_dir> --include='*.md' --include='*.json' | grep -v 'mcp__plugin_' ``` Every hit in hook matchers, permission rules, agent tools lists, or skill allowed-tools is a FAIL — unless it names an EXTERNAL server the plugin does not bundle (check against the plugin's `.mcp.json` before failing). **1d. Freshness lint** — deprecated shapes must not ship: ``` grep -rnE '"decision"[[:space:]]*:[[:space:]]*"(approve|block)"' <plugin_dir> grep -rn '/mnt/skills' <plugin_dir> ``` Both greps must return nothing. The decision-JSON hook shape is deprecated (current discipline: stderr + exit 2 to block, or `hookSpecificOutput`); `/mnt/skills` paths are foreign-environment rot that never resolves in Claude Code. Fix at the source, never by weakening the lint. ## Step 2 — Install the flywheel (trace capture) ``` mkdir -p <plugin_dir>/hooks/scripts cp "${CLAUDE_PLUGIN_ROOT}/templates/hooks/trace-capture.sh" <plugin_dir>/hooks/scripts/trace-capture.sh chmod +x <plugin_dir>/hooks/scripts/trace-capture.sh ``` Merge `${CLAUDE_PLUGIN_ROOT}/templates/hooks/hooks-snippet.json` into `<plugin_dir>/hooks/hooks.json`: create the file from the snippet when the plugin ships no hooks yet; otherwise append the snippet's PostToolUse entry to the existing array — never clobber existing entries. Confirm the merged entry is exec-form and references `"${CLAUDE_PLUGIN_ROOT}/hooks/scripts/trace-capture.sh"` — at the generated plugin's runtime that variable resolves to ITS root, not to plugin-forge's. Why this ships in every generated plugin: the hook appends tool args/results/errors to `${CLAUDE_PLUGIN_DATA}/traces/*.jsonl`, so production failures become eval tasks — `/plugin-forge:build-evals mine` converts traces to tasks, `/plugin-forge:arm-evals` re-freezes vN+1, and `/plugin-forge:build-loop` rebuilds until green. Document the loop in the README (Step 4). ## Step 3 — Optional observability (fleet/long-job plugins) Offer when the generated plugin dispatches subagent fleets or long jobs; otherwise skip. - **subagentStatusLine** — a plugin `settings.json` supports ONLY the `agent` and `subagentStatusLine` keys: ```json {"subagentStatusLine": {"type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/status.sh"}} ``` with the script reading a `${CLAUDE_PLUGIN_DATA}/status.json` the plugin's long-running jobs update. - **Monitor** — pair each long-job skill with `monitors/monitors.json`: ```json [{"name": "<job>-watch", "command": "tail -F <log path>", "description": "streams <job> progress into the session", "when": "on-skill-invoke:<skill-name>"}] ``` State the caveats wherever this is offered: monitors are experimental, interactive-CLI only, do not load from project-scope skills-dir installs, reject `${user_config.*}` in commands, and need a session restart after plugin updates. ## Step 4 — README for the generated plugin Instantiate `${CLAUDE_PLUGIN_ROOT}/skills/generation-standards/templates/readme.tmpl.md` into `<plugin_dir>/README.md`. Required sections: 1. **Overview + invocations** — what the plugin does; every user-facing skill as `/<plugin_name>:<skill>` with its argument hint. 2. **Permission story** — a `permissions.allow` list covering EVERY command the plugin's skills and agents run. allowed-tools grants are turn-scoped (cleared on the next user message), so long flows need standing allows in the consumer's `.claude/settings.json`: `{"permissions": {"allow": ["Bash(<cmd> *)", "..."]}}`. 3. **Minimum Claude Code version** — the highest floor among features used (from CONTRACT C6). Known floors: `${CLAUDE_SKILL_DIR}` in allowed-tools 2.1.129; `defaultEnabled` 2.1.154; marketplace `renames` 2.1.193; `--json-schema` hard errors / LSP `restartOnCrash` 2.1.205; worktree resume 2.1.212; frontmatter `name` segment override 2.1.216; yes/no boolean forms and background-fork default 2.1.218. 4. **Headless recipe** — the `claude -p` invocation for CI/routines, using the suite's sandbox settings profile (never `--dangerously-skip-permissions`), plus `CLAUDE_CODE_PLUGIN_SEED_DIR` for network-free container loading. 5. **How to run your evals** — the `evals/` layout; `evals/bin/run.py` is vendored and stdlib-only, so the suite runs WITHOUT plugin-forge installed; red baseline via the no-plugin target; the final `FORGE_EVAL:` line and exit code as the CI signal; run the regression suite in CI; cost honesty (`--max-cost-usd`); current baseline for future battle A/B: suite version from `.forge/freeze.json`, pass^3 and cost from `verify-report.md`. 6. **Flywheel** — traces land in `${CLAUDE_PLUGIN_DATA}/traces/`; feed failures back with `/plugin-forge:build-evals mine`. ## Step 5 — Versioning decision (ask the user) Present exactly two options via AskUserQuestion: - **Still iterating** — OMIT `version` from plugin.json. The git SHA becomes the version: every pushed commit reaches users. - **Releasing** — pin semver in plugin.json, add a CHANGELOG entry, and run `claude plugin tag --push` (creates the `<name>--v<version>` tag that dependency constraints resolve against). Explain the trap before they choose: a pinned semver means users update ONLY when the field is bumped — pushing commits under a stale pin does nothing ("already at latest version"). A plugin pinned at 1.0.0 and then iterated strands every installed user on the first build. Never pin without a release process that bumps it. Also state: the plugin `name` is a permanent public identifier (the marketplace `renames` map is the only migration path, 2.1.193+); `displayName` is the safe-to-change label. ## Step 6 — Final re-validation Steps 2–4 added files. Re-run on the FINAL tree: ``` claude plugin validate --strict <plugin_dir> ``` and repeat the Step 1c/1d greps over the added files. Ship nothing that is not green here. ## Step 7 — Distribution menu Ask via AskUserQuestion unless `$ARGUMENTS` pre-selects a path: **A. skills-dir plugin (lightest)** — copy `<plugin_dir>` (with `.claude-plugin/plugin.json`) to `~/.claude/skills/<plugin_name>/` (personal) or `<repo>/.claude/skills/<plugin_name>/` (project). It loads as `<plugin_name>@skills-dir` next session; SKILL.md edits are live, hooks/MCP changes need `/reload-plugins`. Warn: project scope is trust-gated, does NOT walk up to the repo root (launch Claude from the root), and monitors do not load. **B. Private marketplace** — add or extend `.claude-plugin/marketplace.json` in the hosting repo: ```json {"name": "<marketplace-name>", "owner": {"name": "<owner>"}, "plugins": [{"name": "<plugin_name>", "source": "./<relative-path>", "description": "<one line>", "category": "<category>"}]} ``` Validate the marketplace repo with `claude plugin validate .`. Consumers run: ``` claude plugin marketplace add <org>/<repo> claude plugin install <plugin_name>@<marketplace-name> ``` Maintenance: `claude plugin marketplace update <marketplace-name>`; inspection: `claude plugin marketplace list --json`. Warn: marketplace names collide globally per user (a second add replaces the first) and several names are reserved — pick something distinctive. **C. Community submission** — pre-submission requirements: kebab-case plugin name (the claude.ai marketplace sync REJECTS non-kebab names); `claude plugin validate --strict` clean (the community pipeline runs the same check as Step 1a); README and LICENSE present; semver pinned and tagged via `claude plugin tag --push`; public repo. Then submit the repo through the community marketplace submission form, with the marketplace-entry fields (name, source, description, category, tags) prepared to paste. ## Step 8 — Repo-declared settings (cloud/routine contracts) If CONTRACT.md says the plugin runs in cloud sessions or scheduled routines, personal installs are invisible there (cloud sessions never read `~/.claude/skills`) — the consuming repo must declare the plugin. Instantiate `${CLAUDE_PLUGIN_ROOT}/skills/generation-standards/templates/settings-snippet.tmpl.json` into the consuming repo's `.claude/settings.json`: ```json {"extraKnownMarketplaces": {"<marketplace-name>": {"source": {"source": "github", "repo": "<org>/<repo>"}}}, "enabledPlugins": {"<plugin_name>@<marketplace-name>": true}} ``` Teammates get a one-time trust prompt, then auto-install. When the contract includes long headless runs, fold the Step 4 `permissions.allow` block into the same snippet. ## Step 9 — Wrap up Report to the user: - Gates re-passed on the final tree (Step 6). - Files added: `hooks/scripts/trace-capture.sh`, the hooks.json entry, `README.md`, plus `settings.json`/`monitors/monitors.json` if chosen. - Version strategy chosen and why; distribution path with the exact consumer commands; settings snippet emitted (or why not). - Baseline carried into the README: suite vN, pass^3, cost, run timestamp. - The honest claim: the plugin is "green + holdout + triaged" against suite vN — not proven "correct". Leave `.forge/` in place: it is the flywheel's memory (freeze hash, change requests, run history). Phase stays `ship`. To start a fresh forge run in this project later, `forge-eval doctor` clears stuck state safely.
GitHub에서 보기