x
This commit is contained in:
@@ -374,7 +374,12 @@ def pretooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
|
||||
# Only analyze for write/edit tools
|
||||
if tool_name not in ["Write", "Edit", "MultiEdit"]:
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
|
||||
# Extract content based on tool type
|
||||
content = None
|
||||
@@ -390,11 +395,21 @@ def pretooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
|
||||
# Only analyze Python files
|
||||
if not file_path or not file_path.endswith(".py") or not content:
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
|
||||
# Skip analysis for configured patterns
|
||||
if should_skip_file(file_path, config):
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
# Store state if tracking enabled
|
||||
@@ -415,31 +430,59 @@ def pretooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
|
||||
# Make decision based on enforcement mode
|
||||
if config.enforcement_mode == "strict":
|
||||
return {"decision": "deny", "message": message}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": message
|
||||
}
|
||||
}
|
||||
if config.enforcement_mode == "warn":
|
||||
return {"decision": "ask", "message": message}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "ask",
|
||||
"permissionDecisionReason": message
|
||||
}
|
||||
}
|
||||
# permissive
|
||||
return {
|
||||
"decision": "allow",
|
||||
"message": f"⚠️ Quality Warning:\n{message}",
|
||||
"systemMessage": f"⚠️ Quality Warning:\n{message}",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
return {"decision": "allow"} # noqa: TRY300
|
||||
|
||||
except Exception as e: # noqa: BLE001
|
||||
return {
|
||||
"decision": "allow",
|
||||
"message": f"Warning: Code quality check failed with error: {e}",
|
||||
"systemMessage": f"Warning: Code quality check failed with error: {e}",
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def posttooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
"""Handle PostToolUse hook - verify quality after write/edit."""
|
||||
tool_name = hook_data.get("tool_name", "")
|
||||
tool_output = hook_data.get("tool_output", {})
|
||||
tool_output = hook_data.get("tool_response", {})
|
||||
|
||||
# Only process write/edit tools
|
||||
if tool_name not in ["Write", "Edit", "MultiEdit"]:
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse"
|
||||
}
|
||||
}
|
||||
|
||||
# Extract file path from output
|
||||
file_path = None
|
||||
@@ -451,10 +494,18 @@ def posttooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
file_path = match[1]
|
||||
|
||||
if not file_path or not file_path.endswith(".py"):
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse"
|
||||
}
|
||||
}
|
||||
|
||||
if not Path(file_path).exists():
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse"
|
||||
}
|
||||
}
|
||||
|
||||
issues = []
|
||||
|
||||
@@ -479,14 +530,27 @@ def posttooluse_hook(hook_data: dict, config: QualityConfig) -> dict:
|
||||
f"📝 Post-write quality notes for {Path(file_path).name}:\n"
|
||||
+ "\n".join(issues)
|
||||
)
|
||||
return {"decision": "allow", "message": message}
|
||||
if config.show_success:
|
||||
return {
|
||||
"decision": "allow",
|
||||
"message": f"✅ {Path(file_path).name} passed post-write verification",
|
||||
"systemMessage": message,
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse",
|
||||
"additionalContext": message
|
||||
}
|
||||
}
|
||||
if config.show_success:
|
||||
message = f"✅ {Path(file_path).name} passed post-write verification"
|
||||
return {
|
||||
"systemMessage": message,
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse"
|
||||
}
|
||||
}
|
||||
|
||||
return {"decision": "allow"}
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PostToolUse"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -499,11 +563,16 @@ def main() -> None:
|
||||
try:
|
||||
hook_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError:
|
||||
print(json.dumps({"decision": "allow"})) # noqa: T201
|
||||
print(json.dumps({
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "allow"
|
||||
}
|
||||
})) # noqa: T201
|
||||
return
|
||||
|
||||
# Detect hook type based on tool_output (PostToolUse) vs tool_input (PreToolUse)
|
||||
if "tool_output" in hook_data:
|
||||
# Detect hook type based on tool_response (PostToolUse) vs tool_input (PreToolUse)
|
||||
if "tool_response" in hook_data:
|
||||
# PostToolUse hook
|
||||
response = posttooluse_hook(hook_data, config)
|
||||
else:
|
||||
@@ -512,16 +581,19 @@ def main() -> None:
|
||||
|
||||
print(json.dumps(response)) # noqa: T201
|
||||
|
||||
# Handle exit codes according to Claude Code spec
|
||||
if response.get("decision") == "deny":
|
||||
# Handle exit codes based on hook output
|
||||
hook_output = response.get("hookSpecificOutput", {})
|
||||
permission_decision = hook_output.get("permissionDecision")
|
||||
|
||||
if permission_decision == "deny":
|
||||
# Exit code 2: Blocking error - stderr fed back to Claude
|
||||
if "message" in response:
|
||||
sys.stderr.write(response["message"])
|
||||
reason = hook_output.get("permissionDecisionReason", "Permission denied")
|
||||
sys.stderr.write(reason)
|
||||
sys.exit(2)
|
||||
elif response.get("decision") == "ask":
|
||||
elif permission_decision == "ask":
|
||||
# Also use exit code 2 for ask decisions to ensure Claude sees the message
|
||||
if "message" in response:
|
||||
sys.stderr.write(response["message"])
|
||||
reason = hook_output.get("permissionDecisionReason", "Permission request")
|
||||
sys.stderr.write(reason)
|
||||
sys.exit(2)
|
||||
# Exit code 0: Success (default)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user