2026年春、育休から復帰したらAI戦国時代の真っ只中でした。
これまで離乳食の相談でAIを使っていましたが、「開発でAIって何ができるの?どこまで使えるの?」と、まさに浦島太郎状態。
調べているうちに、どうやらAIを真に使いこなすには「Skill(SKILL.md)」の定義が鍵を握ることに気づきました。
便利なプラグインも配布されていますが、まずは自分で動かしてみたい!
ということで、今回はClaude Codeの「Skill(SKILL.md)」を書き換えて、AIの動きがどう変わるのか実験してみようと思います。
Skill(SKILL.md)とは
公式ドキュメントはこちらです。
Skillとは、特定のタスクを実行するための「専門知識」や「手順」をパッケージ化して渡す機能です。
特定のタスクを毎回&長文で指示しなくても、一度SKILL.mdにルールを書いておけば、AIが必要な時に勝手に読み込んで、理想通りに作業をしてくれます。
(もっと詳しく知りたい方は、こちらの記事がおすすめです!)
スキル構造
すべてのSkillには、SKILL.mdファイルが必要です
---
name: your-skill-name
description: Brief description of what this Skill does and when to use it
---
# Your Skill Name
## Instructions
[Clear, step-by-step guidance for Claude to follow]
## Examples
[Concrete examples of using this Skill]
| フィールド | 必須か | 内容 |
|---|---|---|
| name | ⚪︎ | 最大64文字 予約語を含むことはできません:「anthropic」、「claude」 |
| description | ⚪︎ | 最大1024文字 Skillが何をするか、Claudeがいつそれを使用すべきかを記述する |
| Instructions | 「明確で段階的な指示」や「プロジェクトのベストプラクティス(ルール)」を記述する | |
| Examples | ユーザーがどんなデータを入力し、AIがどんな形式で回答を返すのが理想的か、その例を記述する |
仕組み
Skillはコンテキストを圧迫しないよう、必要に応じて段階的に読み込まれます。
-
レベル1: 起動時に常に読み込まれます。
SKILL.mdのnameとdescriptionだけ読み込み、コンテキストのメモリを圧迫せずに多数のSkillをインストールすることができます -
レベル2: トリガーされた時のみ読み込まれます。
SKILL.md内の手順知識、ワークフロー、ベストプラクティスなどが含まれます - レベル3以上: 追加の資料を必要に応じて読み込みます
一から作ってみる
理解を深めるためにSkillを自作します。
「JSONをTypeScriptの型に変換スキル」を作ってみようと思います。
前提条件:
- Claude Code: v2.1.145を使用
- チャットは
SKILL.mdを書き換える度リセットする
SKILL.mdを書いてみよう
スキル構造の通りに書いてみます。あえて簡潔に。
今回はプロジェクトの中に作成します。
---
name: json-to-ts-type
description: JSONが入力されたら、TypeScriptの型定義を行なう
---
# JSON to TypeScript Type
## Instructions
ユーザーがJSONを入力したら、TypeScriptの型定義を行なってください。
## Examples
[入力]
[ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ]
[出力]
```typescript
interface User {
id: number;
name: string;
}
```
使ってみよう
## 入力1
["Tom", "Alice", "Bob"]
## 出力1
> type Names = string[];
## 入力2
"Tom"
## 出力2
> type Name = string;
## 入力3
{
"post_id": "POST-789",
"author": {
"user_id": 42,
"role": "admin"
},
"comments": [
{
"comment_id": 101,
"text": "参考になりました!",
"likes": 5
},
{
"comment_id": 102,
"text": "質問があります。",
"likes": 0
}
],
"is_published": true
}
## 出力3
> interface Author {
user_id: number;
role: string;
}
interface Comment {
comment_id: number;
text: string;
likes: number;
}
interface Post {
post_id: string;
author: Author;
comments: Comment[];
is_published: boolean;
}
## 入力4
明日の天気は?
## 出力4
> 明日の天気はわかりません。天気予報サービス(tenki.jpや気象庁サイトなど)をご確認ください。
入力4であえてJSONではないプロンプトを送ってみましたが、意図通りの出力が返ってきています。
このSKILL.mdを元に、3つの実験をしてみます。
- 「トリガー(description)」の書き換え実験
- 「指示(Instructions)」の書き換え実験
- 「具体例(Examples)」の補完実験
「トリガー(description)」の書き換え実験
トリガー(description)が重要な理由は、「AIのメモリ節約」と「無駄な動作の防止」の2点です。
AIのメモリ節約: 前述しましたが、Claudeは起動時にSkillの詳細までは読まず、メタデータ(name、description)だけを読み込んでいます。これにより、メモリを圧迫することなく、大量のSkillを待機させておくことができます。
無駄な動作の防止: Claudeはdescriptionの意味を読み取り、「このSkillを今使うべきか」推論してトリガーを判断します。曖昧なdescriptionは誤発火の原因になります。
これを踏まえて、まずは現在のSKILL.mdのままプロンプトを投げてみます。
## 入力1
配列にあと5つ追加して
["Tom", "Alice", "Bob"]
## 出力1
> ["Tom", "Alice", "Bob", "Emma", "Liam", "Olivia", "Noah", "Sophia"]
## 入力2
["Tom", "Alice", "Bob"]
## 出力2
> ["Tom", "Alice", "Bob", "Emma", "Liam", "Olivia", "Noah", "Sophia"]
## 入力3
["Tom", "Alice", "Bob"]
## 出力3
> type Names = string[];
## 入力4
"配列にあと3つ追加して"
## 出力4
> ["Tom", "Alice", "Bob", "Emma", "Liam", "Olivia"]
入力2はJSONだけを入力して型定義が返ってくることを期待していましたが、入力1と同じ配列が返ってきました。
入力3は、入力2と同じプロンプトでしたが、型定義が返ってきました。
セッション内の文脈が影響している可能性があります。
入力4もJSONなので型定義が返ってくることを期待していましたが、JSONではなく指示と認識されてしまい、配列が返ってきました。
「JSON」という1つの条件だけではうまくトリガーできていないので、「型定義」という合言葉を追加します。
- description: JSONが入力されたら、TypeScriptの型定義を行なう
+ description: 「型定義」とJSONが入力されたら、TypeScriptの型定義を行なう
## 入力1
配列にあと5つ追加して
["Tom", "Alice", "Bob"]
## 出力1
> ["Tom", "Alice", "Bob", "Emma", "Liam", "Olivia", "Noah", "Sophia"]
## 入力2
型定義
["Tom", "Alice", "Bob"]
## 出力2
> type Names = "Tom" | "Alice" | "Bob";
または、文字列配列として扱う場合:
type Names = string[];
const names: Names = ["Tom", "Alice", "Bob"];
## 入力3
型
["Tom", "Alice", "Bob"]
## 出力3
> type Names = string[];
## 入力4
型定義
"配列にあと3つ追加して"
## 出力4
> 3つ追加した配列:
["Tom", "Alice", "Bob", "Charlie", "Diana", "Eve"]
型定義:
type Names = string[];
出力2, 4で型定義以外が返ってきているのが気になりますね。
descriptionの「型定義を行なう」という表現が曖昧なのかもしれません。
入力3は「型」しか入力していないのでSkillはトリガーされないと思いきや、型定義してくれました。
合言葉を入力していない場合の指示も必要そうです。
- description: 「型定義」とJSONが入力されたら、TypeScriptの型定義を行なう
+ description: ユーザーが「型定義」かつJSONを入力したら、TypeScriptの型定義のみ行なう。「型定義」と入力していない場合は、型定義を行わない
## 入力1
配列にあと5つ追加して
["Tom", "Alice", "Bob"]
## 出力1
> ["Tom", "Alice", "Bob", "Emma", "Liam", "Olivia", "Noah", "Sophia"]
## 入力2
型定義
["Tom", "Alice", "Bob"]
## 出力2
> type Names = string[];
## 入力3
型
["Tom", "Alice", "Bob"]
## 出力3
> 「型定義」と入力した場合のみ型定義を行います。
## 入力4
型定義
"配列にあと3つ追加して"
## 出力4
> type Message = string;
## 入力5
型定義して
"配列にあと3つ追加して"
## 出力5
> type Message = string;
## 入力6
この型ってなに?
"型定義"
## 出力6
> JSON の string 型です。
## 入力7
"型定義"
## 出力7
> JSON の string 型です。
入力6, 7でややこしいプロンプトを投げましたが、AIは引っかからずに意図通りの出力を返してくれました。
実験結果:
想定外のトリガーを防ぐには、
- 限定語を使って厳密に書く
- 合言葉+入力形式の2条件以上設定した方がいい
- 条件を入力していない場合も制御する
※合言葉でなくても、コマンドでスキルの呼び出し可能です。
今回はdescriptionの挙動を探る実験だったので、あえて触れませんでした。
「指示(Instructions)」の書き換え実験
指示(Instructions)が重要な理由は、「ドメインの専門家にするため」と「作業マニュアルを与えるため」と「無駄をなくすため」の3点です。
ドメインの専門家にするため: ベストプラクティスを提供することで、汎用エージェントから「ユーザー独自のルールを熟知した専門家」へとカスタマイズできます
作業マニュアルを与えるため: AIが適当に作業するのを防ぎ、ユーザーが望む通りに正確に動くようになります
無駄をなくすため: 複数の会話に渡って、同じような長文プロンプトを投げる必要がなくなります。
早速SKILL.mdを書き換えてみましょう。
出力のルールを提供します。
## Instructions
- ユーザーがJSONを入力したら、TypeScriptの型定義を行なってください。
+ 以下のルールを必ず厳守してください:
+ 1. **型名:** 必ず「パスカルケース(PascalCase)」で命名すること。
+ 2. **プロパティ名:** 元のJSONキーがスネークケース(例: `user_id`)であっても、必ず「キャメルケース(例: `userId`)」に変換して出力すること。
## 入力1
型定義して
{
"user_id": 101,
"first_name": "Alice",
"is_premium_member": true
}
## 変更前の出力
> interface User {
user_id: number;
first_name: string;
is_premium_member: boolean;
}
## 変更後の出力
> interface User {
userId: number;
firstName: string;
isPremiumMember: boolean;
}
変更前と変更後の出力を見比べると、型名はもともとパスカルケースだったので差分がありませんが、プロパティ名はキャメルケースになりました。
ルールを追加してみます。
## Instructions
+ 3. **nullの処理:** JSONの値に `null` が含まれるプロパティは、必ずユニオン型を用いて `型 | null` と定義すること。
+ 4. **欠損(undefined)の処理:** 配列内の複数のオブジェクトを比較し、プロパティ自体が存在しない(省略されている)場合があるものは、必ずオプショナルプロパティ(例: `propertyName?: string;`)として定義すること。
## 入力1
型定義して
[
{
"userId": 1,
"nickname": "Alice",
"bio": "Hello! I am Alice."
},
{
"userId": 2,
"nickname": null
}
]
## 変更前の出力
> interface User {
userId: number;
nickname: any;
bio?: string;
}
## 変更後の出力
> interface User {
userId: number;
nickname: string | null;
bio?: string;
}
変更前はnicknameの型がanyでしたが、変更後はユニオン型の型 | nullで返ってきました。
欠損していたbioはもともとオプショナルプロパティ(?)を使って返ってきていたので、差分はありませんでした。
実験結果:
当初の「型定義を行なってください」という指示だけでも、Claudeは高精度の型定義を返してくれていました。
ただ、実務レベルで出力のブレを無くし、常に期待通りのフォーマットで返してもらうためにも、日々のアップデートが重要そうです。
「具体例(Examples)」の補完実験
具体例(Examples)が重要な理由は、「出力のブレをなくすため」と「暗黙のルールを読み込ませるため」の2点です。
出力のブレをなくすため: AIはたまに気を利かせて余計な文章を足したり、指定したフォーマットを崩してしまうことがあります。明確な「完成見本」を提示しておくことで、毎回期待通りの出力が返ってくるようになります。
暗黙のルールを読み込ませるため: わざわざ Instructionsで長文の指示を書かなくても、Examplesの出力例にコメント付きのコードを載せておけば、AIが空気を読み、指示以上の丁寧なアウトプットを自動的にしてくれるようになります。
早速SKILL.mdを書き換えてみましょう。
業務でJSDocを書く機会が多いため、追加します。
## Examples
[出力]
```typescript
+ /**
+ * データ情報のインターフェース
+ * @property id ユーザーの識別子
+ * @property name ユーザーの名前
+ */
interface User {
## 入力1
型定義して
{
"user_id": 101,
"first_name": "Alice",
"is_premium_member": true
}
## 変更前の出力
> interface User {
user_id: number;
first_name: string;
is_premium_member: boolean;
}
## 変更後の出力
> /**
* ユーザー情報のインターフェース
* @property userId ユーザーの識別子
* @property firstName ユーザーの名
* @property isPremiumMember プレミアム会員かどうか
*/
interface User {
userId: number;
firstName: string;
isPremiumMember: boolean;
}
意図通りの出力です。
AIが本当に空気を読めるのか試してみましょう。
実験のため、型Userを複数形に書き換えてみます。
## Examples
- interface User {
+ interface Users {
## 入力1
型定義
{
"product_id": "PRD-999",
"name": "ワイヤレスイヤホン",
"price": 15000,
}
## 変更前の出力
> // JSDoc省略
interface Product {
productId: string;
name: string;
price: number;
}
## 変更後の出力
> // JSDoc省略
interface Product {
productId: string;
name: string;
price: number;
}
複数形Productsの型が返ってくることを期待していましたが、単数形で返ってきました。
AIが拾ってくれなかったルールはInstructionsに追記した方が良さそうです。
## Instructions
+ 5. **型名の命名規則:** 生成するTypeScriptの型名は、必ず「複数形」で命名すること(例: `User` ではなく `Users`、`Task` ではなく `Tasks`)。
## 入力1
型定義
{
"product_id": "PRD-999",
"name": "ワイヤレスイヤホン",
"price": 15000,
}
## 変更後の出力
> // JSDoc省略
interface Products {
productId: string;
name: string;
price: number;
}
複数形Productsで返ってきました!
実験結果:
AIが推測しきれなかったルールは、適宜Instructionsへ明文化する必要がありそうです。
まとめ
SKILL.mdの各セクションにはそれぞれ異なる役割があり、
-
description(トリガー)は、条件が曖昧だと意図しないタイミングでSkillがトリガーされてしまうため、合言葉+入力形式の2条件を組み合わせて、厳密に書いた方がいい -
Instructions(指示)は、最初から完璧な指示を書く必要はないけど、使いながら想定外の出力が返ってきた時に都度ルールを追記していく運用がいい -
Examples(具体例)で拾えないルールもあるため、その場合はInstructionsへ明文化する
SKILL.mdは一度書いたら終わりではなく、日々アップデートして育てていくものだと実感しました。
完成したSKILL.md
---
name: json-to-ts-type
description: ユーザーが「型定義」かつJSONを入力したら、TypeScriptの型定義のみ行なう。「型定義」と入力していない場合は、型定義を行わない
---
# JSON to TypeScript Type
## Instructions
以下のルールを必ず厳守してください:
1. **型名:** 必ず「パスカルケース(PascalCase)」で命名すること。
2. **プロパティ名:** 元のJSONキーがスネークケース(例: `user_id`)であっても、必ず「キャメルケース(例: `userId`)」に変換して出力すること。
3. **nullの処理:** JSONの値に `null` が含まれるプロパティは、必ずユニオン型を用いて `型 | null` と定義すること。
4. **欠損(undefined)の処理:** 配列内の複数のオブジェクトを比較し、プロパティ自体が存在しない(省略されている)場合があるものは、必ずオプショナルプロパティ(例: `propertyName?: string;`)として定義すること。
5. **型名の命名規則:** 生成するTypeScriptの型名は、必ず「複数形」で命名すること(例: `User` ではなく `Users`、`Task` ではなく `Tasks`)。
## Examples
[入力]
[ { "id": 1, "name": "Alice" }, { "id": 2, "name": "Bob" } ]
[出力]
```typescript
/**
* データ情報のインターフェース
* @property id ユーザーの識別子
* @property name ユーザーの名前
*/
interface Users {
id: number;
name: string;
}
```