雨の降る午後に、手元のスキル置き場を開いて、フォルダを用途ごとに色分けしてみました。SEO の調査に使うもの、フロントエンドの実装に使うもの、サイトの運用に使うもの。分けているうちに、いちばん短い列がいちばんよく使う列なのだと気づきました。
数を数えた話はスキルを数えたら42本ありましたのほうに書き残しましたので、ここでは繰り返しません。今回お伝えしたいのは、数えたあとに何をするかです。
最初のうち、私は「束ねれば軽くなる」と思っておりました。結果は芳しくありませんでした。束ねても毎ターンの重さは1文字も減らなかったのです。それでも組み直してよかったと感じております。減ったのは重さではなく、手数のほうでした。
用途ごとに数え分けると、比率が見えてきます
数え方は前回と同じで、frontmatter の description の文字数を足すだけです。違うのは、足す前に用途で分けるところになります。
# group_skills.py — スキル置き場を用途ごとに数え分ける
import os
import re
import sys
BASE = sys.argv[1] if len(sys.argv) > 1 else os.path.expanduser("~/.claude/skills")
GROUPS = {
"seo": ["ai-seo", "seo-audit", "programmatic-seo", "content-research-writer"],
"web-front": ["vercel-optimize", "vercel-react-best-practices", "webapp-testing"],
"design": ["frontend-design", "implement-design", "web-design-guidelines"],
"site-ops": ["claudelab-site", "gemilab-site"],
}
def description_length(folder: str) -> int:
path = os.path.join(BASE, folder, "SKILL.md")
if not os.path.isfile(path):
return 0
text = open(path, encoding="utf-8", errors="replace").read()
front = re.search(r"^---\n(.*?)\n---", text, re.S | re.M)
if not front:
return 0 # frontmatter がない=説明を持たないフォルダ
field = re.search(r"^description:\s*(.*?)(?=^\w+:|\Z)", front.group(1), re.S | re.M)
if not field:
return 0
return len(" ".join(field.group(1).split()))
lengths = {d: description_length(d) for d in sorted(os.listdir(BASE))
if os.path.isdir(os.path.join(BASE, d))}
total = sum(lengths.values()) or 1
assigned = set()
print(f"{'group':<12}{'n':>4}{'chars':>8}{'share':>8}")
for name, members in GROUPS.items():
present = [m for m in members if m in lengths]
assigned.update(present)
chars = sum(lengths[m] for m in present)
print(f"{name:<12}{len(present):>4}{chars:>8}{chars / total * 100:>7.1f}%")
rest = [k for k in lengths if k not in assigned]
rest_chars = sum(lengths[k] for k in rest)
print(f"{'(rest)':<12}{len(rest):>4}{rest_chars:>8}{rest_chars / total * 100:>7.1f}%")
print(f"total {len(lengths)} folders / {total} chars")os.path.isfile で弾いている行と、frontmatter がないときに 0 を返している行は、どちらも実際に必要でした。私の棚には、中身が案内文だけになったフォルダが残っていて、そこで例外を吐いて止まったからです。
手元で走らせた結果が下の表になります。分類は自分の仕事の区切りに合わせたもので、正解があるわけではありません。
| グループ | 本数 | 説明文の文字数 | 全体に占める割合 |
|---|---|---|---|
| SEO の調査・監査 | 5 | 2,442 | 20.7% |
| フロントエンド実装 | 6 | 2,370 | 20.1% |
| デザイン・画像 | 9 | 2,416 | 20.5% |
| サイト運用 | 4 | 623 | 5.3% |
| 開発の進め方 | 7 | 1,145 | 9.7% |
| そのほか | 11 | 2,774 | 23.6% |
| 合計 | 42 | 11,770 | 100% |
ここで手が止まりました。ほとんど毎日開くサイト運用の一群は、説明文の総量では 5.3% しか占めていません。 残りの 94.7% は、その日にやらない仕事のための説明でした。棚が膨らんでいく感覚の正体はこれだったのかもしれません。
なお文字数はあくまで自分の環境を比べるための目盛りです。実際のトークン見積りは claude plugin details <name> が count_tokens API を使って出してくれますので、絶対値が要るときはそちらをご覧ください。
.claude-plugin/plugin.json を1枚置くと、フォルダの意味が変わります
Claude Code のスキル置き場は、三つの違うものを同じ木の下に置ける仕組みになっております。
| 置いてあるもの | 何として読み込まれるか |
|---|---|
<skills-dir>/foo/SKILL.md(マニフェストなし) | ただのスキル foo |
<skills-dir>/foo/.claude-plugin/plugin.json | プラグイン foo@skills-dir。スキル・エージェント・フックなどを束ねられます |
<plugin>/skills/bar/SKILL.md | プラグインの中に入ったスキル bar |
読み込まれる場所は二つです。~/.claude/skills/ に置けば個人スコープで、どのプロジェクトでも読み込まれます。<cwd>/.claude/skills/ に置けばプロジェクトスコープになり、そのフォルダのワークスペース信頼を受け入れたあとに読み込まれます。
足場は claude plugin init が作ってくれます。
# ~/.claude/skills/site-ops/ に足場を作る(skills と hooks の入れ物つき)
claude plugin init site-ops --with skills hooks
# 既存の足場を作り直すとき
claude plugin init site-ops --force作られる plugin.json は最小限で構いません。マニフェストそのものが省略可能で、必須の項目は name だけです。
{
"name": "site-ops",
"displayName": "Site Ops",
"version": "0.1.0",
"description": "4サイトの記事更新とデプロイまわりのスキル一式",
"author": { "name": "Masaki Hirokawa" },
"skills": "./skills/",
"hooks": "./hooks/hooks.json"
}平置きだったフォルダを移すと、こういう形になります。
~/.claude/skills/
├── site-ops/
│ ├── .claude-plugin/
│ │ └── plugin.json
│ ├── skills/
│ │ ├── claudelab-site/SKILL.md
│ │ ├── gemilab-site/SKILL.md
│ │ ├── antigravitylab-site/SKILL.md
│ │ └── rorklab-site/SKILL.md
│ └── hooks/
│ └── hooks.json
└── seo-kit/
├── .claude-plugin/
│ └── plugin.json
└── skills/
├── seo-audit/SKILL.md
└── programmatic-seo/SKILL.md
一群を移すときの順番は、次の4段階で固定しております。
- 移したいフォルダ名を控えてから
claude plugin init <name> --with skillsで足場を作ります - 元のフォルダを
<name>/skills/の下へそのまま移動します(SKILL.mdの中身は触りません) claude plugin validate ~/.claude/skills/<name>を実行して、frontmatter の壊れたファイルがないかを確かめます- Claude Code を開き直し、
claude plugin details <name>で Always-on の値を控えます
3 の validate は、通れば終了コード 0、落ちれば 1、パスが読めないなど検査そのものが失敗したときは 2 を返します。私は 4 で控えた値を、移す前の合計と並べて手元のメモに残すようにしております。
commands/ や agents/ や skills/ を .claude-plugin/ の中に入れないでください。.claude-plugin/ に入るのは plugin.json だけで、ほかは1つ上の階層に置きます。私はここを一度取り違えて、コンポーネントが1つも読み込まれないプラグインを作ってしまいました。