Claude Code Skillsは、AIコーディングアシスタントに「こう動いてほしい」を再利用可能なワークフローとして定義する仕組みです。一度書けばプロジェクトをまたいで呼び出せる......はずなのですが。
Skillsを20個ほど書いた頃、ふと気づきます。プロジェクトAで作ったスキルをプロジェクトBにコピペしている自分に。GA4のプロパティIDを手で書き換え、出力先ディレクトリを修正し、SNSの投稿設定を調整して......。
それ、自動化ツールを手動で移植してどうする。
この記事では、70以上のSkillsを5つのプロジェクトで共有している私たちが、「一度書いたら全プロジェクトで動く」ポータブル設計をどう実現したかを解説します。実際のコードはGitHubリポジトリで公開しています。
この記事で学べること
skill-config.jsonによる3層コンフィグマージの設計と実装_lib/共有ライブラリで重複コードをゼロにするパターン- 外部スキルのsymlink統合と
.gitignore自動管理 - プロジェクト固有設定とグローバル設定の使い分け
前提
Skillsの設計思想と自作ガイドと設計パターン上級編の内容を前提としています。
ざっくり言うと、1つのSkillは3層で構成されます。SKILL.md(Claudeへの指示書)、scripts/(実際に実行されるBash/Pythonスクリプト)、references/(プロンプトや設定の参照ファイル)。この3層構造と、JSON契約パターンが頭に入っていれば大丈夫です。
問題:スキルが「プロジェクト専用」になる
最初のプロジェクトでSkillsを書くと、コード内にこういう値がベタ書きされます:
PROPERTY_ID="123456789" # GA4のプロパティID
OUTPUT_DIR="./claudedocs" # レポート出力先
SITE="sc-domain:example.com" # GSCのサイトURL
2つ目のプロジェクトでも同じスキルを使いたい。さて、どうする。
選択肢は3つ:
| 方法 | 結果 |
|---|---|
| コピペして値を書き換え | スキルが2つに分裂。片方だけバグ修正される未来が見える |
| 環境変数で渡す | .env が50行超えて「どれがどのスキル用?」状態に |
| 設定ファイルで外部化 | 一箇所を変えるだけ。スキル本体は共通のまま |
3つ目を体系化したのが skill-config.json です。
設計1:3層コンフィグマージ
マージの優先順位
スキルが設定を読むとき、3つの層がdeep mergeされます:
スキル内蔵デフォルト ← グローバル設定 ← プロジェクト設定
(最低優先) (ユーザー共通) (最優先)
具体的なファイル配置:
| 層 | パス | 用途 |
|---|---|---|
| グローバル | ~/.config/skills/config.json | 全プロジェクト共通の設定(タイムゾーン、言語など) |
| プロジェクト | <project>/skill-config.json | プロジェクト固有の設定(API ID、出力先など) |
| デフォルト | スクリプト内の初期値 | 未設定時のフォールバック |
実装:Bash版
共有ライブラリの load_skill_config 関数がマージを担います:
# 使う側は1行で完結
source "$SKILLS_DIR/_lib/common.sh"
config=$(load_skill_config "ga-analyzer")
# configには グローバル + プロジェクト のマージ済みJSONが入る
property_id=$(echo "$config" | jq -r '.property_id')
内部では jq の * 演算子でdeep mergeしています:
# グローバルとプロジェクトをマージ(プロジェクトが勝つ)
echo "$global_cfg" | jq --argjson proj "$project_cfg" '. * $proj'
実装:Python版
Python製スキルも同じインターフェースで設定を読めます:
from _lib.config import load_skill_config
config = load_skill_config("ga-analyzer")
property_id = config.get("property_id")
Bash版とPython版で同じconfig.jsonを読むので、設定の二重管理が発生しません。地味ですが、スキルが増えるほど効いてきます(20個超えたあたりで「書いておいてよかった」と思う瞬間が来ます)。
マージ動作の具体例
グローバルで「SNSはX投稿をデフォルトON」にしつつ、プロジェクトBでは「LinkedInをOFF」にしたい場合:
// ~/.config/skills/config.json(グローバル)
{
"sns-announce": {
"default_lang": "ja",
"platforms": {
"x": { "enabled": true },
"linkedin": { "enabled": true }
}
}
}
// projectB/skill-config.json(プロジェクト)
{
"sns-announce": {
"base_url": "https://projectb.example.com",
"platforms": {
"linkedin": { "enabled": false }
}
}
}
マージ結果:
{
"sns-announce": {
"default_lang": "ja", // グローバルから継承
"base_url": "https://projectb.example.com", // プロジェクトで追加
"platforms": {
"x": { "enabled": true }, // グローバルから継承
"linkedin": { "enabled": false } // プロジェクトで上書き
}
}
}
触ったキーだけが上書きされる。全体を再定義する必要がないのがdeep mergeの利点です。
設計2:_lib/ 共有ライブラリ
「同じ関数を5回書く」問題
SNS投稿スキル、GA分析スキル、SEO戦略スキル......それぞれのスクリプトに load_skill_config をコピペしていたら、バグ修正が5ファイルに波及します。
そこで _lib/ ディレクトリに共有コードを集約します:
skills/
├── _lib/
│ ├── common.sh # Bash共通関数
│ ├── config.py # Python設定ローダー
│ ├── schemas/ # JSON Schema定義
│ ├── scripts/ # 共通ユーティリティ
│ ├── templates/ # テンプレート
│ └── infra/ # リポジトリ基盤管理
├── ga-analyzer/
│ └── scripts/
│ └── analyze.sh # source "$SKILLS_DIR/_lib/common.sh"
├── gsc/
└── sns-announce/
common.sh の中身
common.sh が提供する関数群は大きく4カテゴリ:
| カテゴリ | 主な関数 | やること |
|---|---|---|
| JSON操作 | json_escape, json_str, json_array | 文字列を安全にJSON化 |
| エラー処理 | die_json, err, warn | 構造化エラー出力 |
| 要件チェック | require_cmd, require_gh_auth | コマンド・認証の存在確認 |
| Git操作 | git_root, git_current_branch | リポジトリ情報の取得 |
各スキルのスクリプトは先頭で1行 source するだけ:
#!/usr/bin/env bash
source "$(dirname "$0")/../../_lib/common.sh"
require_cmd jq
require_gh_auth
config=$(load_skill_config "my-skill")
これで設定読み込み、エラー処理、コマンドチェックが自動的に使えます。新しいスキルを作るときの「まずボイラープレートを......」が消えるのが大きい。
二重ロード防止
common.sh の先頭にガードがあります:
[[ -n "${_SKILL_COMMON_LOADED:-}" ]] && return 0
_SKILL_COMMON_LOADED=1
スキルAがスキルBを呼び、Bも common.sh を source する......というチェーン実行でも、一度だけ読み込まれます。パフォーマンスへの影響はゼロ。ささやかですが、こういう細部が70スキル規模で効きます。
設計3:外部スキルのsymlink統合
skills.sh や外部リポジトリからのスキル
自作スキルだけでなく、skills.sh などのマーケットプレイスから取得した外部スキルも統合したい。ただし外部スキルは自分のリポジトリにコミットしたくない。
解決策:symlinkで参照し、.gitignore で自動管理。
skills/
├── .agents/
│ └── skills/ # 外部スキルの実体(gitignored)
│ ├── ui-ux-pro-max/
│ ├── prisma-cli/
│ └── rust-best-practices/
├── ui-ux-pro-max -> .agents/skills/ui-ux-pro-max # symlink
├── prisma-cli -> .agents/skills/prisma-cli # symlink
└── rust-best-practices -> .agents/skills/rust-best-practices
自動化スクリプト
link-agent-skills.sh が3つの処理を冪等に実行します:
.agents/skills/配下のディレクトリを走査- リポジトリルートにsymlinkを作成(既存なら何もしない)
.gitignoreのマーカーセクションを自動更新
.gitignore はこう管理されます:
# --- external skills (auto-managed) ---
ui-ux-pro-max
prisma-cli
rust-best-practices
# --- end external skills ---
マーカー内のエントリはスクリプトが自動生成するため、手動編集は不要。使わなくなったスキルのsymlinkも自動で掃除されます(stale symlink cleanup)。
何度実行しても結果が同じ冪等設計なので、CIから呼んでも安心です。
設計4:レガシーフォールバック
設定ファイルの置き場所は進化してきました。最初は .claude/<skill-name>.json に個別ファイルで置いていたのが、skill-config.json に統合された。でも旧形式のプロジェクトがまだ残っている。
load_skill_config は旧形式も透過的に読みます:
探索順序:
1. skill-config.json の該当セクション(最優先)
2. .claude/skill-config.json の該当セクション
3. .claude/{skill-name}.json(旧形式フォールバック)
移行を急がなくていい。新プロジェクトは新形式、旧プロジェクトはそのまま動く。全プロジェクト一斉移行という破壊的作業を回避できます。
実践:5プロジェクトで共有する設定の全体像
私たちの実際の構成をまとめます:
| 設定項目 | グローバル | プロジェクト固有 |
|---|---|---|
| タイムゾーン | "Asia/Tokyo" | -- |
| 投稿言語 | "ja" | -- |
| GA4プロパティID | -- | プロジェクトごとに異なる |
| GSCサイトURL | -- | プロジェクトごとに異なる |
| SNS投稿先 | X, LinkedIn ON | 社内ツールはSNS OFF |
| Google Trends地域 | "JP" | 海外案件は "US" |
| ブログ出力先 | -- | "content/blog" or "posts/" |
グローバル設定を1回書けば、新しいプロジェクトではプロジェクト固有値だけ追加すればいい。設定のコピペが消える。
導入ステップ
Step 1:グローバル設定を作る
mkdir -p ~/.config/skills
cat > ~/.config/skills/config.json << 'EOF'
{
"sns-announce": {
"default_lang": "ja",
"platforms": {
"x": { "enabled": true },
"linkedin": { "enabled": true }
}
},
"sns-schedule-post": {
"timezone": "Asia/Tokyo"
},
"trends-analyzer": {
"geo": "JP"
}
}
EOF
Step 2:プロジェクトに固有設定を追加
# プロジェクトルートに配置
cat > skill-config.json << 'EOF'
{
"ga-analyzer": {
"property_id": "YOUR_GA4_PROPERTY_ID",
"output_dir": "claudedocs"
},
"gsc": {
"site": "sc-domain:your-domain.com"
},
"sns-announce": {
"base_url": "https://your-domain.com",
"url_pattern": "/blog/{slug}"
}
}
EOF
Step 3:スクリプトから使う
source "$SKILLS_DIR/_lib/common.sh"
config=$(merge_config '{"output_dir":"./reports"}' "ga-analyzer")
# → デフォルト値 + グローバル + プロジェクトの3層マージ結果が取れる
output_dir=$(echo "$config" | jq -r '.output_dir')
よくある落とし穴
jq未インストール
common.sh の has_jq チェックでフォールバック処理が走りますが、deep mergeは jq がないと動きません。スキルリポジトリを使うマシンには jq を入れておきましょう(Nixなら nix profile install nixpkgs#jq で一瞬です)。
Git管理外ディレクトリ
load_skill_config は git rev-parse --show-toplevel でプロジェクトルートを特定します。Gitリポジトリ外で実行するとプロジェクト設定が読めず、グローバル設定のみになります。意図的な挙動ですが、「設定が反映されない」と思ったらまず git status を確認してください。
symlink先の消失
外部スキルを削除しても、symlinkが残ることがあります。link-agent-skills.sh を再実行すれば stale symlinkが自動削除されるので、定期実行を推奨します。
まとめ
Skillsをポータブルにするための設計は、突き詰めると 「スキル本体にプロジェクト固有値を書かない」 という1つの原則に集約されます。
3層コンフィグマージでプロジェクトごとの差分を吸収し、_lib/ で共通処理を一元管理し、外部スキルはsymlinkで疎結合に統合する。どれも単体では地味な仕組みですが、スキルが30個を超えたあたりから「やっておいてよかった」の複利が効いてきます。
一度書いたスキルが、次のプロジェクトでもそのまま動く。その安心感は、コピペ移植の手間を省く以上に、「新しいスキルを書く心理的ハードルを下げる」 という二次効果を生みます。



