1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
"""Shared helpers for Claude Code PreToolUse confirmation hooks.
Not a hook itself — imported by sibling scripts in ~/.claude/hooks/
(installed as symlinks by `make install-hooks`). Python resolves imports
relative to the invoked script's directory, so sibling symlinks just work.
Provides:
read_payload() → dict parsed from stdin (empty dict on failure)
respond_ask(...) → emit a PreToolUse permissionDecision=ask response
scan_attribution() → detect AI-attribution patterns in commit/PR text
AI-attribution scanning targets structural leak patterns (trailers,
footers, robot emoji) — NOT bare mentions of 'Claude' or 'Anthropic',
which are legitimate words and would false-positive on diffs discussing
the tools themselves.
"""
import json
import re
import sys
from typing import Optional
ATTRIBUTION_PATTERNS: list[tuple[str, str]] = [
(r"Co-Authored-By:\s*(?:Claude|Anthropic|GPT|AI\b|an? LLM)",
"Co-Authored-By trailer crediting an AI"),
(r"🤖",
"robot emoji (🤖)"),
(r"Generated (?:with|by) (?:Claude|Anthropic|AI|an? LLM)",
"'Generated with AI' footer"),
(r"Created (?:with|by) (?:Claude|Anthropic|AI|an? LLM)",
"'Created with AI' footer"),
(r"Assisted by (?:Claude|Anthropic|AI|an? LLM)",
"'Assisted by AI' credit"),
(r"\[\s*(?:Claude|AI|LLM)\s*(?:Code)?\s*\]",
"[Claude] / [AI] bracketed tag"),
]
def read_payload() -> dict:
"""Parse tool-call JSON from stdin. Return {} on any parse failure."""
try:
return json.loads(sys.stdin.read())
except (json.JSONDecodeError, ValueError):
return {}
def respond_ask(reason: str, system_message: Optional[str] = None) -> None:
"""Emit a PreToolUse response asking the user to confirm.
`reason` fills the modal body (permissionDecisionReason).
`system_message`, if set, surfaces a secondary banner/warning to the
user in a slot distinct from the modal.
"""
output: dict = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "ask",
"permissionDecisionReason": reason,
}
}
if system_message:
output["systemMessage"] = system_message
print(json.dumps(output))
def scan_attribution(text: str) -> list[str]:
"""Return human-readable descriptions of any AI-attribution hits."""
if not text:
return []
hits: list[str] = []
for pattern, description in ATTRIBUTION_PATTERNS:
if re.search(pattern, text, re.IGNORECASE):
hits.append(description)
return hits
|