TL;DRと結論
Claude Codeに打ち直している4つの仕事は、それぞれ別のファイルに属します。リリースのチェックリストはskillです。絶対に破れないルールはPreToolUse hookです。多くのファイルを読む監査はsubagentです。Claudeが見えないデータはMCPサーバーです。
私たちは、34件のテストがある請求書APIという実際のリポジトリで4つすべてを試し、毎回まず間違った選択をしました。間違った選択は、画面上で失敗しました。
- CLAUDE.mdに入れたチェックリストは、/contextに
Memory files: 210 tokensの行を足し、毎ターン読み込まれました。 - 最初のhookは実行され、警告を出したのに、書き込みは通りました(unitPrice 8000から12000)。
- インラインで実行した監査はスレッドをあふれさせました。Messagesは19.3kトークンで、名前を呼んだsubagentでは7.2kでした。
- docsサーバーがないと、Claudeは「I couldn't find that setting... I won't guess one.」と答えました。
結論: docsの表から種類を選び、そのうえで、ファイルが存在するだけでなく答えを返すようにすることです。ログを残すだけのhookは、置き換えたCLAUDE.mdの1行と同じで、助言にすぎません。4つのうち、確認なしに動くのはhookだけです。
以下のファイルをコピーし、コミットして、いちばん打ち直しているものから始めてください。
出典つきの要約
最初の一言の前にセッションが読み込むもの。 私たちの基準値の/contextは26.2k/1mトークンで、メモリの行もMCPの行もありませんでした。追加する仕事は、この表に1行を足すか、足さないかのどちらかです。まずこれを読み、これを基準に測ってください。
CLAUDE.mdのルールはお願いで、hookは強制です。 docsによると、CLAUDE.mdやskillにある「never edit .env」のような指示はお願いであり、編集をブロックするPreToolUse hookが強制ですs1。MemoryのページはCLAUDE.mdを、強制ではなくcontextと呼んでいますs8。Hacker Newsのスレッドも、hook側から同じことを報告していました。0で終了するhookは何もブロックしませんs10。
トリガーの表が、どのファイルかを教えてくれます。 同じ手順を3回貼ったらskillです。出力がチャットをあふれさせるならsubagentです。Claudeが見えないタブがあるならMCPサーバーです。毎回必ず起きるべきことならhookですs1。
仕事1、リリースのチェックリストはskillです。
- CLAUDE.mdの中で育った手順はskillに属し、その本文は使われるときだけ読み込まれますs3。
- 私たちの実行では、CLAUDE.mdに貼ったチェックリストが
Memory files: 210 tokensの行を足しました。disable-model-invocation: trueつきのskillにすると、/contextは基準値に戻りました。 /releaseは6つのステップを実行しました(34件のテスト、0.4.0から0.4.1、タグv0.4.1)。git commit -amが追跡されていないファイルを飛ばすことにも気づき、新しいchangelogを先にステージしました。
仕事2、守るべきルールはhookで、保証はそれが返す答えです。
- 出力なしの終了コード0は、何も判断していません。終了コード2はブロックします。deny JSONもブロックしますs2。
- 最初のhookはログを残して0で終了し、書き込みは通りました(unitPrice 8000から12000)。Claudeの返答はhookに触れませんでした。
- 同じhookがdeny JSONを返すと、
PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).で編集が拒否されました。そのあとClaudeは、上書きするか、マイグレーションを書くかを尋ねました。 - docsは、保護対象ファイル向けのPreToolUseのレシピを公開していますs23。
- 限界: このhookはEditとWriteを監視します。シェルも覆うか、シェルをallowリストに入れないでおいてください。
onFailure: "block"は、失敗したhookをfail openではなくブロックにします。v2.1.295で追加されましたs2 s15。
仕事3、あふれる監査はsubagentです。
- subagentは何も知らない状態から始まり、要約を返しますs4。
- インラインではMessagesは19.3kでした。名前を呼んだsubagentでは7.2kでした。
- agentファイルだけでは、2回の実行のうち0回しか委任されませんでした。プロンプトで名前を呼ぶと委任されました。ファイルがそれを存在させ、プロンプトがClaudeにそれを使わせます。
- ファイルに
model:を固定してください。料金は変わりませんでした。読み込みは行われていて、ウィンドウの中で起きないだけです。
仕事4、Claudeが届かないデータはMCPサーバーです。
claude mcp add --scope projectは.mcp.jsonを書き、tool searchはツール名だけを読み込みますs5。- Claude Code自身のdocs向けに、無料でログイン不要のHTTPサーバーがあり、
search_claude_code_docsとquery_docs_filesystem_claude_code_docsを備えていますs24。 - それがないと、Claudeは「I couldn't find that setting... I won't guess one.」と答えました。あると、Claudeはv2.1.295で追加された
"onFailure": "block"と答えました。私たちのインストールは2.1.294でした。 - モデルがすでに知っているライブラリのdocsサーバーは、私たちには何も変えませんでした。モデルが持てないものをつないでください。
現場の報告。 あるRedditのスレッドは、ClaudeがCLAUDE.mdの「MUST use agent」の行を無視すると報告していましたs9。私たちはこれを測定ではなく、報告として引用します。Anthropicのエンジニアリング記事も、同じ分け方を扱っていますs6 s7。
コード
実行で使った4つのファイルと、2つの補助ファイルです。パスは、それぞれがあなたのリポジトリのどこに置かれるかを示しています。
1. releaseのskill、.claude/skills/release/SKILL.mdに置きます。/releaseと打ちます。disable-model-invocation: trueにより、起動できるのは自分だけになります。コミットとタグ付けを行う仕事にはこれが望ましい挙動です。
SKILL.md
---
name: release
description: Cut a patch release of ledgerly (typecheck, tests, version bump, changelog line, commit, tag).
disable-model-invocation: true
---
Run these steps in order and stop at the first failure:
1. `pnpm typecheck` must exit 0.
2. `pnpm test` must pass.
3. Bump the patch version in `package.json` (e.g. 0.4.0 -> 0.4.1).
4. Add one line at the top of `CHANGELOG.md` (create it if missing): `## vX.Y.Z - YYYY-MM-DD` followed by a one-line summary of the commits since the last tag.
5. `git commit -am "release vX.Y.Z"`
6. `git tag vX.Y.Z`. Never push.
2. データ保護のhook、.claude/settings.jsonに入れます。Claudeが読む理由つきでdenyの判定を返します。PATHにjqが必要で、インラインに収まるため、hook専用のスクリプトファイルは要りません。data/.*\.json$のパターンと理由は、自分のルールに合わせて変えてください。
settings.json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
}
]
}
]
}
}
3. conventions auditor、.claude/agents/conventions-auditor.mdに置きます。ツールは読み取り専用のものに限り、モデルは固定されています。規約のリストは、自分のREADMEに合わせて編集してください。
conventions-auditor.md
---
name: conventions-auditor
description: Use when asked to audit the code against the README conventions
model: sonnet
tools: Read, Grep, Glob
---
You audit ledgerly's source against README § Conventions:
- Money is always an integer number of cents. Never floats.
- Domain functions return new objects; they never mutate their input.
- Validation failures throw `ValidationError` (400), missing records `NotFoundError` (404), illegal state transitions `ConflictError` (409).
- A change of stored shape bumps `schemaVersion` and adds a migration in `src/store/migrations.ts`.
Read every file under `src/`. Return only the findings: one line per violation as `file:line - convention - what is wrong`, then borderline cases the same way, then one line with the count of files read. No file contents, no summary of clean files.
conventions-auditor.md on GitHub
4. MCPの設定、リポジトリのルートに.mcp.jsonとして保存します。claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcpが書き出す内容です。Claude Codeは、次のセッションで承認するまで、プロジェクトのサーバーをpendingと表示します。
mcp.json
{
"mcpServers": {
"claude-docs": {
"type": "http",
"url": "https://code.claude.com/docs/mcp"
}
}
}
補助ファイル。 ctx.shはヘッドレスのセッションで/contextを実行してトークンの行を出力するので、前後を比較できます。mcp-probe.shはHTTPのMCPのurlにinitializeとtools/listを送り、追加する前にサーバーが応答するかを確認します。
ctx.sh
#!/usr/bin/env bash
# Usage: ctx.sh <step> -> runs "/context" through run-session.sh, writes context-<step>.txt (honours STRICT_MCP / EXTRA_ALLOW)
C="$(cd "$(dirname "$0")" && pwd)"
"$C/run-session.sh" "$C/out/context-$1.json" "/context" >/dev/null
python3 -c "import json,sys;d=json.load(open(sys.argv[1]));print(d['result'])" "$C/out/context-$1.json" > "$C/context-$1.txt"
grep -E '^\*\*Tokens|^\| (System prompt|System tools|MCP tools|Memory files|Skills|Custom agents|Messages) ' "$C/context-$1.txt"
mcp-probe.sh
#!/usr/bin/env bash
# Usage: mcp-probe.sh <url> [tools/call json params] -> initialize + notifications/initialized + tools/list (+ optional tools/call) over streamable HTTP
U="$1"; H=$(mktemp)
curl -s -D "$H" -m 30 -X POST "$U" -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'
echo; SID=$(grep -i '^mcp-session-id:' "$H" | tr -d '\r' | cut -d' ' -f2); echo "session-id: ${SID:-none}"
HS=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream'); [ -n "$SID" ] && HS+=(-H "Mcp-Session-Id: $SID")
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
curl -s -m 30 -X POST "$U" "${HS[@]}" -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; echo
[ -n "${2:-}" ] && { curl -s -m 90 -X POST "$U" "${HS[@]}" -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":$2}"; echo; }
rm -f "$H"
月曜にやること
- 新しいセッションを開いて/contextを実行します。合計を書き留めます。それが基準値です。
- いちばんよく貼るチェックリストを選びます。
.claude/skills/<name>/SKILL.mdに移し、コミットやタグ付けを行うならdisable-model-invocation: trueを足して、/<name>で実行します。 - 絶対に破れないルールを1つ選び、
permissionDecision: "deny"を返すか、2で終了するPreToolUse hookに入れます。Claudeにそのルールを破らせてみて、編集が拒否されることを確認します。 - hookがログを残すだけなら、変更します。0で終了するhookは何もブロックしません。
- 多くのファイルを読む作業を1つ、
model:を固定した.claude/agents/<name>.mdに移します。Claudeが自分で選ぶようになるまで、プロンプトで名前を呼びます。 - Claudeが最近あなたのために推測した事実を1つ確認します。モデルより新しいなら、
claude mcp add --transport http <name> --scope project <url>でdocsサーバーを追加し、.mcp.jsonをコミットします。 - 設定に頼る前に、Claude Codeのバージョンを確認します。
onFailure: "block"には2.1.295が必要です。 -
git add -A && git statusを実行して、新しいファイルをコミットします。私たちの実行では、ちょうど4つでした。
さらに進むために
選び方カード。 1ページです。docsの行s1に、私たちの4つの仕事を書き込んだものです。このパックの主役は、上の4つのファイルとこのカードです。
| 種類 | 何をするか | 使う場面 | 私たちの仕事 | 私たちのファイル |
|---|---|---|---|---|
| CLAUDE.md | すべての会話で読み込まれる永続的なcontext | Claudeが2回間違える規約 | 手順ではなく1行 | なし |
| Skill | Claudeが使える指示とワークフロー | 同じ手順書を3回目に貼るとき | リリースのチェックリスト | .claude/skills/release/SKILL.md |
| Hook | イベントで起動するスクリプト | 聞かれることなく毎回起きてほしいとき | 保存データを手作業で編集しない | .claude/settings.json |
| Subagent | 要約した結果を返す独立したcontext | 脇の作業が会話をあふれさせるとき | 規約の監査 | .claude/agents/conventions-auditor.md |
| MCP | 外部サービスへの接続 | Claudeが見えないタブからデータを何度もコピーするとき | Claude Codeのdocs | .mcp.json |
| Plugin | 4つを1つのインストール単位にまとめる | 2つ目のリポジトリが同じ構成を必要とするとき | 使っていない | なし |
カードが決めるのは種類で、品質ではありません。 ログを残すだけのhook、名前を呼ばないsubagent、モデルが知っているdocsのためのサーバーは、どれも正しい種類に置かれながら失敗しました。種類を選び、そのうえでファイルが答えを返すようにしてください。
これらに頼らないほうがいい場面。
- Claudeが間違え続ける規約は、手順ではなく1行として、CLAUDE.mdに置いたままにします。
- 行き来しながら進め、自分の履歴を見ていたい仕事は、subagentではなくメインのスレッドに残します。
- モデルがすでに知っているライブラリのdocsサーバーは、contextを使うだけで何も変えません。
pnpm testはすでに手元にあるCLIです。MCPの出番ではありません。
Plugin。 pluginは、4つをまとめて届ける箱です。別のリポジトリが同じ構成を必要としたときに使ってください。
SkillとMCPの組み合わせ。 docsはこの2つを組み合わせています。サーバーが接続を提供し、skillがClaudeにそれを上手に使う方法を教えます。
context: forkつきのskill。 skillはフォークしたsubagentの中で動かせるので、読み込みがウィンドウの外に留まります。頼る前に、1つの仕事で試してください。
command以外のhookの種類。 私たちが使ったのはcommandです。hooksのリファレンスには、httpやpromptのhookなど、ほかのhandlerの種類も載っています。ルールは同じです。hookが返すものが判断を決め、黙っているhookは何も決めません。
MCPとCLI。 MCPサーバーがcontextを食うことをめぐる議論が続いています。CLIをまず使うべきだとする記事s11と、MCPサーバーのcontextコストを扱った別の記事s12があります。私たちのルールは、CLIがすでにその仕事をするならCLIを使うことです。MCPは、CLIがないものに使います。tool searchは今はツール名だけを読み込むので、決める前に自分の環境で/contextを使って測ってくださいs5。
skillは短く、検証済みに。 1つの仕事に短いskillを1つ、実際の実行で確かめます。SkillsBenchの論文s13と、skillが使われるまでほとんどコストがかからない理由についての実践者の見方s14を参照してください。
FAQ
hookは実行されたのに、なぜ編集は通ってしまったのですか? hookは、コマンドが返すものだけで判断します。出力なしの終了コード0は、何も判断していませんs2。deny JSONを返すか、2で終了してください。
subagentがあるのに、なぜClaudeは自分でファイルを読むのですか? 私たちの実行では、agentファイルだけでは、2回の実行のうち0回しか委任されませんでした。「use the conventions auditor subagent」のように、プロンプトで名前を呼んでください。
hookはClaudeにできることすべてを覆いますか? いいえ。私たちのhookはEditとWriteに一致します。後の実行で、Claudeはシェル経由で変更することを提案してきたので、シェルも覆うか、シェルをallowリストに入れないでおいてください。
使うライブラリごとにMCPサーバーを追加すべきですか? いいえ。モデルがすでにそのライブラリを知っているなら、サーバーは何も変えず、contextを使うだけです。モデルより新しいデータや、ディスクの外にあるデータのために追加し、インストール済みのバージョンを、その機能が追加されたバージョンと照らし合わせてください。
これらすべてをコミットしてかまいませんか?
はい。skill、設定ファイル、agentファイル、.mcp.jsonはすべてリポジトリの中にあるので、クローンした人は誰でも受け取れます。プロジェクトのMCPサーバーは、次のセッションで一度だけ承認してください。
AIDive