claude-mpm 4.13.2__py3-none-any.whl → 4.18.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  3. claude_mpm/agents/BASE_PM.md +48 -17
  4. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  5. claude_mpm/agents/PM_INSTRUCTIONS.md +227 -8
  6. claude_mpm/agents/agent_loader.py +17 -5
  7. claude_mpm/agents/frontmatter_validator.py +284 -253
  8. claude_mpm/agents/templates/agentic-coder-optimizer.json +9 -2
  9. claude_mpm/agents/templates/api_qa.json +7 -1
  10. claude_mpm/agents/templates/clerk-ops.json +8 -1
  11. claude_mpm/agents/templates/code_analyzer.json +4 -1
  12. claude_mpm/agents/templates/dart_engineer.json +11 -1
  13. claude_mpm/agents/templates/data_engineer.json +11 -1
  14. claude_mpm/agents/templates/documentation.json +6 -1
  15. claude_mpm/agents/templates/engineer.json +18 -1
  16. claude_mpm/agents/templates/gcp_ops_agent.json +8 -1
  17. claude_mpm/agents/templates/golang_engineer.json +11 -1
  18. claude_mpm/agents/templates/java_engineer.json +12 -2
  19. claude_mpm/agents/templates/local_ops_agent.json +1217 -6
  20. claude_mpm/agents/templates/nextjs_engineer.json +11 -1
  21. claude_mpm/agents/templates/ops.json +8 -1
  22. claude_mpm/agents/templates/php-engineer.json +11 -1
  23. claude_mpm/agents/templates/project_organizer.json +10 -3
  24. claude_mpm/agents/templates/prompt-engineer.json +5 -1
  25. claude_mpm/agents/templates/python_engineer.json +11 -1
  26. claude_mpm/agents/templates/qa.json +7 -1
  27. claude_mpm/agents/templates/react_engineer.json +11 -1
  28. claude_mpm/agents/templates/refactoring_engineer.json +8 -1
  29. claude_mpm/agents/templates/research.json +4 -1
  30. claude_mpm/agents/templates/ruby-engineer.json +11 -1
  31. claude_mpm/agents/templates/rust_engineer.json +11 -1
  32. claude_mpm/agents/templates/security.json +6 -1
  33. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  34. claude_mpm/agents/templates/ticketing.json +6 -1
  35. claude_mpm/agents/templates/typescript_engineer.json +11 -1
  36. claude_mpm/agents/templates/vercel_ops_agent.json +8 -1
  37. claude_mpm/agents/templates/version_control.json +8 -1
  38. claude_mpm/agents/templates/web_qa.json +7 -1
  39. claude_mpm/agents/templates/web_ui.json +11 -1
  40. claude_mpm/cli/__init__.py +34 -706
  41. claude_mpm/cli/commands/agent_manager.py +25 -12
  42. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  43. claude_mpm/cli/commands/agents.py +204 -148
  44. claude_mpm/cli/commands/aggregate.py +7 -3
  45. claude_mpm/cli/commands/analyze.py +9 -4
  46. claude_mpm/cli/commands/analyze_code.py +7 -2
  47. claude_mpm/cli/commands/auto_configure.py +7 -9
  48. claude_mpm/cli/commands/config.py +47 -13
  49. claude_mpm/cli/commands/configure.py +294 -1788
  50. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  51. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  52. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  53. claude_mpm/cli/commands/configure_models.py +18 -0
  54. claude_mpm/cli/commands/configure_navigation.py +167 -0
  55. claude_mpm/cli/commands/configure_paths.py +104 -0
  56. claude_mpm/cli/commands/configure_persistence.py +254 -0
  57. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  58. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  59. claude_mpm/cli/commands/configure_validators.py +73 -0
  60. claude_mpm/cli/commands/local_deploy.py +537 -0
  61. claude_mpm/cli/commands/memory.py +54 -20
  62. claude_mpm/cli/commands/mpm_init.py +39 -25
  63. claude_mpm/cli/commands/mpm_init_handler.py +8 -3
  64. claude_mpm/cli/executor.py +202 -0
  65. claude_mpm/cli/helpers.py +105 -0
  66. claude_mpm/cli/interactive/__init__.py +3 -0
  67. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  68. claude_mpm/cli/parsers/__init__.py +7 -1
  69. claude_mpm/cli/parsers/base_parser.py +98 -3
  70. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  71. claude_mpm/cli/shared/output_formatters.py +28 -19
  72. claude_mpm/cli/startup.py +481 -0
  73. claude_mpm/cli/utils.py +52 -1
  74. claude_mpm/commands/mpm-help.md +3 -0
  75. claude_mpm/commands/mpm-version.md +113 -0
  76. claude_mpm/commands/mpm.md +1 -0
  77. claude_mpm/config/agent_config.py +2 -2
  78. claude_mpm/config/model_config.py +428 -0
  79. claude_mpm/core/base_service.py +13 -12
  80. claude_mpm/core/enums.py +452 -0
  81. claude_mpm/core/factories.py +1 -1
  82. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  83. claude_mpm/core/interactive_session.py +9 -3
  84. claude_mpm/core/logging_config.py +6 -2
  85. claude_mpm/core/oneshot_session.py +8 -4
  86. claude_mpm/core/optimized_agent_loader.py +3 -3
  87. claude_mpm/core/output_style_manager.py +12 -192
  88. claude_mpm/core/service_registry.py +5 -1
  89. claude_mpm/core/types.py +2 -9
  90. claude_mpm/core/typing_utils.py +7 -6
  91. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  92. claude_mpm/dashboard/templates/index.html +3 -41
  93. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  94. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  95. claude_mpm/models/resume_log.py +340 -0
  96. claude_mpm/services/agents/auto_config_manager.py +10 -11
  97. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  98. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  99. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  100. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  101. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  102. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  103. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  104. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  105. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  106. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  107. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  108. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  109. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  110. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  111. claude_mpm/services/agents/local_template_manager.py +1 -1
  112. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  113. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  114. claude_mpm/services/command_handler_service.py +11 -5
  115. claude_mpm/services/core/interfaces/__init__.py +74 -2
  116. claude_mpm/services/core/interfaces/health.py +172 -0
  117. claude_mpm/services/core/interfaces/model.py +281 -0
  118. claude_mpm/services/core/interfaces/process.py +372 -0
  119. claude_mpm/services/core/interfaces/restart.py +307 -0
  120. claude_mpm/services/core/interfaces/stability.py +260 -0
  121. claude_mpm/services/core/models/__init__.py +33 -0
  122. claude_mpm/services/core/models/agent_config.py +12 -28
  123. claude_mpm/services/core/models/health.py +162 -0
  124. claude_mpm/services/core/models/process.py +235 -0
  125. claude_mpm/services/core/models/restart.py +302 -0
  126. claude_mpm/services/core/models/stability.py +264 -0
  127. claude_mpm/services/core/path_resolver.py +23 -7
  128. claude_mpm/services/diagnostics/__init__.py +2 -2
  129. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  130. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  131. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  132. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  133. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  134. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  135. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  136. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  137. claude_mpm/services/diagnostics/checks/mcp_services_check.py +36 -31
  138. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  139. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  140. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  141. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  142. claude_mpm/services/diagnostics/models.py +19 -24
  143. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  144. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  145. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  146. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  147. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  148. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  149. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  150. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  151. claude_mpm/services/local_ops/__init__.py +163 -0
  152. claude_mpm/services/local_ops/crash_detector.py +257 -0
  153. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  154. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  155. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  156. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  157. claude_mpm/services/local_ops/health_manager.py +430 -0
  158. claude_mpm/services/local_ops/log_monitor.py +396 -0
  159. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  160. claude_mpm/services/local_ops/process_manager.py +595 -0
  161. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  162. claude_mpm/services/local_ops/restart_manager.py +401 -0
  163. claude_mpm/services/local_ops/restart_policy.py +387 -0
  164. claude_mpm/services/local_ops/state_manager.py +372 -0
  165. claude_mpm/services/local_ops/unified_manager.py +600 -0
  166. claude_mpm/services/mcp_config_manager.py +9 -4
  167. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  168. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  169. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +71 -24
  170. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  171. claude_mpm/services/memory_hook_service.py +4 -1
  172. claude_mpm/services/model/__init__.py +147 -0
  173. claude_mpm/services/model/base_provider.py +365 -0
  174. claude_mpm/services/model/claude_provider.py +412 -0
  175. claude_mpm/services/model/model_router.py +453 -0
  176. claude_mpm/services/model/ollama_provider.py +415 -0
  177. claude_mpm/services/monitor/daemon_manager.py +3 -2
  178. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  179. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  180. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  181. claude_mpm/services/monitor/server.py +2 -1
  182. claude_mpm/services/session_management_service.py +3 -2
  183. claude_mpm/services/session_manager.py +205 -1
  184. claude_mpm/services/shared/async_service_base.py +16 -27
  185. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  186. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  187. claude_mpm/services/socketio/handlers/hook.py +13 -2
  188. claude_mpm/services/socketio/handlers/registry.py +4 -2
  189. claude_mpm/services/socketio/server/main.py +10 -8
  190. claude_mpm/services/subprocess_launcher_service.py +14 -5
  191. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  192. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  193. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  194. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  195. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  196. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  197. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  198. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  199. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  200. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  201. claude_mpm/services/unified/interfaces.py +3 -1
  202. claude_mpm/services/unified/unified_analyzer.py +14 -10
  203. claude_mpm/services/unified/unified_config.py +2 -1
  204. claude_mpm/services/unified/unified_deployment.py +9 -4
  205. claude_mpm/services/version_service.py +104 -1
  206. claude_mpm/skills/__init__.py +21 -0
  207. claude_mpm/skills/bundled/__init__.py +6 -0
  208. claude_mpm/skills/bundled/api-documentation.md +393 -0
  209. claude_mpm/skills/bundled/async-testing.md +571 -0
  210. claude_mpm/skills/bundled/code-review.md +143 -0
  211. claude_mpm/skills/bundled/database-migration.md +199 -0
  212. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  213. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  214. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  215. claude_mpm/skills/bundled/git-workflow.md +414 -0
  216. claude_mpm/skills/bundled/imagemagick.md +204 -0
  217. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  218. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  219. claude_mpm/skills/bundled/pdf.md +141 -0
  220. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  221. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  222. claude_mpm/skills/bundled/security-scanning.md +327 -0
  223. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  224. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  225. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  226. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  227. claude_mpm/skills/bundled/xlsx.md +157 -0
  228. claude_mpm/skills/registry.py +286 -0
  229. claude_mpm/skills/skill_manager.py +310 -0
  230. claude_mpm/tools/code_tree_analyzer.py +177 -141
  231. claude_mpm/tools/code_tree_events.py +4 -2
  232. claude_mpm/utils/agent_dependency_loader.py +2 -2
  233. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +117 -8
  234. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +238 -174
  235. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  236. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  237. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  238. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  239. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  240. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  241. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  242. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  243. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  244. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  245. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  246. claude_mpm/services/project/analyzer_refactored.py +0 -450
  247. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  248. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  249. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  250. {claude_mpm-4.13.2.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
@@ -1,425 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Optimized Claude Code hook handler with EventBus architecture.
3
-
4
- This handler uses the EventBus for decoupled event emission instead of
5
- direct Socket.IO connections. This provides better separation of concerns
6
- and improved testability.
7
-
8
- WHY EventBus approach:
9
- - Decouples hook processing from Socket.IO implementation
10
- - Enables multiple event consumers without code changes
11
- - Simplifies testing by removing Socket.IO dependencies
12
- - Provides centralized event routing and filtering
13
- - Maintains backward compatibility with existing hooks
14
- """
15
-
16
- import json
17
- import os
18
- import select
19
- import signal
20
- import subprocess
21
- import sys
22
- import threading
23
- import time
24
- from collections import deque
25
- from datetime import datetime, timezone
26
- from pathlib import Path
27
- from typing import Optional
28
-
29
- # Add parent path for imports
30
- sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
31
-
32
- # Import EventBus
33
- try:
34
- from claude_mpm.services.event_bus import EventBus
35
-
36
- EVENTBUS_AVAILABLE = True
37
- except ImportError:
38
- EVENTBUS_AVAILABLE = False
39
- EventBus = None
40
-
41
- # Import EventNormalizer for consistent event formatting
42
- try:
43
- from claude_mpm.services.socketio.event_normalizer import EventNormalizer
44
- except ImportError:
45
- # Create a simple fallback EventNormalizer if import fails
46
- class EventNormalizer:
47
- def normalize(self, event_data, source="hook"):
48
- """Simple fallback normalizer that returns event as-is."""
49
- return type(
50
- "NormalizedEvent",
51
- (),
52
- {
53
- "to_dict": lambda: {
54
- "event": "claude_event",
55
- "type": event_data.get("type", "unknown"),
56
- "subtype": event_data.get("subtype", "generic"),
57
- "timestamp": event_data.get(
58
- "timestamp", datetime.now(timezone.utc).isoformat()
59
- ),
60
- "data": event_data.get("data", event_data),
61
- "source": source,
62
- }
63
- },
64
- )
65
-
66
-
67
- # Import constants for configuration
68
- try:
69
- from claude_mpm.core.constants import TimeoutConfig
70
- except ImportError:
71
- # Fallback values if constants module not available
72
- class TimeoutConfig:
73
- QUICK_TIMEOUT = 2.0
74
-
75
-
76
- # Import other handler modules
77
- try:
78
- from .event_handlers import EventHandlers
79
- from .memory_integration import MemoryHookManager
80
- from .response_tracking import ResponseTrackingManager
81
- except ImportError:
82
- # Fallback for direct execution
83
- from event_handlers import EventHandlers
84
- from memory_integration import MemoryHookManager
85
- from response_tracking import ResponseTrackingManager
86
-
87
- # Debug mode is enabled by default for better visibility into hook processing
88
- DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
89
-
90
- # Global singleton handler instance
91
- _global_handler = None
92
- _handler_lock = threading.Lock()
93
-
94
- # Track recent events to detect duplicates
95
- _recent_events = deque(maxlen=10)
96
- _events_lock = threading.Lock()
97
-
98
-
99
- class HookHandler:
100
- """Main hook handler class using EventBus for event emission.
101
-
102
- WHY EventBus integration:
103
- - Replaces direct Socket.IO connections with EventBus publishing
104
- - Events are published once and consumed by multiple listeners
105
- - Failures in one consumer don't affect others
106
- - Simplified testing without Socket.IO dependencies
107
- """
108
-
109
- # Tracking dictionaries with size limits
110
- MAX_DELEGATION_TRACKING = 100
111
- MAX_PROMPT_TRACKING = 50
112
- MAX_CACHE_AGE_SECONDS = 1800 # 30 minutes
113
-
114
- def __init__(self):
115
- """Initialize the hook handler with EventBus."""
116
- # Initialize EventBus if available
117
- self.event_bus = EventBus.get_instance() if EVENTBUS_AVAILABLE else None
118
- self.event_normalizer = EventNormalizer()
119
-
120
- # Initialize tracking managers
121
- self.memory_manager = MemoryHookManager()
122
- self.response_tracker = ResponseTrackingManager()
123
- self.event_handlers = EventHandlers(self)
124
-
125
- # Delegation tracking
126
- self.active_delegations = {}
127
- self.delegation_requests = {}
128
- self.delegation_history = deque(maxlen=20)
129
-
130
- # Prompt tracking
131
- self.pending_prompts = {}
132
-
133
- # Git branch caching
134
- self._git_branch_cache = {}
135
- self._git_branch_cache_time = {}
136
-
137
- # Session tracking
138
- self.current_session_id = None
139
-
140
- # Cleanup old entries periodically
141
- self._last_cleanup = time.time()
142
-
143
- if self.event_bus:
144
- logger_msg = "HookHandler initialized with EventBus"
145
- else:
146
- logger_msg = "HookHandler initialized (EventBus not available)"
147
-
148
- if DEBUG:
149
- print(f"šŸš€ {logger_msg}", file=sys.stderr)
150
-
151
- def _emit_event(self, event_type: str, data: dict):
152
- """Emit an event through the EventBus.
153
-
154
- WHY this approach:
155
- - Single point of event emission
156
- - Consistent event normalization
157
- - Graceful fallback if EventBus unavailable
158
- - Easy to add metrics and monitoring
159
-
160
- Args:
161
- event_type: The event type (e.g., 'pre_tool', 'subagent_stop')
162
- data: The event data
163
- """
164
- if not self.event_bus:
165
- if DEBUG:
166
- print(
167
- f"EventBus not available, cannot emit: hook.{event_type}",
168
- file=sys.stderr,
169
- )
170
- return
171
-
172
- try:
173
- # Create event data for normalization
174
- raw_event = {
175
- "type": "hook",
176
- "subtype": event_type,
177
- "timestamp": datetime.now(timezone.utc).isoformat(),
178
- "data": data,
179
- "source": "claude_hooks",
180
- "session_id": data.get("sessionId", self.current_session_id),
181
- }
182
-
183
- # Normalize the event
184
- normalized_event = self.event_normalizer.normalize(raw_event, source="hook")
185
- event_data = normalized_event.to_dict()
186
-
187
- # Publish to EventBus
188
- success = self.event_bus.publish(f"hook.{event_type}", event_data)
189
-
190
- if DEBUG:
191
- if success:
192
- print(
193
- f"āœ… Published to EventBus: hook.{event_type}", file=sys.stderr
194
- )
195
- else:
196
- print(
197
- f"āš ļø EventBus rejected event: hook.{event_type}", file=sys.stderr
198
- )
199
-
200
- # Log important events
201
- if DEBUG and event_type in ["subagent_stop", "pre_tool"]:
202
- if event_type == "subagent_stop":
203
- agent_type = data.get("agent_type", "unknown")
204
- print(
205
- f"šŸ“¤ Published SubagentStop for agent '{agent_type}'",
206
- file=sys.stderr,
207
- )
208
- elif event_type == "pre_tool" and data.get("tool_name") == "Task":
209
- delegation = data.get("delegation_details", {})
210
- agent_type = delegation.get("agent_type", "unknown")
211
- print(
212
- f"šŸ“¤ Published Task delegation to agent '{agent_type}'",
213
- file=sys.stderr,
214
- )
215
-
216
- except Exception as e:
217
- if DEBUG:
218
- print(
219
- f"āŒ Failed to publish event hook.{event_type}: {e}",
220
- file=sys.stderr,
221
- )
222
-
223
- def _get_git_branch(self, working_dir: Optional[str] = None) -> str:
224
- """Get git branch for the given directory with caching."""
225
- # Use current working directory if not specified
226
- if not working_dir:
227
- working_dir = Path.cwd()
228
-
229
- # Check cache first (cache for 30 seconds)
230
- current_time = time.time()
231
- cache_key = working_dir
232
-
233
- if (
234
- cache_key in self._git_branch_cache
235
- and cache_key in self._git_branch_cache_time
236
- and current_time - self._git_branch_cache_time[cache_key] < 30
237
- ):
238
- return self._git_branch_cache[cache_key]
239
-
240
- # Try to get git branch
241
- try:
242
- # Change to the working directory temporarily
243
- original_cwd = Path.cwd()
244
- os.chdir(working_dir)
245
-
246
- # Run git command to get current branch
247
- result = subprocess.run(
248
- ["git", "branch", "--show-current"],
249
- capture_output=True,
250
- text=True,
251
- timeout=TimeoutConfig.QUICK_TIMEOUT,
252
- check=False,
253
- )
254
-
255
- # Restore original directory
256
- os.chdir(original_cwd)
257
-
258
- if result.returncode == 0 and result.stdout.strip():
259
- branch = result.stdout.strip()
260
- # Cache the result
261
- self._git_branch_cache[cache_key] = branch
262
- self._git_branch_cache_time[cache_key] = current_time
263
- return branch
264
- return "unknown"
265
-
266
- except Exception:
267
- return "unknown"
268
-
269
- def _cleanup_old_entries(self):
270
- """Clean up old entries to prevent memory growth."""
271
- time.time() - self.MAX_CACHE_AGE_SECONDS
272
-
273
- # Clean up delegation tracking dictionaries
274
- for storage in [self.active_delegations, self.delegation_requests]:
275
- if len(storage) > self.MAX_DELEGATION_TRACKING:
276
- # Keep only the most recent entries
277
- sorted_keys = sorted(storage.keys())
278
- excess = len(storage) - self.MAX_DELEGATION_TRACKING
279
- for key in sorted_keys[:excess]:
280
- del storage[key]
281
-
282
- # Clean up pending prompts
283
- if len(self.pending_prompts) > self.MAX_PROMPT_TRACKING:
284
- sorted_keys = sorted(self.pending_prompts.keys())
285
- excess = len(self.pending_prompts) - self.MAX_PROMPT_TRACKING
286
- for key in sorted_keys[:excess]:
287
- del self.pending_prompts[key]
288
-
289
- # Clean up git branch cache
290
- expired_keys = [
291
- key
292
- for key, cache_time in self._git_branch_cache_time.items()
293
- if time.time() - cache_time > self.MAX_CACHE_AGE_SECONDS
294
- ]
295
- for key in expired_keys:
296
- self._git_branch_cache.pop(key, None)
297
- self._git_branch_cache_time.pop(key, None)
298
-
299
- def handle_event(self, event: dict):
300
- """Process an event from Claude Code.
301
-
302
- Args:
303
- event: The event dictionary from Claude
304
- """
305
- # Periodic cleanup
306
- current_time = time.time()
307
- if current_time - self._last_cleanup > 300: # Every 5 minutes
308
- self._cleanup_old_entries()
309
- self._last_cleanup = current_time
310
-
311
- # Extract event details
312
- event_type = event.get("type", "")
313
- event_name = event.get("name", "")
314
-
315
- # Update session ID if present
316
- if "sessionId" in event:
317
- self.current_session_id = event["sessionId"]
318
-
319
- # Detect duplicate events
320
- event_signature = (
321
- f"{event_type}:{event_name}:{json.dumps(event.get('data', ''))[:100]}"
322
- )
323
- with _events_lock:
324
- if event_signature in _recent_events:
325
- if DEBUG:
326
- print(f"Skipping duplicate event: {event_type}", file=sys.stderr)
327
- return
328
- _recent_events.append(event_signature)
329
-
330
- # Route to appropriate handler
331
- if event_type == "Start":
332
- self.event_handlers.handle_start(event)
333
- elif event_type == "Stop":
334
- self.event_handlers.handle_stop(event)
335
- elif event_type == "UserPrompt":
336
- self.event_handlers.handle_user_prompt(event)
337
- elif event_type == "AssistantResponse":
338
- self.event_handlers.handle_assistant_response(event)
339
- elif event_type == "SubagentStart":
340
- self.event_handlers.handle_subagent_start(event)
341
- elif event_type == "SubagentStop":
342
- self.event_handlers.handle_subagent_stop(event)
343
- elif event_type == "PreToolExecution" and event_name == "Task":
344
- self.event_handlers.handle_task_delegation(event)
345
- elif event_type == "PreToolExecution":
346
- self.event_handlers.handle_pre_tool(event)
347
- elif event_type == "PostToolExecution":
348
- self.event_handlers.handle_post_tool(event)
349
- elif event_type == "PromptCachingBetaStats":
350
- # Ignore caching stats events
351
- pass
352
- # Log unhandled events in debug mode
353
- elif DEBUG:
354
- print(f"Unhandled event type: {event_type}", file=sys.stderr)
355
-
356
-
357
- def get_handler() -> HookHandler:
358
- """Get or create the global hook handler instance.
359
-
360
- Returns:
361
- HookHandler: The singleton handler instance
362
- """
363
- global _global_handler
364
- if _global_handler is None:
365
- with _handler_lock:
366
- if _global_handler is None:
367
- _global_handler = HookHandler()
368
- return _global_handler
369
-
370
-
371
- def main():
372
- """Main entry point for the hook handler."""
373
- if DEBUG:
374
- print("šŸŽÆ EventBus Hook Handler starting...", file=sys.stderr)
375
-
376
- handler = get_handler()
377
-
378
- # Set up signal handling for clean shutdown
379
- def signal_handler(signum, frame):
380
- if DEBUG:
381
- print("\nšŸ‘‹ Hook handler shutting down...", file=sys.stderr)
382
- sys.exit(0)
383
-
384
- signal.signal(signal.SIGINT, signal_handler)
385
- signal.signal(signal.SIGTERM, signal_handler)
386
-
387
- # Process events from stdin
388
- try:
389
- while True:
390
- # Check if data is available with timeout
391
- readable, _, _ = select.select([sys.stdin], [], [], 0.1)
392
- if readable:
393
- line = sys.stdin.readline()
394
- if not line:
395
- break
396
-
397
- try:
398
- event = json.loads(line.strip())
399
- handler.handle_event(event)
400
-
401
- # Acknowledge event
402
- print(json.dumps({"status": "ok"}))
403
- sys.stdout.flush()
404
-
405
- except json.JSONDecodeError as e:
406
- if DEBUG:
407
- print(f"Invalid JSON: {e}", file=sys.stderr)
408
- print(json.dumps({"status": "error", "message": str(e)}))
409
- sys.stdout.flush()
410
- except Exception as e:
411
- if DEBUG:
412
- print(f"Error processing event: {e}", file=sys.stderr)
413
- print(json.dumps({"status": "error", "message": str(e)}))
414
- sys.stdout.flush()
415
-
416
- except KeyboardInterrupt:
417
- if DEBUG:
418
- print("\nšŸ‘‹ Hook handler interrupted", file=sys.stderr)
419
- finally:
420
- if DEBUG:
421
- print("Hook handler exiting", file=sys.stderr)
422
-
423
-
424
- if __name__ == "__main__":
425
- main()