-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add git quality gates hooks for Claude Code #268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| # Claude Code Hooks | ||
|
|
||
| このディレクトリには、Claude Codeの動作をカスタマイズするためのHooksスクリプトが格納されています。 | ||
|
|
||
| ## 概要 | ||
|
|
||
| Hooksは、Claude Codeの特定のイベント(ツール実行前後、タスク完了時など)に自動的に実行されるスクリプトです。これにより、品質チェック、通知、自動化などを実現できます。 | ||
|
|
||
| ## 利用可能なHooks | ||
|
|
||
| ### 1. `block_git_no_verify.py` | ||
|
|
||
| **目的**: `git commit --no-verify` や `HUSKY=0` の使用をブロックし、必ずGit Hooksを実行させる | ||
|
|
||
| **トリガー**: `PreToolUse(Bash)` | ||
|
|
||
| **動作**: | ||
|
|
||
| - `--no-verify` フラグを検出してブロック | ||
| - `HUSKY=0` 環境変数を検出してブロック | ||
| - 違反が見つかった場合、exit code 2でツール実行を阻止 | ||
|
|
||
| **設定例**: | ||
|
|
||
| ```json | ||
| { | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "comment": "Block git --no-verify and HUSKY=0", | ||
| "matcher": "tool_name == 'Bash'", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 .claude/hooks/block_git_no_verify.py" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### 2. `pre_git_quality_gates.py` | ||
|
|
||
| **目的**: Git操作(commit/push)の前にQuality Gatesを実行し、品質基準を満たさない変更のコミット/プッシュを防止 | ||
|
|
||
| **トリガー**: `PreToolUse(Bash)` で `git commit` または `git push` を検出 | ||
|
|
||
| **実行されるチェック**: | ||
|
|
||
| 1. **Format Check** (`npm run format:check`) - コードフォーマットの検証 | ||
| 2. **Lint** (`npm run lint`) - コード品質の検証 | ||
| 3. **Test** (`npm run test`) - ユニットテストの実行 | ||
| 4. **ShellCheck** (`npm run shellcheck`) - シェルスクリプトの検証 | ||
| 5. **Security Credential Scan** (`./script/security-credential-scan.sh --strict`) - 認証情報の漏洩チェック | ||
| 6. **Code Complexity Check** (`./script/code-complexity-check.sh --strict`) - コード複雑度の検証 | ||
|
|
||
| **動作**: | ||
|
|
||
| - すべてのチェックに合格した場合のみ、Git操作を許可 | ||
| - 1つでも失敗した場合、exit code 2でツール実行を阻止 | ||
| - 失敗したチェックの詳細を標準エラー出力に表示 | ||
|
|
||
| **設定例**: | ||
|
|
||
| ```json | ||
| { | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "comment": "Run Quality Gates before git commit/push", | ||
| "matcher": "tool_name == 'Bash'", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 .claude/hooks/pre_git_quality_gates.py" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Hooksの設定方法 | ||
|
|
||
| ### ステップ1: settings.local.json に設定を追加 | ||
|
|
||
| `.claude/settings.local.json` ファイルに `hooks` フィールドを追加します: | ||
|
|
||
| ```json | ||
| { | ||
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | ||
| "hooks": { | ||
| "PreToolUse": [ | ||
| { | ||
| "comment": "Block git --no-verify and HUSKY=0", | ||
| "matcher": "tool_name == 'Bash'", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 .claude/hooks/block_git_no_verify.py" | ||
| } | ||
| ] | ||
| }, | ||
| { | ||
| "comment": "Run Quality Gates before git commit/push", | ||
| "matcher": "tool_name == 'Bash'", | ||
| "hooks": [ | ||
| { | ||
| "type": "command", | ||
| "command": "python3 .claude/hooks/pre_git_quality_gates.py" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### ステップ2: Hookスクリプトに実行権限を付与 | ||
|
|
||
| ```bash | ||
| chmod +x .claude/hooks/*.py | ||
| ``` | ||
|
|
||
| ### ステップ3: Claude Codeを再起動 | ||
|
|
||
| 設定変更を反映させるため、Claude Codeを再起動します。 | ||
|
|
||
| ## トラブルシューティング | ||
|
|
||
| ### Hookが実行されない | ||
|
|
||
| 1. `settings.local.json` の構文が正しいか確認 | ||
| 2. Hookスクリプトに実行権限があるか確認(`ls -l .claude/hooks/`) | ||
| 3. Pythonがインストールされているか確認(`python3 --version`) | ||
|
|
||
| ### Quality Gatesで意図せずブロックされる | ||
|
|
||
| 以下のいずれかの対処を行います: | ||
|
|
||
| 1. **修正してコミット**: エラーメッセージに従って問題を修正 | ||
| 2. **特定のチェックをスキップ**: 一時的に `pre_git_quality_gates.py` の該当チェックをコメントアウト | ||
| 3. **Hookを無効化**: `settings.local.json` から該当のHook設定を削除 | ||
|
|
||
| ### タイムアウトエラー | ||
|
|
||
| テストやビルドに時間がかかる場合、`pre_git_quality_gates.py` の `timeout` 値を増やします: | ||
|
|
||
| ```python | ||
| result = subprocess.run( | ||
| check["command"], | ||
| timeout=600 # 10分に変更 | ||
| ) | ||
| ``` | ||
|
|
||
| ## カスタムHooksの作成 | ||
|
|
||
| 新しいHookスクリプトを作成する場合の基本構造: | ||
|
|
||
| ```python | ||
| #!/usr/bin/env python3 | ||
| import sys | ||
| import json | ||
|
|
||
| # Read input from Claude | ||
| data = json.load(sys.stdin) | ||
| tool_input = data.get("tool_input", {}) or {} | ||
|
|
||
| # Your hook logic here | ||
| # ... | ||
|
|
||
| # Exit codes: | ||
| # 0 = Allow tool execution | ||
| # 2 = Block tool execution with error message | ||
| sys.exit(0) | ||
| ``` | ||
|
|
||
| ## 参考資料 | ||
|
|
||
| - [Claude Code Hooks ドキュメント](https://docs.anthropic.com/claude-code/hooks) | ||
| - [settings.local.json.template](./../settings.local.json.template) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| #!/usr/bin/env python3 | ||
| """ | ||
| Git操作前のQuality Gatesチェック | ||
|
|
||
| git commit や git push の前に以下のチェックを実行: | ||
| 1. npm run format:check | ||
| 2. npm run lint | ||
| 3. npm run test | ||
| 4. npm run shellcheck | ||
| 5. ./script/security-credential-scan.sh --strict | ||
| 6. ./script/code-complexity-check.sh --strict | ||
| """ | ||
| import sys | ||
| import json | ||
| import shlex | ||
| import subprocess | ||
| import os | ||
|
|
||
| # Read input from Claude | ||
| data = json.load(sys.stdin) | ||
| cmd = (data.get("tool_input", {}) or {}).get("command") or "" | ||
| tokens = shlex.split(cmd) if cmd else [] | ||
|
|
||
| if not tokens: | ||
| sys.exit(0) | ||
|
|
||
| # Git操作(commit, push)を検出 | ||
| is_git_commit = False | ||
| is_git_push = False | ||
|
|
||
| for i, token in enumerate(tokens): | ||
| if token == "git": | ||
| if i + 1 < len(tokens): | ||
| next_token = tokens[i + 1] | ||
| if next_token == "commit": | ||
| is_git_commit = True | ||
| elif next_token == "push": | ||
| is_git_push = True | ||
|
|
||
| # git commit または git push でない場合はスルー | ||
| if not (is_git_commit or is_git_push): | ||
| sys.exit(0) | ||
|
|
||
| # Quality Gatesを実行 | ||
| print("🔍 Git操作前のQuality Gatesを実行中...\n", file=sys.stderr) | ||
|
|
||
| checks = [ | ||
| { | ||
| "name": "Format Check", | ||
| "command": ["npm", "run", "format:check"], | ||
| "description": "コードフォーマットの検証" | ||
| }, | ||
| { | ||
| "name": "Lint", | ||
| "command": ["npm", "run", "lint"], | ||
| "description": "コード品質の検証" | ||
| }, | ||
| { | ||
| "name": "Test", | ||
| "command": ["npm", "run", "test"], | ||
| "description": "ユニットテストの実行" | ||
| }, | ||
| { | ||
| "name": "ShellCheck", | ||
| "command": ["npm", "run", "shellcheck"], | ||
| "description": "シェルスクリプトの検証" | ||
| }, | ||
| { | ||
| "name": "Security Credential Scan", | ||
| "command": ["./script/security-credential-scan.sh", "--strict"], | ||
| "description": "認証情報の漏洩チェック" | ||
| }, | ||
| { | ||
| "name": "Code Complexity Check", | ||
| "command": ["./script/code-complexity-check.sh", "--strict"], | ||
| "description": "コード複雑度の検証" | ||
| } | ||
| ] | ||
|
|
||
| failed_checks = [] | ||
|
|
||
| for check in checks: | ||
| print(f"▶ {check['name']}: {check['description']}", file=sys.stderr) | ||
|
|
||
| try: | ||
| result = subprocess.run( | ||
| check["command"], | ||
| cwd=os.getcwd(), | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=300 # 5分でタイムアウト | ||
| ) | ||
|
|
||
| if result.returncode != 0: | ||
| failed_checks.append({ | ||
| "name": check["name"], | ||
| "stdout": result.stdout, | ||
| "stderr": result.stderr, | ||
| "returncode": result.returncode | ||
| }) | ||
| print(f" ❌ 失敗 (exit code: {result.returncode})", file=sys.stderr) | ||
| else: | ||
| print(f" ✅ 成功", file=sys.stderr) | ||
|
|
||
| except subprocess.TimeoutExpired: | ||
| failed_checks.append({ | ||
| "name": check["name"], | ||
| "error": "タイムアウト (5分)" | ||
| }) | ||
| print(f" ❌ タイムアウト", file=sys.stderr) | ||
|
|
||
| except FileNotFoundError: | ||
| # スクリプトが存在しない場合はスキップ | ||
| print(f" ⚠️ スキップ (コマンドが見つかりません)", file=sys.stderr) | ||
|
|
||
| except Exception as e: | ||
| failed_checks.append({ | ||
| "name": check["name"], | ||
| "error": str(e) | ||
| }) | ||
| print(f" ❌ エラー: {e}", file=sys.stderr) | ||
|
|
||
| print("", file=sys.stderr) | ||
|
|
||
| # 失敗したチェックがある場合はブロック | ||
| if failed_checks: | ||
| print("=" * 60, file=sys.stderr) | ||
| print("❌ Quality Gatesに失敗しました", file=sys.stderr) | ||
| print("=" * 60, file=sys.stderr) | ||
| print("", file=sys.stderr) | ||
|
|
||
| for failed in failed_checks: | ||
| print(f"【{failed['name']}】", file=sys.stderr) | ||
|
|
||
| if "error" in failed: | ||
| print(f" エラー: {failed['error']}", file=sys.stderr) | ||
| else: | ||
| if failed.get("stdout"): | ||
| print(f" 標準出力:\n{failed['stdout']}", file=sys.stderr) | ||
| if failed.get("stderr"): | ||
| print(f" 標準エラー:\n{failed['stderr']}", file=sys.stderr) | ||
|
|
||
| print("", file=sys.stderr) | ||
|
|
||
| print("修正してから再度コミット/プッシュしてください。", file=sys.stderr) | ||
| sys.exit(2) | ||
|
|
||
| print("✅ すべてのQuality Gatesに合格しました", file=sys.stderr) | ||
| sys.exit(0) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing scripts should fail the gate, not skip silently.
When a required quality check script (e.g.,
security-credential-scan.sh) is missing, it's silently skipped without adding tofailed_checks. This allows git operations to proceed even when critical security checks cannot run, defeating the purpose of quality gates.🔎 Proposed fix to fail on missing scripts
📝 Committable suggestion
🧰 Tools
🪛 Ruff (0.14.10)
114-114: f-string without any placeholders
Remove extraneous
fprefix(F541)
🤖 Prompt for AI Agents