「/repo-export → /seed-to-blog → /generate-thumbnail → /sns-announce...」
ブログを1本公開するたびに、4つのコマンドを順番に叩く。自動化したはずなのに、なんか...手作業感ありませんか?
前回の記事でSKILL.md肥大化問題を解決したplayparkですが、新たな悩みが生まれていました。Skillが増えすぎて、逆にめんどくさい。
1コマンドで全部やってくれないの?という自問自答の末、たどり着いたのがSkillオーケストレーションです。
Skillオーケストレーションとは
複数のSkillを「指揮者」が統括し、適切な順序で実行するパターンです。
/blog-publish https://github.com/example/repo
↓ 自動で以下が実行される
1. リポジトリ情報をエクスポート
2. ブログ記事を生成
3. サムネイル画像を作成
4. SNS投稿文を生成
ユーザーは1つのコマンドを叩くだけ。あとはオーケストレーターが「次はこのSkill」「この条件ならスキップ」と判断してくれます(Slackを眺めてる間に全部終わる)。
設計原則:3層構造
「全部AIに任せればいい」と思っていた時期が私たちにもありました。結果、実行のたびに微妙に挙動が違うという地獄を見ました(深夜2時のデバッグ、つらかった)。
試行錯誤の末、3層構造に落ち着きました。
第1層:決定論的スクリプト
何が入力されても同じ結果になる処理はシェルスクリプトに押し出します。
#!/bin/bash
# orchestrate.sh - モード検出、パス計算、日付計算
detect_mode() {
local src="$1"
if [[ "$src" =~ ^https://github\.com/ ]]; then
echo "repo"
elif [[ "$src" =~ \.mdx$ ]]; then
echo "blog"
elif [[ "$src" =~ \.md$ ]]; then
echo "export"
else
echo "unknown"
fi
}
get_next_publish_date() {
# 次の月曜or木曜を計算(公開曜日固定)
local dow=$(date +%u) # 1=Mon, 4=Thu
local days_to_mon=$(( (8 - dow) % 7 ))
local days_to_thu=$(( (11 - dow) % 7 ))
if [ $days_to_mon -le $days_to_thu ]; then
date -v+${days_to_mon}d +%Y-%m-%d
else
date -v+${days_to_thu}d +%Y-%m-%d
fi
}
# メイン処理
SOURCE="$1"
MODE=$(detect_mode "$SOURCE")
PUBLISH_DATE=$(get_next_publish_date)
# JSONで結果を返す
jq -n \
--arg mode "$MODE" \
--arg date "$PUBLISH_DATE" \
--arg source "$SOURCE" \
'{mode: $mode, publish_date: $date, source: $source}'
なぜスクリプト化するのか?という疑問への答えがこれです。
| スクリプト化 | SKILL.mdに書く |
|---|---|
| 実行のたびに同じ結果 | AIの「気分」で変わりうる |
| トークン消費ゼロ | 毎回コンテキストを消費 |
| デバッグしやすい | ログが追いにくい |
| 1秒で完了 | 数秒〜数十秒かかる |
モード検出、パス計算、日付計算...こういう「考える余地のない処理」をAIに任せるのは、GPSがあるのに星を読んで航海するようなもの。ロマンはあるけど非効率です(そして星が読めない)。
第2層:条件分岐ロジック
「この条件のときだけ実行」という判断は、スクリプトがJSONで返した情報をもとにSKILL.mdで定義します。
## Phase Execution
| Phase | Condition | Skill |
| ---------- | ---------------------- | -------------------- |
| Seed check | mode=repo, seed.exists | AskUser |
| Export | mode=repo, !reuse | `repo-export` |
| Blog | !--skip-blog | `seed-to-blog` |
| Thumbnail | !--skip-thumbnail | `generate-thumbnail` |
| SNS | !--skip-sns | `sns-announce` |
ポイントは「条件」と「実行するSkill」だけを書くこと。HOWではなくWHATを定義します。
第3層:AI判断が必要な処理
創造性が必要な処理だけAIに任せます。
- 記事の切り口を提案する
- ユーザーの意図を解釈する
- 曖昧な指示を補完する
- 既存記事と重複しない角度を考える
これがSkillオーケストレーションの核心。決定論的な処理をスクリプトに押し出すことで、AIは本来の強み(創造性・判断力)に集中できるわけです。
実装例:blog-publish
実際に運用しているblog-publishの構成を見てみましょう。
.claude/skills/blog-publish/
├── SKILL.md # オーケストレーション定義
└── scripts/
└── orchestrate.sh # 初期化・モード検出・状態確認
orchestrate.sh の全体像
スクリプトは「準備」と「状態確認」を担当します。
#!/bin/bash
set -euo pipefail
SEED_DIR="seed"
SOURCE="$1"
# モード検出
detect_mode() {
local src="$1"
if [[ "$src" =~ ^https://github\.com/([^/]+)/([^/]+) ]]; then
echo "repo"
elif [[ "$src" =~ \.mdx$ ]]; then
echo "blog"
elif [[ "$src" =~ /seed/.*\.md$ ]]; then
echo "export"
else
echo "unknown"
fi
}
# GitHub URLからseedパスを計算
get_seed_path() {
local url="$1"
if [[ "$url" =~ github\.com/([^/]+)/([^/]+) ]]; then
echo "${SEED_DIR}/${BASH_REMATCH[1]}-${BASH_REMATCH[2]}"
fi
}
# Seed状態を確認
check_seed_status() {
local seed_path="$1"
local exists=false
local article_count=0
local articles="[]"
if [ -d "$seed_path" ]; then
exists=true
if [ -f "$seed_path/articles.json" ]; then
article_count=$(jq '.articles | length' "$seed_path/articles.json")
articles=$(jq '.articles | map({angle, category, path})' "$seed_path/articles.json")
fi
fi
jq -n \
--argjson exists "$exists" \
--argjson count "$article_count" \
--argjson articles "$articles" \
'{exists: $exists, article_count: $count, existing_articles: $articles}'
}
# メイン
MODE=$(detect_mode "$SOURCE")
SEED_PATH=$(get_seed_path "$SOURCE")
SEED_STATUS=$(check_seed_status "$SEED_PATH")
jq -n \
--arg mode "$MODE" \
--arg source "$SOURCE" \
--arg seed_path "$SEED_PATH" \
--argjson seed "$SEED_STATUS" \
'{
mode: $mode,
source: $source,
seed_path: $seed_path,
seed: $seed
}'
出力されるJSONは、AIが「次に何をすべきか」を判断するための材料です。
SKILL.md の構造
AIはJSONを受け取り、フェーズを順次実行します。
# blog-publish
Orchestrates: repo-export → seed-to-blog → generate-thumbnail → sns-announce
## Init
\`\`\`bash
bash .claude/skills/blog-publish/scripts/orchestrate.sh <source>
\`\`\`
Returns: `{mode, source, seed_path, seed: {exists, article_count, existing_articles}}`
## Phase Execution
1. **Seed Check**: If `seed.exists && seed.article_count > 0`:
- Show existing articles
- AskUser: Reuse / Re-export / Cancel
2. **Export**: If `mode=repo && !reuse`:
- Run `/repo-export <source> -o <seed_path>/exported.md`
3. **Blog**: If `!--skip-blog`:
- Run `/seed-to-blog <seed_path>/exported.md`
4. **Parallel Phase** (if applicable):
- Thumbnail: `/generate-thumbnail <blog_path>`
- SNS: `/sns-announce <blog_path>`
## Skip Flags
- `--skip-blog`: Skip blog generation
- `--skip-thumbnail`: Skip thumbnail generation
- `--skip-sns`: Skip SNS post generation
フェーズ4は並列実行できることも明示しています。依存関係のない処理は並列化することで、全体の実行時間を短縮できます(コーヒー淹れてる間に終わる)。
Seedディレクトリ:状態管理の要
オーケストレーションで重要なのは「どこまで進んだか」「何を作ったか」の状態管理です。
seed/playpark-llc-corporate-site/
├── manifest.json # エクスポート日時、ファイル数
├── articles.json # 作成済み記事の一覧(重複防止の要)
├── exported.md # リポジトリのコード
├── context.md # 開発背景(手動追加で記事の深みが出る)
├── commits.md # コミット履歴
├── issues.md # Issue履歴
└── pr-summary.md # PR履歴
articles.json:同じネタで2本目を書くとき
{
"articles": [
{
"path": "content/blog/2026-01-21-blog-publish-automation.mdx",
"category": "case-studies",
"angle": "ブログ運用を完全自動化 - GitHubリポジトリから記事・サムネイル・SNS投稿まで",
"createdAt": "2026-01-21T10:00:00Z"
},
{
"path": "content/blog/2026-01-22-claude-code-skills-design.mdx",
"category": "tech-tips",
"angle": "Claude Code Skills設計 - SKILL.md肥大化問題と解決策",
"createdAt": "2026-01-22T10:00:00Z"
}
]
}
同じSeedから2回目以降の記事を作るとき、AIは既存のangleを参照して重複しない切り口を提案します。
「この切り口はもう書いた」→「じゃあ別の角度で」という判断を、articles.jsonが可能にしているわけです(人間の記憶力には限界がある)。
実行例:GitHubリポジトリからブログ公開まで
実際のフローを追ってみましょう。
User: /blog-publish https://github.com/playpark-llc/kazoeru-kun
[Phase 1: Init]
→ orchestrate.sh 実行
→ JSON: {mode: "repo", seed: {exists: true, article_count: 1}}
[Phase 2: Seed Check]
→ 既存Seedあり、記事1本作成済み
→ AskUser: "Reuse" / "Re-export" / "Cancel"
→ User: "Reuse"を選択
[Phase 3: Blog]
→ 既存記事: "カウンター管理アプリ開発記"
→ AI: 新しい切り口を3つ提案
1. Fly.io + Supabaseデプロイ手順
2. Rustバックエンドの設計判断
3. PWA対応の実装ポイント
→ User: 1を選択
→ /seed-to-blog 実行 → MDX生成
[Phase 4-5: Thumbnail & SNS(並列)]
→ /generate-thumbnail 実行 → WebP生成
→ /sns-announce 実行 → 投稿文生成
✅ Complete(トータル3分くらい)
Blog: content/blog/2026-03-12-flyio-supabase-deploy.mdx
Thumbnail: public/blog/2026-03-12-flyio-supabase-deploy.webp
SNS: posts/2026-03-12-flyio-supabase-deploy.json
1コマンドで、記事・画像・SNS投稿文が揃いました。
失敗から学んだこと
失敗1:全部SKILL.mdに書いた
最初は「AIに全部任せれば楽」と思っていました。
# 当初のSKILL.md(肥大化バージョン)
## モード検出ロジック
URLがhttps://github.comで始まる場合はrepoモード...
.mdxで終わる場合はblogモード...
日付計算は次の月曜か木曜を...
## 実行フロー
1. まずモードを判定して...
2. Seedディレクトリを確認して...
(以下300行続く)
結果:
- 実行のたびに微妙に違う挙動(AIが「解釈」してしまう)
- トークン消費が膨大(毎回300行読み込み)
- デバッグ困難(何が起きたか追えない)
教訓: 決定論的な処理はスクリプトに押し出す。SKILL.mdは50行以内を目指す。
失敗2:フェーズ間の依存関係を明示しなかった
「ブログ記事がないとサムネイルは作れない」という当たり前のことを書いていませんでした。
# 当初の曖昧な定義
## Phases
- Blog generation
- Thumbnail generation
- SNS generation
結果:
- 記事生成に失敗してもサムネイル生成が走る
- エラーメッセージが意味不明(「ファイルがない」としか言わない)
教訓: 依存関係を明示し、失敗時の挙動も定義する。
# 改善後
## Phase Dependencies
Blog → Thumbnail (requires blog_path)
Blog → SNS (requires blog_path)
## On Failure
If Blog fails: Stop. Do not proceed to Thumbnail/SNS.
失敗3:状態管理をサボった
「今どこまで終わったか」を記録していませんでした。
結果:
- 途中で止まると最初からやり直し(デジャヴ...)
- 同じ記事を二度生成してしまう
教訓: Seedディレクトリで状態を永続化する。articles.jsonは必須。
まとめ
Claude Code Skillオーケストレーションの設計原則をまとめます。
| 原則 | 実践 | 効果 |
|---|---|---|
| 決定論的処理はスクリプト化 | モード検出、パス計算、日付計算 | トークン節約、挙動安定 |
| 条件分岐はSKILL.mdで定義 | フェーズ表、スキップ条件 | 見通しが良い |
| AI判断は創造的タスクに限定 | 切り口提案、記事生成 | AIの強みを活かす |
| 状態はSeedディレクトリで管理 | manifest.json, articles.json | 重複防止、再開可能 |
| 並列実行できるものは並列化 | Thumbnail + SNS | 時間短縮 |
「ブログを書く」という1つの目標に対して、5つのSkillが連携する。それぞれが得意分野に集中することで、全体として効率的なワークフローが実現できます。
次は何をオーケストレーションしようか...リリースノート→Changelog→社内Slack通知の連携あたり、面白そうですね(また沼にハマりそう)。
「うちもブログ運用を自動化したい」という方へ
playparkでは、AIを活用した業務効率化の支援を行っています。「このめんどくさい作業、なんとかならない?」というご相談、お待ちしています。



