配布用の zip を作って、中身を unzip -l で何気なく眺めていたとき、見覚えのあるファイル名が目に入って指が止まった。Pro 版のライセンスキーを生成する、開発のときにしか使わないツールだった。配布物には、絶対に入ってはいけないものだ。もしこれがそのままユーザーの手に渡っていたら、ライセンス生成の仕組みや内部の秘密情報を解析され、有効な Pro ライセンスを作られるおそれがあった。
間に合った、と言いたいところだけれど、正しくは「たまたま目に入った」だ。除外し忘れていただけで、気づかなければそのまま出していた。その「たまたま」が後から効いてきて、リリースのたびに同じ作業を手でやって、疲れているときに一個飛ばす自分が怖くなった。
それで、リリース前の手順を Claude Code の skill にして、毎回機械的に同じチェックを通すことにした。skills が流行っているから乗った、というより、自分の地味なミスを二度とやらないための仕組みが欲しかった、という順番だ。この記事は、そのとき作った SKILL.md を実物で共有する。動作は Claude Code(2026年5月時点)、macOS、自作プラグインのリポジトリで確認している。
skill とは何か(最小限)
知っている人は読み飛ばしてほしい。Claude Code の skill は、SKILL.md を置いたフォルダだ。SKILL.md は --- で囲った YAML フロントマターと、その下の Markdown 本文でできている。Claude Code ではフロントマターの項目はすべて任意で、推奨されるのは description だけ。name は省略するとディレクトリ名が使われる。Claude が skill を自動で使うかどうかは、主にこの description で判断される(省略すると本文の最初の段落が使われる)。トリガーになりそうな言い回しや利用例は、任意の when_to_use に足せる。だから description(と when_to_use)には、自分が実際に打ちそうな言葉を入れておくと、必要なときに自動で読み込まれる。
呼び出しは、/skill名 と明示する方法と、頼んだ内容が description に合致して自動で読み込まれる方法の二通り。個人用に ~/.claude/skills/ に置けば全プロジェクトで使えるし、リポジトリの .claude/skills/ に置けばそのプロジェクト限定になる。仕様の詳しいところは公式(Claude Code Docs の skills ページ)に譲る。
毎回プロンプトに書くのでは足りなかった
最初は、リリースのたびにチェック項目をプロンプトに書いていた。でもプロンプトは終われば消える。次のリリースでまた書くし、書くのが面倒だと省略する。省略したときに限って、今回の事件のような取りこぼしが起きる。
SKILL.md はリポジトリに残る。一度ちゃんと書いておけば、次の自分が同じ手順をそのまま踏める。手順書を頭の中に置くのをやめて、ファイルに固定した、という感覚に近い。
実際の SKILL.md
私のリリース手順は4ステップだ。それをそのまま skill の手順に落とした。.claude/skills/release-build/SKILL.md:
---
name: release-build
description: プラグインのリリース前作業を補助する。readme.txtとメインPHPのバージョン更新、readme.txtの変更履歴の追記、配布用zipのビルドを行う。
disable-model-invocation: true
allowed-tools:
- Read
- Edit
- Bash(git status *)
- Bash(git log *)
- Bash(scripts/build-zip.sh *)
- Bash(unzip -l *)
---
# リリース前ビルド補助
リリース時の手順を順番に、抜けなく踏むための skill。
バージョンと変更履歴は確認のうえ書き換えてよいが、
コミット/プッシュは「提案」までに留め、実行しない。
## 注意
`allowed-tools` はここに挙げたツールを「確認なしで使ってよい」と事前承認するもので、
使えるツールの範囲を制限するものではない。
commit / push は実行せず、コマンド案の提示までに留めること。
## 手順
1. バージョン番号を上げる(2か所)
- `readme.txt` の `Stable tag:`
- メインPHPファイルのヘッダの `Version:`
両方の現在値を読み出して提示し、新しい番号を確認してから、
この2か所を一致させて書き換える。
2. `readme.txt` の変更履歴を更新する
`git log <前回のバージョン>..HEAD` を要約し、`== Changelog ==` に
新バージョンのエントリ下書きを出す。文面を確認してから書き込む。
3. コミットしてプッシュする(提案のみ)
`git status` で対象を確認し、コミットメッセージ案(例: `Release vX.Y.Z`)と、
実行すべき `git commit` / `git push` コマンドを「提案」として提示する。
実際の commit / push はここでは実行しない。ユーザーが自分の手で行う。
4. 配布用zipを作る(Pro版・Free版それぞれ)
`scripts/build-zip.sh <スラッグ>` を、Pro版・Free版のスラッグそれぞれで実行する。
ビルド後は必ず `unzip -l` の出力を見せ、配布してはいけないものが
入っていないことを確認する。特に、開発専用のツール類が混ざっていないか。
最終チェックは人間が目視で行う。
ここで注意したいのが allowed-tools だ。これは「Claude が使えるツールを絞る」設定ではなく、挙げたツールを確認なしで使ってよい、と事前承認する設定になっている。リストに無いツールも呼び出し自体は可能で、その場合は通常の権限設定に従う。だから本当に使わせたくない操作は、allowed-tools から外すだけでは塞げず、権限設定の deny ルールや disallowed-tools で別途止める必要がある。今回は Bash 全体を白紙委任せず、git status や git log、ビルドスクリプト、unzip -l のように使うコマンドだけを絞って事前承認している。そして取り返しのつかない commit / push は、allowed-tools の話とは別に、本文の指示で「提案のみ・実行しない」と明示して止めている。
ビルドスクリプト
ステップ4の実体はシェルスクリプトにして、skill から呼ぶ。私は zip -x で除外するやり方を使っているので、それに合わせている。scripts/build-zip.sh:
#!/usr/bin/env bash
set -euo pipefail
# 使い方: ./scripts/build-zip.sh <スラッグ> [バージョン]
SLUG="${1:?スラッグを指定してください}"
VERSION="${2:-$(grep -m1 '^Stable tag:' "${SLUG}/readme.txt" | sed 's/.*:[[:space:]]*//')}"
OUT="${SLUG}-${VERSION}.zip"
rm -f "${OUT}"
# プラグインフォルダごと固め、配布に含めないものを -x で除外する
# -X は macOS の拡張属性を付けない指定
zip -rX "${OUT}" "${SLUG}/" \
-x "*/.git/*" \
"*/.github/*" \
"*/.ci/*" \
"*/.claude/*" \
"*/docs/*" \
"*/tools/*" \
"*/__MACOSX/*" \
"*.DS_Store" \
"*/README.md" \
"*/CLAUDE.md"
echo "built: ${OUT}"
unzip -l "${OUT}" # 中身を一覧表示。最後に人間が混入チェック
*/docs/* と */tools/* が、今回の事件の再発防止だ。開発でしか使わないツール(私の場合はライセンス生成ツール)を tools/ に、ドキュメントを docs/ のような配布しないフォルダにまとめて置き、丸ごと除外する。README.md(GitHub 用)は落とすが readme.txt(WordPress.org 用)は残す、という取り違えにも注意する。
このスクリプトはいくつか前提を置いている。カレントディレクトリ直下に <スラッグ>/readme.txt がある構成を想定しているので、リポジトリのルート自体がプラグイン本体ならパスを直す必要がある。Stable tag: が trunk 運用のリポジトリではバージョン名として使えないので、その場合は第2引数でバージョンを明示する。ステップ2で使う git log <前回のバージョン>..HEAD も、その名前の Git タグが存在する前提で、タグが v1.2.3 形式か 1.2.3 形式かはリポジトリの運用に合わせて確認する。
置き場所と、手動起動に倒すまで
このリリースチェックはプラグインごとに微妙に違うので、個人用ではなくリポジトリの .claude/skills/ に置いている。
最初は、description に「リリース」「バージョンを上げる」「配布zipを作る」といった言葉を入れて、自動で拾われるように調整していた。ただ、実際に使ってみると、この skill は readme.txt やメインPHPのバージョンを書き換える。つまり参照用ではなく、副作用のあるワークフローだ。Claude Code 公式でも、commit / deploy 系のようにタイミングを人間が制御したい skill には disable-model-invocation: true が推奨されている。
そこで最終的には、自動ロードではなく /release-build と明示して起動する形にした。少し手間は増えるが、リリース作業だけは「Claude が気を利かせて始める」のではなく、自分が明示的に引き金を引くほうが安心だった。disable-model-invocation: true を付けても description が無駄になるわけではなく、/ の一覧や人間が読むときの説明としては残る。ただ、Claude の自動判断には使われなくなる。
ハマったところと、そこから考えたこと
この skill を作って一番はっきりしたのは、zip -x が「名前を挙げたものしか除外できない」という当たり前の事実だった。今回ヒヤッとしたのは、除外リストに無い新しいファイルが増えていたからで、リストに書いてある限りは安全、という前提そのものが崩れていた。-x のブラックリストは、自分が予想できたものしか守ってくれない。
だから skill にしても、最後の unzip -l の目視だけは省けない。むしろ skill の本当の値打ちは、その目視を毎回必ず通すところにある、と今は思っている。理屈の上では、ホワイトリスト方式(配布してよいファイルだけを明示して固める)にすれば、未知のファイルは構造的に入らない。次はそちらに寄せたいと考えているが、まだ手元の運用は zip -x のままなので、ここは断定せず、目視を最後の砦として残している。
SKILL.md を太らせすぎないことも途中で学んだ。手順の詳しい背景まで全部書くと長くなるので、本体は短く保ち、込み入った補足は別ファイルに逃がして、必要なときだけ読ませる形にした。
どこまで任せるか
バージョンの書き換えや変更履歴の下書きは skill に任せている。けれど、コミットとプッシュは提案までにして、実行は自分の手でやる。取り返しのつかない操作だけは、一拍置いて人間が引き金を引く、という線引きだ。AI エージェントにどこまで実行を許すか、未信頼の入力をどう扱うか、という重たい話は別の記事にまとめたので、ここでは深入りしない。
まとめ
派手な AI 活用ではなく、毎回やる地味な定常作業を SKILL.md に固定する、という使い方が、自分には一番効いた。きっかけは、配布物にライセンス生成ツールを混ぜかけたヒヤッとした一件だった。手順を仕組みに落としたいま、同じミスは起きにくくなったし、未来の自分が同じ手順をそのまま踏める。
プラグインの配布まわりでは、WordPress.org への SVN 公開作業の記録 や、審査で差し戻された全記録 を本家ブログに書いている。この記事を読んで、自分の配布zipの中身を一度 unzip -l で確かめてみた人がいたら、それでこの文章の役目は果たせている。