Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,27 @@ Lint → Test → Build → SCA → Deploy:Stg → E2E → Deploy:Prod
## 6. デプロイ

基本的にgithub actionsを使用する。

## 7. Quality Gates(品質ゲート)

Git操作(commit/push)の前に自動的に品質チェックを実行する仕組みを導入しています。

### 実行されるチェック

1. **Format Check** - コードフォーマットの検証
2. **Lint** - コード品質の検証
3. **Test** - ユニットテストの実行
4. **ShellCheck** - シェルスクリプトの検証
5. **Security Credential Scan** - 認証情報の漏洩チェック
6. **Code Complexity Check** - コード複雑度の検証

### Hooks設定

`.claude/hooks/` ディレクトリに以下のHooksスクリプトが配置されています:

- `block_git_no_verify.py`: `--no-verify` や `HUSKY=0` の使用をブロック
- `pre_git_quality_gates.py`: Git操作前にQuality Gatesを実行

これらは `.claude/settings.local.json` の `hooks` フィールドで設定されており、Claudeによる `git commit` や `git push` の実行前に自動的にトリガーされます。

詳細は [.claude/hooks/README.md](./.claude/hooks/README.md) を参照してください。
184 changes: 184 additions & 0 deletions .claude/hooks/README.md
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)
149 changes: 149 additions & 0 deletions .claude/hooks/pre_git_quality_gates.py
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)
Comment on lines +112 to +114

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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 to failed_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
     except FileNotFoundError:
-        # スクリプトが存在しない場合はスキップ
-        print(f"  ⚠️  スキップ (コマンドが見つかりません)", file=sys.stderr)
+        # スクリプトが存在しない場合は失敗として記録
+        failed_checks.append({
+            "name": check["name"],
+            "error": "コマンドが見つかりません"
+        })
+        print(f"  ❌ 失敗 (コマンドが見つかりません)", file=sys.stderr)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except FileNotFoundError:
# スクリプトが存在しない場合はスキップ
print(f" ⚠️ スキップ (コマンドが見つかりません)", file=sys.stderr)
except FileNotFoundError:
# スクリプトが存在しない場合は失敗として記録
failed_checks.append({
"name": check["name"],
"error": "コマンドが見つかりません"
})
print(f" ❌ 失敗 (コマンドが見つかりません)", file=sys.stderr)
🧰 Tools
🪛 Ruff (0.14.10)

114-114: f-string without any placeholders

Remove extraneous f prefix

(F541)

🤖 Prompt for AI Agents
In .claude/hooks/pre_git_quality_gates.py around lines 112-114, the
FileNotFoundError handler currently only prints a skip message; change it to
treat missing scripts as failures by appending a descriptive failure entry to
failed_checks (e.g., failed_checks.append((script_name, "script not found")))
and printing an error to stderr, and ensure the script sets a non-zero exit (or
lets the outer logic detect failed_checks) so the quality gate fails rather than
skipping; keep the message informative (include script name/path) and do not
swallow the exception silently.


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)