-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdispatcher_post.py
More file actions
126 lines (108 loc) · 3.74 KB
/
dispatcher_post.py
File metadata and controls
126 lines (108 loc) · 3.74 KB
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
"""Single dispatcher for all PostToolUse hooks (project + global).
Routes by tool_name so only relevant hooks run. Replaces ~25 separate python3 spawns.
"""
import importlib.util
import io
import json
import sys
from pathlib import Path
HOOKS_DIR = Path(__file__).parent
# tool_name → list of hook scripts to run
ROUTING = {
"Edit": [
"file_unlock.py", "auto_pip_install.py", "auto_memory_index.py",
"auto_skill_sync.py", "auto_bot_restart.py", "auto_dependency_grep.py",
"reddit_api_block.py", "mcp_server_restart.py", "reasoning_leak_canary.py",
# Global guards (Tier 2)
"admin_only_guard.py", "async_safety_guard.py", "hardcoded_model_guard.py",
"resource_leak_guard.py", "temp_file_guard.py", "tg_api_guard.py",
"tg_security_guard.py", "auto_hook_commit.py", "auto_test_after_edit.py",
],
"Write": [
"file_unlock.py", "auto_memory_index.py", "auto_copyright_header.py",
"auto_dependency_grep.py",
# Global guards (Tier 2)
"admin_only_guard.py", "async_safety_guard.py", "hardcoded_model_guard.py",
"resource_leak_guard.py", "temp_file_guard.py", "tg_api_guard.py",
"tg_security_guard.py", "auto_hook_commit.py", "auto_test_after_edit.py",
],
"Bash": [
"auto_vps_sync.py", "auto_license.py", "auto_repo_check.py",
"auto_dependency_grep.py", "auto_restart_process.py", "verify_infra.py",
"revert_memory_chain.py", "pre_commit_validate.py",
],
"Read": [
"memory_access_tracker.py", "memory_conflict_guard.py",
],
"Skill": [
"skill_disable_hook.py",
],
"mcp__claude_ai_Gmail__gmail_create_draft": [
"gmail_humanizer.py",
],
}
def load_and_run(script_name, event_data):
"""Import and run a hook's main() function, capturing its stdout."""
path = HOOKS_DIR / script_name
if not path.exists():
return None
spec = importlib.util.spec_from_file_location(script_name.replace(".py", ""), path)
if not spec or not spec.loader:
return None
mod = importlib.util.module_from_spec(spec)
old_stdin = sys.stdin
old_stdout = sys.stdout
sys.stdin = io.StringIO(json.dumps(event_data))
captured = io.StringIO()
sys.stdout = captured
try:
spec.loader.exec_module(mod)
if hasattr(mod, "main"):
mod.main()
except SystemExit:
pass
except Exception:
pass
finally:
sys.stdin = old_stdin
sys.stdout = old_stdout
output = captured.getvalue().strip()
if output:
try:
return json.loads(output)
except json.JSONDecodeError:
pass
return None
def main():
try:
event = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
print("{}")
return
tool_name = event.get("tool_name", "")
# Collect hooks to run
hooks_to_run = []
if tool_name in ROUTING:
hooks_to_run.extend(ROUTING[tool_name])
if not hooks_to_run:
print("{}")
return
# Run hooks, merge results
merged = {}
for script in hooks_to_run:
result = load_and_run(script, event)
if result:
# Merge additionalContext
if "additionalContext" in result:
if "additionalContext" in merged:
merged["additionalContext"] += "\n" + result["additionalContext"]
else:
merged["additionalContext"] = result["additionalContext"]
# Merge other keys (decision, etc)
for k, v in result.items():
if k != "additionalContext":
merged[k] = v
print(json.dumps(merged) if merged else "{}")
if __name__ == "__main__":
main()