From 4937509f551d669ae860563dfb606e4cbf97ae83 Mon Sep 17 00:00:00 2001 From: keito4 Date: Mon, 2 Feb 2026 04:02:33 +0000 Subject: [PATCH 1/5] docs: update hooks README with v1.61.0 default configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DevContainerイメージv1.61.0以降では、Hooksがデフォルトで 有効化されていることを明記。手動設定が不要になった旨を追加。 Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/README.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/.claude/hooks/README.md b/.claude/hooks/README.md index 9a63a6a6..a56426d2 100644 --- a/.claude/hooks/README.md +++ b/.claude/hooks/README.md @@ -85,9 +85,23 @@ Hooksは、Claude Codeの特定のイベント(ツール実行前後、タス ## Hooksの設定方法 -### ステップ1: settings.local.json に設定を追加 +### DevContainer環境(v1.61.0以降) -`.claude/settings.local.json` ファイルに `hooks` フィールドを追加します: +**v1.61.0以降のDevContainerイメージでは、Hooksはデフォルトで有効化されています。** + +DevContainerを使用している場合、以下のHooksが自動的に設定されます: + +- `block_git_no_verify.py` - `--no-verify` のブロック +- `pre_git_quality_gates.py` - Git操作前の品質チェック +- `post_git_push_ci.py` - push後のCI監視 +- `post_pr_ai_review.py` - PR作成後のAIレビュー +- `pre_exit_plan_ai_review.py` - プランモード終了前のレビュー + +これらは `/home/vscode/.claude/settings.json` に設定されており、追加の設定なしで動作します。 + +### 手動設定(DevContainer以外の環境) + +DevContainer以外の環境では、`.claude/settings.local.json` ファイルに `hooks` フィールドを追加します: ```json { From b1cb1139bcc3428bd89d9d19f010b98c76b64dc6 Mon Sep 17 00:00:00 2001 From: keito4 Date: Mon, 2 Feb 2026 04:08:53 +0000 Subject: [PATCH 2/5] docs: align CLAUDE.md hooks section with README updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex AIレビューで指摘された不整合を修正: - 全5つのhooksを記載 - DevContainer v1.61.0以降でデフォルト有効化の説明を追加 - 設定ファイルの場所を明確化 Co-Authored-By: Claude Opus 4.5 --- .claude/CLAUDE.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4acb46e2..3c9d1faf 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -128,7 +128,13 @@ Git操作(commit/push)の前に自動的に品質チェックを実行する - `block_git_no_verify.py`: `--no-verify` や `HUSKY=0` の使用をブロック - `pre_git_quality_gates.py`: Git操作前にQuality Gatesを実行 +- `post_git_push_ci.py`: push後のCI監視 +- `post_pr_ai_review.py`: PR作成後のAIレビュー(Codex/Gemini) +- `pre_exit_plan_ai_review.py`: プランモード終了前のレビュー -これらは `.claude/settings.local.json` の `hooks` フィールドで設定されており、Claudeによる `git commit` や `git push` の実行前に自動的にトリガーされます。 +**DevContainer環境(v1.61.0以降)では、これらのHooksはデフォルトで有効化されています。** +設定は `/home/vscode/.claude/settings.json` に含まれており、追加の設定なしで動作します。 + +DevContainer以外の環境では、`.claude/settings.local.json` の `hooks` フィールドで手動設定が必要です。 詳細は [.claude/hooks/README.md](./.claude/hooks/README.md) を参照してください。 From a8c7166f6f97d583551abdd81b9a9a5ea833cdb1 Mon Sep 17 00:00:00 2001 From: keito4 Date: Mon, 2 Feb 2026 04:19:49 +0000 Subject: [PATCH 3/5] fix: improve Gemini error handling in post_pr_ai_review hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gemini CLIのstderr警告メッセージを誤ってエラーとして 報告していた問題を修正。returncode のみでエラー判定を行い、 致命的なエラー(認証エラー等)のみを表示するよう改善。 Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/post_pr_ai_review.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.claude/hooks/post_pr_ai_review.py b/.claude/hooks/post_pr_ai_review.py index 69a98764..0e42bbd7 100755 --- a/.claude/hooks/post_pr_ai_review.py +++ b/.claude/hooks/post_pr_ai_review.py @@ -162,8 +162,19 @@ def run_gemini_review(): if result.stdout: print(result.stdout, file=sys.stderr) - if result.returncode != 0 and result.stderr: - print(f"⚠️ Geminiエラー: {result.stderr[:300]}", file=sys.stderr) + # returncode が 0 でない場合のみエラーとして扱う + # (Gemini CLI は警告を stderr に出力するが、これはエラーではない) + if result.returncode != 0: + # 致命的なエラーメッセージのみ抽出(認証エラーなど) + error_lines = [ + line for line in result.stderr.split('\n') + if 'error' in line.lower() or 'auth' in line.lower() or 'api_key' in line.lower() + ] + if error_lines: + print(f"⚠️ Geminiエラー: {error_lines[0][:300]}", file=sys.stderr) + elif not result.stdout: + # stdout も空で returncode が 0 でない場合はエラー + print(f"⚠️ Geminiエラー: {result.stderr[:300]}", file=sys.stderr) except subprocess.TimeoutExpired: print("⚠️ Geminiレビューがタイムアウトしました(10分)", file=sys.stderr) From 8fa0d7947b5198d255669480d7e79b274bcdc97d Mon Sep 17 00:00:00 2001 From: keito4 Date: Mon, 2 Feb 2026 04:33:35 +0000 Subject: [PATCH 4/5] fix: add verdict analysis and robust error handling to AI review hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AIレビュー結果の verdict 解析機能を追加: - "patch is incorrect" 検出時にクリティカル警告を表示 - confidence スコアの抽出と表示 - 主な指摘事項のサマリー表示 - 修正促進メッセージの表示 エラー処理の改善: - non-zero exit code は常にエラーとして扱う - 既知の警告パターン(IDE接続エラー等)のみスキップ - exit code を含むエラーメッセージの表示 Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/post_pr_ai_review.py | 179 ++++++++++++++++++++++++----- 1 file changed, 151 insertions(+), 28 deletions(-) diff --git a/.claude/hooks/post_pr_ai_review.py b/.claude/hooks/post_pr_ai_review.py index 0e42bbd7..d90cf178 100755 --- a/.claude/hooks/post_pr_ai_review.py +++ b/.claude/hooks/post_pr_ai_review.py @@ -4,6 +4,7 @@ gh pr create 成功後に自動的にCodexとGeminiによるコードレビューを実行します。 インストールされているツールのみ実行されます。 +クリティカルな問題(patch is incorrect)が検出された場合は警告を表示します。 """ import sys import json @@ -64,13 +65,55 @@ Review the current branch against origin/main. Use git merge-base to find the merge base, then review the diff from that merge base to HEAD.""" -print("", file=sys.stderr) -print("=" * 60, file=sys.stderr) -print("🔍 PR作成完了。AIレビューを実行中...", file=sys.stderr) -print("=" * 60, file=sys.stderr) +# レビュー結果を格納 +review_results = [] + + +def parse_verdict(output: str) -> dict: + """レビュー結果から verdict と confidence を抽出""" + result = { + "verdict": None, + "confidence": None, + "is_incorrect": False, + "issues": [] + } + + if not output: + return result + output_lower = output.lower() -def run_codex_review(): + # verdict を検出 + if "patch is incorrect" in output_lower: + result["verdict"] = "incorrect" + result["is_incorrect"] = True + elif "patch is correct" in output_lower: + result["verdict"] = "correct" + + # confidence を抽出(様々なフォーマットに対応) + confidence_patterns = [ + r"confidence[:\s]+([0-9]+(?:\.[0-9]+)?)", + r"confidence[:\s]+([0-9]+(?:\.[0-9]+)?)\s*/\s*1", + r"([0-9]+(?:\.[0-9]+)?)\s*/\s*1", + ] + for pattern in confidence_patterns: + match = re.search(pattern, output_lower) + if match: + try: + result["confidence"] = float(match.group(1)) + break + except ValueError: + pass + + # 問題点を抽出(行番号を含む行を検出) + issue_pattern = r"[-•]\s*(.+?(?:line|\.(?:py|js|ts|tsx|md|json|yml|yaml))[^\n]*)" + issues = re.findall(issue_pattern, output, re.IGNORECASE) + result["issues"] = issues[:5] # 最大5件 + + return result + + +def run_codex_review() -> str: """Codexによるレビューを実行""" print("", file=sys.stderr) print("## 🤖 Codex Review", file=sys.stderr) @@ -91,20 +134,24 @@ def run_codex_review(): timeout=600 ) - if result.stdout: - print(result.stdout, file=sys.stderr) + output = result.stdout or "" + if output: + print(output, file=sys.stderr) if result.returncode != 0 and result.stderr: - # エラー出力の先頭部分のみ表示 print(f"⚠️ Codexエラー: {result.stderr[:300]}", file=sys.stderr) + return output + except subprocess.TimeoutExpired: print("⚠️ Codexレビューがタイムアウトしました(10分)", file=sys.stderr) + return "" except Exception as e: print(f"⚠️ Codexレビュー実行エラー: {e}", file=sys.stderr) + return "" -def run_gemini_review(): +def run_gemini_review() -> str: """Geminiによるレビューを実行(diffをstdinで渡す)""" print("", file=sys.stderr) print("## ✨ Gemini Review", file=sys.stderr) @@ -122,7 +169,7 @@ def run_gemini_review(): if not merge_base: print("⚠️ マージベースの取得に失敗しました", file=sys.stderr) - return + return "" # diffを取得 diff_result = subprocess.run( @@ -135,7 +182,7 @@ def run_gemini_review(): if not diff_content: print("⚠️ diffが空です", file=sys.stderr) - return + return "" # Gemini用のプロンプト(diffを含める) gemini_prompt = f"""You are acting as a reviewer for a proposed code change. @@ -159,40 +206,116 @@ def run_gemini_review(): timeout=600 ) - if result.stdout: - print(result.stdout, file=sys.stderr) + output = result.stdout or "" + if output: + print(output, file=sys.stderr) - # returncode が 0 でない場合のみエラーとして扱う - # (Gemini CLI は警告を stderr に出力するが、これはエラーではない) + # returncode が 0 でない場合はエラーとして扱う + # ただし、stdout に有効な出力がある場合は警告のみ if result.returncode != 0: - # 致命的なエラーメッセージのみ抽出(認証エラーなど) - error_lines = [ - line for line in result.stderr.split('\n') - if 'error' in line.lower() or 'auth' in line.lower() or 'api_key' in line.lower() + stderr_content = result.stderr.strip() if result.stderr else "" + + # 既知の警告パターン(致命的でないもの) + warning_patterns = [ + "hook registry initialized", + "failed to connect to ide", + "extension is running" ] - if error_lines: - print(f"⚠️ Geminiエラー: {error_lines[0][:300]}", file=sys.stderr) - elif not result.stdout: - # stdout も空で returncode が 0 でない場合はエラー - print(f"⚠️ Geminiエラー: {result.stderr[:300]}", file=sys.stderr) + is_only_warning = stderr_content and all( + any(pattern in line.lower() for pattern in warning_patterns) + for line in stderr_content.split('\n') if line.strip() + ) + + if is_only_warning and output: + # 既知の警告のみで、かつ有効な出力がある場合はスキップ + pass + elif stderr_content: + # stderr に内容がある場合は最初の意味のある行を表示 + first_line = next( + (line.strip() for line in stderr_content.split('\n') if line.strip()), + stderr_content[:100] + ) + print(f"⚠️ Geminiエラー (exit {result.returncode}): {first_line[:300]}", file=sys.stderr) + else: + # stderr が空の場合 + print(f"⚠️ Geminiエラー: 終了コード {result.returncode}", file=sys.stderr) + + return output except subprocess.TimeoutExpired: print("⚠️ Geminiレビューがタイムアウトしました(10分)", file=sys.stderr) + return "" except Exception as e: print(f"⚠️ Geminiレビュー実行エラー: {e}", file=sys.stderr) + return "" -# 利用可能なツールでレビューを実行 +print("", file=sys.stderr) +print("=" * 60, file=sys.stderr) +print("🔍 PR作成完了。AIレビューを実行中...", file=sys.stderr) +print("=" * 60, file=sys.stderr) + +# 利用可能なツールでレビューを実行し、結果を収集 if has_codex: - run_codex_review() + codex_output = run_codex_review() + codex_result = parse_verdict(codex_output) + codex_result["reviewer"] = "Codex" + review_results.append(codex_result) if has_gemini: - run_gemini_review() + gemini_output = run_gemini_review() + gemini_result = parse_verdict(gemini_output) + gemini_result["reviewer"] = "Gemini" + review_results.append(gemini_result) + +# レビュー結果の解析 +incorrect_reviews = [r for r in review_results if r["is_incorrect"]] +has_critical_issues = len(incorrect_reviews) > 0 print("", file=sys.stderr) print("=" * 60, file=sys.stderr) -print("✅ AIレビュー完了", file=sys.stderr) + +if has_critical_issues: + print("🚨 クリティカルな問題が検出されました!", file=sys.stderr) + print("=" * 60, file=sys.stderr) + print("", file=sys.stderr) + + for review in incorrect_reviews: + reviewer = review.get("reviewer", "Unknown") + confidence = review.get("confidence") + confidence_str = f" (confidence: {confidence})" if confidence else "" + print(f"❌ {reviewer}: patch is incorrect{confidence_str}", file=sys.stderr) + + if review.get("issues"): + print(" 主な指摘事項:", file=sys.stderr) + for issue in review["issues"][:3]: + print(f" • {issue[:100]}", file=sys.stderr) + + print("", file=sys.stderr) + print("─" * 60, file=sys.stderr) + print("⚠️ 対応が必要です:", file=sys.stderr) + print(" 1. 上記の指摘事項を確認してください", file=sys.stderr) + print(" 2. 必要に応じてコードを修正してください", file=sys.stderr) + print(" 3. 修正後、PRを更新してください", file=sys.stderr) + print("─" * 60, file=sys.stderr) +else: + print("✅ AIレビュー完了", file=sys.stderr) + + # 成功した場合も verdict サマリーを表示 + for review in review_results: + reviewer = review.get("reviewer", "Unknown") + verdict = review.get("verdict", "unknown") + confidence = review.get("confidence") + confidence_str = f" (confidence: {confidence})" if confidence else "" + + if verdict == "correct": + print(f" ✓ {reviewer}: patch is correct{confidence_str}", file=sys.stderr) + elif verdict: + print(f" ? {reviewer}: {verdict}{confidence_str}", file=sys.stderr) + print("=" * 60, file=sys.stderr) # PostToolUseフックは常に成功で終了(ブロックしない) +# ※ PR は既に作成されているため、ブロックしても意味がない +# 代わりに警告メッセージで対応を促す sys.exit(0) From 9c40515e02d6584c7dfae5a40cf9b91470d0cddb Mon Sep 17 00:00:00 2001 From: keito4 Date: Mon, 2 Feb 2026 04:40:05 +0000 Subject: [PATCH 5/5] fix: improve verdict parsing to avoid false positives from quoted strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verdict解析の改善: - 引用符で囲まれた文字列(例: "patch is incorrect")を除外 - verdict行のパターンを優先的に検出 - 行単位での判定により誤検出を防止 Co-Authored-By: Claude Opus 4.5 --- .claude/hooks/post_pr_ai_review.py | 46 ++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/.claude/hooks/post_pr_ai_review.py b/.claude/hooks/post_pr_ai_review.py index d90cf178..6714c9a2 100755 --- a/.claude/hooks/post_pr_ai_review.py +++ b/.claude/hooks/post_pr_ai_review.py @@ -83,12 +83,46 @@ def parse_verdict(output: str) -> dict: output_lower = output.lower() - # verdict を検出 - if "patch is incorrect" in output_lower: - result["verdict"] = "incorrect" - result["is_incorrect"] = True - elif "patch is correct" in output_lower: - result["verdict"] = "correct" + # verdict を検出(より正確なパターンマッチング) + # 引用符で囲まれた文字列(例: "patch is incorrect" という説明文)を除外 + # verdict/判定/結論の直後に出現するパターンを優先 + + # verdict 行を探す("verdict:" や "**verdict**" の後) + verdict_patterns = [ + r"verdict[:\s*]+\*{0,2}patch is (incorrect|correct)\*{0,2}", + r"overall[^:]*verdict[:\s*]+\*{0,2}patch is (incorrect|correct)\*{0,2}", + r"判定[:\s*]+patch is (incorrect|correct)", + ] + + for pattern in verdict_patterns: + match = re.search(pattern, output_lower) + if match: + if match.group(1) == "incorrect": + result["verdict"] = "incorrect" + result["is_incorrect"] = True + else: + result["verdict"] = "correct" + break + + # 上記で見つからない場合、文脈を考慮して検出 + if result["verdict"] is None: + # 引用符で囲まれていない "patch is incorrect/correct" を検出 + # 引用符内を除外するために、行単位で判定 + for line in output.split('\n'): + line_lower = line.lower() + # 引用符内のテキストを除外 + if '"patch is incorrect"' in line_lower or "'patch is incorrect'" in line_lower: + continue + if '("patch is incorrect")' in line_lower: + continue + + if "patch is incorrect" in line_lower: + result["verdict"] = "incorrect" + result["is_incorrect"] = True + break + elif "patch is correct" in line_lower: + result["verdict"] = "correct" + break # confidence を抽出(様々なフォーマットに対応) confidence_patterns = [