Skip to content

Instantly share code, notes, and snippets.

@na0x2c6
Last active August 9, 2025 07:44
Show Gist options
  • Save na0x2c6/e69ee11e51795ad6e7c25ea5cb686a21 to your computer and use it in GitHub Desktop.
Save na0x2c6/e69ee11e51795ad6e7c25ea5cb686a21 to your computer and use it in GitHub Desktop.
Claude Code PreToolUse hook for Bash matcher
#!/usr/bin/env python3
import json
import re
import sys
# Define validation rules as a list of (validation function, message) tuples
VALIDATION_RULES = [
(
lambda cmd: cmd.strip().startswith("grep "),
"grep の変わりに git grep --function-context [--and|--or|--not|(|)|-e <pattern>...] -- <pathspec>... を使ってください。--function-context フラグにより出力行が多すぎる場合、 --show-function と -C フラグを利用してください",
),
(
lambda cmd: cmd.strip().startswith("rg "),
"rg の変わりに git grep --function-context [--and|--or|--not|(|)|-e <pattern>...] -- <pathspec>... を使ってください。--function-context フラグにより出力行が多すぎる場合、 --show-function と -C フラグを利用してください",
),
(
lambda cmd: (
re.match(r"^git\s+grep\s+", cmd) and
not re.search(r"-W|-p|--function-context|--show-function", cmd)
),
"git grep では --function-context か --show-function フラグを使ってください。まず --function-context フラグを利用し、結果行が多すぎる場合、 --show-function と [ -C | -A | -B ] フラグを利用してください",
),
(
lambda cmd: re.search(r"\bfind\s+.+\s+-name\b", cmd),
"find -name の変わりに git ls-files -- <パターン> を使ってください。git ls-files -o --exclude-standard を使うと、未追跡のファイルも確認できます。チェックアウトしていないコミットを確認するときは --with-tree=<tree-ish> でコミットを指定してください",
),
(
lambda cmd: (
re.match(r"^git\s+checkout\s+", cmd) and
# Extract the branch/ref being checked out
(match := re.match(r"^git\s+checkout\s+(\S+)", cmd)) and
match.group(1) not in ["-b", "-B", "--"] and
not match.group(1).startswith("origin/") and
not match.group(1).startswith("-") # Exclude flags
),
"ローカルのブランチはチェックアウトせず、detached checkout してください。例えば git checkout master はせず git checkout origin/master してください",
),
(
lambda cmd: (
re.search(r"^git\s+ls-files\b.*\|\s*xargs\s+(git\s+)?grep", cmd)
),
"git ls-files を xargs へパイプして使うのではなく、git grep --show-function [-C|-A|-B] -- <path...> を使ってください。xargs は不要です",
),
(
lambda cmd: (
re.match(r"^cd", cmd)
),
"cd コマンドは使わないでください。例えば yarn の場合 --cwd フラグ、make の場合 -C フラグ、docker compose なら --project-directory フラグが利用できます",
),
]
def validate_command(command: str) -> list[str]:
issues = []
for validation_func, message in VALIDATION_RULES:
if validation_func(command):
issues.append(message)
return issues
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")
if tool_name != "Bash" or not command:
sys.exit(1)
# Validate the command
issues = validate_command(command)
if issues:
for message in issues:
print(f"• {message}", file=sys.stderr)
sys.exit(2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment