claude-mpm 5.0.2__py3-none-any.whl → 5.4.3__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 (184) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +1218 -905
  4. claude_mpm/agents/agent_loader.py +10 -17
  5. claude_mpm/agents/base_agent_loader.py +10 -35
  6. claude_mpm/agents/frontmatter_validator.py +68 -0
  7. claude_mpm/agents/templates/circuit-breakers.md +431 -45
  8. claude_mpm/cli/__init__.py +0 -1
  9. claude_mpm/cli/commands/__init__.py +2 -0
  10. claude_mpm/cli/commands/agent_state_manager.py +67 -23
  11. claude_mpm/cli/commands/agents.py +446 -25
  12. claude_mpm/cli/commands/auto_configure.py +535 -233
  13. claude_mpm/cli/commands/configure.py +1500 -147
  14. claude_mpm/cli/commands/configure_agent_display.py +13 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +158 -1
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/postmortem.py +401 -0
  19. claude_mpm/cli/commands/run.py +1 -39
  20. claude_mpm/cli/commands/skills.py +322 -19
  21. claude_mpm/cli/commands/summarize.py +413 -0
  22. claude_mpm/cli/executor.py +8 -0
  23. claude_mpm/cli/interactive/agent_wizard.py +302 -195
  24. claude_mpm/cli/parsers/agents_parser.py +137 -0
  25. claude_mpm/cli/parsers/auto_configure_parser.py +13 -0
  26. claude_mpm/cli/parsers/base_parser.py +9 -0
  27. claude_mpm/cli/parsers/skills_parser.py +7 -0
  28. claude_mpm/cli/startup.py +133 -85
  29. claude_mpm/commands/mpm-agents-auto-configure.md +2 -2
  30. claude_mpm/commands/mpm-agents-list.md +2 -2
  31. claude_mpm/commands/mpm-config-view.md +2 -2
  32. claude_mpm/commands/mpm-help.md +3 -0
  33. claude_mpm/commands/{mpm-ticket-organize.md → mpm-organize.md} +4 -5
  34. claude_mpm/commands/mpm-postmortem.md +123 -0
  35. claude_mpm/commands/mpm-session-resume.md +2 -2
  36. claude_mpm/commands/mpm-ticket-view.md +2 -2
  37. claude_mpm/config/agent_presets.py +312 -82
  38. claude_mpm/config/agent_sources.py +27 -0
  39. claude_mpm/config/skill_presets.py +392 -0
  40. claude_mpm/constants.py +1 -0
  41. claude_mpm/core/claude_runner.py +2 -25
  42. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  43. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  44. claude_mpm/core/interactive_session.py +19 -5
  45. claude_mpm/core/oneshot_session.py +16 -4
  46. claude_mpm/core/output_style_manager.py +173 -43
  47. claude_mpm/core/protocols/__init__.py +23 -0
  48. claude_mpm/core/protocols/runner_protocol.py +103 -0
  49. claude_mpm/core/protocols/session_protocol.py +131 -0
  50. claude_mpm/core/shared/singleton_manager.py +11 -4
  51. claude_mpm/core/socketio_pool.py +3 -3
  52. claude_mpm/core/system_context.py +38 -0
  53. claude_mpm/core/unified_agent_registry.py +134 -16
  54. claude_mpm/core/unified_config.py +22 -0
  55. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  56. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-313.pyc +0 -0
  57. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-313.pyc +0 -0
  58. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-313.pyc +0 -0
  59. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-313.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-313.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-313.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  63. claude_mpm/hooks/claude_hooks/event_handlers.py +35 -2
  64. claude_mpm/hooks/claude_hooks/hook_handler.py +4 -0
  65. claude_mpm/hooks/claude_hooks/memory_integration.py +12 -1
  66. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-313.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-313.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-313.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-313.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-313.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  72. claude_mpm/models/agent_definition.py +7 -0
  73. claude_mpm/scripts/launch_monitor.py +93 -13
  74. claude_mpm/services/agents/agent_recommendation_service.py +279 -0
  75. claude_mpm/services/agents/cache_git_manager.py +621 -0
  76. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -2
  77. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +110 -3
  78. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +518 -55
  79. claude_mpm/services/agents/git_source_manager.py +20 -0
  80. claude_mpm/services/agents/sources/git_source_sync_service.py +45 -6
  81. claude_mpm/services/agents/toolchain_detector.py +6 -5
  82. claude_mpm/services/analysis/__init__.py +35 -0
  83. claude_mpm/services/analysis/clone_detector.py +1030 -0
  84. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  85. claude_mpm/services/analysis/postmortem_service.py +765 -0
  86. claude_mpm/services/command_deployment_service.py +106 -5
  87. claude_mpm/services/core/base.py +7 -2
  88. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  89. claude_mpm/services/event_bus/config.py +3 -1
  90. claude_mpm/services/git/git_operations_service.py +8 -8
  91. claude_mpm/services/mcp_config_manager.py +75 -145
  92. claude_mpm/services/mcp_service_verifier.py +6 -3
  93. claude_mpm/services/monitor/daemon.py +37 -10
  94. claude_mpm/services/monitor/daemon_manager.py +134 -21
  95. claude_mpm/services/monitor/server.py +225 -19
  96. claude_mpm/services/project/project_organizer.py +4 -0
  97. claude_mpm/services/runner_configuration_service.py +16 -3
  98. claude_mpm/services/session_management_service.py +16 -4
  99. claude_mpm/services/socketio/event_normalizer.py +15 -1
  100. claude_mpm/services/socketio/server/core.py +160 -21
  101. claude_mpm/services/version_control/git_operations.py +103 -0
  102. claude_mpm/utils/agent_filters.py +261 -0
  103. claude_mpm/utils/gitignore.py +3 -0
  104. claude_mpm/utils/migration.py +372 -0
  105. claude_mpm/utils/progress.py +5 -1
  106. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/METADATA +69 -84
  107. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/RECORD +112 -153
  108. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/entry_points.txt +0 -2
  109. claude_mpm/dashboard/analysis_runner.py +0 -455
  110. claude_mpm/dashboard/index.html +0 -13
  111. claude_mpm/dashboard/open_dashboard.py +0 -66
  112. claude_mpm/dashboard/static/css/activity.css +0 -1958
  113. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  114. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  115. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  116. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  117. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  118. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  119. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  120. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  121. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  122. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  123. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  124. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  125. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  126. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  127. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  128. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  129. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  130. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  131. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  132. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  133. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  134. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  135. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  136. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  137. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  138. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  139. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  140. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  141. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  142. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  143. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  144. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  145. claude_mpm/dashboard/templates/code_simple.html +0 -153
  146. claude_mpm/dashboard/templates/index.html +0 -606
  147. claude_mpm/dashboard/test_dashboard.html +0 -372
  148. claude_mpm/scripts/mcp_server.py +0 -75
  149. claude_mpm/scripts/mcp_wrapper.py +0 -39
  150. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  151. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  152. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  153. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  154. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  155. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  156. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  157. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  158. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  159. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  160. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  161. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  162. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  163. claude_mpm/services/mcp_gateway/main.py +0 -589
  164. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  165. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  166. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  167. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  168. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  169. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  170. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  171. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  172. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  173. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  174. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  175. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  176. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  177. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  178. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  179. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  180. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  181. /claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +0 -0
  182. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/WHEEL +0 -0
  183. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/licenses/LICENSE +0 -0
  184. {claude_mpm-5.0.2.dist-info → claude_mpm-5.4.3.dist-info}/top_level.txt +0 -0
@@ -15,7 +15,6 @@ DESIGN DECISIONS:
15
15
  """
16
16
 
17
17
  import asyncio
18
- import contextlib
19
18
  import os
20
19
  import threading
21
20
  import time
@@ -25,6 +24,8 @@ from typing import Dict, Optional
25
24
 
26
25
  import socketio
27
26
  from aiohttp import web
27
+ from watchdog.events import FileSystemEventHandler
28
+ from watchdog.observers import Observer
28
29
 
29
30
  from ...core.enums import ServiceState
30
31
  from ...core.logging_config import get_logger
@@ -45,6 +46,91 @@ except ImportError:
45
46
  EVENTBUS_AVAILABLE = False
46
47
 
47
48
 
49
+ class SvelteBuildWatcher(FileSystemEventHandler):
50
+ """File watcher for Svelte build directory changes.
51
+
52
+ Watches for file changes in svelte-build directory and triggers
53
+ hot reload via Socket.IO event emission.
54
+
55
+ STABILITY FIX: Added thread lock and stop() method to prevent timer leaks.
56
+ """
57
+
58
+ def __init__(
59
+ self, sio: socketio.AsyncServer, loop: asyncio.AbstractEventLoop, logger
60
+ ):
61
+ """Initialize the file watcher.
62
+
63
+ Args:
64
+ sio: Socket.IO server instance for emitting events
65
+ loop: Event loop for async operations
66
+ logger: Logger instance
67
+ """
68
+ super().__init__()
69
+ self.sio = sio
70
+ self.loop = loop
71
+ self.logger = logger
72
+ self.debounce_timer = None
73
+ self.debounce_delay = 0.5 # Wait 500ms after last change
74
+ self._timer_lock = threading.Lock() # STABILITY FIX: Prevent race condition
75
+
76
+ def stop(self):
77
+ """Stop the watcher and cancel any pending timers.
78
+
79
+ STABILITY FIX: Ensures timer is cancelled on shutdown.
80
+ """
81
+ with self._timer_lock:
82
+ if self.debounce_timer:
83
+ self.debounce_timer.cancel()
84
+ self.debounce_timer = None
85
+
86
+ def on_any_event(self, event):
87
+ """Handle any file system event.
88
+
89
+ Args:
90
+ event: File system event from watchdog
91
+ """
92
+ # Ignore directory events and temporary files
93
+ if event.is_directory or event.src_path.endswith((".tmp", ".swp", "~")):
94
+ return
95
+
96
+ self.logger.debug(
97
+ f"File change detected: {event.event_type} - {event.src_path}"
98
+ )
99
+
100
+ # STABILITY FIX: Use lock to prevent timer race condition
101
+ with self._timer_lock:
102
+ # Cancel existing timer
103
+ if self.debounce_timer:
104
+ self.debounce_timer.cancel()
105
+
106
+ # Schedule reload after debounce delay
107
+ self.debounce_timer = threading.Timer(
108
+ self.debounce_delay, self._trigger_reload
109
+ )
110
+ self.debounce_timer.start()
111
+
112
+ def _trigger_reload(self):
113
+ """Trigger hot reload by emitting Socket.IO event."""
114
+ try:
115
+ # Schedule the async emit in the event loop
116
+ asyncio.run_coroutine_threadsafe(self._emit_reload_event(), self.loop)
117
+ self.logger.info("Hot reload triggered - Svelte build changed")
118
+ except Exception as e:
119
+ self.logger.error(f"Error triggering reload: {e}")
120
+
121
+ async def _emit_reload_event(self):
122
+ """Emit the reload event to all connected clients."""
123
+ if self.sio:
124
+ await self.sio.emit(
125
+ "reload",
126
+ {
127
+ "type": "reload",
128
+ "timestamp": datetime.now(timezone.utc).isoformat() + "Z",
129
+ "reason": "svelte-build-updated",
130
+ },
131
+ )
132
+
133
+
48
134
  class UnifiedMonitorServer:
49
135
  """Unified server that combines HTTP dashboard and Socket.IO functionality.
50
136
 
@@ -52,15 +138,19 @@ class UnifiedMonitorServer:
52
138
  Replaces multiple competing server implementations with one stable solution.
53
139
  """
54
140
 
55
- def __init__(self, host: str = "localhost", port: int = 8765):
141
+ def __init__(
142
+ self, host: str = "localhost", port: int = 8765, enable_hot_reload: bool = False
143
+ ):
56
144
  """Initialize the unified monitor server.
57
145
 
58
146
  Args:
59
147
  host: Host to bind to
60
148
  port: Port to bind to
149
+ enable_hot_reload: Enable file watching and hot reload for development
61
150
  """
62
151
  self.host = host
63
152
  self.port = port
153
+ self.enable_hot_reload = enable_hot_reload
64
154
  self.logger = get_logger(__name__)
65
155
 
66
156
  # Core components
@@ -78,6 +168,10 @@ class UnifiedMonitorServer:
78
168
  # High-performance event emitter
79
169
  self.event_emitter = None
80
170
 
171
+ # File watching (optional for dev mode)
172
+ self.file_observer: Optional[Observer] = None
173
+ self.file_watcher: Optional[SvelteBuildWatcher] = None
174
+
81
175
  # State
82
176
  self.running = False
83
177
  self.loop = None
@@ -184,6 +278,9 @@ class UnifiedMonitorServer:
184
278
 
185
279
  time.sleep(0.1)
186
280
 
281
+ # STABILITY FIX: Give tasks more time to clean up before closing
282
+ time.sleep(0.5)
283
+
187
284
  # Clear the event loop from the thread BEFORE closing
188
285
  # This prevents other code from accidentally using it
189
286
  asyncio.set_event_loop(None)
@@ -229,6 +326,10 @@ class UnifiedMonitorServer:
229
326
  self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
230
327
  self.logger.info("Heartbeat task started (3-minute interval)")
231
328
 
329
+ # Setup file watching for hot reload (if enabled)
330
+ if self.enable_hot_reload:
331
+ self._setup_file_watcher()
332
+
232
333
  # Setup HTTP routes
233
334
  self._setup_http_routes()
234
335
 
@@ -304,20 +405,64 @@ class UnifiedMonitorServer:
304
405
  self.logger.error(f"Error setting up event emitter: {e}")
305
406
  raise
306
407
 
408
+ def _setup_file_watcher(self):
409
+ """Setup file watcher for Svelte build directory.
410
+
411
+ Watches for changes in svelte-build and triggers hot reload.
412
+ Only enabled when enable_hot_reload is True.
413
+ """
414
+ try:
415
+ dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
416
+ svelte_build_dir = dashboard_dir / "static" / "svelte-build"
417
+
418
+ if not svelte_build_dir.exists():
419
+ self.logger.warning(
420
+ f"Svelte build directory not found: {svelte_build_dir}. "
421
+ "Hot reload disabled."
422
+ )
423
+ return
424
+
425
+ # Create file watcher with Socket.IO reference
426
+ self.file_watcher = SvelteBuildWatcher(
427
+ sio=self.sio, loop=self.loop, logger=self.logger
428
+ )
429
+
430
+ # Create observer and schedule watching
431
+ self.file_observer = Observer()
432
+ self.file_observer.schedule(
433
+ self.file_watcher, str(svelte_build_dir), recursive=True
434
+ )
435
+ self.file_observer.start()
436
+
437
+ self.logger.info(f"🔥 Hot reload enabled - watching {svelte_build_dir}")
438
+
439
+ except Exception as e:
440
+ self.logger.error(f"Error setting up file watcher: {e}")
441
+ # Don't raise - hot reload is optional
442
+
307
443
  def _setup_http_routes(self):
308
444
  """Setup HTTP routes for the dashboard."""
309
445
  try:
310
- # Dashboard static files
311
- dashboard_dir = Path(__file__).parent.parent.parent / "dashboard"
446
+ # Dashboard static files - use .resolve() for absolute path
447
+ dashboard_dir = Path(__file__).resolve().parent.parent.parent / "dashboard"
448
+ static_dir = dashboard_dir / "static"
312
449
 
313
- # Main dashboard route
450
+ # Main dashboard route - serve Svelte dashboard
314
451
  async def dashboard_index(request):
315
- template_path = dashboard_dir / "templates" / "index.html"
316
- if template_path.exists():
317
- with template_path.open() as f:
452
+ svelte_index = static_dir / "svelte-build" / "index.html"
453
+ if svelte_index.exists():
454
+ with svelte_index.open(encoding="utf-8") as f:
318
455
  content = f.read()
319
456
  return web.Response(text=content, content_type="text/html")
320
- return web.Response(text="Dashboard not found", status=404)
457
+
458
+ # Log error with path details for debugging
459
+ self.logger.error(
460
+ f"Dashboard index.html not found at: {svelte_index.resolve()}"
461
+ )
462
+ return web.Response(
463
+ text=f"Dashboard not found. Expected location: {svelte_index.resolve()}",
464
+ status=404,
465
+ )
321
466
 
322
467
  # Health check
323
468
  async def health_check(request):
@@ -325,7 +470,8 @@ class UnifiedMonitorServer:
325
470
  version = "1.0.0"
326
471
  try:
327
472
  version_file = (
328
- Path(__file__).parent.parent.parent.parent.parent / "VERSION"
473
+ Path(__file__).resolve().parent.parent.parent.parent.parent
474
+ / "VERSION"
329
475
  )
330
476
  if version_file.exists():
331
477
  version = version_file.read_text().strip()
@@ -546,12 +692,43 @@ class UnifiedMonitorServer:
546
692
  "/monitor/events", lambda r: monitor_page_handler(r)
547
693
  )
548
694
 
549
- # Static files with cache busting headers for development
550
- static_dir = dashboard_dir / "static"
695
+ # Serve Svelte _app assets (compiled JS/CSS)
696
+ svelte_build_dir = static_dir / "svelte-build"
697
+ if svelte_build_dir.exists():
698
+ svelte_app_dir = svelte_build_dir / "_app"
699
+ if svelte_app_dir.exists():
700
+ # Serve _app assets with proper caching
701
+ async def app_assets_handler(request):
702
+ """Serve Svelte _app assets."""
703
+ from aiohttp.web_fileresponse import FileResponse
704
+
705
+ rel_path = request.match_info["filepath"]
706
+ file_path = svelte_app_dir / rel_path
707
+
708
+ if not file_path.exists() or not file_path.is_file():
709
+ raise web.HTTPNotFound()
710
+
711
+ response = FileResponse(file_path)
712
+
713
+ # Add cache headers for immutable assets
714
+ if "/immutable/" in str(rel_path):
715
+ response.headers["Cache-Control"] = (
716
+ "public, max-age=31536000, immutable"
717
+ )
718
+ else:
719
+ response.headers["Cache-Control"] = (
720
+ "no-cache, no-store, must-revalidate"
721
+ )
722
+
723
+ return response
724
+
725
+ self.app.router.add_get("/_app/{filepath:.*}", app_assets_handler)
726
+
727
+ # Legacy static files (for backward compatibility)
551
728
  if static_dir.exists():
552
729
 
553
730
  async def static_handler(request):
554
- """Serve static files with cache-control headers for development."""
731
+ """Serve legacy static files with cache-control headers for development."""
555
732
 
556
733
  from aiohttp.web_fileresponse import FileResponse
557
734
 
@@ -576,10 +753,13 @@ class UnifiedMonitorServer:
576
753
 
577
754
  self.app.router.add_get("/static/{filepath:.*}", static_handler)
578
755
 
579
- # Templates
580
- templates_dir = dashboard_dir / "templates"
581
- if templates_dir.exists():
582
- self.app.router.add_static("/templates/", templates_dir)
756
+ # Log dashboard availability
757
+ if svelte_build_dir.exists():
758
+ self.logger.info(
759
+ f"✅ Svelte dashboard available at / (root) (build: {svelte_build_dir})"
760
+ )
761
+ else:
762
+ self.logger.warning(f"Svelte build not found at: {svelte_build_dir}")
583
763
 
584
764
  self.logger.info("HTTP routes registered successfully")
585
765
 
@@ -691,11 +871,37 @@ class UnifiedMonitorServer:
691
871
  async def _cleanup_async(self):
692
872
  """Cleanup async resources."""
693
873
  try:
874
+ # Stop file observer if running
875
+ # STABILITY FIX: Ensure watcher is stopped and verify observer termination
876
+ if self.file_observer:
877
+ try:
878
+ # Stop the watcher first to cancel pending timers
879
+ if self.file_watcher:
880
+ self.file_watcher.stop()
881
+
882
+ # Stop the observer
883
+ self.file_observer.stop()
884
+ self.file_observer.join(timeout=2)
885
+
886
+ # Verify observer actually stopped
887
+ if self.file_observer.is_alive():
888
+ self.logger.warning("File observer did not stop cleanly")
889
+
890
+ self.logger.debug("File observer stopped")
891
+ except Exception as e:
892
+ self.logger.debug(f"Error stopping file observer: {e}")
893
+ finally:
894
+ self.file_observer = None
895
+ self.file_watcher = None
896
+
694
897
  # Cancel heartbeat task if running
898
+ # STABILITY FIX: Add timeout to prevent infinite wait on cancellation
695
899
  if self.heartbeat_task and not self.heartbeat_task.done():
696
900
  self.heartbeat_task.cancel()
697
- with contextlib.suppress(asyncio.CancelledError):
698
- await self.heartbeat_task
901
+ try:
902
+ await asyncio.wait_for(self.heartbeat_task, timeout=2.0)
903
+ except (asyncio.CancelledError, asyncio.TimeoutError):
904
+ pass
699
905
  self.logger.debug("Heartbeat task cancelled")
700
906
 
701
907
  # Close the Socket.IO server first to stop accepting new connections
@@ -58,6 +58,10 @@ class ProjectOrganizer:
58
58
  ".mcp-vector-search/",
59
59
  ".kuzu-memory/",
60
60
  "kuzu-memories/", # kuzu-memory database directory
61
+ # User-specific config files (should NOT be committed)
62
+ ".mcp.json",
63
+ ".claude.json",
64
+ ".claude/",
61
65
  # Python artifacts
62
66
  "__pycache__/",
63
67
  "*.py[cod]",
@@ -10,10 +10,14 @@ This service handles:
10
10
  5. Hook service registration
11
11
 
12
12
  Extracted from ClaudeRunner to follow Single Responsibility Principle.
13
+
14
+ DEPENDENCY INJECTION:
15
+ This service uses protocol-based dependency injection to avoid circular imports
16
+ when registering the SessionManagementService.
13
17
  """
14
18
 
15
19
  import os
16
- from typing import Any, Dict, List, Optional, Tuple
20
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
17
21
 
18
22
  from claude_mpm.core.base_service import BaseService
19
23
  from claude_mpm.core.config import Config
@@ -22,6 +26,13 @@ from claude_mpm.core.logger import get_project_logger
22
26
  from claude_mpm.core.shared.config_loader import ConfigLoader
23
27
  from claude_mpm.services.core.interfaces import RunnerConfigurationInterface
24
28
 
29
+ # Protocol imports for type checking without circular dependencies
30
+ if TYPE_CHECKING:
31
+ from claude_mpm.core.protocols import ClaudeRunnerProtocol
32
+ else:
33
+ # At runtime, accept any object with matching interface
34
+ ClaudeRunnerProtocol = Any
35
+
25
36
 
26
37
  class RunnerConfigurationService(BaseService, RunnerConfigurationInterface):
27
38
  """Service for configuring and initializing ClaudeRunner components."""
@@ -495,12 +506,14 @@ class RunnerConfigurationService(BaseService, RunnerConfigurationInterface):
495
506
  )
496
507
  return None
497
508
 
498
- def register_session_management_service(self, container, runner):
509
+ def register_session_management_service(
510
+ self, container, runner: "ClaudeRunnerProtocol"
511
+ ):
499
512
  """Register session management service in the DI container.
500
513
 
501
514
  Args:
502
515
  container: DI container instance
503
- runner: ClaudeRunner instance dependency
516
+ runner: ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
504
517
 
505
518
  Returns:
506
519
  Initialized session management service or None if failed
@@ -9,29 +9,41 @@ This service handles:
9
9
  4. Session logging and cleanup
10
10
 
11
11
  Extracted from ClaudeRunner to follow Single Responsibility Principle.
12
+
13
+ DEPENDENCY INJECTION:
14
+ This service uses protocol-based dependency injection to avoid circular imports.
15
+ It accepts a ClaudeRunnerProtocol instead of importing ClaudeRunner directly.
12
16
  """
13
17
 
14
18
  import time
15
19
  import uuid
16
20
  from datetime import timezone
17
- from typing import Any, Dict, List, Optional
21
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
18
22
 
19
23
  from claude_mpm.core.base_service import BaseService
20
24
  from claude_mpm.core.enums import OperationResult, ServiceState
21
25
  from claude_mpm.services.core.interfaces import SessionManagementInterface
22
26
 
27
+ # Protocol imports for type checking without circular dependencies
28
+ if TYPE_CHECKING:
29
+ from claude_mpm.core.protocols import ClaudeRunnerProtocol
30
+ else:
31
+ # At runtime, accept any object with matching interface
32
+ ClaudeRunnerProtocol = Any
33
+
23
34
 
24
35
  class SessionManagementService(BaseService, SessionManagementInterface):
25
36
  """Service for managing Claude session orchestration."""
26
37
 
27
- def __init__(self, runner=None):
38
+ def __init__(self, runner: Optional["ClaudeRunnerProtocol"] = None):
28
39
  """Initialize the session management service.
29
40
 
30
41
  Args:
31
- runner: ClaudeRunner instance for delegation
42
+ runner: ClaudeRunner instance (or any object matching ClaudeRunnerProtocol)
43
+ for delegation
32
44
  """
33
45
  super().__init__(name="session_management_service")
34
- self.runner = runner
46
+ self.runner: Optional[ClaudeRunnerProtocol] = runner
35
47
  self.active_sessions = {} # Track active sessions
36
48
 
37
49
  async def _initialize(self) -> None:
@@ -78,10 +78,13 @@ class NormalizedEvent:
78
78
  subtype: str = "" # Specific event type
79
79
  timestamp: str = "" # ISO format timestamp
80
80
  data: Dict[str, Any] = field(default_factory=dict) # Event payload
81
+ correlation_id: Optional[str] = (
82
+ None # For correlating related events (e.g., pre_tool/post_tool)
83
+ )
81
84
 
82
85
  def to_dict(self) -> Dict[str, Any]:
83
86
  """Convert to dictionary for emission."""
84
- return {
87
+ result = {
85
88
  "event": self.event,
86
89
  "source": self.source,
87
90
  "type": self.type,
@@ -89,6 +92,10 @@ class NormalizedEvent:
89
92
  "timestamp": self.timestamp,
90
93
  "data": self.data,
91
94
  }
95
+ # Include correlation_id if present
96
+ if self.correlation_id:
97
+ result["correlation_id"] = self.correlation_id
98
+ return result
92
99
 
93
100
 
94
101
  class EventNormalizer:
@@ -218,6 +225,11 @@ class EventNormalizer:
218
225
  # Get or generate timestamp
219
226
  timestamp = self._extract_timestamp(event_data)
220
227
 
228
+ # Extract correlation_id if present
229
+ correlation_id = None
230
+ if isinstance(event_data, dict):
231
+ correlation_id = event_data.get("correlation_id")
232
+
221
233
  # Create normalized event
222
234
  normalized = NormalizedEvent(
223
235
  event="claude_event",
@@ -226,6 +238,7 @@ class EventNormalizer:
226
238
  subtype=subtype,
227
239
  timestamp=timestamp,
228
240
  data=data,
241
+ correlation_id=correlation_id,
229
242
  )
230
243
 
231
244
  self.stats["normalized"] += 1
@@ -281,6 +294,7 @@ class EventNormalizer:
281
294
  "timestamp", datetime.now(timezone.utc).isoformat()
282
295
  ),
283
296
  data=event_data.get("data", {}),
297
+ correlation_id=event_data.get("correlation_id"),
284
298
  )
285
299
 
286
300
  def _extract_event_info(self, event_data: Any) -> Tuple[str, str, Dict[str, Any]]: