Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:46:35 +08:00
commit c41279f305
10 changed files with 718 additions and 0 deletions

117
hooks/README.md Normal file
View File

@@ -0,0 +1,117 @@
# Hooks - Claude Codeフック集
Claude Codeのセッションやツール実行時に自動的に実行されるフックスクリプト集です。
## 利用可能なフック
### start-session.js
**イベント**: SessionStart
**目的**: セッション開始時に動作モードの設定を促す
セッション開始時にmode_listの実行と適切なモードの有効化を促します。
セッションのソースタイプstartup/resume/clear/compactに応じて適切な指示を提供します。
これにより、コンテキストリセット後も一貫した作業スタイルを維持できます。
### post-tool-use.sh
**イベント**: PostToolUse (Bashツール限定)
**目的**: cdコマンド実行後の現在ディレクトリをコンテキストに追加
Bashツールで`cd`コマンドが実行された際に、移動後の現在ディレクトリを`[cwd: /path/to/dir]`形式で
コンテキストに追加します。これにより、ディレクトリ移動の追跡が容易になります。
## フックの仕組み
### 実行フロー
1. Claude Codeが特定のイベントSessionStart、PostToolUse等を検出
2. `~/.claude/settings.json`のmatcherパターンに一致するか確認
3. 一致した場合、指定されたコマンドを実行
4. フックスクリプトがJSON形式で追加コンテキストを出力
5. Claude Codeがコンテキストを会話に追加
### JSON出力形式
```json
{
"hookSpecificOutput": {
"hookEventName": "EventName",
"additionalContext": "追加するテキスト"
}
}
```
## 新規フックの作成方法
### 1. スクリプトの作成
```bash
cat > hooks/my-hook.sh << 'EOF'
#!/bin/bash
echo '{
"hookSpecificOutput": {
"hookEventName": "MyEvent",
"additionalContext": "カスタムメッセージ"
}
}'
EOF
```
### 2. 実行権限の付与
```bash
chmod +x hooks/my-hook.sh
```
### 3. settings.jsonへの登録
```json
"EventName": [
{
"matcher": "パターン",
"hooks": [
{
"type": "command",
"command": "~/Develop/otolab/ai-agent-prompts/hooks/my-hook.sh"
}
]
}
]
```
## 環境変数
フックスクリプト内で利用可能な環境変数(イベントによって異なる):
### PostToolUse
- `CLAUDE_CODE_TOOL_NAME` - 実行されたツール名(例: "Bash", "Read", "Write"
- `CLAUDE_CODE_TOOL_PARAMS` - ツールパラメータJSON文字列
- `CLAUDE_CODE_TOOL_RESULT` - ツール実行結果(利用可能な場合)
## デバッグ方法
### ログファイルへの出力
```bash
#!/bin/bash
# デバッグ情報をログファイルに記録
echo "$(date): Hook executed" >> /tmp/hook-debug.log
echo "Tool: $CLAUDE_CODE_TOOL_NAME" >> /tmp/hook-debug.log
```
### 環境変数の確認
```bash
#!/bin/bash
# すべての環境変数を記録
env > /tmp/hook-env.txt
```
## ベストプラクティス
1. **軽量に保つ**: フックの実行時間を最小限に
2. **エラーハンドリング**: 失敗してもセッションを妨げない
3. **条件分岐**: 必要な場合のみ出力を生成
4. **ログ記録**: デバッグ用のログは別ファイルに
## 注意事項
- フックスクリプトの出力はJSON形式である必要があります
- 不正なJSON出力はエラーになる可能性があります
- matcherパターンは正確に設定してください
- 設定変更後はClaude Codeの再起動が必要です
---
**作成**: 2025年10月6日

15
hooks/hooks.json Normal file
View File

@@ -0,0 +1,15 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "startup|compact|resume|clear",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/start-session.js"
}
]
}
]
}
}

69
hooks/post-tool-use.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/bin/bash
# PostToolUse hook for Bash tool
# 1. Adds current working directory to context when cd command is executed
# 2. Warns about git add -A/. without git status in the same command
#
# NOTE: このhookは一時的に無効化されています (2025-10-22)
# 理由: Bashツール実行時にエラーが発生してツールが異常終了する問題が確認された
# 問題が解決次第、hooks/hooks.jsonで再度有効化する予定
# Function to output JSON response
output_json() {
local additional_context="$1"
if [ -n "$additional_context" ]; then
cat <<EOF
{
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "$additional_context"
}
}
EOF
fi
}
# Check if this is a Bash tool call
if [ "$CLAUDE_CODE_TOOL_NAME" == "Bash" ]; then
# Extract the command from JSON params
COMMAND=$(echo "$CLAUDE_CODE_TOOL_PARAMS" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
# Collect all messages to output
MESSAGES=""
# Check if cd command was executed
if echo "$COMMAND" | grep -q "cd[[:space:]]"; then
# Get current working directory
CWD=$(pwd)
# Add to messages
if [ -z "$MESSAGES" ]; then
MESSAGES="[cwd: $CWD]"
else
MESSAGES="${MESSAGES}
[cwd: $CWD]"
fi
fi
# Check if git add -A or git add . was executed
if echo "$COMMAND" | grep -q "git[[:space:]]\+add[[:space:]]\+\(-A\|\.\)" ; then
# Check if git status is also in the same command (e.g., git add -A && git status)
if ! echo "$COMMAND" | grep -q "git[[:space:]]\+status"; then
# Count modified files
if git rev-parse --git-dir > /dev/null 2>&1; then
MODIFIED_COUNT=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
WARNING="[git] ⚠️ ${MODIFIED_COUNT}個のファイルが対象 (git add -A/.) - git statusで内容を確認してください"
if [ -z "$MESSAGES" ]; then
MESSAGES="$WARNING"
else
MESSAGES="${MESSAGES}
${WARNING}"
fi
fi
fi
fi
# Output combined messages
if [ -n "$MESSAGES" ]; then
output_json "$MESSAGES"
fi
fi

24
hooks/pre-compact.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# コンテキスト圧縮前のモード状態復元
MESSAGE="⚠️ コンテキスト圧縮前の処理\n\n"
MESSAGE="${MESSAGE}以下の手順を実行してください:\n"
MESSAGE="${MESSAGE}1. コンテキストから最新のアクティブなモードリストを検索\n"
MESSAGE="${MESSAGE}2. mode_setツールで状態を保存MCPオンメモリに保存\n\n"
MESSAGE="${MESSAGE}※ この処理により、compact後もMCPサーバーのモード状態が保持されます\n\n"
MESSAGE="${MESSAGE}⚠️ コンテキスト圧縮完了後の処理\n\n"
MESSAGE="${MESSAGE}以下の手順をTODOの最初に追加してください\n"
MESSAGE="${MESSAGE}1. **mode_show()** を実行してモード文書を再読み込み\n\n"
MESSAGE="${MESSAGE}※ 保存されたモードの状態を復元します。"
# Use jq to properly encode JSON with multi-line strings
jq -n \
--arg message "$MESSAGE" \
'{
hookSpecificOutput: {
hookEventName: "PreCompact",
additionalContext: $message
}
}'

113
hooks/start-session.js Executable file
View File

@@ -0,0 +1,113 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// 標準入力からJSONを読み取る
let inputData = '';
process.stdin.on('data', (chunk) => {
inputData += chunk;
});
process.stdin.on('end', () => {
try {
const hookData = JSON.parse(inputData);
const source = hookData.source || 'unknown';
// sourceに応じたメッセージを構築
const lines = [
'═══════════════════════════════════════════'
];
switch (source) {
case 'startup':
lines.push([
'📋 **Session Setup - 新規セッション開始**',
'',
'作業環境をセットアップします。',
'TodoWriteツールで以下のタスクをリストの最初に追加してください',
'',
'1. mode_list()を実行して利用可能なモードを確認する',
'2. 適切なモードを有効化する',
'',
'これらは作業開始前の準備ステップです。',
].join('\n'));
break;
case 'resume':
lines.push([
'📋 **Session Setup - セッション再開**',
'',
'中断していたセッションを再開します。',
'TodoWriteツールで以下のタスクをリストの最初に追加してください',
'',
'1. コンテキストから最新のアクティブモードリストを検索する',
'2. mode_set()を実行してモードをMCPメモリに復元する',
'',
'これらは作業再開前の準備ステップです。',
].join('\n'));
break;
case 'clear':
lines.push([
'📋 **Session Setup - モードクリア後**',
'',
'アシスタント動作モードがクリアされました。',
'TodoWriteツールで以下のタスクをリストの最初に追加してください',
'',
'1. mode_show()を実行してモードドキュメントを再読み込みする',
'',
'これは作業継続前の準備ステップです。',
].join('\n'));
break;
case 'compact':
lines.push([
'📋 **Session Setup - コンテキスト圧縮後の再開**',
'',
'コンテキストが圧縮されました。モード状態を復元します。',
'TodoWriteツールで以下のタスクをリストの最初に追加してください',
'',
'1. mode_show()を実行してモード状態を復元する',
'',
'これは作業継続前の準備ステップです。',
'',
'💡 ヒント: mode_show()は「サマリが存在する場合でも原文を読み直す」指示を含んでいます。',
].join('\n'));
break;
default:
lines.push([
'📋 **Session Setup**',
'',
`セッションが開始されました (source: ${source})。`,
'TodoWriteツールで以下のタスクをリストの最初に追加してください',
'',
'1. mode_list()を実行する',
].join('\n'));
break;
}
// .serenaディレクトリのチェック
const serenaPath = path.join(process.cwd(), '.serena');
if (fs.existsSync(serenaPath) && fs.statSync(serenaPath).isDirectory()) {
lines.push('* Serenaのアクティベートを行ってください');
}
lines.push('═══════════════════════════════════════════');
// JSON出力
const output = {
hookSpecificOutput: {
hookEventName: 'SessionStart',
additionalContext: lines.join('\n')
}
};
console.log(JSON.stringify(output, null, 2));
} catch (error) {
console.error('Error processing hook input:', error.message);
process.exit(1);
}
});