claude-mpm 4.1.1__py3-none-any.whl → 4.1.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 (357) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +1 -1
  4. claude_mpm/agents/BASE_PM.md +74 -46
  5. claude_mpm/agents/INSTRUCTIONS.md +11 -153
  6. claude_mpm/agents/WORKFLOW.md +61 -321
  7. claude_mpm/agents/__init__.py +11 -11
  8. claude_mpm/agents/agent_loader.py +23 -20
  9. claude_mpm/agents/agent_loader_integration.py +1 -1
  10. claude_mpm/agents/agents_metadata.py +27 -0
  11. claude_mpm/agents/async_agent_loader.py +5 -8
  12. claude_mpm/agents/base_agent_loader.py +36 -25
  13. claude_mpm/agents/frontmatter_validator.py +6 -6
  14. claude_mpm/agents/schema/agent_schema.json +1 -1
  15. claude_mpm/agents/system_agent_config.py +9 -9
  16. claude_mpm/agents/templates/api_qa.json +47 -2
  17. claude_mpm/agents/templates/imagemagick.json +256 -0
  18. claude_mpm/agents/templates/qa.json +41 -2
  19. claude_mpm/agents/templates/ticketing.json +5 -5
  20. claude_mpm/agents/templates/web_qa.json +50 -2
  21. claude_mpm/cli/__init__.py +51 -46
  22. claude_mpm/cli/__main__.py +1 -1
  23. claude_mpm/cli/commands/__init__.py +10 -12
  24. claude_mpm/cli/commands/agent_manager.py +186 -181
  25. claude_mpm/cli/commands/agents.py +271 -268
  26. claude_mpm/cli/commands/aggregate.py +30 -29
  27. claude_mpm/cli/commands/cleanup.py +50 -44
  28. claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
  29. claude_mpm/cli/commands/config.py +162 -127
  30. claude_mpm/cli/commands/doctor.py +52 -62
  31. claude_mpm/cli/commands/info.py +37 -25
  32. claude_mpm/cli/commands/mcp.py +3 -7
  33. claude_mpm/cli/commands/mcp_command_router.py +14 -18
  34. claude_mpm/cli/commands/mcp_install_commands.py +28 -23
  35. claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
  36. claude_mpm/cli/commands/mcp_server_commands.py +23 -17
  37. claude_mpm/cli/commands/memory.py +192 -141
  38. claude_mpm/cli/commands/monitor.py +117 -88
  39. claude_mpm/cli/commands/run.py +120 -84
  40. claude_mpm/cli/commands/run_config_checker.py +4 -5
  41. claude_mpm/cli/commands/socketio_monitor.py +17 -19
  42. claude_mpm/cli/commands/tickets.py +92 -92
  43. claude_mpm/cli/parser.py +1 -5
  44. claude_mpm/cli/parsers/__init__.py +1 -1
  45. claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
  46. claude_mpm/cli/parsers/agents_parser.py +2 -3
  47. claude_mpm/cli/parsers/base_parser.py +7 -5
  48. claude_mpm/cli/parsers/mcp_parser.py +4 -2
  49. claude_mpm/cli/parsers/monitor_parser.py +26 -18
  50. claude_mpm/cli/shared/__init__.py +10 -10
  51. claude_mpm/cli/shared/argument_patterns.py +57 -71
  52. claude_mpm/cli/shared/base_command.py +61 -53
  53. claude_mpm/cli/shared/error_handling.py +62 -58
  54. claude_mpm/cli/shared/output_formatters.py +78 -77
  55. claude_mpm/cli/startup_logging.py +204 -172
  56. claude_mpm/cli/utils.py +10 -11
  57. claude_mpm/cli_module/__init__.py +1 -1
  58. claude_mpm/cli_module/args.py +1 -1
  59. claude_mpm/cli_module/migration_example.py +5 -5
  60. claude_mpm/config/__init__.py +9 -9
  61. claude_mpm/config/agent_config.py +15 -14
  62. claude_mpm/config/experimental_features.py +4 -4
  63. claude_mpm/config/paths.py +0 -1
  64. claude_mpm/config/socketio_config.py +5 -6
  65. claude_mpm/constants.py +1 -2
  66. claude_mpm/core/__init__.py +8 -8
  67. claude_mpm/core/agent_name_normalizer.py +1 -1
  68. claude_mpm/core/agent_registry.py +20 -23
  69. claude_mpm/core/agent_session_manager.py +3 -3
  70. claude_mpm/core/base_service.py +7 -15
  71. claude_mpm/core/cache.py +4 -6
  72. claude_mpm/core/claude_runner.py +85 -113
  73. claude_mpm/core/config.py +43 -28
  74. claude_mpm/core/config_aliases.py +0 -9
  75. claude_mpm/core/config_constants.py +52 -30
  76. claude_mpm/core/constants.py +0 -1
  77. claude_mpm/core/container.py +18 -27
  78. claude_mpm/core/exceptions.py +2 -2
  79. claude_mpm/core/factories.py +10 -12
  80. claude_mpm/core/framework_loader.py +581 -280
  81. claude_mpm/core/hook_manager.py +26 -22
  82. claude_mpm/core/hook_performance_config.py +58 -47
  83. claude_mpm/core/injectable_service.py +1 -1
  84. claude_mpm/core/interactive_session.py +61 -152
  85. claude_mpm/core/interfaces.py +1 -100
  86. claude_mpm/core/lazy.py +5 -5
  87. claude_mpm/core/log_manager.py +587 -0
  88. claude_mpm/core/logger.py +125 -8
  89. claude_mpm/core/logging_config.py +15 -15
  90. claude_mpm/core/minimal_framework_loader.py +5 -8
  91. claude_mpm/core/oneshot_session.py +15 -33
  92. claude_mpm/core/optimized_agent_loader.py +4 -6
  93. claude_mpm/core/optimized_startup.py +2 -1
  94. claude_mpm/core/output_style_manager.py +147 -106
  95. claude_mpm/core/pm_hook_interceptor.py +0 -1
  96. claude_mpm/core/service_registry.py +11 -8
  97. claude_mpm/core/session_manager.py +1 -2
  98. claude_mpm/core/shared/__init__.py +1 -1
  99. claude_mpm/core/shared/config_loader.py +101 -97
  100. claude_mpm/core/shared/path_resolver.py +72 -68
  101. claude_mpm/core/shared/singleton_manager.py +56 -50
  102. claude_mpm/core/socketio_pool.py +26 -6
  103. claude_mpm/core/tool_access_control.py +4 -5
  104. claude_mpm/core/typing_utils.py +50 -59
  105. claude_mpm/core/unified_agent_registry.py +14 -19
  106. claude_mpm/core/unified_config.py +4 -6
  107. claude_mpm/core/unified_paths.py +197 -109
  108. claude_mpm/dashboard/open_dashboard.py +2 -4
  109. claude_mpm/experimental/cli_enhancements.py +51 -36
  110. claude_mpm/generators/agent_profile_generator.py +2 -4
  111. claude_mpm/hooks/base_hook.py +1 -2
  112. claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
  113. claude_mpm/hooks/claude_hooks/event_handlers.py +93 -38
  114. claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
  115. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
  116. claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
  117. claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
  118. claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
  119. claude_mpm/hooks/memory_integration_hook.py +5 -5
  120. claude_mpm/hooks/tool_call_interceptor.py +1 -1
  121. claude_mpm/hooks/validation_hooks.py +4 -4
  122. claude_mpm/init.py +4 -9
  123. claude_mpm/models/__init__.py +2 -2
  124. claude_mpm/models/agent_session.py +11 -14
  125. claude_mpm/scripts/mcp_server.py +20 -11
  126. claude_mpm/scripts/mcp_wrapper.py +5 -5
  127. claude_mpm/scripts/mpm_doctor.py +321 -0
  128. claude_mpm/scripts/socketio_daemon.py +28 -25
  129. claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
  130. claude_mpm/scripts/socketio_server_manager.py +116 -95
  131. claude_mpm/services/__init__.py +49 -49
  132. claude_mpm/services/agent_capabilities_service.py +12 -18
  133. claude_mpm/services/agents/__init__.py +22 -22
  134. claude_mpm/services/agents/agent_builder.py +140 -119
  135. claude_mpm/services/agents/deployment/__init__.py +3 -3
  136. claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
  137. claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
  138. claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
  139. claude_mpm/services/agents/deployment/agent_deployment.py +136 -106
  140. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
  141. claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
  142. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
  143. claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
  144. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
  145. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
  146. claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
  147. claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
  148. claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
  149. claude_mpm/services/agents/deployment/agent_validator.py +0 -1
  150. claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
  151. claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
  152. claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
  153. claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
  154. claude_mpm/services/agents/deployment/config/__init__.py +1 -1
  155. claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
  156. claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
  157. claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
  158. claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
  159. claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
  160. claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
  161. claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
  162. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
  163. claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
  164. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
  165. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
  166. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
  167. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
  168. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
  169. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
  170. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
  171. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
  172. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
  173. claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
  174. claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
  175. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
  176. claude_mpm/services/agents/deployment/results/__init__.py +1 -1
  177. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
  178. claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
  179. claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
  180. claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
  181. claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
  182. claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
  183. claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
  184. claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
  185. claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
  186. claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
  187. claude_mpm/services/agents/loading/__init__.py +1 -1
  188. claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
  189. claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
  191. claude_mpm/services/agents/management/__init__.py +1 -1
  192. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
  193. claude_mpm/services/agents/management/agent_management_service.py +5 -9
  194. claude_mpm/services/agents/memory/__init__.py +4 -4
  195. claude_mpm/services/agents/memory/agent_memory_manager.py +280 -160
  196. claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
  197. claude_mpm/services/agents/memory/content_manager.py +44 -38
  198. claude_mpm/services/agents/memory/template_generator.py +4 -6
  199. claude_mpm/services/agents/registry/__init__.py +10 -6
  200. claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
  201. claude_mpm/services/agents/registry/modification_tracker.py +3 -6
  202. claude_mpm/services/async_session_logger.py +1 -2
  203. claude_mpm/services/claude_session_logger.py +1 -2
  204. claude_mpm/services/command_deployment_service.py +173 -0
  205. claude_mpm/services/command_handler_service.py +20 -22
  206. claude_mpm/services/core/__init__.py +25 -25
  207. claude_mpm/services/core/base.py +0 -5
  208. claude_mpm/services/core/interfaces/__init__.py +32 -32
  209. claude_mpm/services/core/interfaces/agent.py +0 -21
  210. claude_mpm/services/core/interfaces/communication.py +0 -27
  211. claude_mpm/services/core/interfaces/infrastructure.py +0 -56
  212. claude_mpm/services/core/interfaces/service.py +0 -29
  213. claude_mpm/services/diagnostics/__init__.py +1 -1
  214. claude_mpm/services/diagnostics/checks/__init__.py +6 -6
  215. claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
  216. claude_mpm/services/diagnostics/checks/base_check.py +12 -16
  217. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
  218. claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
  219. claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
  220. claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
  221. claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
  222. claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
  223. claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
  224. claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
  225. claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
  226. claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
  227. claude_mpm/services/diagnostics/models.py +21 -19
  228. claude_mpm/services/event_aggregator.py +10 -17
  229. claude_mpm/services/event_bus/__init__.py +1 -1
  230. claude_mpm/services/event_bus/config.py +54 -35
  231. claude_mpm/services/event_bus/event_bus.py +76 -71
  232. claude_mpm/services/event_bus/relay.py +74 -64
  233. claude_mpm/services/events/__init__.py +11 -11
  234. claude_mpm/services/events/consumers/__init__.py +3 -3
  235. claude_mpm/services/events/consumers/dead_letter.py +71 -63
  236. claude_mpm/services/events/consumers/logging.py +39 -37
  237. claude_mpm/services/events/consumers/metrics.py +56 -57
  238. claude_mpm/services/events/consumers/socketio.py +82 -81
  239. claude_mpm/services/events/core.py +110 -99
  240. claude_mpm/services/events/interfaces.py +56 -72
  241. claude_mpm/services/events/producers/__init__.py +1 -1
  242. claude_mpm/services/events/producers/hook.py +38 -38
  243. claude_mpm/services/events/producers/system.py +46 -44
  244. claude_mpm/services/exceptions.py +81 -80
  245. claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
  246. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
  247. claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
  248. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
  249. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
  250. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
  251. claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
  252. claude_mpm/services/hook_service.py +6 -9
  253. claude_mpm/services/infrastructure/__init__.py +1 -1
  254. claude_mpm/services/infrastructure/context_preservation.py +8 -12
  255. claude_mpm/services/infrastructure/monitoring.py +21 -23
  256. claude_mpm/services/mcp_gateway/__init__.py +37 -37
  257. claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
  258. claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
  259. claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
  260. claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
  261. claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
  262. claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
  263. claude_mpm/services/mcp_gateway/core/base.py +0 -3
  264. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
  265. claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
  266. claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
  267. claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
  268. claude_mpm/services/mcp_gateway/main.py +2 -1
  269. claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
  270. claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
  271. claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
  272. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
  273. claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
  274. claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
  275. claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
  276. claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
  277. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
  278. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
  279. claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
  280. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
  281. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
  282. claude_mpm/services/memory/__init__.py +3 -3
  283. claude_mpm/services/memory/builder.py +3 -6
  284. claude_mpm/services/memory/cache/__init__.py +1 -1
  285. claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
  286. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  287. claude_mpm/services/memory/indexed_memory.py +5 -7
  288. claude_mpm/services/memory/optimizer.py +7 -10
  289. claude_mpm/services/memory/router.py +8 -9
  290. claude_mpm/services/memory_hook_service.py +48 -34
  291. claude_mpm/services/monitor_build_service.py +77 -73
  292. claude_mpm/services/port_manager.py +130 -108
  293. claude_mpm/services/project/analyzer.py +12 -10
  294. claude_mpm/services/project/registry.py +11 -11
  295. claude_mpm/services/recovery_manager.py +10 -19
  296. claude_mpm/services/response_tracker.py +0 -1
  297. claude_mpm/services/runner_configuration_service.py +19 -20
  298. claude_mpm/services/session_management_service.py +7 -11
  299. claude_mpm/services/shared/__init__.py +1 -1
  300. claude_mpm/services/shared/async_service_base.py +58 -50
  301. claude_mpm/services/shared/config_service_base.py +73 -67
  302. claude_mpm/services/shared/lifecycle_service_base.py +82 -78
  303. claude_mpm/services/shared/manager_base.py +94 -82
  304. claude_mpm/services/shared/service_factory.py +96 -98
  305. claude_mpm/services/socketio/__init__.py +3 -3
  306. claude_mpm/services/socketio/client_proxy.py +5 -5
  307. claude_mpm/services/socketio/event_normalizer.py +199 -181
  308. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  309. claude_mpm/services/socketio/handlers/base.py +5 -4
  310. claude_mpm/services/socketio/handlers/connection.py +163 -136
  311. claude_mpm/services/socketio/handlers/file.py +13 -14
  312. claude_mpm/services/socketio/handlers/git.py +12 -7
  313. claude_mpm/services/socketio/handlers/hook.py +49 -44
  314. claude_mpm/services/socketio/handlers/memory.py +0 -1
  315. claude_mpm/services/socketio/handlers/project.py +0 -1
  316. claude_mpm/services/socketio/handlers/registry.py +37 -19
  317. claude_mpm/services/socketio/migration_utils.py +98 -84
  318. claude_mpm/services/socketio/server/__init__.py +1 -1
  319. claude_mpm/services/socketio/server/broadcaster.py +81 -87
  320. claude_mpm/services/socketio/server/core.py +65 -54
  321. claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
  322. claude_mpm/services/socketio/server/main.py +64 -38
  323. claude_mpm/services/socketio_client_manager.py +10 -12
  324. claude_mpm/services/subprocess_launcher_service.py +4 -7
  325. claude_mpm/services/system_instructions_service.py +13 -14
  326. claude_mpm/services/ticket_manager.py +2 -2
  327. claude_mpm/services/utility_service.py +5 -13
  328. claude_mpm/services/version_control/__init__.py +16 -16
  329. claude_mpm/services/version_control/branch_strategy.py +5 -8
  330. claude_mpm/services/version_control/conflict_resolution.py +9 -23
  331. claude_mpm/services/version_control/git_operations.py +5 -7
  332. claude_mpm/services/version_control/semantic_versioning.py +16 -17
  333. claude_mpm/services/version_control/version_parser.py +13 -18
  334. claude_mpm/services/version_service.py +10 -11
  335. claude_mpm/storage/__init__.py +1 -1
  336. claude_mpm/storage/state_storage.py +22 -28
  337. claude_mpm/utils/__init__.py +6 -6
  338. claude_mpm/utils/agent_dependency_loader.py +47 -33
  339. claude_mpm/utils/config_manager.py +11 -14
  340. claude_mpm/utils/dependency_cache.py +1 -1
  341. claude_mpm/utils/dependency_manager.py +13 -17
  342. claude_mpm/utils/dependency_strategies.py +8 -10
  343. claude_mpm/utils/environment_context.py +3 -9
  344. claude_mpm/utils/error_handler.py +3 -13
  345. claude_mpm/utils/file_utils.py +1 -1
  346. claude_mpm/utils/path_operations.py +8 -12
  347. claude_mpm/utils/robust_installer.py +110 -33
  348. claude_mpm/utils/subprocess_utils.py +5 -6
  349. claude_mpm/validation/agent_validator.py +3 -6
  350. claude_mpm/validation/frontmatter_validator.py +1 -1
  351. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
  352. claude_mpm-4.1.2.dist-info/RECORD +498 -0
  353. claude_mpm-4.1.1.dist-info/RECORD +0 -494
  354. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
  355. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
  356. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
  357. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
@@ -14,67 +14,70 @@ DESIGN DECISION: Transform all events to a consistent schema:
14
14
  """
15
15
 
16
16
  import re
17
- from datetime import datetime
18
- from typing import Any, Dict, Optional, Tuple
19
17
  from dataclasses import dataclass, field
18
+ from datetime import datetime
20
19
  from enum import Enum
20
+ from typing import Any, Dict, Optional, Tuple
21
21
 
22
22
  from ...core.logging_config import get_logger
23
23
 
24
24
 
25
25
  class EventSource(Enum):
26
26
  """Event sources.
27
-
27
+
28
28
  WHY: Identifying where events come from helps with debugging,
29
29
  filtering, and understanding system behavior.
30
30
  """
31
- HOOK = "hook" # Events from Claude Code hooks
31
+
32
+ HOOK = "hook" # Events from Claude Code hooks
32
33
  DASHBOARD = "dashboard" # Events from dashboard UI
33
- SYSTEM = "system" # System/server operations
34
- AGENT = "agent" # Agent operations
35
- CLI = "cli" # CLI commands
36
- API = "api" # API calls
37
- TEST = "test" # Test scripts
34
+ SYSTEM = "system" # System/server operations
35
+ AGENT = "agent" # Agent operations
36
+ CLI = "cli" # CLI commands
37
+ API = "api" # API calls
38
+ TEST = "test" # Test scripts
38
39
 
39
40
 
40
41
  class EventType(Enum):
41
42
  """Main event categories.
42
-
43
+
43
44
  WHY: Categorizing events helps with filtering, routing, and understanding
44
45
  the system's behavior at a high level.
45
46
  """
46
- HOOK = "hook" # Claude Code hook events
47
- SYSTEM = "system" # System health and status events
48
- SESSION = "session" # Session lifecycle events
49
- FILE = "file" # File system events
47
+
48
+ HOOK = "hook" # Claude Code hook events
49
+ SYSTEM = "system" # System health and status events
50
+ SESSION = "session" # Session lifecycle events
51
+ FILE = "file" # File system events
50
52
  CONNECTION = "connection" # Client connection events
51
- MEMORY = "memory" # Memory system events
52
- GIT = "git" # Git operation events
53
- TODO = "todo" # Todo list updates
54
- TICKET = "ticket" # Ticket system events
55
- AGENT = "agent" # Agent delegation events
56
- ERROR = "error" # Error events
53
+ MEMORY = "memory" # Memory system events
54
+ GIT = "git" # Git operation events
55
+ TODO = "todo" # Todo list updates
56
+ TICKET = "ticket" # Ticket system events
57
+ AGENT = "agent" # Agent delegation events
58
+ ERROR = "error" # Error events
57
59
  PERFORMANCE = "performance" # Performance metrics
58
- CLAUDE = "claude" # Claude process events
59
- TEST = "test" # Test events
60
- TOOL = "tool" # Tool events
61
- SUBAGENT = "subagent" # Subagent events
60
+ CLAUDE = "claude" # Claude process events
61
+ TEST = "test" # Test events
62
+ TOOL = "tool" # Tool events
63
+ SUBAGENT = "subagent" # Subagent events
62
64
 
63
65
 
64
66
  @dataclass
65
67
  class NormalizedEvent:
66
68
  """Represents a normalized event with consistent structure.
67
-
69
+
68
70
  WHY: Using a dataclass ensures type safety and makes the event
69
71
  structure explicit and self-documenting.
70
72
  """
73
+
71
74
  event: str = "claude_event" # Socket.IO event name
72
- source: str = "" # WHERE the event comes from
73
- type: str = "" # WHAT category of event
74
- subtype: str = "" # Specific event type
75
- timestamp: str = "" # ISO format timestamp
75
+ source: str = "" # WHERE the event comes from
76
+ type: str = "" # WHAT category of event
77
+ subtype: str = "" # Specific event type
78
+ timestamp: str = "" # ISO format timestamp
76
79
  data: Dict[str, Any] = field(default_factory=dict) # Event payload
77
-
80
+
78
81
  def to_dict(self) -> Dict[str, Any]:
79
82
  """Convert to dictionary for emission."""
80
83
  return {
@@ -83,17 +86,17 @@ class NormalizedEvent:
83
86
  "type": self.type,
84
87
  "subtype": self.subtype,
85
88
  "timestamp": self.timestamp,
86
- "data": self.data
89
+ "data": self.data,
87
90
  }
88
91
 
89
92
 
90
93
  class EventNormalizer:
91
94
  """Normalizes events to a consistent schema.
92
-
95
+
93
96
  WHY: This class handles the transformation of various event formats
94
97
  into a single, consistent schema that clients can reliably parse.
95
98
  """
96
-
99
+
97
100
  # Mapping of event names to (type, subtype) tuples
98
101
  EVENT_MAPPINGS = {
99
102
  # Hook events
@@ -103,81 +106,66 @@ class EventNormalizer:
103
106
  "post_response": (EventType.HOOK, "post_response"),
104
107
  "hook_event": (EventType.HOOK, "generic"),
105
108
  "UserPrompt": (EventType.HOOK, "user_prompt"), # Legacy format
106
-
107
109
  # Test events (legacy format)
108
110
  "TestStart": (EventType.TEST, "start"),
109
111
  "TestEnd": (EventType.TEST, "end"),
110
-
111
- # Tool events (legacy format)
112
+ # Tool events (legacy format)
112
113
  "ToolCall": (EventType.TOOL, "call"),
113
-
114
114
  # Subagent events (legacy format)
115
115
  "SubagentStart": (EventType.SUBAGENT, "start"),
116
116
  "SubagentStop": (EventType.SUBAGENT, "stop"),
117
-
118
117
  # System events
119
118
  "heartbeat": (EventType.SYSTEM, "heartbeat"),
120
119
  "system_status": (EventType.SYSTEM, "status"),
121
120
  "system_event": (EventType.SYSTEM, "generic"),
122
-
123
121
  # Session events
124
122
  "session_started": (EventType.SESSION, "started"),
125
123
  "session_ended": (EventType.SESSION, "ended"),
126
124
  "session_event": (EventType.SESSION, "generic"),
127
-
128
125
  # File events
129
126
  "file_changed": (EventType.FILE, "changed"),
130
127
  "file_created": (EventType.FILE, "created"),
131
128
  "file_deleted": (EventType.FILE, "deleted"),
132
129
  "file_event": (EventType.FILE, "generic"),
133
-
134
130
  # Connection events
135
131
  "client_connected": (EventType.CONNECTION, "connected"),
136
132
  "client_disconnected": (EventType.CONNECTION, "disconnected"),
137
133
  "connection_event": (EventType.CONNECTION, "generic"),
138
-
139
134
  # Memory events
140
135
  "memory_loaded": (EventType.MEMORY, "loaded"),
141
136
  "memory_created": (EventType.MEMORY, "created"),
142
137
  "memory_updated": (EventType.MEMORY, "updated"),
143
138
  "memory_injected": (EventType.MEMORY, "injected"),
144
139
  "memory_event": (EventType.MEMORY, "generic"),
145
-
146
140
  # Git events
147
141
  "git_operation": (EventType.GIT, "operation"),
148
142
  "git_commit": (EventType.GIT, "commit"),
149
143
  "git_push": (EventType.GIT, "push"),
150
144
  "git_pull": (EventType.GIT, "pull"),
151
-
152
145
  # Todo events
153
146
  "todo_updated": (EventType.TODO, "updated"),
154
147
  "todo_created": (EventType.TODO, "created"),
155
148
  "todo_completed": (EventType.TODO, "completed"),
156
-
157
149
  # Ticket events
158
150
  "ticket_created": (EventType.TICKET, "created"),
159
151
  "ticket_updated": (EventType.TICKET, "updated"),
160
152
  "ticket_closed": (EventType.TICKET, "closed"),
161
-
162
153
  # Agent events
163
154
  "agent_delegated": (EventType.AGENT, "delegated"),
164
155
  "agent_completed": (EventType.AGENT, "completed"),
165
-
166
156
  # Claude events
167
157
  "claude_status": (EventType.CLAUDE, "status"),
168
158
  "claude_output": (EventType.CLAUDE, "output"),
169
159
  "claude_started": (EventType.CLAUDE, "started"),
170
160
  "claude_stopped": (EventType.CLAUDE, "stopped"),
171
-
172
161
  # Error events
173
162
  "error": (EventType.ERROR, "general"),
174
163
  "error_occurred": (EventType.ERROR, "occurred"),
175
-
176
164
  # Performance events
177
165
  "performance": (EventType.PERFORMANCE, "metric"),
178
166
  "performance_metric": (EventType.PERFORMANCE, "metric"),
179
167
  }
180
-
168
+
181
169
  # Patterns to extract event type from various formats
182
170
  TYPE_PATTERNS = [
183
171
  # Pattern 1: event_type field
@@ -189,26 +177,28 @@ class EventNormalizer:
189
177
  # Pattern 4: Hook format (hook:event_name)
190
178
  (r'"hook"\s*:\s*"([^"]+)"', lambda m: f"hook_{m.group(1)}"),
191
179
  ]
192
-
180
+
193
181
  def __init__(self):
194
182
  self.logger = get_logger(self.__class__.__name__)
195
183
  self.stats = {
196
184
  "normalized": 0,
197
185
  "already_normalized": 0,
198
186
  "unknown_format": 0,
199
- "errors": 0
187
+ "errors": 0,
200
188
  }
201
-
202
- def normalize(self, event_data: Any, source: str = None) -> NormalizedEvent:
189
+
190
+ def normalize(
191
+ self, event_data: Any, source: Optional[str] = None
192
+ ) -> NormalizedEvent:
203
193
  """Normalize an event to the standard schema.
204
-
194
+
205
195
  WHY: This method handles various input formats and transforms them
206
196
  into a consistent structure that all clients can understand.
207
-
197
+
208
198
  Args:
209
199
  event_data: The event data in any supported format
210
200
  source: Optional source override (e.g., "hook", "dashboard", "test")
211
-
201
+
212
202
  Returns:
213
203
  NormalizedEvent with consistent structure
214
204
  """
@@ -217,16 +207,16 @@ class EventNormalizer:
217
207
  if self._is_normalized(event_data):
218
208
  self.stats["already_normalized"] += 1
219
209
  return self._validate_normalized(event_data)
220
-
210
+
221
211
  # Extract event information from various formats
222
212
  event_type, subtype, data = self._extract_event_info(event_data)
223
-
213
+
224
214
  # Determine event source
225
215
  event_source = self._determine_source(event_data, event_type, source)
226
-
216
+
227
217
  # Get or generate timestamp
228
218
  timestamp = self._extract_timestamp(event_data)
229
-
219
+
230
220
  # Create normalized event
231
221
  normalized = NormalizedEvent(
232
222
  event="claude_event",
@@ -234,18 +224,18 @@ class EventNormalizer:
234
224
  type=event_type,
235
225
  subtype=subtype,
236
226
  timestamp=timestamp,
237
- data=data
227
+ data=data,
238
228
  )
239
-
229
+
240
230
  self.stats["normalized"] += 1
241
231
  self.logger.debug(f"Normalized event: {event_type}/{subtype}")
242
-
232
+
243
233
  return normalized
244
-
234
+
245
235
  except Exception as e:
246
236
  self.stats["errors"] += 1
247
237
  self.logger.error(f"Failed to normalize event: {e}")
248
-
238
+
249
239
  # Return a generic event on error
250
240
  return NormalizedEvent(
251
241
  event="claude_event",
@@ -253,24 +243,24 @@ class EventNormalizer:
253
243
  type="unknown",
254
244
  subtype="error",
255
245
  timestamp=datetime.now().isoformat(),
256
- data={"original": str(event_data), "error": str(e)}
246
+ data={"original": str(event_data), "error": str(e)},
257
247
  )
258
-
248
+
259
249
  def _is_normalized(self, event_data: Any) -> bool:
260
250
  """Check if event is already in normalized format.
261
-
251
+
262
252
  WHY: Avoid double-normalization and preserve already correct events.
263
253
  """
264
254
  if not isinstance(event_data, dict):
265
255
  return False
266
-
256
+
267
257
  # Check for normalized format (must have source, type, subtype, timestamp, and data)
268
258
  required_fields = {"source", "type", "subtype", "timestamp", "data"}
269
259
  return all(field in event_data for field in required_fields)
270
-
260
+
271
261
  def _validate_normalized(self, event_data: Dict[str, Any]) -> NormalizedEvent:
272
262
  """Validate and convert an already normalized event.
273
-
263
+
274
264
  WHY: Ensure even pre-normalized events are valid and properly typed.
275
265
  """
276
266
  # Map source if it's a known indicator
@@ -280,19 +270,19 @@ class EventNormalizer:
280
270
  elif source not in [e.value for e in EventSource]:
281
271
  # If source is not a valid EventSource value, keep it as-is
282
272
  pass
283
-
273
+
284
274
  return NormalizedEvent(
285
275
  event="claude_event", # Always use standard event name
286
276
  source=source,
287
277
  type=event_data.get("type", "unknown"),
288
278
  subtype=event_data.get("subtype", "generic"),
289
279
  timestamp=event_data.get("timestamp", datetime.now().isoformat()),
290
- data=event_data.get("data", {})
280
+ data=event_data.get("data", {}),
291
281
  )
292
-
282
+
293
283
  def _extract_event_info(self, event_data: Any) -> Tuple[str, str, Dict[str, Any]]:
294
284
  """Extract event type, subtype, and data from various formats.
295
-
285
+
296
286
  WHY: The system has multiple event formats that need to be handled:
297
287
  - Simple strings (event names)
298
288
  - Dictionaries with type field
@@ -303,7 +293,7 @@ class EventNormalizer:
303
293
  if isinstance(event_data, str):
304
294
  event_type, subtype = self._map_event_name(event_data)
305
295
  return event_type, subtype, {"event_name": event_data}
306
-
296
+
307
297
  # Handle dictionary events
308
298
  if isinstance(event_data, dict):
309
299
  # Special case: type="hook" with event field (legacy hook format)
@@ -312,25 +302,25 @@ class EventNormalizer:
312
302
  subtype = event_data["event"]
313
303
  data = self._extract_data_payload(event_data)
314
304
  return event_type, subtype, data
315
-
305
+
316
306
  # Try to extract event name/type
317
307
  event_name = self._extract_event_name(event_data)
318
-
308
+
319
309
  # Map to type and subtype
320
310
  event_type, subtype = self._map_event_name(event_name)
321
-
311
+
322
312
  # Extract data payload
323
313
  data = self._extract_data_payload(event_data)
324
-
314
+
325
315
  return event_type, subtype, data
326
-
316
+
327
317
  # Unknown format
328
318
  self.stats["unknown_format"] += 1
329
319
  return "unknown", "generic", {"original": str(event_data)}
330
-
320
+
331
321
  def _extract_event_name(self, event_dict: Dict[str, Any]) -> str:
332
322
  """Extract event name from dictionary.
333
-
323
+
334
324
  WHY: Events use different field names for the event identifier.
335
325
  """
336
326
  # Priority order for event name fields
@@ -339,26 +329,28 @@ class EventNormalizer:
339
329
  value = event_dict[field]
340
330
  if isinstance(value, str):
341
331
  return value
342
-
332
+
343
333
  # Try to extract from JSON string representation
344
334
  event_str = str(event_dict)
345
335
  for pattern, extractor in self.TYPE_PATTERNS:
346
336
  match = re.search(pattern, event_str)
347
337
  if match:
348
338
  return extractor(match)
349
-
339
+
350
340
  return "unknown"
351
-
341
+
352
342
  def _map_event_name(self, event_name: str) -> Tuple[str, str]:
353
343
  """Map event name to (type, subtype) tuple.
354
-
344
+
355
345
  WHY: Consistent categorization helps clients filter and handle events.
356
346
  """
357
347
  # Direct mapping
358
348
  if event_name in self.EVENT_MAPPINGS:
359
349
  event_type, subtype = self.EVENT_MAPPINGS[event_name]
360
- return event_type.value if isinstance(event_type, EventType) else event_type, subtype
361
-
350
+ return (
351
+ event_type.value if isinstance(event_type, EventType) else event_type
352
+ ), subtype
353
+
362
354
  # Handle dotted event names (e.g., "connection.status", "session.started")
363
355
  if "." in event_name:
364
356
  parts = event_name.split(".", 1)
@@ -366,19 +358,34 @@ class EventNormalizer:
366
358
  type_part, subtype_part = parts
367
359
  # Map the type part to known types
368
360
  type_lower = type_part.lower()
369
- if type_lower in ["hook", "session", "file", "system", "connection",
370
- "memory", "git", "todo", "ticket", "agent", "claude",
371
- "error", "performance", "test", "tool", "subagent"]:
361
+ if type_lower in [
362
+ "hook",
363
+ "session",
364
+ "file",
365
+ "system",
366
+ "connection",
367
+ "memory",
368
+ "git",
369
+ "todo",
370
+ "ticket",
371
+ "agent",
372
+ "claude",
373
+ "error",
374
+ "performance",
375
+ "test",
376
+ "tool",
377
+ "subagent",
378
+ ]:
372
379
  return type_lower, subtype_part
373
-
380
+
374
381
  # Try to infer from event name patterns
375
382
  event_lower = event_name.lower()
376
-
383
+
377
384
  # Check if event name matches a known EventType value directly
378
385
  for event_type_enum in EventType:
379
386
  if event_lower == event_type_enum.value:
380
387
  return event_type_enum.value, "generic"
381
-
388
+
382
389
  # Hook events (hook_* or *_hook or hook.*)
383
390
  if "hook" in event_lower:
384
391
  # Handle "hook.event_name" format
@@ -388,83 +395,86 @@ class EventNormalizer:
388
395
  if len(parts) > 1:
389
396
  return EventType.HOOK.value, parts[1]
390
397
  # Handle pre_ and post_ prefixes
391
- if event_lower.startswith("pre_"):
398
+ if event_lower.startswith(("pre_", "post_")):
392
399
  return EventType.HOOK.value, event_lower
393
- elif event_lower.startswith("post_"):
394
- return EventType.HOOK.value, event_lower
395
- else:
396
- return EventType.HOOK.value, "generic"
397
-
400
+ return EventType.HOOK.value, "generic"
401
+
398
402
  # Session events
399
403
  if "session" in event_lower:
400
404
  if "start" in event_lower:
401
405
  return EventType.SESSION.value, "started"
402
- elif "end" in event_lower:
406
+ if "end" in event_lower:
403
407
  return EventType.SESSION.value, "ended"
404
- else:
405
- return EventType.SESSION.value, "generic"
406
-
408
+ return EventType.SESSION.value, "generic"
409
+
407
410
  # File events
408
411
  if "file" in event_lower:
409
412
  if "create" in event_lower:
410
413
  return EventType.FILE.value, "created"
411
- elif "delete" in event_lower:
414
+ if "delete" in event_lower:
412
415
  return EventType.FILE.value, "deleted"
413
- elif "change" in event_lower or "modify" in event_lower:
416
+ if "change" in event_lower or "modify" in event_lower:
414
417
  return EventType.FILE.value, "changed"
415
- else:
416
- return EventType.FILE.value, "generic"
417
-
418
+ return EventType.FILE.value, "generic"
419
+
418
420
  # System events
419
421
  if "system" in event_lower or "heartbeat" in event_lower:
420
422
  if "heartbeat" in event_lower:
421
423
  return EventType.SYSTEM.value, "heartbeat"
422
- else:
423
- return EventType.SYSTEM.value, "status"
424
-
424
+ return EventType.SYSTEM.value, "status"
425
+
425
426
  # Connection events
426
427
  if "connect" in event_lower or "client" in event_lower:
427
428
  if "disconnect" in event_lower:
428
429
  return EventType.CONNECTION.value, "disconnected"
429
- elif "connect" in event_lower:
430
+ if "connect" in event_lower:
430
431
  return EventType.CONNECTION.value, "connected"
431
- else:
432
- return EventType.CONNECTION.value, "generic"
433
-
432
+ return EventType.CONNECTION.value, "generic"
433
+
434
434
  # Memory events
435
435
  if "memory" in event_lower:
436
436
  if "load" in event_lower:
437
437
  return EventType.MEMORY.value, "loaded"
438
- elif "create" in event_lower:
438
+ if "create" in event_lower:
439
439
  return EventType.MEMORY.value, "created"
440
- elif "update" in event_lower:
440
+ if "update" in event_lower:
441
441
  return EventType.MEMORY.value, "updated"
442
- elif "inject" in event_lower:
442
+ if "inject" in event_lower:
443
443
  return EventType.MEMORY.value, "injected"
444
- else:
445
- return EventType.MEMORY.value, "generic"
446
-
444
+ return EventType.MEMORY.value, "generic"
445
+
447
446
  # Default to unknown with lowercase subtype
448
447
  return "unknown", event_name.lower() if event_name else ""
449
-
448
+
450
449
  def _extract_data_payload(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
451
450
  """Extract the data payload from an event dictionary.
452
-
451
+
453
452
  WHY: Different event formats store the payload in different places.
454
453
  """
455
454
  # If there's a explicit data field, use it
456
455
  if "data" in event_dict:
457
- return event_dict["data"] if isinstance(event_dict["data"], dict) else {"value": event_dict["data"]}
458
-
456
+ return (
457
+ event_dict["data"]
458
+ if isinstance(event_dict["data"], dict)
459
+ else {"value": event_dict["data"]}
460
+ )
461
+
459
462
  # Otherwise, use the entire dict minus metadata fields
460
- metadata_fields = {"event", "type", "subtype", "timestamp", "event_type", "hook"}
463
+ metadata_fields = {
464
+ "event",
465
+ "type",
466
+ "subtype",
467
+ "timestamp",
468
+ "event_type",
469
+ "hook",
470
+ }
461
471
  data = {k: v for k, v in event_dict.items() if k not in metadata_fields}
462
-
472
+
463
473
  return data if data else event_dict
464
-
474
+
465
475
  def _extract_timestamp(self, event_data: Any) -> str:
466
476
  """Extract or generate timestamp.
467
-
477
+
468
478
  WHY: Consistent timestamp format is essential for event ordering
469
479
  and debugging.
470
480
  """
@@ -482,28 +492,30 @@ class EventNormalizer:
482
492
  return datetime.fromtimestamp(timestamp).isoformat()
483
493
  except:
484
494
  pass
485
-
495
+
486
496
  # Generate new timestamp if not found
487
497
  return datetime.now().isoformat()
488
-
489
- def _determine_source(self, event_data: Any, event_type: str, source_override: str = None) -> str:
498
+
499
+ def _determine_source(
500
+ self, event_data: Any, event_type: str, source_override: Optional[str] = None
501
+ ) -> str:
490
502
  """Determine the source of an event.
491
-
503
+
492
504
  WHY: Knowing where events originate helps with debugging,
493
505
  filtering, and understanding system behavior.
494
-
506
+
495
507
  Args:
496
508
  event_data: The raw event data
497
509
  event_type: The determined event type
498
510
  source_override: Optional explicit source
499
-
511
+
500
512
  Returns:
501
513
  The event source as a string
502
514
  """
503
515
  # Use explicit source override if provided
504
516
  if source_override:
505
517
  return source_override
506
-
518
+
507
519
  # Check if event data contains source field
508
520
  if isinstance(event_data, dict):
509
521
  # Direct source field
@@ -519,48 +531,55 @@ class EventNormalizer:
519
531
  return source
520
532
  # Otherwise, keep the original source value
521
533
  return source
522
-
534
+
523
535
  # Check for indicators of specific sources
524
536
  # Test indicator - only if type is actually "test"
525
- if event_type == "test" or (isinstance(event_data.get("type"), str) and event_data.get("type") == "test"):
537
+ if event_type == "test" or (
538
+ isinstance(event_data.get("type"), str)
539
+ and event_data.get("type") == "test"
540
+ ):
526
541
  return EventSource.TEST.value
527
-
542
+
528
543
  # Dashboard indicator
529
544
  if "dashboard" in str(event_data).lower() or "ui_action" in event_data:
530
545
  return EventSource.DASHBOARD.value
531
-
546
+
532
547
  # CLI indicator
533
548
  if "cli" in str(event_data).lower() or "command" in event_data:
534
549
  return EventSource.CLI.value
535
-
550
+
536
551
  # API indicator
537
552
  if "api" in str(event_data).lower() or "endpoint" in event_data:
538
553
  return EventSource.API.value
539
-
554
+
540
555
  # Infer from event type
541
556
  if event_type == EventType.HOOK.value:
542
557
  return EventSource.HOOK.value
543
- elif event_type == EventType.TEST.value:
558
+ if event_type == EventType.TEST.value:
544
559
  return EventSource.TEST.value
545
- elif event_type in [EventType.AGENT.value, EventType.SUBAGENT.value]:
560
+ if event_type in [EventType.AGENT.value, EventType.SUBAGENT.value]:
546
561
  return EventSource.AGENT.value
547
- elif event_type in [EventType.SYSTEM.value, EventType.SESSION.value,
548
- EventType.CONNECTION.value, EventType.PERFORMANCE.value]:
562
+ if event_type in [
563
+ EventType.SYSTEM.value,
564
+ EventType.SESSION.value,
565
+ EventType.CONNECTION.value,
566
+ EventType.PERFORMANCE.value,
567
+ ]:
549
568
  return EventSource.SYSTEM.value
550
-
569
+
551
570
  # Default to system source
552
571
  return EventSource.SYSTEM.value
553
-
572
+
554
573
  def get_stats(self) -> Dict[str, int]:
555
574
  """Get normalization statistics.
556
-
575
+
557
576
  WHY: Monitoring normalization helps identify problematic event sources.
558
577
  """
559
578
  return self.stats.copy()
560
-
579
+
561
580
  def reset_stats(self):
562
581
  """Reset statistics counters.
563
-
582
+
564
583
  WHY: Periodic reset prevents counter overflow and enables
565
584
  rate calculations.
566
585
  """
@@ -568,100 +587,99 @@ class EventNormalizer:
568
587
  "normalized": 0,
569
588
  "already_normalized": 0,
570
589
  "unknown_format": 0,
571
- "errors": 0
590
+ "errors": 0,
572
591
  }
573
592
 
574
593
 
575
594
  # Utility functions for consistent event type checking
576
595
  def is_hook_event(event_data: Dict[str, Any]) -> bool:
577
596
  """Check if an event is a hook event (handles both normalized and legacy formats).
578
-
597
+
579
598
  WHY: Hook events can come in multiple formats and we need consistent checking
580
599
  across the codebase to avoid missing events.
581
-
600
+
582
601
  Args:
583
602
  event_data: Event dictionary to check
584
-
603
+
585
604
  Returns:
586
605
  True if this is a hook event, False otherwise
587
606
  """
588
607
  if not isinstance(event_data, dict):
589
608
  return False
590
-
609
+
591
610
  event_type = event_data.get("type", "")
592
-
611
+
593
612
  # Check normalized format: type="hook"
594
613
  if event_type == "hook":
595
614
  return True
596
-
615
+
597
616
  # Check legacy format: type="hook.something"
598
- if isinstance(event_type, str) and event_type.startswith("hook."):
599
- return True
600
-
601
- return False
617
+ return bool(isinstance(event_type, str) and event_type.startswith("hook."))
602
618
 
603
619
 
604
620
  def get_hook_event_name(event_data: Dict[str, Any]) -> str:
605
621
  """Extract the hook event name from either normalized or legacy format.
606
-
622
+
607
623
  WHY: Hook events store their specific name differently in normalized vs legacy
608
624
  formats, and we need a consistent way to extract it.
609
-
625
+
610
626
  Args:
611
627
  event_data: Event dictionary containing a hook event
612
-
628
+
613
629
  Returns:
614
630
  The specific hook event name (e.g., "pre_tool", "user_prompt")
615
631
  or empty string if not a hook event
616
632
  """
617
633
  if not is_hook_event(event_data):
618
634
  return ""
619
-
635
+
620
636
  event_type = event_data.get("type", "")
621
637
  event_subtype = event_data.get("subtype", "")
622
-
638
+
623
639
  # Normalized format: type="hook", subtype="pre_tool"
624
640
  if event_type == "hook" and event_subtype:
625
641
  return event_subtype
626
-
642
+
627
643
  # Legacy format: type="hook.pre_tool"
628
644
  if isinstance(event_type, str) and event_type.startswith("hook."):
629
645
  return event_type[5:] # Remove "hook." prefix
630
-
646
+
631
647
  # Fallback: check 'event' field (another legacy format)
632
648
  return event_data.get("event", "")
633
649
 
634
650
 
635
- def is_event_type(event_data: Dict[str, Any], type_name: str, subtype: Optional[str] = None) -> bool:
651
+ def is_event_type(
652
+ event_data: Dict[str, Any], type_name: str, subtype: Optional[str] = None
653
+ ) -> bool:
636
654
  """Check if an event matches a specific type and optionally subtype.
637
-
655
+
638
656
  WHY: This provides a consistent way to check event types that works with
639
657
  both normalized and legacy formats.
640
-
658
+
641
659
  Args:
642
660
  event_data: Event dictionary to check
643
661
  type_name: The type to check for (e.g., "hook", "session", "file")
644
662
  subtype: Optional subtype to also check (e.g., "pre_tool", "started")
645
-
663
+
646
664
  Returns:
647
665
  True if the event matches the specified type (and subtype if provided)
648
666
  """
649
667
  if not isinstance(event_data, dict):
650
668
  return False
651
-
669
+
652
670
  event_type = event_data.get("type", "")
653
671
  event_subtype = event_data.get("subtype", "")
654
-
672
+
655
673
  # Check normalized format
656
674
  if event_type == type_name:
657
675
  if subtype is None:
658
676
  return True
659
677
  return event_subtype == subtype
660
-
678
+
661
679
  # Check legacy dotted format (e.g., "hook.pre_tool")
662
680
  if subtype and isinstance(event_type, str):
663
681
  legacy_type = f"{type_name}.{subtype}"
664
682
  if event_type == legacy_type:
665
683
  return True
666
-
667
- return False
684
+
685
+ return False