はじめに
Claude Codeには「Skill」という仕組みがあります。特定のタスクに特化した知識やチェックリストをSKILL.mdというMarkdownファイルにまとめておき、会話の内容に応じてClaude自身が「このタスクならこのSkillを読み込もう」と自動的に判断して呼び出す、というものです。
今回、Googleが公開している学習教材 Comprehensive Rust を題材に、Rustの中〜上級者向けトピック(所有権・並行性・async・unsafe・FFIなど)を扱うSkillを11本作成し、OSSとして公開しました。
この記事では、単に「Skillを移植しました」ではなく、LLM向けドキュメントとして何を意識して設計・運用したかにフォーカスしてまとめます。
Skillとは何か(前提)
Claude CodeのSkillは、以下のようなfrontmatter付きMarkdownです。
---
name: rust-ownership-and-lifetimes
description: Diagnose Rust borrow checker and lifetime errors, and choose between references, Cell/RefCell, Rc/Weak, and owned data. Use when rustc reports a borrow/lifetime error (E0502, E0499, E0597, E0106, "cannot borrow as mutable")...
source: |
Adapted from Comprehensive Rust (...), © Google LLC, CC-BY-4.0. ...
---
# 本文(Markdown)
ポイントはdescriptionフィールドです。Claudeはユーザーとの会話中、ツールを呼ぶ前に「使えそうなSkill一覧」を眺めて、descriptionの文面だけを根拠にロードするかどうかを判断します。つまり**descriptionは人間向けの説明文ではなく、LLMのルーティング用プロンプトそのもの**だということです。ここの設計を誤ると、内容がどれだけ良くても一生呼び出されないSkillになります。
コンセプト
1. 「トピック」ではなく「ジョブ」で切る
最初は「所有権について」「並行処理について」のようなトピック単位でSkillを作ろうとしましたが、それだと網羅性は高くなる一方で、実際の会話(=「このコンパイルエラーどう直す?」「dyn Traitとgenericsどっちがいい?」)に対してどのSkillを呼べばいいのか曖昧になります。
そこで各Skillを「タスクの種類(ジョブ)」で定義し直しました。
| Skill | 対応するジョブ |
|---|---|
rust-ownership-and-lifetimes |
borrow/lifetimeエラー(E0502, E0499, E0597, E0106)の診断 |
rust-api-design |
ライブラリの公開APIの命名・doc・std trait選定 |
rust-concurrency-sync |
Send/Syncエラー、チャネル選定、Arc<Mutex<T>>のデッドロック |
rust-async |
「並行のはずが直列」「.awaitでハング」「キャンセルで状態消失」 |
rust-newtype-and-raii |
newtypeで不変条件を強制、Dropでクリーンアップ保証 |
rust-typestate-and-tokens |
コンパイル時ステートマシン、権限トークン型 |
rust-polymorphism |
dyn Trait vs generics、継承設計の移植、trait sealing |
rust-unsafe-fundamentals |
生ポインタ、mutable static、union、unsafe extern "C"
|
rust-unsafe-soundness |
unsafe fnの健全性証明、MaybeUninit
|
rust-pinning |
Pin、Unpin、自己参照構造体 |
rust-ffi |
C/C++連携、文字列表現の不一致、bindgen vs cxx
|
descriptionにはあえてコンパイルエラーコード(E0502等)や具体的なキーワード(.await, Arc<Mutex<T>>, dyn Trait)を列挙しています。これはLLMが「今の会話の文脈にこのSkillが刺さるか」を判定する際の照合キーになるため、抽象的な一文で済ませず検索性の高い語彙を意図的に詰め込んでいます。
2. 姉妹Skill同士の自己解決(disambiguation)
rust-concurrency-syncとrust-asyncはどちらも「並行処理」を扱いますが、前者はOSスレッド、後者はasync/awaitという別物です。同様にrust-unsafe-fundamentals(基礎的な書き方)とrust-unsafe-soundness(健全性の証明)も紛らわしいペアです。
これらはdescriptionの語彙を意図的に分離し、実運用で約15個の代表的なプロンプトに対して「正しいSkillが選ばれるか/誤爆や無反応がないか」を手動でトリガーテストしました。結果として語彙だけで自己解決できることを確認しています。
3. 汎用Skillとの棲み分け
このリポジトリより前からrust-patterns(Rustのイディオム全般を扱う汎用Skill)が存在する前提で設計しています。各SKILL.mdの冒頭には次のような一文を必ず入れています。
For "which idiom should I default to," see the general
rust-patternsskill first. This skill is for when a borrow/lifetime/ownership decision is the task.
「一般的にどう書くか」は汎用Skillに任せ、「今まさにこのエラーを直す」「この設計判断をする」というピンポイントな深掘りだけをこちらが担当する、という役割分担を明示しています。これにより、汎用Skillとの内容重複を防いでいます。
4. 出典管理(Attribution)をfrontmatterに埋め込む
コンテンツはGoogleのComprehensive Rust(CC-BY-4.0、コード例はApache-2.0)からの翻案なので、各SKILL.mdのfrontmatterにどのcommit・どのpathから作ったかを機械可読な形で残しています。
source: |
Adapted from Comprehensive Rust (https://google.github.io/comprehensive-rust/),
© Google LLC, CC-BY-4.0. Code samples Apache-2.0. Source pin: 351fafa (2026-08-05).
Paths: src/{borrowing,lifetimes,memory-management,smart-pointers}/**,
src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/**
これにより、上流のCourseが更新された際に「このSkillはどの範囲が古くなっているか」を差分追跡できます。実際に、upstreamのdiffを検知するCI(scheduled workflow)を組み、比較結果が途中で切れていないかまでチェックしています(後述)。
工夫した点
LLM運用を前提にしたCIゲート
このリポジトリは「コード」ではなく「Claude Code向けコンテンツ」を配布するリポジトリなので、ユニットテストの代わりに以下のCIジョブを用意しています。
-
shellcheck:
install.sh/scripts/*.sh - skill-frontmatter: 各SKILL.mdの必須frontmatterキーと行数予算(200〜500行目安)を検証
-
install-list-sync:
install.sh --listの出力とskills/*ディレクトリの実体が一致するか -
link-check:
[[skill-name]]形式の相互参照や相対リンクの死link検出 -
markdown-lint: 見出し構造とコードフェンス言語指定(
markdownlint-cli2、ルールは意図的に絞る) -
install-smoke-test:
install.shの一覧表示・コピーインストール・symlinkインストールの実動作確認 - upstream-drift(scheduled): 上流Comprehensive Rustの該当pathに変更が入っていないか定期検知
特に地味に効いたのがskill-frontmatterチェックです。ある時点でrust-pinningのdescriptionが、Claude Codeのfrontmatterパーサー内でバッククォート内の「コロン+スペース」(`T: Unpin`のような記法)につまずいて、descriptionがタイトル文字列だけに切り詰められてしまうという実害が発生しました。これはSkill一覧に出てくる説明文が壊れるため、致命的にトリガー率を落とします。原因を特定した後、CIの正規表現チェックを「バッククォート内に限らずdescription値中の: を全て禁止」に強化し、再発を防止しました。
行数予算とファイル分割の基準
各SKILL.mdは137〜233行に収まっています。これは「1回のツール呼び出しでコンテキストに乗るボリューム」を意識した上限で、これを超える内容が必要な場合はreferences/*.mdに分割する方針をあらかじめ決めています(今回は全Skillがこの予算内に収まりました)。
Exercise-basedな品質評価の試み
上流のComprehensive Rustには演習問題(exercise.md)と解答(solution.md)がペアで存在するchapterがあります。11 Skillのうち、自己完結したコーディング演習を持つのは3 Skill分(rust-ownership-and-lifetimes、rust-unsafe-fundamentals、およびrust-concurrency-sync/rust-asyncが共有するsolutions.md)だけでしたが、これらについては次の2段階の評価パイプラインを試験導入しました。
- Skillの内容だけを見て解答を導出させる(solve-blind)
- 上流の公式solutionと突き合わせて判定する(judge-with-solution)
結果はdocs/eval/にレポート形式で残しています。残り8 Skillはprose/discussion-onlyの章から構成されているため、この方式が使えないという制約自体も明文化しています(「評価をサボった」のではなく「評価対象外である理由」を残すのが重要だと考えています)。
インストール体験
配布はシンプルなbashスクリプト一本です。
git clone https://github.com/takurot/rust-skills-comprehensive.git
cd rust-skills-comprehensive
# 現在のプロジェクトに全部入れる
/path/to/rust-skills-comprehensive/install.sh
# 一部だけ選んで入れる
./install.sh rust-ownership-and-lifetimes rust-async rust-pinning
# 常に最新内容を追従させたい場合はsymlink
./install.sh --symlink
--globalでユーザーレベル(~/.claude/skills/)、省略時はプロジェクトローカル(./.claude/skills/)にインストールされます。再実行しても既存installはスキップされる(--forceで上書き可)ので、CIやセットアップスクリプトに組み込んでも安全です。
まとめ
- Claude CodeのSkillは「トピック」ではなく「ジョブ」単位で切ると、LLMのルーティング精度が上がる
-
descriptionfrontmatterはLLM向けの検索インデックスとして書く(エラーコードやAPI名を具体的に列挙する) - コンテンツリポジトリでも、frontmatter検証・リンク切れ検出・upstream追従といった「LLM運用ならではのCIゲート」を用意する価値は大きい
- Skillの正しさより先に「そもそも呼ばれているか」を疑う
Rustの中〜上級トピックでClaude Codeを使っている方はぜひ試してみてフィードバックをいただけると嬉しいです。
- 元ネタ: Comprehensive Rust(Google LLC, CC-BY-4.0)