Claude Media
Claude Codeモノレポのテスト戦略をSKILL.mdで教える手順

Claude Codeモノレポのテスト戦略をSKILL.mdで教える手順

モノレポの各パッケージにテスト規約を教えるには、CLAUDE.mdでなくパッケージ単位のSKILL.mdが向きます。4節の書き方からpathsでの自動発火、可視範囲対策、プラグイン配布までを手順化します。

Claude Codeがモノレポのテスト規約を外すのはなぜか

packages/apiはsupertestでHTTPアサーションを書き、DBテストは必ずトランザクションでロールバックする。packages/webはまた別の道具立てでコンポーネントをテストする。モノレポではパッケージごとにテストの作法が違うのが普通です。この作法がパッケージ単位で伝わっていないと、Claude Codeはそれを知りようがありません。rawなfetchでHTTPを叩いたり、ロールバックを忘れたテストになったりするのは、その状態の典型です。

原因は情報の置き場所です。CLAUDE.mdは起動時に常に読み込まれる分、内容を絞らざるを得ません。パッケージ固有のテスト規約のような細部を書き込むと、無関係な作業でも毎回読み込まれてコンテキストを圧迫します。Claude Codeの公式ガイドlarge-codebasesは、この種の細部をSkillsに切り出すことを勧めています。Skillは名前と説明だけが常時ロードされ、本文はClaudeが関連すると判断したときだけ読み込まれます。

本記事は、パッケージ単位のSKILL.mdでテスト規約を教える書き方から、発火の絞り込み、書き間違いの検出、実際に使われているかの確認までを順に進めます。モノレポのCLAUDE.md階層設計そのものはClaude Codeモノレポ設計で扱っているので、権限スコープやworktreeの絞り込みはそちらを参照してください。

始める前に確認すること

SKILL.mdでテスト規約を教える対象は、パッケージごとに違うテストの書き方です。全パッケージ共通のルール(コミットメッセージの書式、PRレビューの通し方など)はリポジトリルートのCLAUDE.mdかルートのSkillに置いたままにします。パッケージ固有かどうかの見分けが、置き場所を決める最初の判断です。

もう一つの前提は起動場所です。Claude Codeをどのディレクトリから起動するかで、後述する可視範囲の挙動が変わります。packages/api/から起動する運用が主なのか、リポジトリルートから起動して複数パッケージを横断する運用が主なのかを先に確認しておくと、次の手順で迷いません。

手順1: SKILL.mdの4節でパッケージのテスト規約を書く

パッケージのディレクトリ内に.claude/skills/を作り、その下にSKILL.mdを置きます。

mkdir -p packages/api/.claude/skills/api-testing

packages/api/.claude/skills/api-testing/SKILL.mdの中身は、テストを書く判断のたびに必要になる情報を4節に分けて書きます。

---
name: api-testing
description: Testing patterns for the API package. Use when writing or modifying tests in packages/api/.
---
 
## Test structure
 
Tests are in `src/__tests__/` mirroring the `src/` directory structure.
Each route file has a corresponding `.test.ts` file.
 
## Running tests
 
- All tests: `npm test`
- Single file: `npm test -- src/__tests__/routes/users.test.ts`
- Watch mode: `npm test -- --watch`
 
## Test utilities
 
- `src/__tests__/helpers/db.ts`: provides `setupTestDb()` and `teardownTestDb()` for database tests
- `src/__tests__/helpers/auth.ts`: provides `createTestUser()` and `getAuthToken()` for authenticated endpoints
 
## Patterns
 
- Use `supertest` for HTTP assertions, not raw fetch
- Always wrap database tests in a transaction that rolls back
- Mock external services in `src/__tests__/mocks/`
4節

SKILL.mdの4節がそれぞれ答える問い

  • Test structure

    テストファイルをどこに置くか。

  • Running tests

    全体・単一ファイル・watchをどのコマンドで回すか。

  • Test utilities

    DBや認証のセットアップに使い回すヘルパー関数は何か。

  • Patterns

    使う道具(supertest)と、やってはいけないこと(rawなfetch、ロールバック忘れ)は何か。

この4節があれば、Claude Codeは新しいテストを書くときにディレクトリを推測せず、実行コマンドをpackage.jsonから探し回る必要もありません。packages/web/には同じ形で別のcomponent-patternsスキルを置き、フロントエンドのテスト作法を教えます。packages/api/で作業するときはapi-testing、packages/web/ではcomponent-patternsが読み込まれ、互いの規約は混ざりません。ルートとpackages/web/に同名のtestスキルがあっても、/testはルートのスキル、/packages/web:testはpackages/web/のスキルを単独で実行します。

規約を書くだけでなく、実行コマンドの承認プロンプトを毎回出したくない場合はallowed-toolsフロントマターを足します。

allowed-tools: Bash(npm test *)

このスキルが呼ばれたターンの間だけnpm test系のBash実行が許可プロンプトなしで通ります。許可はメッセージを送るたびにクリアされるため、セッション全体で許可したい場合は.claude/settings.jsonのallowルールに書くほうが向いています。

手順2: pathsフロントマターでテストファイルだけに自動発火させる

上のSKILL.mdはdescriptionの文面だけでClaudeの関連判断に頼っています。これをテストファイルに触れたときだけ確実に発火させたいなら、pathsフロントマターでグロブパターンを指定します。

---
name: api-testing
description: Testing patterns for the API package. Use when writing or modifying tests in packages/api/.
paths: "packages/api/src/**/*.test.ts,packages/api/src/__tests__/**"
---

pathsはカンマ区切りの文字列かYAMLリストで指定します。省略したスキルはdescriptionだけを頼りにClaudeが関連性を判断し、指定すると、一致するファイルを扱うときだけ自動で読み込まれます。配置場所とパターンのどちらで絞るかは、そのスキルが1パッケージ専用か、複数パッケージで共有されるかで決まります。

くらべる

スキルの対象を絞る2つの方法

1パッケージ専用

配置場所で絞る

packages/api/.claude/skills/に置きます。packages/api/の中や下から始めたセッションで一覧に載り、ルートから始めたセッションでは、そこのファイルに触れた時点で読み込まれます。

複数箇所で共有

paths で絞る

ルートの.claude/skills/に置き、paths: "**/migrations/**"のようにパターンだけで対象を決めます。パッケージの外に同じ種類のファイルが散らばっていても1本で足ります。

手順3: 起動場所ごとの可視範囲とdescription短縮に備える

Claude Codeがどのスキルを候補に挙げるかは、起動場所で変わります。

起動場所見えるSkills
packages/api/から起動見えるSkillsそのディレクトリと全ての祖先ディレクトリ、user/enterpriseレベル
リポジトリルートから起動見えるSkillsルートのSkillsに加え、セッション中に実際に触れた全サブディレクトリのSkills
--add-dirで兄弟ディレクトリを追加見えるSkillsそのディレクトリのSkillsも読み込み対象に含まれる(additionalDirectories設定はファイルアクセスのみを許可しSkillsは読み込まない点に注意)

ルートから起動した直後は、サブディレクトリのスキルは一覧にも/メニューにも出ません。そのディレクトリのファイルをClaudeが初めて読むか編集したときに読み込まれ、以後はセッションの間使えます。先に読み込みたいときは、サブディレクトリを指定した/add-dirが使えます(v2.1.257以降)。

数百パッケージ規模のモノレポをルートから触り続けると、見えるスキルの一覧が数百件に膨れます。一覧は予算を超えると、呼び出し頻度の低いスキルから説明文が削られ、名前だけが残ります。削られた説明にはClaudeが依頼文と照らし合わせるキーワードも含まれます。

数字

スキル一覧の予算を決める数字

  • 一覧全体の予算

    1%

    モデルのコンテキストウィンドウに対する割合

  • 1スキルの上限

    1,536字

    description と when_to_use の合計

  • 予算を2%にする設定

    0.02

    skillListingBudgetFraction に指定する値

一覧が予算を超えると、--debug のデバッグログに警告が出ます

対策は4つあります。descriptionとwhen_to_useの冒頭に最重要の用途を書き、途中で切られても要点が残るようにします。複数パッケージで共有するスキルはルートに集約して重複を減らします。skillListingBudgetFractionで予算を引き上げるか、使用頻度の低いスキルをskillOverridesで"name-only"にして説明なしで載せる手もあります。1スキルの上限はskillListingMaxDescCharsで変えられ、上げると長い説明を保てる代わりに毎ターンのコンテキストが増えます。現状は/doctorで一覧のコンテキスト消費量と主な内訳が分かります。

手順4: 複数リポジトリに配るならプラグイン化する

api-testingのようなパッケージ専用スキルはリポジトリ内に置くだけで十分ですが、PRレビュー規約やデプロイ手順のように複数リポジトリで共有したいテスト運用ルールはプラグインにまとめます。スキルはプラグインのskills/ディレクトリに置きます。

プラグイン化すると名前空間がplugin-name:skill-nameの形に切られるため、パッケージ単位で置いたスキルと名前が衝突しません。プラットフォームチームがテスト規約を1か所で更新し、各リポジトリはプラグインの更新を取り込むだけで最新の規約に追従できます。プラグインは各リポジトリのコミット履歴とは別にバージョンを持てるので、規約の変更履歴も1か所で追えます。

SKILL.mdの書き間違いを検出できるか

発火しないスキルの原因が、frontmatterの書き間違いということもあります。claude plugin validateはスキルのfrontmatterを検査できます。v2.1.289で、2種類のスキルを置いた一時ディレクトリを--strict付きで検査した結果は次のとおりです。1つはdescriptionの引用符を閉じ忘れたもの、もう1つはpathsをpathと書き間違えたものです。

claude plugin validate --strict packages/api/.claude/skills

引用符を閉じ忘れたスキルは、次のエラーで終了コード1になりました。

Validating skill: packages/api/.claude/skills/unterminated/SKILL.md
 
✘ Found 1 error:
 
  ❯ frontmatter: YAML frontmatter failed to parse: YAML Parse error: Unexpected EOF. At runtime this skill loads with empty metadata (all frontmatter fields silently dropped).
 
✘ Validation failed

実行時にはfrontmatterが丸ごと空として扱われ、descriptionもpathsも効かなくなるという内容です。一方、pathと書き間違えたスキルだけを置いた場合は、--strictでも✔ Validation passedで終了コード0でした。未知のキーはYAMLとして正しいので、pathsのつもりで書いた絞り込みは無効のまま通ってしまいます。

api-testingフォルダそのものを渡すと、No manifest found in directoryというエラーになります。渡すのはスキルフォルダを束ねたskillsディレクトリです。YAMLの壊れはvalidateが教えてくれますが、キー名の綴りは自分の目で見る必要があります。

教えた規約が実際に使われているか確認する

SKILL.mdを書いただけでは、発火しているかは分かりません。OpenTelemetryのログエクスポーターが出すclaude_code.skill_activatedイベントで確かめます。設定の全体像はOpenTelemetryの記事にあります。

手順

スキルの発火を記録する

  1. 1

    テレメトリーを有効にする

    CLAUDE_CODE_ENABLE_TELEMETRY=1を設定し、OTEL_LOGS_EXPORTERにotlpかconsoleを指定します。

  2. 2

    スキル名を伏せ字にしない

    OTEL_LOG_TOOL_DETAILS=1を設定します。未設定だと、自作スキルのskill.nameはcustom_skillという固定の値になります。

  3. 3

    イベントの属性を読む

    skill.nameにスキル名、skill.sourceに読み込み元(projectSettingsなど)が入ります。invocation_triggerはuser-slash(手動起動)・claude-proactive(Claudeの自動判断)・nested-skill(別スキルからの呼び出し)のどれかです。

これらの変数は、リポジトリの.claude/settings.jsonのenvに書いても無視されます。リポジトリ側でテレメトリーを有効にされないためです。ただし、エクスポーターをnoneにする値やOTEL_LOG_USER_PROMPTSなどを0にする値のように、オフ側へ倒す設定だけは効きます。設定場所はシェルか~/.claude/settings.json、組織ならmanaged settingsです。

1〜2週間分のイベントを集めると、書いたテスト規約スキルが発火しているか、手動起動かClaudeの自発判断かが分かります。まったく発火しないスキルは、descriptionのキーワードが実際の依頼文と噛み合っていないか、不要なスキルです。OpenTelemetryの基盤がない環境では、/skill-doctorが各スキルのコンテキストコストと使用回数を一覧にし、一度も呼ばれていないスキルを示します(v2.1.252以降)。

CLAUDE.mdとSkillの使い分け早見表

パッケージのテスト情報をどこに書くかは、情報の性質によっておすすめ度が変わります。

情報の性質置き場所理由
全パッケージ共通のコミット規約置き場所ルートのCLAUDE.md理由常に必要で読み込みコストが小さい
パッケージ固有のテスト構造・実行コマンド置き場所パッケージ単位のSKILL.md理由該当パッケージの作業時だけ読み込みたい
複数リポジトリで共有するデプロイ手順置き場所プラグインのSkill理由バージョン管理と配布を一元化したい
ファイルパターンだけで絞りたい規約(マイグレーション等)置き場所ルートのSKILL.md + paths理由パッケージをまたいで散らばるファイルを1本で拾える

よくあるつまずき

スキルが発火しない: descriptionが実際にユーザーが打ちそうな言葉を含んでいるか確認します。「packages/apiでテストを書くとき」のような具体的な文言が、抽象的な「テスト規約」よりも一致しやすくなります。YAMLの壊れはclaude plugin validate .claude/skillsで見つかりますが、キー名の綴り違いは通ります(「SKILL.mdの書き間違いを検出できるか」の節)。

逆にスキルが発火しすぎる: descriptionを具体的にするか、手動起動だけにしたいならdisable-model-invocation: trueを付けます。この設定はサブエージェントへの事前ロードやスケジュールタスクからの起動も同時に止めます。

モノレポで意図しないスキルが読み込まれる: リポジトリルートから起動するセッションほど、セッション中に触れたサブディレクトリのSkillsが積み上がります。パッケージ横断の作業をしないセッションは、可能な限りパッケージ内から起動するほうが読み込まれるスキルの数を抑えられます。

まとめ

置き場所は、その情報を必要とする作業の範囲で決まります。全パッケージが毎回使うルールはルートのCLAUDE.md、1パッケージのテスト作法はそのパッケージのSKILL.md、複数リポジトリで使う手順はプラグインです。書いた規約が読まれているかは、skill_activatedイベントか/skill-doctorで見ます。

この記事を共有:XはてブLinkedIn