claude-mpm 5.0.9__py3-none-any.whl → 5.4.41__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (263) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/{PM_INSTRUCTIONS_TEACH.md → CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md} +721 -41
  5. claude_mpm/agents/MEMORY.md +1 -1
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +468 -468
  7. claude_mpm/agents/WORKFLOW.md +5 -254
  8. claude_mpm/agents/agent_loader.py +13 -44
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/frontmatter_validator.py +70 -2
  11. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  12. claude_mpm/cli/__init__.py +0 -1
  13. claude_mpm/cli/__main__.py +4 -0
  14. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  15. claude_mpm/cli/commands/agent_state_manager.py +18 -27
  16. claude_mpm/cli/commands/agents.py +175 -37
  17. claude_mpm/cli/commands/auto_configure.py +723 -236
  18. claude_mpm/cli/commands/config.py +88 -2
  19. claude_mpm/cli/commands/configure.py +1262 -157
  20. claude_mpm/cli/commands/configure_agent_display.py +25 -6
  21. claude_mpm/cli/commands/mpm_init/core.py +225 -46
  22. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  23. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  24. claude_mpm/cli/commands/postmortem.py +1 -1
  25. claude_mpm/cli/commands/profile.py +277 -0
  26. claude_mpm/cli/commands/skills.py +214 -189
  27. claude_mpm/cli/commands/summarize.py +413 -0
  28. claude_mpm/cli/executor.py +21 -3
  29. claude_mpm/cli/interactive/agent_wizard.py +85 -10
  30. claude_mpm/cli/parsers/agents_parser.py +54 -9
  31. claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
  32. claude_mpm/cli/parsers/base_parser.py +12 -0
  33. claude_mpm/cli/parsers/config_parser.py +153 -83
  34. claude_mpm/cli/parsers/profile_parser.py +148 -0
  35. claude_mpm/cli/parsers/skills_parser.py +3 -2
  36. claude_mpm/cli/startup.py +879 -149
  37. claude_mpm/commands/mpm-config.md +28 -0
  38. claude_mpm/commands/mpm-doctor.md +9 -22
  39. claude_mpm/commands/mpm-help.md +5 -287
  40. claude_mpm/commands/mpm-init.md +81 -507
  41. claude_mpm/commands/mpm-monitor.md +15 -402
  42. claude_mpm/commands/mpm-organize.md +120 -0
  43. claude_mpm/commands/mpm-postmortem.md +6 -108
  44. claude_mpm/commands/mpm-session-resume.md +12 -363
  45. claude_mpm/commands/mpm-status.md +5 -69
  46. claude_mpm/commands/mpm-ticket-view.md +52 -495
  47. claude_mpm/commands/mpm-version.md +5 -107
  48. claude_mpm/config/agent_sources.py +27 -0
  49. claude_mpm/core/config.py +2 -4
  50. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  51. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  52. claude_mpm/core/framework/loaders/instruction_loader.py +52 -11
  53. claude_mpm/core/framework_loader.py +4 -2
  54. claude_mpm/core/logger.py +13 -0
  55. claude_mpm/core/optimized_startup.py +59 -0
  56. claude_mpm/core/output_style_manager.py +173 -43
  57. claude_mpm/core/shared/config_loader.py +1 -1
  58. claude_mpm/core/socketio_pool.py +3 -3
  59. claude_mpm/core/unified_agent_registry.py +134 -16
  60. claude_mpm/core/unified_config.py +22 -0
  61. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  62. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  63. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  64. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  65. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  66. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  67. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  68. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  69. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  70. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  71. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  72. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  73. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  74. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  75. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  76. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  77. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  78. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  79. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  80. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  81. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  82. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  83. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  84. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  85. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  86. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  87. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  88. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  89. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  90. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  91. claude_mpm/hooks/claude_hooks/memory_integration.py +28 -0
  92. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  93. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  94. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  95. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  96. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  97. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  98. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  99. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  100. claude_mpm/hooks/memory_integration_hook.py +46 -1
  101. claude_mpm/init.py +63 -19
  102. claude_mpm/models/agent_definition.py +7 -0
  103. claude_mpm/models/git_repository.py +3 -3
  104. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  105. claude_mpm/scripts/launch_monitor.py +93 -13
  106. claude_mpm/scripts/start_activity_logging.py +0 -0
  107. claude_mpm/services/agents/agent_builder.py +3 -3
  108. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  109. claude_mpm/services/agents/agent_review_service.py +280 -0
  110. claude_mpm/services/agents/cache_git_manager.py +6 -6
  111. claude_mpm/services/agents/deployment/agent_deployment.py +29 -7
  112. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -5
  113. claude_mpm/services/agents/deployment/agent_template_builder.py +5 -3
  114. claude_mpm/services/agents/deployment/agents_directory_resolver.py +2 -2
  115. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +320 -29
  116. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +546 -68
  117. claude_mpm/services/agents/git_source_manager.py +36 -2
  118. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  119. claude_mpm/services/agents/recommender.py +5 -3
  120. claude_mpm/services/agents/single_tier_deployment_service.py +2 -2
  121. claude_mpm/services/agents/sources/git_source_sync_service.py +13 -6
  122. claude_mpm/services/agents/startup_sync.py +22 -2
  123. claude_mpm/services/agents/toolchain_detector.py +10 -6
  124. claude_mpm/services/analysis/__init__.py +11 -1
  125. claude_mpm/services/analysis/clone_detector.py +1030 -0
  126. claude_mpm/services/command_deployment_service.py +81 -10
  127. claude_mpm/services/diagnostics/checks/agent_check.py +2 -2
  128. claude_mpm/services/diagnostics/checks/agent_sources_check.py +1 -1
  129. claude_mpm/services/event_bus/config.py +3 -1
  130. claude_mpm/services/git/git_operations_service.py +101 -16
  131. claude_mpm/services/monitor/daemon.py +9 -2
  132. claude_mpm/services/monitor/daemon_manager.py +39 -3
  133. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  134. claude_mpm/services/monitor/server.py +698 -22
  135. claude_mpm/services/pm_skills_deployer.py +676 -0
  136. claude_mpm/services/profile_manager.py +331 -0
  137. claude_mpm/services/project/project_organizer.py +4 -0
  138. claude_mpm/services/self_upgrade_service.py +120 -12
  139. claude_mpm/services/skills/__init__.py +3 -0
  140. claude_mpm/services/skills/git_skill_source_manager.py +130 -2
  141. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  142. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  143. claude_mpm/services/skills_deployer.py +126 -9
  144. claude_mpm/services/socketio/dashboard_server.py +1 -0
  145. claude_mpm/services/socketio/event_normalizer.py +51 -6
  146. claude_mpm/services/socketio/server/core.py +386 -108
  147. claude_mpm/services/version_control/git_operations.py +103 -0
  148. claude_mpm/skills/skill_manager.py +92 -3
  149. claude_mpm/utils/agent_dependency_loader.py +14 -2
  150. claude_mpm/utils/agent_filters.py +17 -44
  151. claude_mpm/utils/gitignore.py +3 -0
  152. claude_mpm/utils/migration.py +4 -4
  153. claude_mpm/utils/robust_installer.py +47 -3
  154. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +57 -87
  155. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +160 -211
  156. claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
  157. claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
  158. claude_mpm-5.4.41.dist-info/licenses/LICENSE-FAQ.md +153 -0
  159. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  160. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  161. claude_mpm/agents/BASE_OPS.md +0 -219
  162. claude_mpm/agents/BASE_PM.md +0 -480
  163. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  164. claude_mpm/agents/BASE_QA.md +0 -167
  165. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  166. claude_mpm/agents/base_agent_loader.py +0 -601
  167. claude_mpm/cli/commands/agents_detect.py +0 -380
  168. claude_mpm/cli/commands/agents_recommend.py +0 -309
  169. claude_mpm/cli/ticket_cli.py +0 -35
  170. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  171. claude_mpm/commands/mpm-agents-detect.md +0 -177
  172. claude_mpm/commands/mpm-agents-list.md +0 -131
  173. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  174. claude_mpm/commands/mpm-config-view.md +0 -150
  175. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  176. claude_mpm/dashboard/analysis_runner.py +0 -455
  177. claude_mpm/dashboard/index.html +0 -13
  178. claude_mpm/dashboard/open_dashboard.py +0 -66
  179. claude_mpm/dashboard/static/css/activity.css +0 -1958
  180. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  181. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  182. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  183. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  184. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  185. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  186. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  187. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  188. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  189. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  190. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  191. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  192. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  193. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  194. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  195. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  196. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  197. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  198. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  199. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  200. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  201. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  202. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  203. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  204. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  205. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  206. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  207. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  208. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  209. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  210. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  211. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  212. claude_mpm/dashboard/templates/code_simple.html +0 -153
  213. claude_mpm/dashboard/templates/index.html +0 -606
  214. claude_mpm/dashboard/test_dashboard.html +0 -372
  215. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  216. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  217. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  218. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  219. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  220. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  221. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  222. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  223. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  224. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  225. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  226. claude_mpm/scripts/mcp_server.py +0 -75
  227. claude_mpm/scripts/mcp_wrapper.py +0 -39
  228. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  229. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  230. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  231. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  232. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  233. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  234. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  235. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  236. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  237. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  238. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  239. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  240. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  241. claude_mpm/services/mcp_gateway/main.py +0 -589
  242. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  243. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  244. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  245. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  246. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  247. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  248. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  249. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  250. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  251. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  252. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  253. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  254. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  255. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  256. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  257. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  258. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  259. claude_mpm-5.0.9.dist-info/entry_points.txt +0 -10
  260. claude_mpm-5.0.9.dist-info/licenses/LICENSE +0 -21
  261. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  262. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
  263. {claude_mpm-5.0.9.dist-info → claude_mpm-5.4.41.dist-info}/top_level.txt +0 -0
@@ -271,7 +271,22 @@ class SocketIOServerCore:
271
271
  """Handle POST /api/events from hook handlers."""
272
272
  try:
273
273
  # Parse JSON payload
274
- event_data = await request.json()
274
+ payload = await request.json()
275
+
276
+ # Extract event data from payload (handles both direct and wrapped formats)
277
+ # ConnectionManagerService sends: {"namespace": "...", "event": "...", "data": {...}}
278
+ # Direct hook events may send data directly
279
+ # CRITICAL: Check if payload has the expected event structure (type, subtype, timestamp)
280
+ # If it does, use it directly. Only extract 'data' field if it's a wrapper object.
281
+ if "type" in payload and "subtype" in payload:
282
+ # Payload is already in normalized format, use it directly
283
+ event_data = payload
284
+ elif "data" in payload and isinstance(payload.get("data"), dict):
285
+ # Payload is a wrapper with 'data' field (from ConnectionManagerService)
286
+ event_data = payload["data"]
287
+ else:
288
+ # Fallback: use entire payload
289
+ event_data = payload
275
290
 
276
291
  # Log receipt with more detail
277
292
  event_type = (
@@ -292,38 +307,96 @@ class SocketIOServerCore:
292
307
 
293
308
  normalizer = EventNormalizer()
294
309
 
310
+ # Map hook event names to dashboard subtypes
311
+ # Comprehensive mapping of all known Claude Code hook event types
312
+ subtype_map = {
313
+ # User interaction events
314
+ "UserPromptSubmit": "user_prompt_submit",
315
+ "UserPromptCancel": "user_prompt_cancel",
316
+ # Tool execution events
317
+ "PreToolUse": "pre_tool_use",
318
+ "PostToolUse": "post_tool_use",
319
+ "ToolStart": "tool_start",
320
+ "ToolUse": "tool_use",
321
+ # Assistant events
322
+ "AssistantResponse": "assistant_response",
323
+ # Session lifecycle events
324
+ "Start": "start",
325
+ "Stop": "stop",
326
+ "SessionStart": "session_start",
327
+ # Subagent events
328
+ "SubagentStart": "subagent_start",
329
+ "SubagentStop": "subagent_stop",
330
+ "SubagentEvent": "subagent_event",
331
+ # Task events
332
+ "Task": "task",
333
+ "TaskStart": "task_start",
334
+ "TaskComplete": "task_complete",
335
+ # File operation events
336
+ "FileWrite": "file_write",
337
+ "Write": "write",
338
+ # System events
339
+ "Notification": "notification",
340
+ }
341
+
342
+ # Helper function to convert PascalCase to snake_case
343
+ def to_snake_case(name: str) -> str:
344
+ """Convert PascalCase event names to snake_case.
345
+
346
+ Examples:
347
+ UserPromptSubmit → user_prompt_submit
348
+ PreToolUse → pre_tool_use
349
+ TaskComplete → task_complete
350
+ """
351
+ import re
352
+
353
+ return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
354
+
355
+ # Get hook event name and map to subtype
356
+ hook_event_name = event_data.get("hook_event_name", "unknown")
357
+ subtype = subtype_map.get(
358
+ hook_event_name, to_snake_case(hook_event_name)
359
+ )
360
+
361
+ # Debug log for unmapped events to discover new event types
362
+ if (
363
+ hook_event_name not in subtype_map
364
+ and hook_event_name != "unknown"
365
+ ):
366
+ self.logger.debug(
367
+ f"Unmapped hook event: {hook_event_name} → {subtype}"
368
+ )
369
+
295
370
  # Create the format expected by normalizer
296
371
  raw_event = {
297
372
  "type": "hook",
298
- "subtype": event_data.get("hook_event_name", "unknown")
299
- .lower()
300
- .replace("submit", "")
301
- .replace("use", "_use"),
373
+ "subtype": subtype,
302
374
  "timestamp": event_data.get("timestamp"),
303
375
  "data": event_data.get("hook_input_data", {}),
304
376
  "source": "claude_hooks",
305
377
  "session_id": event_data.get("session_id"),
306
378
  }
307
379
 
308
- # Map hook event names to dashboard subtypes
309
- subtype_map = {
310
- "UserPromptSubmit": "user_prompt",
311
- "PreToolUse": "pre_tool",
312
- "PostToolUse": "post_tool",
313
- "Stop": "stop",
314
- "SubagentStop": "subagent_stop",
315
- "AssistantResponse": "assistant_response",
316
- }
317
- raw_event["subtype"] = subtype_map.get(
318
- event_data.get("hook_event_name"), "unknown"
319
- )
320
-
321
380
  normalized = normalizer.normalize(raw_event, source="hook")
322
381
  event_data = normalized.to_dict()
323
382
  self.logger.debug(
324
383
  f"Normalized event: type={event_data.get('type')}, subtype={event_data.get('subtype')}"
325
384
  )
326
385
 
386
+ # Publish to EventBus for cross-component communication
387
+ # WHY: This allows other parts of the system to react to hook events
388
+ # without coupling to Socket.IO directly
389
+ try:
390
+ from claude_mpm.services.event_bus import EventBus
391
+
392
+ event_bus = EventBus.get_instance()
393
+ event_type = f"hook.{event_data.get('subtype', 'unknown')}"
394
+ event_bus.publish(event_type, event_data)
395
+ self.logger.debug(f"Published to EventBus: {event_type}")
396
+ except Exception as e:
397
+ # Non-fatal: EventBus publication failure shouldn't break event flow
398
+ self.logger.warning(f"Failed to publish to EventBus: {e}")
399
+
327
400
  # Broadcast to all connected dashboard clients via SocketIO
328
401
  if self.sio:
329
402
  # CRITICAL: Use the main server's broadcaster for proper event handling
@@ -342,9 +415,23 @@ class SocketIOServerCore:
342
415
  self.event_buffer.append(event_data)
343
416
  self.stats["events_buffered"] = len(self.event_buffer)
344
417
 
345
- # Add to main server's event history
346
- if hasattr(self.main_server, "event_history"):
418
+ # Add to main server's event history UNCONDITIONALLY
419
+ # WHY: event_history is always initialized in SocketIOServer.__init__
420
+ # This ensures events persist for new clients who connect later
421
+ if self.main_server and hasattr(
422
+ self.main_server, "event_history"
423
+ ):
347
424
  self.main_server.event_history.append(event_data)
425
+ self.logger.debug(
426
+ f"Added to history (total: {len(self.main_server.event_history)})"
427
+ )
428
+ else:
429
+ # CRITICAL: Log warning if event_history is not available
430
+ # This indicates a configuration or initialization problem
431
+ self.logger.warning(
432
+ "event_history not initialized on main_server! "
433
+ "Events will not persist for new clients."
434
+ )
348
435
 
349
436
  # Use the broadcaster's sio to emit (it's the same as self.sio)
350
437
  # This ensures the event goes through the proper channels
@@ -378,6 +465,21 @@ class SocketIOServerCore:
378
465
  self.event_buffer.append(event_data)
379
466
  self.stats["events_buffered"] = len(self.event_buffer)
380
467
 
468
+ # Add to main server's event history (fallback path)
469
+ # WHY: Ensure events persist even when broadcaster is unavailable
470
+ if self.main_server and hasattr(
471
+ self.main_server, "event_history"
472
+ ):
473
+ self.main_server.event_history.append(event_data)
474
+ self.logger.debug(
475
+ f"Added to history via fallback (total: {len(self.main_server.event_history)})"
476
+ )
477
+ else:
478
+ self.logger.warning(
479
+ "event_history not initialized on main_server (fallback path)! "
480
+ "Events will not persist for new clients."
481
+ )
482
+
381
483
  # Return 204 No Content for success
382
484
  self.logger.debug(f"✅ HTTP event processed successfully: {event_type}")
383
485
  return web.Response(status=204)
@@ -390,13 +492,54 @@ class SocketIOServerCore:
390
492
  self.app.router.add_post("/api/events", api_events_handler)
391
493
  self.logger.info("✅ HTTP API endpoint registered at /api/events")
392
494
 
495
+ # Add health check endpoint
496
+ async def health_handler(request):
497
+ """Handle GET /api/health for health checks."""
498
+ try:
499
+ # Get server status
500
+ uptime_seconds = 0
501
+ if self.stats.get("start_time"):
502
+ uptime_seconds = int(
503
+ (
504
+ datetime.now(timezone.utc) - self.stats["start_time"]
505
+ ).total_seconds()
506
+ )
507
+
508
+ health_data = {
509
+ "status": "healthy",
510
+ "service": "claude-mpm-socketio",
511
+ "timestamp": datetime.now(timezone.utc).isoformat(),
512
+ "uptime_seconds": uptime_seconds,
513
+ "connected_clients": len(self.connected_clients),
514
+ "total_events": self.stats.get("events_sent", 0),
515
+ "buffered_events": self.stats.get("events_buffered", 0),
516
+ }
517
+
518
+ return web.json_response(health_data)
519
+ except Exception as e:
520
+ self.logger.error(f"Error in health check: {e}")
521
+ return web.json_response(
522
+ {
523
+ "status": "unhealthy",
524
+ "service": "claude-mpm-socketio",
525
+ "error": str(e),
526
+ },
527
+ status=503,
528
+ )
529
+
530
+ self.app.router.add_get("/api/health", health_handler)
531
+ self.app.router.add_get("/health", health_handler) # Alias for convenience
532
+ self.logger.info(
533
+ "✅ Health check endpoints registered at /api/health and /health"
534
+ )
535
+
393
536
  # Add working directory endpoint
394
537
  async def working_directory_handler(request):
395
538
  """Handle GET /api/working-directory to provide current working directory."""
396
539
  from pathlib import Path
397
540
 
398
541
  try:
399
- working_dir = Path.cwd()
542
+ working_dir = str(Path.cwd())
400
543
  home_dir = str(Path.home())
401
544
 
402
545
  return web.json_response(
@@ -486,6 +629,169 @@ class SocketIOServerCore:
486
629
  self.app.router.add_get("/api/file/read", file_read_handler)
487
630
  self.logger.info("✅ File reading API registered at /api/file/read")
488
631
 
632
+ # Add files listing endpoint for file browser
633
+ async def files_list_handler(request):
634
+ """Handle GET /api/files for listing files in working directory."""
635
+
636
+ try:
637
+ # Get working directory from query params or use current directory
638
+ working_dir = request.query.get("path", str(Path.cwd()))
639
+ abs_working_dir = Path(working_dir).resolve().expanduser()
640
+
641
+ # Security check - ensure directory is accessible
642
+ if not abs_working_dir.exists():
643
+ return web.json_response(
644
+ {"error": "Directory not found"}, status=404
645
+ )
646
+
647
+ if not abs_working_dir.is_dir():
648
+ return web.json_response(
649
+ {"error": "Path is not a directory"}, status=400
650
+ )
651
+
652
+ # Collect files and directories
653
+ files = []
654
+ directories = []
655
+
656
+ # Common patterns to exclude
657
+ exclude_patterns = {
658
+ ".git",
659
+ ".venv",
660
+ "venv",
661
+ "node_modules",
662
+ "__pycache__",
663
+ ".pytest_cache",
664
+ ".mypy_cache",
665
+ "dist",
666
+ "build",
667
+ ".next",
668
+ "coverage",
669
+ ".coverage",
670
+ ".tox",
671
+ ".eggs",
672
+ "*.egg-info",
673
+ }
674
+
675
+ for entry in abs_working_dir.iterdir():
676
+ # Skip hidden files and excluded patterns
677
+ if entry.name.startswith("."):
678
+ # Allow .py, .ts, .md, etc. files but skip directories like .git
679
+ if entry.is_dir():
680
+ continue
681
+
682
+ # Skip excluded directories
683
+ if entry.name in exclude_patterns:
684
+ continue
685
+
686
+ try:
687
+ stat_info = entry.stat()
688
+ entry_data = {
689
+ "name": entry.name,
690
+ "path": str(entry),
691
+ "type": "directory" if entry.is_dir() else "file",
692
+ "size": stat_info.st_size if entry.is_file() else 0,
693
+ "modified": stat_info.st_mtime,
694
+ }
695
+
696
+ if entry.is_dir():
697
+ directories.append(entry_data)
698
+ else:
699
+ # Add file extension for syntax highlighting
700
+ entry_data["extension"] = entry.suffix.lower()
701
+ files.append(entry_data)
702
+ except (PermissionError, OSError):
703
+ # Skip files we can't access
704
+ continue
705
+
706
+ # Sort directories and files alphabetically
707
+ directories.sort(key=lambda x: x["name"].lower())
708
+ files.sort(key=lambda x: x["name"].lower())
709
+
710
+ return web.json_response(
711
+ {
712
+ "success": True,
713
+ "path": str(abs_working_dir),
714
+ "directories": directories,
715
+ "files": files,
716
+ "total_files": len(files),
717
+ "total_directories": len(directories),
718
+ }
719
+ )
720
+
721
+ except Exception as e:
722
+ self.logger.error(f"Error listing files: {e}")
723
+ return web.json_response({"error": str(e)}, status=500)
724
+
725
+ self.app.router.add_get("/api/files", files_list_handler)
726
+ self.logger.info("✅ Files listing API registered at /api/files")
727
+
728
+ # Add git history endpoint
729
+ async def git_history_handler(request):
730
+ """Handle POST /api/git-history for getting file git history."""
731
+ import subprocess
732
+
733
+ try:
734
+ # Parse JSON body
735
+ data = await request.json()
736
+ file_path = data.get("path", "")
737
+ limit = data.get("limit", 10)
738
+
739
+ if not file_path:
740
+ return web.json_response(
741
+ {"success": False, "error": "No path provided", "commits": []},
742
+ status=400,
743
+ )
744
+
745
+ abs_path = Path(Path(file_path).resolve().expanduser())
746
+
747
+ if not Path(abs_path).exists():
748
+ return web.json_response(
749
+ {"success": False, "error": "File not found", "commits": []},
750
+ status=404,
751
+ )
752
+
753
+ # Get git log for file
754
+ result = subprocess.run(
755
+ [
756
+ "git",
757
+ "log",
758
+ f"-{limit}",
759
+ "--pretty=format:%H|%an|%ar|%s",
760
+ "--",
761
+ abs_path,
762
+ ],
763
+ check=False,
764
+ capture_output=True,
765
+ text=True,
766
+ cwd=Path(abs_path).parent,
767
+ )
768
+
769
+ commits = []
770
+ if result.returncode == 0 and result.stdout:
771
+ for line in result.stdout.strip().split("\n"):
772
+ if line:
773
+ parts = line.split("|", 3)
774
+ if len(parts) == 4:
775
+ commits.append(
776
+ {
777
+ "hash": parts[0][:7], # Short hash
778
+ "author": parts[1],
779
+ "date": parts[2],
780
+ "message": parts[3],
781
+ }
782
+ )
783
+
784
+ return web.json_response({"success": True, "commits": commits})
785
+
786
+ except Exception as e:
787
+ self.logger.error(f"Error getting git history: {e}")
788
+ return web.json_response(
789
+ {"success": False, "error": str(e), "commits": []}, status=500
790
+ )
791
+
792
+ self.app.router.add_post("/api/git-history", git_history_handler)
793
+ self.logger.info("✅ Git history API registered at /api/git-history")
794
+
489
795
  def _setup_directory_api(self):
490
796
  """Setup simple directory listing API.
491
797
 
@@ -503,7 +809,7 @@ class SocketIOServerCore:
503
809
  self.logger.error(f"Failed to setup directory API: {e}")
504
810
 
505
811
  def _setup_static_files(self):
506
- """Setup static file serving for the dashboard."""
812
+ """Setup static file serving for the Svelte dashboard."""
507
813
  try:
508
814
  # Add debug logging for deployment context
509
815
  try:
@@ -516,65 +822,43 @@ class SocketIOServerCore:
516
822
  except Exception as e:
517
823
  self.logger.debug(f"Could not detect deployment context: {e}")
518
824
 
519
- self.dashboard_path = self._find_static_path()
825
+ # Find Svelte build directory
826
+ svelte_build_path = self._find_static_path()
520
827
 
521
- if self.dashboard_path and self.dashboard_path.exists():
522
- self.logger.info(f"✅ Dashboard found at: {self.dashboard_path}")
828
+ if svelte_build_path and svelte_build_path.exists():
829
+ self.logger.info(f"✅ Svelte dashboard found at: {svelte_build_path}")
830
+ self.dashboard_path = svelte_build_path
523
831
 
524
- # Serve index.html at root
832
+ # Serve Svelte index.html at root
525
833
  async def index_handler(request):
526
- index_file = self.dashboard_path / "index.html"
834
+ index_file = svelte_build_path / "index.html"
527
835
  if index_file.exists():
528
- self.logger.debug(f"Serving dashboard index from: {index_file}")
836
+ self.logger.debug(
837
+ f"Serving Svelte dashboard from: {index_file}"
838
+ )
529
839
  return web.FileResponse(index_file)
530
- self.logger.warning(
531
- f"Dashboard index.html not found at: {index_file}"
532
- )
840
+ self.logger.warning(f"Svelte index.html not found at: {index_file}")
533
841
  return web.Response(text="Dashboard not available", status=404)
534
842
 
535
843
  self.app.router.add_get("/", index_handler)
536
844
 
537
- # Serve the actual dashboard template at /dashboard
538
- async def dashboard_handler(request):
539
- dashboard_template = (
540
- self.dashboard_path.parent / "templates" / "index.html"
541
- )
542
- if dashboard_template.exists():
543
- self.logger.debug(
544
- f"Serving dashboard template from: {dashboard_template}"
545
- )
546
- return web.FileResponse(dashboard_template)
547
- # Fallback to the main index if template doesn't exist
548
- self.logger.warning(
549
- f"Dashboard template not found at: {dashboard_template}, falling back to index"
845
+ # Serve Svelte app assets at /_app/ (needed for SvelteKit builds)
846
+ svelte_app_path = svelte_build_path / "_app"
847
+ if svelte_app_path.exists():
848
+ self.app.router.add_static(
849
+ "/_app/", svelte_app_path, name="svelte_app"
550
850
  )
551
- return await index_handler(request)
552
-
553
- self.app.router.add_get("/dashboard", dashboard_handler)
554
-
555
- # Serve simple code view template at /code-simple
556
- async def code_simple_handler(request):
557
- code_simple_template = (
558
- self.dashboard_path.parent / "templates" / "code_simple.html"
851
+ self.logger.info(
852
+ f"✅ Svelte dashboard available at http://{self.host}:{self.port}/ (build: {svelte_build_path})"
559
853
  )
560
- if code_simple_template.exists():
561
- self.logger.debug(
562
- f"Serving code simple template from: {code_simple_template}"
563
- )
564
- return web.FileResponse(code_simple_template)
565
- # Return error if template doesn't exist
854
+ else:
566
855
  self.logger.warning(
567
- f"Code simple template not found at: {code_simple_template}"
568
- )
569
- return web.Response(
570
- text="Simple code view not available", status=404
856
+ f"⚠️ Svelte _app directory not found at: {svelte_app_path}"
571
857
  )
572
858
 
573
- self.app.router.add_get("/code-simple", code_simple_handler)
574
-
575
- # Serve version.json from dashboard directory
859
+ # Serve version.json from Svelte build directory
576
860
  async def version_handler(request):
577
- version_file = self.dashboard_path / "version.json"
861
+ version_file = svelte_build_path / "version.json"
578
862
  if version_file.exists():
579
863
  self.logger.debug(f"Serving version.json from: {version_file}")
580
864
  return web.FileResponse(version_file)
@@ -590,24 +874,10 @@ class SocketIOServerCore:
590
874
 
591
875
  self.app.router.add_get("/version.json", version_handler)
592
876
 
593
- # Serve static assets (CSS, JS) from the dashboard static directory
594
- dashboard_static_path = (
595
- get_project_root() / "src" / "claude_mpm" / "dashboard" / "static"
596
- )
597
- if dashboard_static_path.exists():
598
- self.app.router.add_static(
599
- "/static/", dashboard_static_path, name="dashboard_static"
600
- )
601
- self.logger.info(
602
- f"✅ Static assets available at: {dashboard_static_path}"
603
- )
604
- else:
605
- self.logger.warning(
606
- f"⚠️ Static assets directory not found at: {dashboard_static_path}"
607
- )
608
-
609
877
  else:
610
- self.logger.warning("⚠️ No dashboard found, serving fallback response")
878
+ self.logger.warning(
879
+ "⚠️ Svelte dashboard not found, serving fallback response"
880
+ )
611
881
 
612
882
  # Fallback handler
613
883
  async def fallback_handler(request):
@@ -634,10 +904,10 @@ class SocketIOServerCore:
634
904
  self.app.router.add_get("/", error_handler)
635
905
 
636
906
  def _find_static_path(self):
637
- """Find the static files directory using multiple approaches.
907
+ """Find the Svelte build directory using multiple approaches.
638
908
 
639
- WHY: The static files location varies depending on how the application
640
- is installed and run. We try multiple common locations to find them.
909
+ WHY: The dashboard is now pure Svelte, located at dashboard/static/svelte-build/.
910
+ We search for this specific structure across different deployment contexts.
641
911
  """
642
912
  # Get deployment-context-aware paths
643
913
  try:
@@ -658,43 +928,45 @@ class SocketIOServerCore:
658
928
  package_root = None
659
929
  project_root = get_project_root()
660
930
 
661
- # Try multiple possible locations for static files and dashboard
931
+ # Try multiple possible locations for Svelte build directory
662
932
  possible_paths = [
663
933
  # Package-based paths (for pipx and pip installations)
664
- package_root / "dashboard" / "templates" if package_root else None,
665
- package_root / "services" / "socketio" / "static" if package_root else None,
666
- package_root / "static" if package_root else None,
934
+ package_root / "dashboard" / "static" / "svelte-build"
935
+ if package_root
936
+ else None,
667
937
  # Project-based paths (for development)
668
- project_root / "src" / "claude_mpm" / "dashboard" / "templates",
669
- project_root / "dashboard" / "templates",
670
- project_root / "src" / "claude_mpm" / "services" / "static",
671
- project_root / "src" / "claude_mpm" / "services" / "socketio" / "static",
672
- project_root / "static",
673
- project_root / "src" / "static",
938
+ project_root
939
+ / "src"
940
+ / "claude_mpm"
941
+ / "dashboard"
942
+ / "static"
943
+ / "svelte-build",
944
+ project_root / "dashboard" / "static" / "svelte-build",
674
945
  # Package installation locations (fallback)
675
- Path(__file__).parent.parent / "static",
676
- Path(__file__).parent / "static",
946
+ Path(__file__).parent.parent.parent
947
+ / "dashboard"
948
+ / "static"
949
+ / "svelte-build",
677
950
  # Scripts directory (for standalone installations)
678
- get_scripts_dir() / "static",
679
- get_scripts_dir() / "socketio" / "static",
951
+ get_scripts_dir() / "dashboard" / "static" / "svelte-build",
680
952
  # Current working directory
681
- Path.cwd() / "static",
682
- Path.cwd() / "socketio" / "static",
953
+ Path.cwd() / "src" / "claude_mpm" / "dashboard" / "static" / "svelte-build",
954
+ Path.cwd() / "dashboard" / "static" / "svelte-build",
683
955
  ]
684
956
 
685
957
  # Filter out None values
686
958
  possible_paths = [p for p in possible_paths if p is not None]
687
959
  self.logger.debug(
688
- f"Searching {len(possible_paths)} possible static file locations"
960
+ f"Searching {len(possible_paths)} possible Svelte build locations"
689
961
  )
690
962
 
691
963
  for path in possible_paths:
692
- self.logger.debug(f"Checking for static files at: {path}")
964
+ self.logger.debug(f"Checking for Svelte build at: {path}")
693
965
  try:
694
966
  if path.exists() and path.is_dir():
695
- # Check if it contains expected files
967
+ # Check if it contains expected Svelte build files
696
968
  if (path / "index.html").exists():
697
- self.logger.info(f"✅ Found static files at: {path}")
969
+ self.logger.info(f"✅ Found Svelte build at: {path}")
698
970
  return path
699
971
  self.logger.debug(f"Directory exists but no index.html: {path}")
700
972
  else:
@@ -703,7 +975,7 @@ class SocketIOServerCore:
703
975
  self.logger.debug(f"Error checking path {path}: {e}")
704
976
 
705
977
  self.logger.warning(
706
- "⚠️ Static files not found - dashboard will not be available"
978
+ "⚠️ Svelte build not found - dashboard will not be available"
707
979
  )
708
980
  self.logger.debug(f"Searched paths: {[str(p) for p in possible_paths]}")
709
981
  return None
@@ -779,9 +1051,15 @@ class SocketIOServerCore:
779
1051
  },
780
1052
  }
781
1053
 
782
- # Add to event history if main server is available
1054
+ # Add to event history UNCONDITIONALLY
1055
+ # WHY: Heartbeat events should persist for new clients too
783
1056
  if self.main_server and hasattr(self.main_server, "event_history"):
784
1057
  self.main_server.event_history.append(heartbeat_data)
1058
+ self.logger.debug(
1059
+ f"Heartbeat added to history (total: {len(self.main_server.event_history)})"
1060
+ )
1061
+ else:
1062
+ self.logger.warning("event_history not initialized for heartbeat!")
785
1063
 
786
1064
  # Emit heartbeat to all connected clients (already using new schema)
787
1065
  await self.sio.emit("system_event", heartbeat_data)