Claude Modは、もはやHooksではない。
Claude Mod、ようやく正式にリリースされましたね。
私は正式リリース前から、個人的に利用していました。
これは元々、Function Hook という名前で開発されていましたが、
リリース時の名称は Mod でした。
この記事の内容は Claude Code v2.1.291で動作を確認しています。
Mod はv2.1.287以降、デフォルトで有効になっています。
なぜ名前が変わったのかについては私は詳しく知らないのですが、
ただ一つ、断言できることとしては、
これはHookとは明らかに一線を画したものであるということです。
なぜ「一線を画している」と言えるのか
そもそも Hook とはなんだったか
まず、Claude Code の Hook がどのような機能だったのかおさらいしておきましょう。
code.claude.com
公式ドキュメントにはこのように書かれています。
Hooks はユーザー定義のシェルコマンドです。Claude Code はそのライフサイクルの特定のポイントで実行され、決定論的な制御を提供します。LLM が実行を選択するのに依存するのではなく、特定のアクションが常に発生します。Hooks を使用して、プロジェクトルールを適用し、反復的なタスクを自動化し、Claude Code を既存のツールと統合します。
ライフサイクルの中で、ただ一個のコマンドを実行して必要な制御を挟むという機能なわけです。
!
ここまで commandHookの話しかしていませんが、他にも Hook には次の種類があります。
promptHook
agentHook
HTTPHook
これらを、次の理由で本記事では対象としません。
この記事のテーマがブレる
promptHookと agentHookは、結果が安定しないが故に確実な制御を行えない
HTTPHookは個人的にナレッジを持っていないため記述ができない
Mod は Hook とどう違うか
code.claude.com
では、Mod とはどのようなものなのでしょうか。
公式ドキュメントにはこのように書かれています。
mod は Claude Code の外観と動作を変更する プラグイン です。JavaScript または TypeScript のイベントハンドラで構成されています。Claude Code はイベント(ツール呼び出し、送信されたプロンプト、インターフェイスの一部が描画されるなど)が発生したときにハンドラを呼び出し、ハンドラはイベントを監視したり、変更したり、引き継いだりできます。mod を使用して、各リクエスト後のコンテキストの満杯度をチャートするペインなど、Claude Code に独自の機能を追加します。mod のファイルと完全な例については、mod の仕組み を参照してください。
外観と動作を変更する
Claude Codeに独自の機能を追加
という、明らかにこれまでの Hook では不可能だった内容が記載されています。
つまり、これは Claude Code をベースとして、
自分専用の Coding Agent CLI を作れる機能です。
Hook はライフサイクルの途中でイベントに介入するだけでしたが、
Mod はプログラムで Claude Code 自体を変えられます。
この違いが、Mod を Hook と一線を画した機能にしています。
!
公式ドキュメントは、Mod のハンドラも hook と呼び、設定ファイルに書く従来の Hook を settings hook と呼んで区別しています。
本記事では、従来のものを Hook、Mod のものを ハンドラ と書きます。
実は Mod はすでに組み込まれ、使用されている
Mod という機能は、すでに内部的に使用されています。
例えば /diff、コードの差分を Claude Code の画面右側に表示するというものです。
他にも、直近に追加された、AGENTS.md の読み込み機能も、現在、Mod の機能を
通して、提供されています。
つまり、これは機能を追加する Mod なのです。
某有名3Dサンドボックスアドベンチャーゲーム界隈で追加アイテムや敵キャラクターを
追加する、あの Mod のように、独自の機能を追加する機能が公式から提供されました。
公式もガッツリ使用しています。
公式が内部で使っているため、仕組みとしては安定していると考えられます。
ただし、Mod を信頼してよいかどうかは別の話です。
公式ドキュメントは、Mod があなたの権限で動くコードであり、ファイルの読み書き、プロセスの起動、ネットワーク通信ができると警告しています。
Mod はサンドボックス化されておらず、ツール呼び出しを事前承認したり、PreToolUseHookがブロックした呼び出しを通したりできます。
そのため、信頼できる作者とマーケットプレイスの Mod だけをインストールしてください。
社内で配布する場合も、中身をレビューしてから入れてください。
実装してみる
そこからは、具体的にどのような機能を付けたいかによって、書く内容が変わってきます。
今回は、僕個人の課題を解決するため、以下の課題解決を達成する Mod を作成し、
具体例として提示します。
項目
内容
課題
/goal を使用して長時間のセッションになると、コンテキストがガンガン
積み重なっていき、アウトプット品質の低下と、早期にレートリミットに到達してしまう
要求
/goal を指定したプロンプトを受け取った時、行うタスクの全体像を設計し、それらを
段階的に分解し、それぞれを達成するごとに compaction する
要件と、ロジックの流れ
となると、要件はこうなるでしょう。
/goal コマンドの引数となるプロンプトを正常に受け取れること
Claude がタスクを分割し、複数の TODO を作成できること
Claude が順に TODO を解消していくこと
TODO を一個解消したら compaction が実行されること
すでに達成された TODO に戻らないこと
/goal の終了条件と TODO の全解決状態が演繹的に等価であること
/goal の終了条件に達したら、TODO が全てクリアされること
ということは、ロジックとしては以下の流れになるでしょう。
/goal コマンドが実行されたというイベントを受け取る
/goal コマンド時の引数のプロンプトを取り出す
達成したい内容は、どうやったら実現できるのかを推論し、複数のタスクを段階分けさせる
段階分けされたタスクを達成すると、/goal の終了条件を達成するか確認する
その結果を元に TODO リストを作成する
それぞれの TODO をサブエージェントに実行させる
完了したら、/goal の終了条件を達成しているか確認する
TODO をクリアする
ここで作るのは独自の /goal ではなく、組み込みの /goal が送信されたときに追加の指示文を足す Mod です。
Claude Code には組み込みの /goal がすでにあり、$.command.register({ name: 'goal' }) で同名のコマンドを登録すると衝突するためです。
また、今回の実装は Claude への指示文を追記するだけで、compaction の制御は含みません。
この範囲だけなら Hook でも実現できます。
Mod には、compaction を実行する $.session.compact() メソッドと、会話が圧縮される直前に発火する session.compact イベントがあります。
session.compact のハンドラは { skip: reason } を返して圧縮を見送らせることができます。
この2つを組み合わせれば、TODO を1個解消した時点で compaction を実行する制御を実装できると考えます。
ただし本記事では扱わず、今後の課題とします。
それを元に作成すると、以下のようになるでしょう。
register.ts
import type { Register } from 'claude-code'
const SUBCOMMANDS = new Set(['clear', 'status', 'off', 'stop'])
const buildPlaybook = (goal: string) => `
/goal で次のゴールが設定されました。
ゴール: ${goal}
このゴールを、以下の手順で達成してください。
1. ゴールを、独立して検証できる粒度のタスクに分解し、各タスクに完了条件を1つ付けてください。
2. タスクをTODOに登録し、依存関係を明示してください。
依存のないタスク同士は同じグループにし、依存があるタスクは後ろのグループにしてください。
3. 各タスクをAgentツールでSubAgentに委譲してください。
同じグループのタスクは、1回のメッセージで複数のAgent呼び出しを並べて、並列に起動してください。
後ろのグループは、前のグループが完了してから起動してください。
SubAgentは会話履歴を持たないため、
ゴール全体、完了条件、対象ファイル、前のタスクの成果をプロンプトに含めてください。
SubAgentからは、何を完了したかのサマリーだけを受け取ってください。
4. グループが終わるごとに報告を確認し、完了条件を満たしたタスクだけを完了にしてください。
未達のタスクは、理由を添えて再委譲してください。
すべて完了したら、ゴール全体の達成を確認してTODOをクリアしてください。
`
export const register: Register = on => {
// /goal で受け取ったゴールの文面。手順書を添付したら空に戻す
let pendingGoal: string | undefined
// 組み込みの /goal の引数を記録する。結果は変更せず、そのまま返す
on('command.run', { command: 'goal' }, async ($, e, next) => {
const ran = await next(e)
const goal = e.args.trim()
// /goal clear のようなサブコマンドは、ゴールの文面ではないので記録しない
pendingGoal = goal !== '' && !SUBCOMMANDS.has(goal.toLowerCase()) ? goal : undefined
return ran
})
// 直後のプロンプト送信で、Claudeだけが読む手順書を一度だけ追記する
on('prompt.submit', ($, e, next) => {
if (pendingGoal === undefined) return next(e)
const playbook = buildPlaybook(pendingGoal)
pendingGoal = undefined
return next({ ...e, context: [...(e.context ?? []), playbook] })
})
}
この実装は、筆者が公開しているutakata-cc-modのgoal-orchestratorを、記事用に短くしたものです。
code.claude.com
を確認すると、プロンプトへの追記の仕組みがわかります。
この Mod は、Claude だけが確認するテキストをプロンプトの後に追記するため、
先ほどのロジックを手順書のテキストにして渡しています。
ハンドラを2つに分けているのには理由があります。
ゴールの文面は command.run の入力にある e.args から読めますが、ここで結果側に context を足しても、Claude には届きませんでした。
一方、prompt.submit は入力側に context を足せて、足した内容は hook_additional_context として記録され、Claude に届きます。
ただし、prompt.submit のテキストだけを見て /goal を判定すると、/goal clear にも手順書を付けてしまいます。
そこで command.run でゴールの文面を記録し、clear などのサブコマンドを除いたうえで、次の prompt.submit で一度だけ追記しています。
ハンドラの引数は ($, e, next) の3つです。
next を受け取らないとコードが動かず、next の戻り値を返さないとイベントがそこで止まります。
register の型は claude-code から取り込んだ Register を使うと、on の引数に型が付きます。
TODOはどう実行されるのか
この Mod は、TODO を作ることも、SubAgent を起動することも、完了を判定することもしません。
Mod がやるのは、/goal の直後に手順書を一度だけ Claude へ渡すことだけで、TODO の実行は渡された手順書に従って Claude 自身が行います。
/goal 認証処理を整理し、既存テストが通ることを確認する と入力した場合の流れは、次のとおりです。
/goalを入力してからの流れ
command.run のハンドラが、組み込みの /goal を通常どおり実行させたうえで、ゴールの文面を pendingGoal に記録する。
続く prompt.submit のハンドラが、ゴールの文面を埋め込んだ手順書を context に追記し、pendingGoal を空に戻す。
Claude が、ゴールを独立して検証できる粒度のタスクに分け、各タスクに完了条件を1つ付ける。
Claude が、タスクを TODO リストに登録し、依存関係を明示する。依存のないタスク同士は同じグループ、依存があるタスクは後ろのグループにする。
Claude が、グループごとに SubAgent を起動する。同じグループのタスクは1回のメッセージで並べて並列に起動し、後ろのグループは前のグループが完了してから起動する。SubAgent は会話履歴を持たないため、ゴール全体、完了条件、対象ファイル、前のタスクの成果をプロンプトで渡す。
SubAgent が作業し、完了したことを要約だけで Claude に報告する。
Claude がグループが終わるごとに報告を読み、完了条件を満たしたタスクだけを完了にする。未達のタスクは、理由を添えて再委譲する。
全てのタスクが完了したら、Claude がゴール全体の達成を確認し、TODO をクリアする。
コンテキストが積み重なる問題は、5と6で和らげます。
実装の中身を読み書きするのは SubAgent の側で、元の会話に戻ってくるのは要約だけなので、Claude 本体のコンテキストはタスクの数に比例して増えにくくなります。
この方式には限界もあります。
TODO を順に解消すること、達成済みの TODO に戻らないこと、終了条件と TODO の全完了が一致することは、Mod のコードが保証しているのではなく、手順書を Claude が守ることに依存しています。
Claude が手順を飛ばしたとき、Mod は検知できず、是正もできません。
そのため、要件に挙げた TODO を1個解消するごとの compaction も、今の実装では行われません。
!
ここでさらに $.process.run(['claude','-p','プロンプト']) として
コマンドを通して呼び出すこともできるのですが、制御がさらに複雑になるため、
本記事では行いません。
また、そのようにするよりは、Claude Agent SDKを使う方が向いている場面があります。
導入する
プラグインは、これまでの plugin と似たようなディレクトリと同じような方法で
入れることができます。
code.claude.com
mod-plugin/
├── .claude-plugin/
│ └── plugin.json
└── hooks/
├── hooks.json
└── register.ts
plugin.json には、これまでの plugin と同じく name などのマニフェストを書きます。
Mod だからといって追加で必要になるフィールドはありません。
plugin.json
{
"name": "goal-plan",
"version": "0.1.0",
"description": "/goal の送信時に、タスク分割の指示文を追加する",
"author": { "name": "Your Name" }
}
ただし、hooks.json だけは少し違います。
hooks.json
{
"description": "The mod-plugin hooks module",
"modules": ["./register.ts"]
}
必要事項を入れ終わったら、次のコマンドで読み込めます。
claude --plugin-dir ./mod-plugin
ちなみに、これまでの plugin と同様に、
plugin marketplace を用意した上で、
plugin install をする方法も可能です。
claude plugin validate は、next の引数の中身までは確認しないため、型の誤りがあっても通ります。
対話セッションで claude --plugin-dir を使って読み込むと、Mod のディレクトリの .claude-plugin/types/ に、実行中のバージョンの型定義と tsconfig.json が生成されます。
tsc -p ./mod-plugin で型チェックを併用してください。
また、claude plugin test でイベントを発火させるテストを書くこともできます。
まとめ
Mod は、Hook が担っていたイベントへの介入に加えて、コマンドの追加や画面の描画まで変えられます。
今回の /goal の例は、手順書を追記するだけなので、Hook でも書ける範囲でした。
session.compact と $.session.compact() を使った compaction の制御は、次に試す課題です。
1 like
Mentions
Claude Modは、もはやHooksではない。 Claude Mod、ようやく正式にリリースされましたね。 私は正式リリース前から、個人的に利用していました。 これは元々、Function Hook という名前で開発されていましたが、 リリース時の名称は Mod でした。 この記事の内容は Claude Code v2.1.291で動作を確認しています。 Mod はv2.1.287以降、デフォルトで有効になっています。 なぜ名前が変わったのかについては私は詳しく知らないのですが、 ただ一つ、断言できることとしては、 これはHookとは明らかに一線を画したものであるということです。 なぜ「一線を画している」と言えるのか そもそも Hook とはなんだったか まず、Claude Code の Hook がどのような機能だったのかおさらいしておきましょう。 code.claude.com 公式ドキュメントにはこのように書かれています。 Hooks はユーザー定義のシェルコマンドです。Claude Code はそのライフサイクルの特定のポイントで実行され、決定論的な制御を提供します。LLM が実行を選択するのに依存するのではなく、特定のアクションが常に発生します。Hooks を使用して、プロジェクトルールを適用し、反復的なタスクを自動化し、Claude Code を既存のツールと統合します。 ライフサイクルの中で、ただ一個のコマンドを実行して必要な制御を挟むという機能なわけです。 ! ここまで commandHookの話しかしていませんが、他にも Hook には次の種類があります。 promptHook agentHook HTTPHook これらを、次の理由で本記事では対象としません。 この記事のテーマがブレる promptHookと agentHookは、結果が安定しないが故に確実な制御を行えない HTTPHookは個人的にナレッジを持っていないため記述ができない Mod は Hook とどう違うか code.claude.com では、Mod とはどのようなものなのでしょうか。 公式ドキュメントにはこのように書かれています。 mod は Claude Code の外観と動作を変更する プラグイン です。JavaScript または TypeScript のイベントハンドラで構成されています。Claude Code はイベント(ツール呼び出し、送信されたプロンプト、インターフェイスの一部が描画されるなど)が発生したときにハンドラを呼び出し、ハンドラはイベントを監視したり、変更したり、引き継いだりできます。mod を使用して、各リクエスト後のコンテキストの満杯度をチャートするペインなど、Claude Code に独自の機能を追加します。mod のファイルと完全な例については、mod の仕組み を参照してください。 外観と動作を変更する Claude Codeに独自の機能を追加 という、明らかにこれまでの Hook では不可能だった内容が記載されています。 つまり、これは Claude Code をベースとして、 自分専用の Coding Agent CLI を作れる機能です。 Hook はライフサイクルの途中でイベントに介入するだけでしたが、 Mod はプログラムで Claude Code 自体を変えられます。 この違いが、Mod を Hook と一線を画した機能にしています。 ! 公式ドキュメントは、Mod のハンドラも hook と呼び、設定ファイルに書く従来の Hook を settings hook と呼んで区別しています。 本記事では、従来のものを Hook、Mod のものを ハンドラ と書きます。 実は Mod はすでに組み込まれ、使用されている Mod という機能は、すでに内部的に使用されています。 例えば /diff、コードの差分を Claude Code の画面右側に表示するというものです。 他にも、直近に追加された、AGENTS.md の読み込み機能も、現在、Mod の機能を 通して、提供されています。 つまり、これは機能を追加する Mod なのです。 某有名3Dサンドボックスアドベンチャーゲーム界隈で追加アイテムや敵キャラクターを 追加する、あの Mod のように、独自の機能を追加する機能が公式から提供されました。 公式もガッツリ使用しています。 公式が内部で使っているため、仕組みとしては安定していると考えられます。 ただし、Mod を信頼してよいかどうかは別の話です。 公式ドキュメントは、Mod があなたの権限で動くコードであり、ファイルの読み書き、プロセスの起動、ネットワーク通信ができると警告しています。 Mod はサンドボックス化されておらず、ツール呼び出しを事前承認したり、PreToolUseHookがブロックした呼び出しを通したりできます。 そのため、信頼できる作者とマーケットプレイスの Mod だけをインストールしてください。 社内で配布する場合も、中身をレビューしてから入れてください。 実装してみる そこからは、具体的にどのような機能を付けたいかによって、書く内容が変わってきます。 今回は、僕個人の課題を解決するため、以下の課題解決を達成する Mod を作成し、 具体例として提示します。 項目 内容 課題 /goal を使用して長時間のセッションになると、コンテキストがガンガン 積み重なっていき、アウトプット品質の低下と、早期にレートリミットに到達してしまう 要求 /goal を指定したプロンプトを受け取った時、行うタスクの全体像を設計し、それらを 段階的に分解し、それぞれを達成するごとに compaction する 要件と、ロジックの流れ となると、要件はこうなるでしょう。 /goal コマンドの引数となるプロンプトを正常に受け取れること Claude がタスクを分割し、複数の TODO を作成できること Claude が順に TODO を解消していくこと TODO を一個解消したら compaction が実行されること すでに達成された TODO に戻らないこと /goal の終了条件と TODO の全解決状態が演繹的に等価であること /goal の終了条件に達したら、TODO が全てクリアされること ということは、ロジックとしては以下の流れになるでしょう。 /goal コマンドが実行されたというイベントを受け取る /goal コマンド時の引数のプロンプトを取り出す 達成したい内容は、どうやったら実現できるのかを推論し、複数のタスクを段階分けさせる 段階分けされたタスクを達成すると、/goal の終了条件を達成するか確認する その結果を元に TODO リストを作成する それぞれの TODO をサブエージェントに実行させる 完了したら、/goal の終了条件を達成しているか確認する TODO をクリアする ここで作るのは独自の /goal ではなく、組み込みの /goal が送信されたときに追加の指示文を足す Mod です。 Claude Code には組み込みの /goal がすでにあり、$.command.register({ name: 'goal' }) で同名のコマンドを登録すると衝突するためです。 また、今回の実装は Claude への指示文を追記するだけで、compaction の制御は含みません。 この範囲だけなら Hook でも実現できます。 Mod には、compaction を実行する $.session.compact() メソッドと、会話が圧縮される直前に発火する session.compact イベントがあります。 session.compact のハンドラは { skip: reason } を返して圧縮を見送らせることができます。 この2つを組み合わせれば、TODO を1個解消した時点で compaction を実行する制御を実装できると考えます。 ただし本記事では扱わず、今後の課題とします。 それを元に作成すると、以下のようになるでしょう。 register.ts import type { Register } from 'claude-code' const SUBCOMMANDS = new Set(['clear', 'status', 'off', 'stop']) const buildPlaybook = (goal: string) => ` /goal で次のゴールが設定されました。 ゴール: ${goal} このゴールを、以下の手順で達成してください。 1. ゴールを、独立して検証できる粒度のタスクに分解し、各タスクに完了条件を1つ付けてください。 2. タスクをTODOに登録し、依存関係を明示してください。 依存のないタスク同士は同じグループにし、依存があるタスクは後ろのグループにしてください。 3. 各タスクをAgentツールでSubAgentに委譲してください。 同じグループのタスクは、1回のメッセージで複数のAgent呼び出しを並べて、並列に起動してください。 後ろのグループは、前のグループが完了してから起動してください。 SubAgentは会話履歴を持たないため、 ゴール全体、完了条件、対象ファイル、前のタスクの成果をプロンプトに含めてください。 SubAgentからは、何を完了したかのサマリーだけを受け取ってください。 4. グループが終わるごとに報告を確認し、完了条件を満たしたタスクだけを完了にしてください。 未達のタスクは、理由を添えて再委譲してください。 すべて完了したら、ゴール全体の達成を確認してTODOをクリアしてください。 ` export const register: Register = on => { // /goal で受け取ったゴールの文面。手順書を添付したら空に戻す let pendingGoal: string | undefined // 組み込みの /goal の引数を記録する。結果は変更せず、そのまま返す on('command.run', { command: 'goal' }, async ($, e, next) => { const ran = await next(e) const goal = e.args.trim() // /goal clear のようなサブコマンドは、ゴールの文面ではないので記録しない pendingGoal = goal !== '' && !SUBCOMMANDS.has(goal.toLowerCase()) ? goal : undefined return ran }) // 直後のプロンプト送信で、Claudeだけが読む手順書を一度だけ追記する on('prompt.submit', ($, e, next) => { if (pendingGoal === undefined) return next(e) const playbook = buildPlaybook(pendingGoal) pendingGoal = undefined return next({ ...e, context: [...(e.context ?? []), playbook] }) }) } この実装は、筆者が公開しているutakata-cc-modのgoal-orchestratorを、記事用に短くしたものです。 code.claude.com を確認すると、プロンプトへの追記の仕組みがわかります。 この Mod は、Claude だけが確認するテキストをプロンプトの後に追記するため、 先ほどのロジックを手順書のテキストにして渡しています。 ハンドラを2つに分けているのには理由があります。 ゴールの文面は command.run の入力にある e.args から読めますが、ここで結果側に context を足しても、Claude には届きませんでした。 一方、prompt.submit は入力側に context を足せて、足した内容は hook_additional_context として記録され、Claude に届きます。 ただし、prompt.submit のテキストだけを見て /goal を判定すると、/goal clear にも手順書を付けてしまいます。 そこで command.run でゴールの文面を記録し、clear などのサブコマンドを除いたうえで、次の prompt.submit で一度だけ追記しています。 ハンドラの引数は ($, e, next) の3つです。 next を受け取らないとコードが動かず、next の戻り値を返さないとイベントがそこで止まります。 register の型は claude-code から取り込んだ Register を使うと、on の引数に型が付きます。 TODOはどう実行されるのか この Mod は、TODO を作ることも、SubAgent を起動することも、完了を判定することもしません。 Mod がやるのは、/goal の直後に手順書を一度だけ Claude へ渡すことだけで、TODO の実行は渡された手順書に従って Claude 自身が行います。 /goal 認証処理を整理し、既存テストが通ることを確認する と入力した場合の流れは、次のとおりです。 /goalを入力してからの流れ command.run のハンドラが、組み込みの /goal を通常どおり実行させたうえで、ゴールの文面を pendingGoal に記録する。 続く prompt.submit のハンドラが、ゴールの文面を埋め込んだ手順書を context に追記し、pendingGoal を空に戻す。 Claude が、ゴールを独立して検証できる粒度のタスクに分け、各タスクに完了条件を1つ付ける。 Claude が、タスクを TODO リストに登録し、依存関係を明示する。依存のないタスク同士は同じグループ、依存があるタスクは後ろのグループにする。 Claude が、グループごとに SubAgent を起動する。同じグループのタスクは1回のメッセージで並べて並列に起動し、後ろのグループは前のグループが完了してから起動する。SubAgent は会話履歴を持たないため、ゴール全体、完了条件、対象ファイル、前のタスクの成果をプロンプトで渡す。 SubAgent が作業し、完了したことを要約だけで Claude に報告する。 Claude がグループが終わるごとに報告を読み、完了条件を満たしたタスクだけを完了にする。未達のタスクは、理由を添えて再委譲する。 全てのタスクが完了したら、Claude がゴール全体の達成を確認し、TODO をクリアする。 コンテキストが積み重なる問題は、5と6で和らげます。 実装の中身を読み書きするのは SubAgent の側で、元の会話に戻ってくるのは要約だけなので、Claude 本体のコンテキストはタスクの数に比例して増えにくくなります。 この方式には限界もあります。 TODO を順に解消すること、達成済みの TODO に戻らないこと、終了条件と TODO の全完了が一致することは、Mod のコードが保証しているのではなく、手順書を Claude が守ることに依存しています。 Claude が手順を飛ばしたとき、Mod は検知できず、是正もできません。 そのため、要件に挙げた TODO を1個解消するごとの compaction も、今の実装では行われません。 ! ここでさらに $.process.run(['claude','-p','プロンプト']) として コマンドを通して呼び出すこともできるのですが、制御がさらに複雑になるため、 本記事では行いません。 また、そのようにするよりは、Claude Agent SDKを使う方が向いている場面があります。 導入する プラグインは、これまでの plugin と似たようなディレクトリと同じような方法で 入れることができます。 code.claude.com mod-plugin/ ├── .claude-plugin/ │ └── plugin.json └── hooks/ ├── hooks.json └── register.ts plugin.json には、これまでの plugin と同じく name などのマニフェストを書きます。 Mod だからといって追加で必要になるフィールドはありません。 plugin.json { "name": "goal-plan", "version": "0.1.0", "description": "/goal の送信時に、タスク分割の指示文を追加する", "author": { "name": "Your Name" } } ただし、hooks.json だけは少し違います。 hooks.json { "description": "The mod-plugin hooks module", "modules": ["./register.ts"] } 必要事項を入れ終わったら、次のコマンドで読み込めます。 claude --plugin-dir ./mod-plugin ちなみに、これまでの plugin と同様に、 plugin marketplace を用意した上で、 plugin install をする方法も可能です。 claude plugin validate は、next の引数の中身までは確認しないため、型の誤りがあっても通ります。 対話セッションで claude --plugin-dir を使って読み込むと、Mod のディレクトリの .claude-plugin/types/ に、実行中のバージョンの型定義と tsconfig.json が生成されます。 tsc -p ./mod-plugin で型チェックを併用してください。 また、claude plugin test でイベントを発火させるテストを書くこともできます。 まとめ Mod は、Hook が担っていたイベントへの介入に加えて、コマンドの追加や画面の描画まで変えられます。 今回の /goal の例は、手順書を追記するだけなので、Hook でも書ける範囲でした。 session.compact と $.session.compact() を使った compaction の制御は、次に試す課題です。