Blog / Site Maintenance

SkillMedia

Maintenance of site articles and settings. Interactively handles: creating new blog posts, toggling published/unpublished state, editing frontmatter (title, description, tags), deleting posts, listing posts, adding/replacing member images, and editing build article body text (build concept / damage

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Blog / Site Maintenance skill

What this skill tells your AI

The instructions your AI receives, as published by pkdxtools/pkdx in .claude/skills/blog/SKILL.md and read by ahel’s review.

box/blog/, box/teams/, box/site.config.json に対するメンテナンス操作を提供する。team-builder / breed がコンテンツを「生成」する側であるのに対し、このスキルはコンテンツを「管理」する側に特化する。

本スキルは以下を 1 セッションで扱う:

  1. コンテンツ編集(Phase A-F)
  2. 編集結果の確認と git push(Phase G、ユーザー承認必須

git push は Phase G で必ずユーザーの YES 確認を得てから実行する。 無確認で push することはない。push を拒否された場合はローカルに変更を残したまま終了する。

Skip 判定 共通ルール

全 Phase の AskUserQuestion に適用される共通ルール:

skip してよい条件(いずれか該当):

  • ユーザー発話に既に当該質問の回答が含まれている(例: 「カバルドン記事を削除したい」→ コレクション選択・記事選択 を skip)
  • 直前の Phase / AskUserQuestion で既に確定した情報を再確認する質問
  • ユーザーが操作対象パスを絶対 / 相対パスで明示している

skip 時の扱い:

  • 想定される回答を skill 記載の手順通りに内部で決定
  • skip した事実と選んだ想定応答をユーザーへ通常メッセージで開示(例: 「対象コレクションを teams と判定して続行します」)

絶対に skip してはいけない質問:

  • 削除確認ダイアログ(Phase D-3)
  • Phase G-2 の反映可否確認
  • Phase G-3 の送信先最終確認(「反映する」経路)
  • Phase G-4 の commit メッセージ確認

Phase 間遷移の原則

各 Phase の完了報告(例: A-5, B-5, C-5, D-5, E-5, H-5, I-8)直後の Phase G への遷移は自動で行う。間に確認の AskUserQuestion は挟まない。ユーザーは完了報告テキストを見て、Phase G の差分提示へと続けて移行する。

パス定義

SKILL_DIR=(このSKILL.mdが置かれたディレクトリ)
REPO_ROOT=$SKILL_DIR/../../..
BLOG_DIR=$REPO_ROOT/box/blog
TEAMS_DIR=$REPO_ROOT/box/teams
SITE_CONFIG=$REPO_ROOT/box/site.config.json

Phase 0: 初期化

0-1: box 配下の存在確認

ls "$REPO_ROOT/box" >/dev/null 2>&1 || { echo "box/ が見つかりません。setup.sh を先に実行してください。"; exit 1; }

0-2: site.config.json 読み込み

$SITE_CONFIG が存在しない場合は作成する:

if [ ! -f "$SITE_CONFIG" ]; then
  cat > "$SITE_CONFIG" <<'JSON'
{
  "site_name": "pkdx site",
  "author": null,
  "enabled": true
}
JSON
fi

存在する場合は Read で現状を取得する。

0-3: 操作選択

最初の AskUserQuestion で編集系 4 操作(Phase A/B/C/D)のみ提示し、それらすべてに該当しないときだけ 2 段目 AskUserQuestion を出す。ユーザー発話にすでに明確な意図がある場合(「サイト名を〇〇に変えたい」「一覧を見たい」等)は 1 段目を skip して該当 Phase に直接遷移してよい。

AskUserQuestion(1問、ユーザー意図が既に明確なら skip 可):

#質問headerオプションmultiSelect
1どの操作を行いますか?操作新規ブログ記事作成(desc: box/blog/ に新しい記事ファイルを作る), 記事の編集/削除/公開切替(desc: 既存記事の frontmatter 編集・削除・published フラグ反転), サイト設定 or 記事一覧(desc: site_name / author / enabled / 記事一覧表示), その他(desc: 上記以外を Other で入力)false

選択内容に応じて以下へ:

  • 「新規ブログ記事作成」 → Phase A
  • 「記事の編集/削除/公開切替」 → 続く AskUserQuestion(下記)で B/C/D のどれかを確定
  • 「サイト設定 or 記事一覧」 → 続く AskUserQuestion(下記)で E/F のどちらかを確定
  • 「その他」→ Other キーワードから判定

「記事の編集/削除/公開切替」を選んだ場合、続く AskUserQuestion:

#質問headerオプションmultiSelect
1どの操作?操作公開/非公開切替(desc: Phase B), フロントマター編集(desc: Phase C), 削除(desc: Phase D), メンバー画像メンテ(desc: Phase H — team 記事のメンバー画像追加・差し替え), 本文編集(desc: Phase I — 構築コンセプト / ダメージ計算 / ポケモンメモ / 選出 の編集 (team 記事のみ))false

「サイト設定 or 記事一覧」を選んだ場合、続く AskUserQuestion:

#質問headerオプションmultiSelect
1どちら?操作サイト設定変更(desc: Phase E), 記事一覧表示(desc: Phase F)false

Other での直接キーワード判定は以下:

  • 「新規」「作成」「書く」→ Phase A
  • 「公開」「下書き」「非公開」→ Phase B
  • 「編集」「タイトル変更」→ Phase C
  • 「削除」「消す」→ Phase D
  • 「画像」「メンバー画像」「ポケモン画像」→ Phase H
  • 「コンセプト」「構築コンセプト」「ダメ計」「ダメージ計算」「ポケモンメモ」「役割メモ」「role」「選出」「表選出」「裏選出」「本文」→ Phase I
  • 「設定」「サイト名」「author」→ Phase E(2段目 skip 可)
  • 「一覧」「リスト」→ Phase F(2段目 skip 可)

選択結果に応じて該当 Phase へ遷移する。

0-4: ローカル dev プレビューの起動(任意)

操作 Phase へ遷移する直前に、ユーザーへローカル dev プレビューを起動するか確認する。dev サーバーを立ち上げておくと、Edit / Write のたびに Astro が hot reload するので、ユーザーは編集結果をブラウザでリアルタイムに確認しながら作業できる。

Phase F (一覧表示) を選んだ場合は本ステップを skip (差分が出ない / プレビューする意味がないため)。それ以外は以下を実行。

0-4-1: 起動希望の確認

先に port 4321 の使用状況を確認してから AskUserQuestion を出す:

ALREADY_RUNNING=$(lsof -nP -iTCP:4321 -sTCP:LISTEN 2>/dev/null | tail -n +2 | head -1)

$ALREADY_RUNNING が空でない場合は通常メッセージで以下を出してskip (既起動の dev server をユーザーは別ターミナルで使っている想定):

http://localhost:4321 で既に何かがリッスンしています。
別ターミナルで起動済みの dev server を使うか、ご自分で確認してください。

$ALREADY_RUNNING が空の場合のみ AskUserQuestion (1問)。質問には bun runtime が必要である旨の断りを必ず含める (setup.sh は bun を入れないため、ユーザーが未導入の可能性がある):

#質問headerオプションmultiSelect
1ローカルで dev プレビューしながら作業しますか? (Astro の hot reload で編集結果がリアルタイムに反映されます。プレビュー起動には bun runtime が必要です。未導入の場合は scripts/install_bun.sh で自動インストールするか確認します)プレビュー起動しない(default, desc: 編集だけ進めて、ブラウザで確認しない), 起動する(desc: preview_start で site を起動。bun 未導入なら先にインストール確認。Phase G 終了時に停止確認)false

「起動しない」を選ばれた場合はそのまま該当 Phase へ。

0-4-2: dev server を preview_start で起動

「起動する」を選ばれた場合、mcp__Claude_Preview__preview_start ツールname: "site" で呼び出す。生 Bash で bun run dev & を回さない (プロセス管理が手動になり Phase G-7 の停止が不安定になるため、preview_start に一任する)。

前提として .claude/launch.jsonsite エントリが必要 (この repo では既に配置済み)。preview_start は launch.json の runtimeExecutable / runtimeArgs / cwd / port を読んで bun run devsite/ 配下で起動する。

bun の存在確認: runtimeExecutable: "bun" のため bun が PATH 上に無いと preview_start は失敗する。setup.sh は意図的に bun を入れないため、以下で検出し、未インストール時は scripts/install_bun.sh を案内する:

if ! command -v bun &>/dev/null; then
  echo "MISSING_BUN"
fi

MISSING_BUN が出力された場合、通常メッセージで以下を提示してから AskUserQuestion (1問) を出す:

dev server 起動には bun runtime が必要ですが、PATH 上に見つかりません。
scripts/install_bun.sh で bun 1.3.12 (site/ の pin バージョン) をインストールできます。
(~/.bun/bin/bun に入り、~/.zshrc / ~/.bashrc に PATH 追記されます)
#質問headerオプションmultiSelect
1bun を今インストールしますか?インストールインストールする(default, desc: scripts/install_bun.sh を実行), スキップ(desc: dev プレビューを使わず編集だけ進める)false

「インストールする」を選ばれた場合:

bash "$REPO_ROOT/scripts/install_bun.sh"
# 新シェルではなく現行シェル向けに PATH を通す
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
command -v bun  # 再確認

インストールに失敗 (exit code != 0) または command -v bun が依然空なら、「起動しない」相当で該当 Phase へ遷移する (DEV_PREVIEW_ID は設定しない)。成功したら次の preview_start 呼び出しへ進む。

「スキップ」を選ばれた場合は「起動しない」相当で該当 Phase へ。

preview_start(name="site")
→ { serverId, port: 4321, name: "site", reused: <bool> }

serverIdセッション内変数 DEV_PREVIEW_ID として記憶する (Phase G-7 で停止する際に必要)。

起動後、最大 15 秒ほど待ってから http://localhost:4321/pkdx/ がリッスンし始めたかを以下で確認:

for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
  if curl -fsS -o /dev/null http://localhost:4321/pkdx/; then
    echo "ready"
    break
  fi
  sleep 1
done

起動できたら通常メッセージで案内 (Astro は astro.config.mjsbase: "/pkdx/" を設定しているため、ルート / は 404 になる。プレビューを開くときは必ず /pkdx/ 付きで案内する):

✓ dev server を起動しました
  URL: http://localhost:4321/pkdx/   ← base path /pkdx/ を必ず付けること (ルートは 404)
  編集するたびに自動でブラウザに反映されます (要リロード)

  【Claude Desktop でプレビューを開く】
  - Preview ペインを開く: Cmd+Shift+P (macOS) / Ctrl+Shift+P (Windows)
  - または セッションツールバーの「Preview」ドロップダウンから
  - Preview ペインが開いていれば上記 URL が自動でロードされます
  - Claude Desktop 以外で確認する場合はブラウザに URL を直接開いてください

  Phase G 完了時に停止確認します。

15 秒以内に起動しなかった場合は、preview_start の戻り値や mcp__Claude_Preview__preview_logs でエラーを確認し、DEV_PREVIEW_ID を破棄してから該当 Phase へ進む (プレビューなしで作業継続)。


Phase A: 新規ブログ記事作成

A-1: 基本情報の収集

AskUserQuestion(3問、全て Other で自由入力):

#質問headerオプションmultiSelect
1記事のタイトルは?タイトルOtherで入力(desc: 日本語可)false
2ファイル名(slug, 半角英数とハイフン)は?slugOtherで入力(desc: 例 first-post, 2026-release-notes), 日付付きで自動生成(desc: post-YYYY-MM-DD)false
3description(検索結果やカードに出る短い紹介)は?descriptionOtherで入力(desc: 1-2文程度), 省略(desc: 空欄にする)false

slug バリデーション: ^[a-zA-Z0-9][a-zA-Z0-9\-]*$。NG の場合は再入力を促す。

A-2: タグ(任意)

AskUserQuestion(1問):

#質問headerオプションmultiSelect
1タグを追加しますか?タグなし(desc: タグ空で作成), Otherで入力(desc: カンマ区切り 例: 対戦,考察)false

A-3: 公開状態

AskUserQuestion(1問):

#質問headerオプションmultiSelect
1公開状態は?公開下書き(published: false)(desc: まず非公開で作成。後から切り替え), 公開(published: true)(desc: いきなり公開)false

A-4: ファイル生成

frontmatter 表記ルール(site/src/content.config.ts の blogSchema 準拠):

  • title / description: ダブルクォート囲み(例: title: "記事タイトル"
  • date: クォート無し ISO 日付(date: 2026-04-22
  • tags: inline flow 配列、各要素ダブルクォート囲み(例: tags: ["astro", "dev"]
  • published: クォート無しの真偽値(published: false
  • description / tags / eyecatch は schema 側で optional。値が空の場合はキーごと省略してよい(tags: [] 空配列も OK だが省略が推奨)

重複チェック:

DEST="$BLOG_DIR/<slug>.md"
if [ -e "$DEST" ]; then
  echo "既に存在します: $DEST"
  # AskUserQuestion で上書き確認 or slug 再入力
fi

今日の日付を取得:

TODAY=$(date +%Y-%m-%d)

Write tool で $DEST を上述の表記ルールに完全準拠した形式で生成する。以下は具体例 (タイトル="Astro アップグレードメモ", slug=astro-upgrade-notes, tags=astro/dev, published=false, date は 必ず date +%Y-%m-%d の実行結果を使う。skill 内の例日付をそのまま写すと、実行日と違う場合にバグる):

---
title: "Astro アップグレードメモ"
date: 2026-04-22   # ← TODAY=$(date +%Y-%m-%d) の結果。例の値をそのまま使わない
description: "Astro 6 系への移行メモ"
tags: ["astro", "dev"]
published: false
---

# Astro アップグレードメモ

ここから本文を書いてください。

上記の各フィールドについて:

  • title: ユーザー入力を必ずダブルクォートで囲む
  • date: TODAY=$(date +%Y-%m-%d) の値をクォート無しで
  • description: ダブルクォートで囲む。空なら行ごと省略
  • tags: inline flow で各要素ダブルクォート。空なら行ごと省略(tags: [] より推奨)
  • published: true / false をクォート無しで

A-5: 完了報告

新規記事を作成しました: box/blog/<slug>.md
  公開状態: <下書き | 公開>
  本文を編集するには box/blog/<slug>.md を直接エディタで開いてください。

続いて A-6 (dev server 再起動)を判定してから Phase G: 反映確認 に進む(action = "create", slug = <slug>)。

A-6: dev server 再起動(新規ファイル反映のため)

Astro の content collection (glob loader) は既存ファイルの Edit には hot reload で追従するが、新規追加された md は dev server を再起動するまで /pkdx/blog/<slug>/ ルートが生成されない[...slug].astro の getStaticPaths が dev 起動時のスナップショットで固定されるため)。従って Phase A で新規作成した直後は、プレビュー中の dev server を必ず再起動してから Phase G の反映確認へ進む。

skip 条件: DEV_PREVIEW_ID が未設定(Phase 0-4 で dev server を起動していない / 起動失敗した)場合は本ステップを skip。ユーザー確認も挟まず次へ進む。

DEV_PREVIEW_ID が設定されている場合のみ、以下を無確認で自動実行する(ユーザーは Phase 0-4 でプレビュー前提の作業に同意済みのため、再起動ごとに問い合わせると煩わしい):

mcp__Claude_Preview__preview_stop(serverId: "<DEV_PREVIEW_ID>")
mcp__Claude_Preview__preview_start(name: "site")
→ 戻り値の serverId で DEV_PREVIEW_ID を上書き

preview_start 直後は Phase 0-4-2 と同様に最大 15 秒まで curl -fsS http://localhost:4321/pkdx/ でリッスン確認する。起動できたら通常メッセージで以下を報告:

✓ dev server を再起動しました (新規記事ルート反映のため)
  URL: http://localhost:4321/pkdx/blog/<slug>/
  published: false の場合はビルド対象外となるため /pkdx/blog/ 一覧には出ませんが、
  URL 直打ちでプレビューできます。

再起動に失敗した場合(preview_start がエラー / 15 秒タイムアウト)は DEV_PREVIEW_ID をクリアし、通常メッセージで以下を出してから Phase G へ進む(Phase G-7 の停止確認は skip される):

⚠ dev server の再起動に失敗しました。手動で再起動してください:
  mcp__Claude_Preview__preview_list → preview_stop → preview_start

Phase B: 公開/非公開切替

B-1: 対象コレクション選択

skip 判定: ユーザー発話に対象パスが含まれる場合(例: box/teams/...md や「team 記事」「blog 記事」の明示)、本ステップを skip して B-2 に直接進んでよい。skip した場合、対象コレクションは発話から判定する。

AskUserQuestion(1問、skip 条件未適用時のみ):

#質問headerオプションmultiSelect
1どのコレクションの記事を切り替えますか?対象blog(desc: box/blog/ 配下), teams(desc: box/teams/ 配下)false

B-2: 記事選択

対象ディレクトリから *.md をリスト化(TEMPLATE.md.example は除外):

TARGET_DIR=$BLOG_DIR   # or $TEAMS_DIR
find "$TARGET_DIR" -maxdepth 1 -name '*.md' ! -name 'TEMPLATE*' | sort

各記事の現状 published フラグを Read して把握する:

for f in *.md; do
  grep -E '^published:' "$f" || echo "(未設定)"
done

表示例:

[1] SAMPLE-カバルドン-build-2026-04-21.md  published: true

AskUserQuestion(1問、最大4件ずつバッチ):

  • 記事数 ≤ 4: そのまま選択肢に並べる
  • 記事数 > 4: 「ファイル名を直接指定」等で Other 入力を受ける

B-3: published フラグ反転

Edit tool で対象ファイルの frontmatter 内 published: を反転する:

  • published: truepublished: false
  • published: falsepublished: true
  • 未設定の場合: date: 行の直後に published: false を挿入(published のデフォルトは true だが、明示しておく)

B-4: publishedAt リセット考慮

published: false → true へ切り替えた場合、publishedAt の扱い:

  • with-published-at.ts loader が git log で自動補完するため、特に何もしないのが正解
  • ただし「今から公開した」扱いで並び順を最新に寄せたい場合は publishedAt: 行を明示的に追加する提案をする

AskUserQuestion(false → true の場合のみ、1問):

#質問headerオプションmultiSelect
1公開日時をどう扱いますか?公開日時自動(推奨)(desc: git log から自動補完される), 今の時刻を publishedAt に明記(desc: 一覧で最新として扱われる)false

「今の時刻」を選んだ場合:

NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)

Edit tool で frontmatter に publishedAt: <NOW> を追加(既存があれば上書き)。

B-5: 完了報告

<ファイル名> の公開状態を <new_state> に変更しました。

続いて Phase G: 反映確認 に進む(action = "toggle", slug = <slug>, new_state = <new_state>)。


Phase C: 記事のフロントマター編集

C-1: 対象選択

Phase B-1 / B-2 と同じフローで対象記事を選ぶ。B-1 の skip 判定ルールも同様に適用される

C-2: 編集項目選択

Read で現状の frontmatter を取得してユーザーに提示した上で:

AskUserQuestion(1問, multiSelect):

#質問headerオプションmultiSelect
1どの項目を編集しますか?項目title, description, tags, eyecatchtrue

C-3: 新しい値の入力

選ばれた項目ごとに AskUserQuestion で Other 入力を受ける。各項目:

  • title: 文字列(空禁止)
  • description: 文字列(空許可 → 行ごと削除)
  • tags: カンマ区切り → [tag1, tag2] 形式に整形
  • eyecatch: パス文字列(空許可 → 行ごと削除)

C-4: Edit 適用

Edit tool で frontmatter 内の対象行を書き換える。値が空で削除対象の場合は replace_all: false で行そのものを消す。

C-5: 完了報告

<ファイル名> のフロントマターを更新しました:
  - <field>: <before> → <after>

続いて Phase G: 反映確認 に進む(action = "edit", slug = <slug>, fields = <updated fields>)。


Phase D: 記事削除

D-1: 対象選択

Phase B-1 / B-2 と同じフローで対象記事を選ぶ。B-1 の skip 判定ルール(ユーザー発話に対象パスが含まれる場合、コレクション選択 AskUserQuestion を skip)も同様に適用される。ユーザーが「box/teams/SAMPLE-カバルドン-...md を削除したい」のようにパスを明示した場合は、コレクション選択を飛ばして D-2 に進む。

D-2: 関連 meta.json の検出

team-builder 由来の記事(box/teams/ 配下のみ)には <slug>.meta.json が併置されている。box/blog/ 配下には .meta.json 併置無しのため、対象が blog の場合は本ステップ自体を skip する:

SLUG=$(basename "$FILE" .md)
META="$(dirname "$FILE")/$SLUG.meta.json"
# box/teams/ 配下のみ検出対象
if [[ "$FILE" == *"/box/teams/"* ]] && [ -f "$META" ]; then
  echo "meta file exists: $META"
fi

D-3: 最終確認

削除対象一覧の提示方法: AskUserQuestion の質問文本体には含めず、質問直前の通常メッセージとして別途提示する(質問文に長い一覧を詰めない):

# 通常メッセージとして先に出力
削除対象:
  - box/teams/SAMPLE-カバルドン-build-2026-04-21.md
  - box/teams/SAMPLE-カバルドン-build-2026-04-21.meta.json

そのあと AskUserQuestion を出す。

AskUserQuestion(1問):

#質問headerオプションmultiSelect
1上記ファイルを削除してよいですか?削除確認キャンセル(desc: 削除しない), 削除する(desc: ファイルを削除。復元は git から可能)false

削除対象には .md と(存在すれば).meta.json の両方を含める。

user が「削除する」を選択した場合のみ削除を実行する。 キャンセル がデフォルト表示になるよう最初に配置する。本ダイアログは「Skip 判定 共通ルール」で skip 禁止と定めた critical 質問に該当するため、絶対に skip しない

D-4: 削除実行

rm -- "$FILE"
[ -f "$META" ] && rm -- "$META"

D-5: 完了報告

削除しました: <files>
誤って削除してしまった場合、リポジトリルートで以下を実行すると復元できます:
  git checkout HEAD -- <file path>

続いて Phase G: 反映確認 に進む(action = "delete", slug = <slug>)。


Phase E: サイト設定変更

E-1: 現状表示

$SITE_CONFIG を Read して現状を表示:

現在のサイト設定:
  site_name: <value>
  author: <value or null>
  enabled: <true | false>

E-2: 変更項目選択

AskUserQuestion(1問, multiSelect):

#質問headerオプションmultiSelect
1どの項目を変更しますか?項目site_name(desc: サイト名 ヘッダーや に使用), author(desc: 著者名 フッターに表示), enabled(desc: false にするとデプロイが停止する)true

multiSelect 挙動: ユーザーが選んだ項目のみ E-3 で新値入力を求める。選ばれなかった項目は touch しない(Edit も発火しない)。選択が 0 件なら E-4 以降を skip し「変更項目がないため終了」でセッション終了。

E-3: 新しい値の入力

E-2 で選ばれた項目それぞれについて新値を問う。選ばれなかった項目は本ステップを発火させない:

  • site_name: 文字列(空禁止)
  • author: 文字列 or null(「省略」で null に)
  • enabled: true / false の2択 AskUserQuestion

E-4: JSON 書き換え

Edit tool で $SITE_CONFIG のうち E-2 で選ばれた行のみ書き換える。Write tool で全体を書き直してもよいが、Edit の方が他キーを温存できるため優先する。

author: null の場合は "author": null と記述する(文字列 "null" ではなく JSON の null)。

E-5: 完了報告

サイト設定を更新しました:
  - <field>: <before> → <after>

enabled: false にした場合は GitHub Actions のデプロイが skip されます。

続いて Phase G: 反映確認 に進む(action = "site-config", fields = <updated fields>)。


Phase F: 記事一覧表示

$BLOG_DIR$TEAMS_DIR をスキャンし、各記事の titlepublished を Read で抽出してテーブル表示する。

=== box/blog ===
(記事なし)

=== box/teams ===
| ファイル                                | 軸           | 公開状態 |
| SAMPLE-カバルドン-build-2026-04-21.md   | カバルドン   | true     |

表示後、AskUserQuestion で「この記事を編集/切替/削除しますか?」と促し、YES ならそれぞれ Phase B/C/D に遷移する。NO ならスキル終了(Phase G は呼ばない — 一覧表示のみでは差分が無いため)。


Phase H: メンバー画像メンテ

box/teams/<slug>.mdmembers[] に対応するポケモン画像 (site/public/pokemons/<member.name>.{png,jpg}) をユーザーがチャットに添付する形で追加・差し替えする。MemberCard コンポーネントは member.name から自動的に画像を解決して右上に表示する。

ユーザーが手動で site/public/pokemons/ にファイルを置く必要はない。チャット添付されたファイルを agent (Claude) が file で検証して cp で保存する。外部スクリプト・追加 runtime は不要。

H-1: 対象 team 記事を選択

ls "$TEAMS_DIR"/*.md 2>/dev/null

複数ある場合は AskUserQuestion で 1 件を選ばせる (Phase B-2 と同様のパス選択)。発話に box/teams/...md が含まれていれば skip して直接対象記述を採用する。

H-2: members 一覧の取得 + 画像有無の表示

選択された <slug>.md の frontmatter を Read で取得して members: 配列を抽出する。配列の各 <name> について以下を実行:

if [ -f "$REPO_ROOT/site/public/pokemons/${name}.png" ]; then
  echo "${name}  ✓ (png)"
elif [ -f "$REPO_ROOT/site/public/pokemons/${name}.jpg" ]; then
  echo "${name}  ✓ (jpg)"
else
  echo "${name}  ✗ (未配置)"
fi

通常メッセージとして一覧を表示する:

画像配置状況 (site/public/pokemons/):
  カバルドン  ✓ (png)
  マンムー    ✗ (未配置)
  アーマーガア ✗ (未配置)
  ...

H-3: 添付対象を multiSelect で選択

AskUserQuestion (multiSelect):

#質問headerオプションmultiSelect
1どのメンバーの画像を追加 / 差し替えますか?対象members[] 各エントリ (各 desc に「未配置 / 既存あり (差し替え)」を表示)true

選択 0 件なら Phase H をここで終了し Phase G へ遷移する。

H-4: 各メンバーの画像を添付・検証・保存

選択リストを順番に処理する。各メンバーごとに通常メッセージで以下を提示:

[1/3] カバルドン の画像を添付してください
  仕様: 340×340 以上の正方形、PNG または JPEG
  (skip する場合は「skip」と入力)

ユーザーが画像を添付すると、agent にローカルファイルパス (例: /var/folders/.../tmp/Image-xxxxx.png) が渡される。以下をこの順序で実行する。

H-4-a: symlink を拒否
SRC="<添付ファイルの絶対パス>"
if [ -L "$SRC" ]; then
  echo "REJECT_SYMLINK"
fi

REJECT_SYMLINK が出力されたら通常メッセージで「symlink は受け付けません。実体ファイルを添付してください」と提示し再添付要求 (skip なら次のメンバー)。

H-4-b: 形式と寸法を file で取得
file "$SRC"

出力例:

  • …: PNG image data, 480 x 480, 8-bit/color RGB, non-interlaced
  • …: JPEG image data, JFIF standard 1.01, … precision 8, 1024x768
  • …: GIF image data, version 89a, …

agent が出力をパースして以下を判定する (BSD/GNU 共通フォーマット):

項目判定
形式PNG image data / JPEG image data 以外は reject (「対応形式は PNG / JPEG のみ」)
寸法<W> x <H> または <W>x<H> を抽出。失敗したら 添付画像を agent が直接見ておおよその寸法を判断
正方形W !== H なら reject (「正方形にしてください: 480×320 が添付されました」)
最小サイズW < 340 なら reject (「340×340 以上にしてください: 200×200 が添付されました」)

reject の場合は理由を通常メッセージで提示し再添付要求。再添付なし (skip) なら次のメンバーへ。

H-4-c: 拡張子の決定 + 既存衝突確認

形式から拡張子を決める (PNG.pngJPEG.jpg)。保存先パスを組み立て:

DEST="$REPO_ROOT/site/public/pokemons/<member.name>.<ext>"

既存ファイルがある場合のみ AskUserQuestion (1問):

#質問headerオプションmultiSelect
1<member.name> の画像 (<DEST>) が既に存在します。どうしますか?上書き上書きする(desc: 既存を消して新しい画像で置き換え), skip(desc: 既存を残す), 中断(desc: Phase H を中断して Phase G に遷移)false
  • 「skip」→ 次のメンバーへ
  • 「中断」→ 残メンバー処理せず Phase G へ
  • 「上書きする」→ H-4-d へ進む
H-4-d: cp で保存
mkdir -p "$REPO_ROOT/site/public/pokemons"
cp "$SRC" "$DEST"

成功なら通常メッセージで「✓ <member.name>. を保存しました (×)」と報告し、次のメンバーへ。

H-5: 完了報告

全メンバー処理後、まとめを通常メッセージで出力:

画像メンテ完了:
  ✓ カバルドン.png 保存
  ✓ マンムー.png 保存
  - アーマーガア skip (再添付なし)

差分は site/public/pokemons/ 配下に作成されています。
続けて Phase G (反映確認) に遷移します。

直後に自動で Phase G へ遷移する (確認 AskUserQuestion は挟まない)。Phase G-1 の git status --short -- box/ site/public/pokemons/?? site/public/pokemons/カバルドン.png などが検出される。


Phase I: 構築記事 本文編集

box/teams/<slug>.md と同名の .meta.json のペアに対する本文編集を提供する。4 種類のサブ操作 (構築コンセプト / ダメージ計算 追加・削除 / ポケモンメモ / 選出やりなおし) をサポート。

背景・設計原則

  • team-builder が生成する構築記事は .md.meta.json のペア構造。site の team ページ (site/src/pages/teams/[...slug].astro) は .md 本文を描画せず、.meta.json の構造化データを Astro コンポーネント (MemberCard / DamageCalcTable / MatchupPlans 等) に流し込んで描画する。つまり site ページの見た目を司るのは .meta.json 側 (title / description / tags など frontmatter は .md から読む)。
  • 一方 .md 本文は GitHub などで raw 閲覧したときに人間が読む用に同じ内容が書き出されている。.meta.json と一致するように同期を保つのが望ましい。
  • Phase I は meta.json (SSoT) を更新しつつ、同期のため md 本文の対応箇所も Edit で書き換える。両方を更新することで site 表示と raw 閲覧の整合を取る。
  • frontmatter (title / description / tags / published / edited など) は一切触らない。これらは Phase C の守備範囲。
  • pkdx write teams --force による md 再生成は 使わない。再生成は frontmatter を既定値で上書きしてしまい、Phase C で付けた title / description / tags / edited: true を失うため。

対象は box/teams/*.md のみ。box/blog/*.md には meta.json が併置されないため Phase I では扱わない (blog 記事の本文編集はユーザー自身がエディタで直接編集する)。

Phase I 共通 skip ルール

Phase I の AskUserQuestion は、上部「Skip 判定 共通ルール」(L22–30) に加えて次の Phase I 固有 skip 判定が適用される。全て「skip した事実と想定応答を通常メッセージで開示」必須。

  1. I-1 の対象ファイル選択: 発話に以下のいずれかが含まれれば skip して採用ファイルを確定する:

    • 完全パス box/teams/<slug>.md または絶対パス相当
    • slug 文字列 (例: SAMPLE-カバルドン-build-2026-04-21)
    • slug の部分文字列で find "$TEAMS_DIR" -name "*<部分文字列>*.md" が 1 件に収束する場合 (例: SAMPLE-カバルドン)

    2 件以上にヒットした場合は skip せず AskUserQuestion を出す。

  2. I-2 の編集項目選択 (multiSelect): 発話で明示された項目が 1 個以上ある場合は skip して発話項目のみを採用する。発話内容との対応付けキーワード:

    • 「コンセプト」「概念」「狙い」→ 構築コンセプト
    • 「ダメ計 追加」「damage_calcs 追加」「計算を足す」→ ダメージ計算 追加
    • 「ダメ計 削除」「ダメ計 消す」「ダメ計 整理」「重複削除」→ ダメージ計算 削除
    • 「メモ」「役割」「role」→ ポケモンメモ
    • 「選出」「表選出」「裏選出」「選出やりなおし」→ 選出やりなおし

    発話から項目が 1 つも推定できない場合のみ AskUserQuestion を発火。

  3. I-5-2 の削除対象 index 選択: 発話に index 列 (「1, 3, 5」「index 0 と 4」など) が含まれていれば skip して該当 index を採用する。発話の index が 1-indexed0-indexed かはユーザーに確認 (AskUserQuestion Other)。ただし発話文脈から明らかな場合 (SKILL の出力一覧 [0]..[N] を参照していると読める) は 0-indexed で確定し通常メッセージで開示。

  4. I-6-2 / I-7-3 のメンバー・note 選択: 発話にメンバー名列・note 本文が含まれていれば skip。

  5. I-7-2 の選出操作 (multiSelect): 発話から操作 (表書換 / 裏書換 / 表 null / 裏 null) が推定できる場合 skip。「書き換える」と「未設定にする」が同一対象で同時に解釈可能な場合は「書き換える」を優先する (未設定にしてから書き換えるのと最終状態が同じで、かつ意図推定として書き換えが自然)。

meta.json の updated_at 更新規則

Phase I の jq 更新では、該当フィールド (concept / members[i].role / damage_calcs / primary_selection / alternate_selection) を書き換えると同時に .updated_at フィールドも 必ず $(date +%Y-%m-%d) で更新する。writer 側の meta.json と挙動を揃えるため。

具体例 (I-3-3 の jq に統合):

NOW=$(date +%Y-%m-%d)
tmp=$(mktemp)
jq --arg v "$NEW_CONCEPT" --arg ts "$NOW" '.concept = $v | .updated_at = $ts' "$META" > "$tmp" && mv "$tmp" "$META"

I-5-3 / I-6-4 / I-7-4 の全 jq テンプレートにも同様に .updated_at = $ts を追加する。

I-1: 対象 team 記事を選択

前述「Phase I 共通 skip ルール」1 の条件に合致すれば skip して発話のファイルを採用する。

合致しない場合は候補を列挙:

find "$TEAMS_DIR" -maxdepth 1 -name '*.md' ! -name 'TEMPLATE*' | sort
  • 1 件のみ → 自動選択 (通常メッセージで「対象を と判定して続行します」と開示)
  • 複数件 → AskUserQuestion (1問) で 1 件選ばせる (Phase B-2 と同様のバッチ方式)

選択後 meta 併置を確認:

SLUG=$(basename "$FILE" .md)
META="$(dirname "$FILE")/$SLUG.meta.json"
if [ ! -f "$META" ]; then
  echo "NO_META"
fi

NO_META が出力された場合、通常メッセージで以下を提示して skill 終了 (Phase G に遷移しない):

<$FILE> は team-builder 由来ではありません (meta.json なし)。
Phase I は meta.json のある構築記事専用です。frontmatter だけ編集したい場合は Phase C をご利用ください。

I-2: 編集項目を選択 (multiSelect)

AskUserQuestion (1問, multiSelect):

#質問headerオプションmultiSelect
1どの項目を編集しますか? (複数選択可)項目構築コンセプト(desc: concept 本文), ダメージ計算 追加(desc: /calc / pkdx damage --attach-team への案内), ダメージ計算 削除(desc: 既存の damage_calcs を除去), ポケモンメモ(desc: members[i].role の役割・型名メモ), 選出やりなおし(desc: 表選出 / 裏選出 の再入力)true

選択 0 件 → Phase G へそのまま遷移 (通常メッセージで「編集項目なしで終了」)。 選択 1 件以上 → 選んだ順 (I-3 → I-4 → I-5 → I-6 → I-7) で処理。

I-3: 構築コンセプト編集

I-3-1: 現状取得・提示
CUR_CONCEPT=$(jq -r '.concept // ""' "$META")

通常メッセージで提示:

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
54
Forks
113
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
blog-pkdxtools
Source
github.com/pkdxtools/pkdx